news 2026/8/24 3:08:42

BAP-SQL:预算约束下的Text-to-SQL智能体规划与成本优化实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
BAP-SQL:预算约束下的Text-to-SQL智能体规划与成本优化实践

1. 项目背景与核心问题:当Text-to-SQL遇上预算约束

最近在搞一个数据中台项目,对接的业务方提了个需求,说想用自然语言直接查数据库,省去写SQL的麻烦。这需求听起来挺常见,不就是上个Text-to-SQL模型嘛。但真上手一评估,发现事情没那么简单。业务方的数据表动辄几百上千张,字段关系复杂,一个看似简单的用户问题,背后可能需要模型进行多轮“思考”(调用大语言模型API)才能生成准确的SQL。这“思考”的每一轮,可都是真金白银的API调用成本。更头疼的是,有些复杂查询,模型可能“思考”了七八轮,花了大量预算,最后生成的SQL还是错的,或者性能极差,直接拖垮生产库。这让我意识到,在真实的企业级场景里,Text-to-SQL不是一个单纯的准确率问题,而是一个在有限预算(成本)下,如何规划最优的“思考”路径,以最大化获得可用、高效SQL语句的问题。这正是BAP-SQL(Budget-Aware Observation Planning for Agentic Text-to-SQL)要解决的核心痛点。

传统的Text-to-SQL研究,无论是早期的Seq2Seq模型,还是现在基于大语言模型(LLM)的few-shot prompting或微调方案,主要目标都是提升在基准测试集(如Spider)上的精确匹配率。大家比拼的是“在无限次尝试下,最终能不能生成对的SQL”。但在实际部署中,我们面对的是硬性约束:API调用预算(Budget)数据库执行开销(Cost)。预算可能来自月度Token消耗限额,也可能来自单次查询的响应时间要求(时间也是一种预算)。你不能让一个用户查询为了追求那1%的准确率提升,就调用几十次GPT-4,那成本谁也受不了。同样,你也不能生成一个没有索引、涉及全表扫描的复杂连接查询,即使它语法正确,也会在执行阶段造成灾难。

BAP-SQL引入的“Agentic”和“Budget-Aware Observation Planning”这两个概念,正是将Text-to-SQL从一个静态的“翻译”任务,转变为一个动态的、有资源意识的智能体决策过程。这里的“智能体”(Agent),就是那个负责生成SQL的LLM。而“观察规划”(Observation Planning),指的是智能体如何有策略地、分步骤地去“观察”或“探索”数据库模式信息(Schema),比如先看哪些表名,再查看哪些表的字段,最后再深入看某个字段的样例值或外键关系。每一次“观察”都是一次LLM API调用,都消耗预算。BAP-SSQL的核心创新,就是教会这个智能体:在给定的总预算下,如何规划你的观察顺序和内容,用最少的“步数”(成本),获得足够生成高质量SQL的信息,同时还要考虑生成的SQL本身的执行效率。

这就像给你一个陌生的图书馆(数据库),让你找一本特定主题的书(回答用户问题)。笨办法是把所有书架(表)都看一遍,所有目录(字段)都读一遍,那肯定能找到,但时间(预算)早就耗尽了。聪明人的做法是先看图书馆总索引(数据库概览),锁定最相关的区域(候选表),然后快速浏览这些区域的书架标签(表结构),再抽出一两本关键的书翻看目录(字段样例),迅速定位目标。BAP-SQL要学习的,就是这种“聪明人”的预算感知型探索策略。

2. BAP-SQL的核心架构:一个两阶段的决策智能体

BAP-SSQL的解决方案不是一个单一的模型,而是一个精巧的、两阶段的智能体系统架构。理解这个架构,是理解其如何工作的关键。整个流程可以类比为一个经验丰富的DBA(数据库管理员)接到业务需求时的思考与操作过程。

2.1 第一阶段:预算感知的观察规划器

这是BAP-SQL最核心、最具创新性的部分。规划器的任务是在真正动手写SQL之前,先制定一个“侦查计划”。它的输入包括:

  1. 用户自然语言问题:例如,“找出上个季度华东地区销售额超过100万且客户满意度大于4.5的产品名称及其负责人”。
  2. 数据库模式(Schema)的元信息:通常包括所有表名、字段名、字段数据类型、主键/外键关系(如果可用)。注意,一开始智能体只知道这些“名字”,不知道具体的数据内容。
  3. 总预算B:一个数值,代表本次查询允许消耗的最大“资源单位”。这个单位可以是预估的API调用Token数,也可以是抽象的成本点数。

