首页/Agent 能力/Agent 能力总览

Agent 能力总览

首页的「Agent 能力」入口在这里展开成可执行的细节。 组织逻辑是四层:模型能碰什么(工具治理)、模型怎么被驱动(循环与预算)、 跑砸了怎么办(时间旅行与回滚)、跨会话留下什么(记忆与技能)。

四层,不是一个循环

大多数「Text2SQL Agent」的叙事是一句话:让模型调用工具、自己纠错、直到满意。这句话是对的, 但它描述的是第二层。一个能长期跑在生产上的问数 Agent,四层缺一不可—— 而且越往下越不该交给模型。

层要解决的问题确定性占比
① 工具治理 模型能看见哪些工具、越权时发生什么 全部——注册与裁剪在构图时完成,运行期零判断
② 循环与预算 怎么驱动、什么时候停、烧多少 骨架确定,停止条件交给模型
③ 时间旅行与回滚 这一轮改坏了,怎么回到上一轮 全部——版本比对与回滚是纯代码
④ 记忆与技能 跨会话留下什么、下次怎么用上 检索是确定性的,写入要过人工确认
flowchart LR
    TOOLS["① 工具治理
构图时按角色裁剪出可见工具表"]:::entry -- "模型能看见的工具" --> LOOP MEM["④ 记忆与技能
情节与技能进上下文,写入过确认"]:::entry -- "上下文块 / 注入的 skill" --> LOOP subgraph LOOP["② 生成循环(ReAct)"] direction LR M["模型"]:::gen -- "工具调用" --> T["工具执行
语法校验 / 只读探测 / 规则链"]:::exec T -- "观察结果" --> M M -- "finish(SQL)" --> OUT["候选 SQL"]:::gen end LOOP -- "每轮落检查点" --> TRAVEL["③ 时间旅行与回滚
版本比对判有没有进步,改坏了回上一轮"]:::act classDef entry fill:#71717a14,stroke:#71717a59,stroke-width:1px; classDef gen fill:#6366f114,stroke:#6366f199,stroke-width:1.5px; classDef exec fill:#14b8a614,stroke:#14b8a699,stroke-width:1.5px; classDef act fill:#8b5cf614,stroke:#8b5cf699,stroke-width:1.5px; style LOOP fill:#71717a0d,stroke:#71717a2e,stroke-width:1px
图 1 · 四层不是一条流水线:① 与 ④ 决定循环里看得见什么,② 是循环本身,③ 在每一轮外面记账、比对与回滚。 上面这段以源码显示:图渲染库没加载起来(离线、CDN 被拦或浏览器禁用了 JavaScript 时会这样)。这段 Mermaid 文本本身就说明了上面那些框与连线。
一条贯穿全站的原则

能把判断变成代码的地方,就不要变成提示词。这不是「不信任模型」,而是:确定性规则可解释、可回归测试、且换模型不退化。

所以 join 路径由语义层算、结果对不对由规则链判、这一轮有没有进步由版本比对判——模型只负责它真正不可替代的那件事:把一句话理解成一个查询意图。

十二组能力对照表

下表是十二组能力的索引。前三列是「在哪读」,最后一列指向本页或相邻的详解页。

#能力组一句话机制详解
01工具调用与工具治理工具声明可见角色,越权折叠为「未知工具」本页
02工作流编排节点工厂 + 意图分流两条路查询工作流
03规划先出计划再写 SQL,schema 段独立预算查询工作流
04多候选与投票并行多份 SQL → 执行共识投票本页
05状态管理与时间旅行检查点按存储后端选型,可回放任意一步本页
06长任务与定时调度器鸭子类型可测,进程内嵌 tick运维管理
07人在环执行前拦 SQL,载荷认不出就默认拒绝本页
08评估离线回放 + 带方向容差的回归门评测与回归门
09可观测span 树 + 每节点 token 累计可观测性
10记忆分层门面收口,自动写入一律 pending 或自包含记忆子系统
11上下文工程分档预算 + 优先级装块 + 块内裁剪本页
12确定性安全边界只读 AST 防火墙 + 限流 + 审计安全边界

① 工具治理:越权不是一道墙,是「没有这个工具」

每个工具在注册时声明自己需要什么权限级别、对哪些角色可见。这份定义在交给模型之前 就按角色裁剪过了:

agent_loop.py:199 ToolSpec agent_loop.py:246 visible_to agent_loop.py:269 ToolRegistry

有意思的是越权时发生什么。运行时不会返回「权限不足」—— 那样等于告诉模型「这里有一扇门,只是你没钥匙」,而模型会开始试探。 它把越权调用折叠成「未知工具」:

agent_loop.py:627 visible_to

模型看到的是「没有这个工具」,于是它换一条路,而不是在墙上撞。这是一个小设计, 但它决定了权限系统是「引导」还是「对抗」。

工具清单按档位懒注册

工具不是一次性全给。简单问题只给 SQL 语法校验和收工工具,标准/复杂档才解锁只读执行探测与规则链校验; 能以只读方式摸到物理 schema 的三个枚举/结构/执行计划工具,还额外要求 analyst 或 admin 角色。 完整清单与各自的注册条件见 LLM 网关 · 工具注册表。

gen_sql.py:1051 build_sql_registry

② 生成循环:护栏不是停止条件

生成节点走一个 ReAct 循环。关键的设计选择是:由模型自己决定什么时候收工, 而不是「跑到没有工具调用为止」或「跑满 N 轮」。

agent_loop.py:454 run_agent_loop agent_loop.py:462 max_rounds

max_rounds 是护栏而不是目标:正常收敛的循环根本碰不到它。真正让模型停下来的, 是它调用 finish 把最终 SQL 作为载荷交出来——见下面的收工协议。

四道预算,全在图上可调

预算默认值触顶后
tool_timeout_s20.0该次工具调用返回超时错误,循环继续
time_budget_s120.0整轮停止
max_rounds8整轮停止
max_total_tokens2500整轮停止

graphs.py:1137 tool_timeout_s graphs.py:1140 max_total_tokens

任何一道触顶,或者循环空手而归,都会降级回经典子图——「生成 → 校验 → 重试」 那条确定性的老路。所以 agentic 循环是加速器而不是单点依赖:它失灵时系统还在,只是慢一点。 这次降级会作为一个具名折损记进答案的置信度(−0.10,见答案怎么背书)—— 但只在交付的确实是那条降级路径时:如果共识投票后来采纳了某个没经历过降级的候选, 扣分就落不到它头上。

flowchart LR
    A["agentic 循环
自验工具 + 自决收工"]:::gen -- "finish(SQL),载荷过校验" --> OK["候选 SQL → 共识投票 → 执行"]:::entry A -. "空手而归,或任一道预算触顶" .-> C["降级:经典子图
生成 → 校验 → 重试"]:::err C -. "具名折损 -0.10(仅当这轮交付的确实是它)" .-> OK classDef entry fill:#71717a14,stroke:#71717a59,stroke-width:1px; classDef gen fill:#6366f114,stroke:#6366f199,stroke-width:1.5px; classDef err fill:#dc262614,stroke:#dc262699,stroke-width:1.5px; linkStyle 1 stroke:#f59e0b,stroke-width:1.5px
图 2 · 降级是具名的:agentic 循环失灵时系统照常交付,只是这条路产出的答案在置信度上记 −0.10。 上面这段以源码显示:图渲染库没加载起来(离线、CDN 被拦或浏览器禁用了 JavaScript 时会这样)。这段 Mermaid 文本本身就说明了上面那些框与连线。

收工协议:为什么要有 finish

靠「模型不再调用工具了」来判断结束是脆的:模型可能只是忘了、或者话说了一半。 所以这里把收工做成一个显式工具,模型必须把 SQL 作为参数交出来, 而且载荷要过校验才被接受。

agent_loop.py:320 add_finish_tool agent_loop.py:386 validate_finish

好处是「结束」变成了一个有内容、可校验、可记录的事件,而不是一个静默的推断。

④ 多候选与共识投票

对中等以上复杂度的问题,图上会并行跑多个候选子代理,各自生成一份 SQL,然后由共识节点 实际执行并投票——全票一致就直接采用,单一多数采用多数,分裂则带着分歧点回炉。

投票看的是执行结果而不是 SQL 文本。两份写法不同但结果一致的 SQL,在这个判据下是同一票—— 这正是我们想要的:要的是对的数据,不是对的字面。

顺带的一个细节:相似问法会轮换少样本示例。否则每次都从同一个角度切入,候选之间的多样性会塌掉, 投票也就失去了意义。

⑤ 检查点与时间旅行

每一步都落检查点,落哪种存储由后端决定——PostgreSQL 生产环境走 AsyncPostgresSaver, 本地与测试走 SQLite。

这让「回放任意一步」成为可能:出问题时不必猜,直接从某个检查点重放, 看当时模型拿到的是什么上下文、工具返回了什么。

这解决的是可解释性的最后一公里

「这个数字是怎么算出来的」有很多层答案:SQL 在这里、规则链的判定在这里、语义层的口径在这里。但「当时它为什么这么想」只能靠回放。

时间旅行是把 Agent 从「不可复现」变成「可复现」的那一步——没有它,剩下的可解释性都只是事后解释。

⑦ 人在环:默认拒绝

开启后,SQL 在执行前被拦下来交给人确认。这里有一条值得注意的默认值: 载荷认不出来就拒绝,而不是放行。

hitl.py:65 make_hitl

定时任务走同一条路,但自动放行——否则无人值守的任务会永远卡在确认上。 这两者的区别写在调度器里,不在提示词里。

答案怎么背书:置信度与来源

回答里可以带一个分数和一行来源。这两样东西只做一件事——披露, 它们不进任何条件分支。判据是可验证的:把开关关掉再跑一遍,SQL、行数、裁决、 答案来源逐字节相同。

confidence.py:62 TIER_BANDS answer_source.py:27 AnswerSource confidence.py:325 result_confidence

分数 = 档位基准 + 具名微调

基准看这条答案的来源,四档各占一段互不重叠的分数带:

来源是什么分数带
certified复用了一条已认证的资产——有人具名背书0.95 – 0.99
reused复用了 KB 资产(模板或示例),但没人背书0.88 – 0.94
compiled语义编译器拼出的权威 SQL0.70 – 0.87
generated模型生成的(含编译回退与软 MISS 骨架)0.05 – 0.69

分带刻意不重叠:重叠时「缺口多的 compiled」会落进 generated 的带,分数与档位 就互相矛盾了。不重叠让「一个分数读得回它自己的档」真的成立——这是可以断言的, 不是一句愿望。

加性微调:只在档内加减

微调是几条明写理由的加减,且只在本档的分数带内生效—— 越档会让分数与档位打架。每条都带一个机器可读的 name、一个 effect, 以及一句要原样渲染给用户的中文理由:

理由(渲染给用户的文案)增量什么时候发生
语义编译有 N 个组件未解析,由模型补齐 −0.05 每有一个软 MISS 缺口就扣一次,几个缺口累加几次
agent 自检 N 次通过规则链 +0.10 check_result 全部通过
本轮主候选的 agent 循环未走完,降级到单发生成 −0.10 且 select 没有采纳某个备选候选——备选生成失败时返回空,没走过这条路,不该替它挨罚
检索到强相关的参考示例 +0.05 检索融合分 ≥ 3.4(约当相似度 0.8);拿不到分不算坏消息,不扣

乘性折损:交付结果再打一次折

交付出去的是结果而不是 SQL,所以结果分数在 SQL 分数之上再乘一层折损。 它只记异常项——正常交付一条不扣:

理由因子什么时候发生
多候选结果不完全一致(票王得票率 N%) × 得票率 select 跑过且票王没拿满票;select 没跑则不扣
重试预算耗尽后强行交付 × 0.5 状态被置了强行交付位
结果未通过确定性规则链 × 0.5 跑了规则链且没过——没跑与没过是两件事,只有后者该罚
查询返回 0 行 × 0.7 裁决为 EMPTY
执行超时被中止 × 0.7 执行期报告了被主动终止
修正已无进展,提前止损 × 0.8 止损计数非零
执行期降级,结果被加了行数上限或收窄 × 0.85 与正文的降级提示走同一个判据,不另立一套
经过 N 轮修正才交付 × 0.9N 每轮修正各乘一次
修正属纯语义重试,比机械错误更可疑 × 0.9 在轮次折扣之上追加的折扣;能活到交付点的最多一次

连乘有下限 0.25:没有它,修正十轮的连乘会把分数压到 0, 而 0 是留给「没有可披露的答案」的码位。所以结果不会比它的 SQL 更可信, 两个数天然自洽。

confidence.py:106 _sql_adjustments confidence.py:229 _result_discounts

这些权重是判断,不是概率

代码里逐字写着:全部是判断,不是量出来的,不得在任何地方声称它们经过校准。谁把它说成「校准过的置信度」,谁就在编一个不存在的实验。

反过来的一条纪律同样重要:判定不出的,一律倒向最保守的那一档。把编译器的产物标低,用户少得一点信息;把模型编的标高,用户得到一个不该有的信任。两个方向不对称是刻意的——虚低和虚高一样是在编造机制。

怎么开关,显示在哪

开关是 agent.confidence_score(默认开)。它是进程级的: 不在管理端可热更新的设置清单里,要关得改配置再重启——急停要能真的停下来, 所以关掉回退的是整个披露形态(分数回到折叠区、披露面换回枚举行), 而不是半个功能。两个分数照算照写,只在渲染处分流。

⑪ 上下文工程:按优先级装进预算

生成提示不是把所有能塞的都塞进去。上下文按复杂度分档给了 token 预算, 各个信息块按优先级竞争这些预算,块内再按相关度裁剪条目。

档位预算典型问题
simple1500单表、单聚合、无歧义
standard2500需要 join、有过滤或分组
complex4000多跳、时间窗口、口径歧义

context_budget.py:116 assemble_context

优先级顺序本身说明了这套系统的价值排序:少样本示例与用户事实最高, 其次是规则、术语、指标;计划与历史排在中后段;用户画像排最低,只在预算有余量时才进。 完整顺序见 查询工作流 · 上下文预算。

继续读

想了解去哪
28 个节点逐个讲、分支与回滚阶梯查询工作流
记忆分层:情节、偏好、晋升、画像记忆子系统
Skills:四档(注入 / 按需 / validator 结果断言 / guard SQL 拦截)与确认门禁、颗粒停用Skills
模型选择、providers、工具注册表LLM 网关
数据侧的能力(语义层、知识库、检索)数据能力总览
本页源码锚点的基准提交:anchor-base: 7c6df87 GitHub 仓库 文档首页