规划器本身通常也是一个轻量级的模型(例如一个小型LLM或一个经过训练的策略网络)。它基于当前已有的信息(初始状态就是用户问题和表名列表),来决定下一步“观察”什么。这里的“观察”是一个动作(Action),具体形式可以是:

  • Action 1: 获取表T的详细结构:即获取表T的所有字段名、类型、是否为主键/外键。这需要调用一次数据库信息查询(可视为一次低成本操作,但BAP-SQL中可能仍会计入成本)。
  • Action 2: 获取表T中字段F的若干条样例值:例如,从product表的category字段中随机采样5条数据。这需要执行一次SELECT DISTINCT查询,成本高于单纯获取结构。
  • Action 3: 获取表T1和T2之间的外键关系详情:如果元信息中关系不明确,这可能需要进行一次连接查询来验证。
  • Action 4: 终止观察,进入SQL生成阶段

规划器通过一个价值函数来评估每个潜在动作的“性价比”。这个价值函数会考虑:

  • 信息增益:执行这个动作(比如查看某个字段的样例)能多大程度上减少对最终SQL语句的不确定性?例如,用户问题中提到了“华东地区”,那么去观察region字段的样例值,确认其中是否包含“华东”这个枚举值,信息增益就很高。如果去观察一个毫不相干的created_at时间戳字段,增益就很低。
  • 动作成本:执行这个动作需要消耗多少预算?获取表结构成本低,获取数据样例成本高。
  • 剩余预算:当前还剩多少预算可供挥霍?

规划器的目标,是在预算B的约束下,选择一系列动作,使得累计的信息增益最大化。这本质上是一个序列决策问题,可以用强化学习(Reinforcement Learning)来训练规划器,奖励是最终生成的SQL的质量(准确性、效率),惩罚是过程中消耗的预算。

实操心得:在自建类似系统时,动作成本的定义非常关键。如果直接使用商用LLM API,成本可以粗略用输入+输出的总Token数来估算。但如果涉及数据库查询动作,成本模型就更复杂了。一个简单的做法是进行分层:元数据查询(如DESCRIBE table)成本为1,带LIMIT的样例查询成本为2,涉及多表连接的验证查询成本为5。你需要根据自己数据库的负载能力和查询延迟来定义这个成本体系。

2.2 第二阶段:基于增强信息的SQL生成器

当观察规划器决定终止,或者预算耗尽时,系统就进入了第二阶段。此时,智能体(通常是另一个更强大的LLM,如GPT-4)手中已经不再是干巴巴的表名列表了,而是拥有了一份精心筛选、高度相关的数据库上下文信息包

这个信息包可能包括:

  • 与问题高度相关的2-3张核心表的完整结构。
  • 关键字段(如问题中提到的“销售额”、“满意度”、“地区”)的样例值,帮助模型理解数据格式和枚举范围。
  • 确认过的表间连接路径。

这个信息包会作为系统提示(System Prompt)的一部分,与用户问题一起,提交给SQL生成器。由于上下文信息高度精炼且相关,大大降低了LLM的认知负荷,也避免了因上下文过长(引入大量无关表信息)导致的性能下降和成本飙升。

生成器输出最终的SQL语句。之后,系统还会有一个后验证与优化环节(可视为第二阶段的一部分或一个独立阶段):

  1. 语法与权限验证:通过数据库的EXPLAINVALIDATE功能检查SQL语法是否正确,当前用户是否有执行权限。
  2. 执行计划评估:如果权限允许,运行EXPLAIN命令分析SQL的执行计划,预估其开销(如是否全表扫描、连接方式是否高效)。如果预估开销超过某个阈值,可以触发一个低成本的“SQL优化建议”动作(比如让LLM基于执行计划反馈重写SQL),这也会消耗少量预算。
  3. 结果采样验证:对于SELECT查询,可以LIMIT 5执行一下,让业务用户或一个校验规则快速判断结果是否“看起来合理”,这是一个最终的质量关卡。

3. 关键技术实现:如何训练一个“精打细算”的智能体

理解了架构,下一个问题就是:怎么让机器学会这种“精打细算”的观察策略?BAP-SQL类系统的实现,核心在于训练观察规划器。目前主流的研究方向是结合强化学习大语言模型

3.1 基于强化学习的策略训练

这是最经典的方法。我们可以将整个过程建模为一个马尔可夫决策过程:

  • 状态(State):当前时刻智能体所知的一切,包括用户问题、已观察到的数据库信息集合、剩余预算。
  • 动作(Action):如前所述,即下一步观察什么(表结构、字段样例等)或终止。
  • 奖励(Reward)
    • 稀疏最终奖励:只有在智能体选择“终止”并生成SQL后,才根据SQL的执行正确性效率给出一个综合奖励。正确性可以通过在测试数据库上执行并与标准答案对比得出(执行准确率)。效率可以通过EXPLAIN预估的行扫描数、临时表使用情况等指标折算成一个惩罚项。
    • 稠密中间奖励:为了加速训练,可以为每个观察动作设计一个中间奖励,例如“观察到的信息与最终SQL中用到的信息的相关性”。这需要事后来计算,但在模拟环境中可以实现。
  • 策略网络(Policy Network):一个神经网络,输入当前状态,输出各个动作的概率分布。这个网络就是我们要训练的“规划器”。

训练环境通常是一个离线模拟器。我们需要一个包含大量(问题,数据库,标准SQL)三元组的训练集,比如Spider数据集。在模拟器中,智能体与环境交互:它选择一个观察动作,模拟器就返回对应的信息(从训练数据库里取),并扣除相应预算。当它选择终止,模拟器就调用一个固定的SQL生成器(可以是一个预训练的模型)来生成SQL,并计算最终奖励。通过大量这样的“回合”训练,策略网络逐渐学会在何种状态下,选择何种观察动作最能“花小钱,办大事”。

踩坑实录:自己尝试复现强化学习训练时,最大的挑战是奖励函数的设计。如果只奖励最终SQL正确,智能体很容易学会一个保守策略:在不确定时,就疯狂观察直到预算耗尽,指望最后生成器能“大力出奇迹”。这违背了节省预算的初衷。必须在奖励函数中强烈惩罚预算的消耗。一个有效的技巧是使用“预算利用率”作为负奖励项:最终奖励 = 准确性奖励 - λ * (已用预算 / 总预算),其中λ是一个超参数,用来权衡准确性与成本。λ的设置需要反复实验,它直接决定了智能体是“挥金如土”还是“锱铢必较”。

3.2 基于大语言模型的少样本/零样本规划

直接用强化学习训练一个策略网络,数据需求和计算成本都很高。另一种更轻量、更易落地的方法是利用大语言模型本身的规划能力

我们可以设计一套精妙的提示词(Prompt),让LLM自己担任观察规划器。提示词中需要明确说明:

  • 目标:用最少的步骤收集信息,以生成准确、高效的SQL。
  • 可用动作:列出所有可以执行的观察动作及其模拟成本(例如:“查看表结构:成本1点”;“查看某字段5条样例:成本3点”)。
  • 当前状态:用户问题、当前已知信息、剩余预算。
  • 输出格式:要求LLM严格按照指定JSON格式输出,包含“action”“reason”字段,解释为何选择此动作。

例如,给LLM的提示可能是:

你是一个数据库查询规划专家。总预算为10点。 用户问题:“找出上海地区单价超过500元的商品名称和库存量。” 已知信息:数据库中有以下表:`products`, `inventory`, `suppliers`, `orders`。目前未观察任何表的细节。 请从以下动作中选择一个,并说明理由。只输出JSON。 动作列表: 1. {"action": "observe_table_schema", "table": "表名", "cost": 1} 2. {"action": "observe_column_sample", "table": "表名", "column": "字段名", "cost": 3} 3. {"action": "terminate", "cost": 0} 请输出:{"action": ..., "reason": "..."}

LLM基于其内部知识(比如知道“商品”很可能对应products表,“库存”对应inventory表,“地区”可能对应suppliers表里的字段),可能会选择先以低成本观察products表的结构。根据返回的结构,再决定下一步动作。

这种方法无需训练,直接利用现成LLM(如GPT-4)。其效果严重依赖于提示词工程和LLM本身的能力。优点是快速原型验证,缺点是无法针对特定数据库分布进行优化,且每次规划都需要调用LLM,其本身也会产生成本。

3.3 混合方法:LLM生成,小模型判别

一个折中的实践方案是结合两者优势:

  1. 使用LLM(如GPT-4)生成大量的“(状态, 动作)”轨迹数据。在模拟环境中,让一个基于提示词的LLM规划器去解决许多问题,记录下它在每个状态下选择的动作及其理由。这相当于用LLM进行“专家演示”。
  2. 用这些轨迹数据训练一个轻量级的判别模型。这个模型可以是一个简单的分类器(如BERT微调)或一个小型LLM(如Phi-3、Qwen1.5-7B)。它的任务是学习模仿LLM专家的决策:输入当前状态,输出应执行的动作。
  3. 部署时,使用训练好的轻量级模型作为规划器。这样就避免了每次规划都调用昂贵的GPT-4,大幅降低了成本,同时决策质量又接近于LLM专家。

这个轻量级模型可以进一步用强化学习进行微调,利用业务场景特有的奖励信号(如实际生产数据库的查询性能反馈)来优化,使其更适应真实环境。

4. 从研究到落地:企业级应用的挑战与实战指南

BAP-SQL的论文思想很吸引人,但想在企业内部真正用起来,让它创造业务价值,还需要跨过好几道坎。下面结合我自己的实践,聊聊关键的挑战和应对策略。

4.1 挑战一:成本模型的真实定义与校准

论文里的“预算”可能是一个抽象单位。在现实中,成本至少包括三部分:

  1. LLM API成本:这是最直接的。OpenAI、Anthropic等按Token收费。规划器和生成器的每次调用都计费。你需要精确计算每次提示和补全的Token数。
  2. 数据库查询成本:观察动作中的“获取样例值”等操作,会实际执行SQL查询。这在生产库上可能是危险的(消耗IO/CPU)或被DBA禁止的。通常的做法是连接一个从库或专门的数据镜像,并严格限制查询的规模和频率(如LIMIT 5, 禁止SELECT *)。
  3. 时间延迟成本:用户能忍受的查询响应时间也是预算。如果规划阶段就花了10秒,即使SQL完美也无意义。需要为每个动作设定一个预估的时间开销,并在总预算中体现。

实战建议:在项目初期,建立一个成本计算中间件。所有动作(LLM调用、DB查询)都通过这个中间件执行,它负责记录和累加各类成本(Token数、查询耗时、数据扫描行数等)。运行一段时间后,你就能得到真实的成本分布数据,从而回头去校准模拟训练环境中的成本参数,使其更贴近现实。

4.2 挑战二:复杂数据库模式的应对

研究数据集(如Spider)的表结构相对规范。真实企业数据库则充满“坑”:

  • 同名字段不同义account表里有statusorder表里也有status,含义完全不同。
  • 缺乏外键约束:表间关系全靠业务逻辑文档(而文档可能已过时)或命名约定(如user_id)。
  • 巨型宽表与过度范式化并存:既有几百个字段的“大杂烩”事实表,也有拆得极其零碎的维度表。
  • 视图和存储过程:业务逻辑可能封装在视图或存储过程中。

这对观察规划器是巨大考验。如果它无法通过有限的观察理解这些复杂关系,生成的SQL就会出错。

应对策略

  • 预处理与知识注入:在系统启动前,离线分析数据库,自动或半自动地生成一份增强的元数据知识库。例如,用算法推断可能的表间关系(基于字段名相似度和数据关联性),为每个字段添加业务注释(从代码注释或数据字典中抽取),识别出重要的业务实体表。将这些信息作为初始状态的一部分提供给规划器,相当于给了它一张“藏宝图”。
  • 分层观察策略:让规划器优先观察那些被标记为“核心实体”的表(如用户产品订单),因为这些表是大多数查询的枢纽。对于宽表,可以设计一个特殊的“预览表头”动作,只获取前20个字段名,如果发现相关再深入观察。
  • 引入人工反馈回路:当规划器在某个环节反复“卡住”(比如在两个可能的连接路径间犹豫不决),可以设计一个“请求澄清”的动作,将不确定性抛给用户(例如:“您提到的‘项目状态’,是指研发项目状态,还是销售项目状态?”)。这虽然增加了交互成本,但能从根本上解决歧义。

4.3 挑战三:SQL生成的质量与安全

即使观察到了正确的信息,生成的SQL也可能存在以下问题:

  1. 性能低下:缺少WHERE条件中的索引字段,使用了低效的函数(如LIKE ‘%xxx%’),连接顺序不佳。
  2. 逻辑错误:聚合函数使用不当(如SELECT非聚合字段未加GROUP BY),子查询结果集过大。
  3. 安全风险:虽然Text-to-SQL本身不直接拼接用户输入,但若LLM被恶意提示诱导,仍可能生成具有破坏性的SQL(如DROP TABLE),或通过复杂查询进行拖库。

落地时必须建立的防线

  • SQL执行前校验
    • 语法与权限校验:使用数据库驱动提供的解析接口或EXPLAIN进行预检查。
    • 安全规则过滤:维护一个禁止操作列表(如DROP,DELETE,UPDATE,ALTER, 某些系统表查询)。所有生成的SQL必须通过规则引擎过滤。
    • 执行计划分析:对SELECT查询,强制进行EXPLAIN分析。如果发现“全表扫描”(type=ALL)且扫描行数超过阈值(如10万行),则自动触发优化或拒绝执行,并提示“查询过于宽泛,请添加更多筛选条件”。
  • 生成后优化:集成一个轻量级的SQL重写器。它可以基于一些简单规则对生成的SQL进行优化,例如:
    • WHERE DATE(create_time) = ‘2024-01-01’重写为WHERE create_time >= ‘2024-01-01’ AND create_time < ‘2024-01-02’,以利用索引。
    • 提示LLM在连接时,将小表驱动大表(如果元数据中有表行数信息)。
  • 结果集限制:对于即席查询(Ad-hoc Query),在最终执行的SQL后强制添加LIMIT N子句(如N=1000),防止返回海量数据拖垮网络和前端。同时提供“如需更多数据,请优化查询条件”的提示。

4.4 一个简化的企业级部署架构示例

结合以上策略,一个可落地的简化架构如下:

用户提问 | v [API网关] (身份认证、限流) | v [预算与成本管理器] (初始化预算,跟踪消耗) | v [观察规划器] (轻量级本地模型,基于增强元数据知识库决策) | | | (动作:观察) | (动作:终止) v v [元数据/样例查询服务] [SQL生成器] (调用云LLM API,传入精炼上下文) (连接只读从库,严格限流) | | v |——————> [信息包] <——————| | | v v [成本管理器更新] [SQL安全与优化过滤器] | v [SQL执行引擎] (连接生产只读账号,带LIMIT) | v [结果格式化与返回]

在这个架构中,规划器、成本管理、安全过滤都是部署在本地的可控服务,只有SQL生成环节可能调用外部昂贵的LLM API。整个流程闭环,且在每个环节都有控制和保护。

5. 未来展望:超越Text-to-SQL的预算感知智能体

BAP-SQL的思想其实可以推广到更广泛的“智能体(Agent)”应用场景中。任何需要LLM与外部工具或环境交互的任务,都可以引入预算感知规划。

例如:

  • 智能数据分析助手:用户问“分析一下我们最近三个月销售下滑的原因”。智能体需要规划:是先调用“获取销售报表”工具,还是先调用“获取市场活动列表”工具?是先看整体趋势,还是直接钻取到某个异常品类?每一步工具调用都有成本(API费用、查询时间)。
  • 自动化运维机器人:收到报警“服务器CPU飙升”。智能体需要规划:是先执行top命令看进程,还是先查看监控图表?是先检查最近部署,还是先排查网络?在分秒必争的故障处理中,规划的效率直接决定MTTR(平均恢复时间)。
  • 研究助理Agent:给定一个研究主题,Agent需要规划搜索关键词、决定阅读哪些论文、从论文中提取哪些信息进行总结。每一次搜索和长文档阅读都是成本。

未来的方向,可能不再是训练一个通用的“Text-to-X”规划器,而是发展出一套元规划框架。这个框架提供标准的预算定义、状态表示、动作空间和奖励函数接口。对于特定的领域(如SQL、数据分析、运维),开发者只需要定义该领域的工具集和成本模型,就能快速构建出具备预算感知能力的领域智能体。

回到我们开头的数据中台项目,最终我们并没有完全照搬BAP-SQL的学术方案,而是吸收了其核心思想——有策略地、成本可控地利用LLM的能力。我们实现了一个简化版:用提示词工程让GPT-4担任规划器,但严格限制了它的“观察”选项和轮次;将获取数据库样例值的操作,替换为查询我们预先构建的“字段值字典”(一个存储了高频枚举值和数据分布的缓存);并在SQL生成后,加入了强制的执行计划检查和LIMIT。虽然不如学术模型那么“智能”,但在成本、安全和时效性上取得了很好的平衡,已经成功处理了业务方80%以上的即席查询需求。

这个领域的实践才刚刚开始,核心矛盾永远是效果、成本与安全的三角博弈。BAP-SSQL为我们指出了一个明确的方向:让AI智能体学会“精打细算”,才是其真正走向规模化商用的前提。作为工程师,我们需要在理解学术前沿的同时,始终保持对现实约束的清醒认知,设计出既智能又务实的技术方案。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/24 3:07:56

突破AI绘画知识边界:智能体视觉生成中的搜索增强与协同训练

1. 项目缘起&#xff1a;当AI绘画遇到“知识边界”的瓶颈最近在折腾一个基于大模型的智能体视觉生成项目&#xff0c;遇到了一个挺有意思的难题。简单来说&#xff0c;就是想让AI画一些它“没见过”或者“没学过”的东西。比如&#xff0c;你让它画一个“在零重力环境下&#x…

作者头像 李华
网站建设 2026/8/24 3:07:17

Python办公自动化:利用COM接口高效读取Outlook邮件数据

1. 项目概述&#xff1a;为什么我们需要用Python来“管理”Outlook&#xff1f;作为一名长期和数据、自动化打交道的开发者&#xff0c;我处理过太多和邮件相关的“脏活累活”了。比如&#xff0c;市场部的同事需要你从过去三年的客户往来邮件里&#xff0c;提取所有包含“报价…

作者头像 李华
网站建设 2026/8/24 3:06:04

LLM Agent实时纠错:ATLAS-RTC框架下的Token级运行时控制

1. 项目概述&#xff1a;当LLM Agent开始“自我纠错”最近在折腾一个基于大语言模型的智能体项目时&#xff0c;我遇到了一个几乎所有开发者都会头疼的问题&#xff1a;Agent在执行复杂任务时&#xff0c;一旦在某个环节“跑偏”了&#xff0c;整个任务链就可能朝着错误的方向一…

作者头像 李华
网站建设 2026/8/24 3:03:44

快速上手 Yjs:实时协作编辑的安装与配置指南

快速上手 Yjs&#xff1a;实时协作编辑的安装与配置指南 【免费下载链接】yjs Shared data types for building collaborative software 项目地址: https://gitcode.com/GitHub_Trending/yj/yjs Yjs 是一个基于 CRDT 的实时协作编辑库&#xff1a;多人同时编辑同一份文档…

作者头像 李华
网站建设 2026/8/24 3:02:25

C++游戏开发框架设计:从状态机到Lua脚本的迷你农场RPG实践

你有没有过这样的体验&#xff1a;想学游戏开发&#xff0c;跟着教程一步步做&#xff0c;最后却只得到一个能跑起来的“玩具”&#xff1f;代码写了几千行&#xff0c;但总觉得功能之间是散的&#xff0c;加个新功能就要大动干戈&#xff0c;最后项目不了了之。最近&#xff0…

作者头像 李华
网站建设 2026/8/24 3:01:52

MinerU PDF转Markdown的macOS安装避坑指南:3条命令搞定arm64依赖报错

MinerU PDF转Markdown的macOS安装避坑指南&#xff1a;3条命令搞定arm64依赖报错 【免费下载链接】MinerU A high-quality tool for convert PDF to Markdown and JSON.一站式开源高质量数据提取工具&#xff0c;将PDF转换成Markdown和JSON格式。 项目地址: https://gitcode.…

作者头像 李华