Why: 系统缺少面向新人与新任务的现状导览;把结构/意图识别/任务执行/小队 四条主线的架构、模型与调用链整理成五篇参考文档,降低跨仓定位成本。 Evidence: 锚点核验于 Dev 组合 218fc4b1ba00b0a03b5bb79f292b3eba6c987a58 (2026-10-08,引用的函数入口行号逐一 grep 定位);git diff --check 通过; 五篇互链相对路径全部可解析。SRC=SELF(本地核验,任务卡有记录)。 Impact: 纯文档新增(docs/guides 五篇),无代码/契约/配置影响。 Refs: Dev 任务分支 feature/architecture-guides(R0 218fc4b1)。
13 KiB
Agent 的意图识别:架构、模型与调用链
基准:Agent-System-Dev 组合
218fc4b1ba00b0a03b5bb79f292b3eba6c987a58(R0,2026-10-08 锚点核验)。 行号为该提交下的函数入口位置(个别内部区段为约略范围),随代码演进漂移;文件与函数名是稳定锚点。internal/指modules/backend/internal/。
1. 一句话结论
意图识别是**"确定性规则漏斗为主体、语义向量与 LLM 为可选慢通道(默认关闭)"的混合
分层架构**:识别主体在独立库 deps/agent-intent(零 LLM 依赖),backend 通过
internal/intentgate 闸门接入,识别结果由 internal/route 统一裁决为
命令 / 工作流 / 普通回合三分支。
2. 识别架构:四级漏斗
核心识别器 FunnelRecognizer(deps/agent-intent/intent/recognizer.go:23,
入口 Recognize 在 recognizer.go:41,内部 recognize 在 :71),四级依次短路:
| 级 | 名称 | 行为 | 代码 |
|---|---|---|---|
| Stage 0 | 守卫(guard_hit) | 守卫域 spec 命中即短路,表达"识别即拒绝" | recognizer.go 守卫分支 |
| Stage 1 | 挂起预期(expectation_hit) | 多轮澄清场景:宿主注入 ExpectationProvider 声明上轮预期意图 |
接口 intent/expectation.go:17 |
| Stage 2+3 | 规则计分 + 置信度裁决 | 全部非守卫 spec 线性扫描(intent/registry.go 规则计分与裁决),score = base + (正则命中 ? pattern_bonus : 0),上限 1.0;唯一过阈或前两名分差 ≥ margin(默认 0.1)→ matched,同分比 priority → 否则 ambiguous,无命中 → fallback |
intent/spec.go:17-29(默认值)、recognizer.go:146 附近同分裁决 |
| Stage 4 | 慢通道(可选) | ambiguous → 语义向量重排 semanticRerank(recognizer.go:292);fallback → LLM 命令生成 generateCommands(recognizer.go:333) |
见下节 |
识别分支共五种(deps/agent-intent/intent/result.go):
guard_hit / expectation_hit / matched / ambiguous / fallback。
3. 用什么模型:三层各自不同
| 层 | 是否 LLM | 实现与模型来源 | 状态 |
|---|---|---|---|
| 规则层(守卫/计分/裁决) | 否,永不引入 | 纯 Go 确定性计算。设计文档明确规则层永不引入 LLM(deps/agent-intent/docs/records/2026-09-29-decoupling-and-llm-layering.md §2.1) |
主体,默认生效 |
| 语义重排(SemanticScorer) | 向量模型(非生成式) | modules/backend/internal/intentsem/scorer.go:18 EmbeddingScorer:对输入与 spec 的 examples 各取向量算最大余弦相似度。Provider 走 OpenAI 兼容 /embeddings(internal/rag/embed.go),模型/端点来自 settings models.embedding.*;默认阈值 0.8(intent/spec.go) |
可选,默认关(settings runtime.intentSemantic) |
| 命令生成(CommandGenerator) | 生成式 LLM | modules/backend/internal/intentsem/generator.go:18 LightGenerator:走 agentllm.Client.Stream,用轻量档模型(internal/runtime/model.go:84 ResolveLightweightModel,settings models.lightweight.model,缺省回落主 chat 档),MaxTokens 默认 1024;system prompt 在 generator.go:100-129(只暴露 id/描述/槽位键,防提示词注入) |
可选,默认关(settings runtime.intentGenerator) |
| Classifier(LLM 重打分,L2) | 生成式 LLM | 接口在 intent/classifier.go:9 |
接口预留,backend 未接线 |
慢通道装配点:modules/backend/internal/app/app.go:351,357(开关开启才
gate.SetSemantic(...) / gate.SetGenerator(...))。关闭时行为与纯规则版完全一致。
失败安全:agent-intent 库本体零 LLM 依赖,只定义 SemanticScorer
(intent/semantic.go:9)与 CommandGenerator(intent/generator.go:14)窄接口由
宿主注入;所有慢通道失败均回退规则结论(fail-open/closed 到 safe 分支),识别永不因
LLM 失败而失败。生成器幻觉有校验兜底:validateGenerated
(recognizer.go:368)要求 action.id/槽位键必须都在目录内,否则 fail-closed 回 fallback。
4. 意图目录(Intent Catalog / Schema)
意图定义 IntentSpec(deps/agent-intent/intent/spec.go:74 附近,Go 注册与 JSON overlay 共用):
- 标识:
id(必须domain.助记多段,spec.go:122校验)、domain、version、description、priority、disabled。 - matcher(
spec.go:32-39):includes_any / includes_all / excludes_any词表 +patterns正则 +case_sensitive+boundary(词界匹配,压制中文子串误命中)。 - slots(
spec.go:44-62):key/required/pattern(抽取正则)/group,加 HITL 字段label/question(建议澄清提问)与校验链enum/format/normalize/validator。 - confidence(
spec.go:66-70):base 默认 0.7、pattern_bonus 默认 0.2、threshold 默认 0.6。 - 动作绑定:
action(kind ∈ command/workflow/blueprint/handler,intent/command.go:32-43) 优先,其次metadata["blueprint"]、再次handler,否则 auto(宿主在命令总线与工作流间裁决)。 - risk(low/medium/high)+
requires_confirm(透传宿主做授权确认)。 - examples:示例句数组——语义慢通道按它们算相似度。
配置文件形态(intent/loader.go:12-19):
{ "intents": [ { …IntentSpec… } ], "disable": ["demo.run.status_query"] }
插件可经 intents/*.json overlay 注入(internal/plugins/host.go:216-220 采集)。
样例:deps/agent-intent/examples/specs/demo.json。
识别产物命令集(intent/command.go:11-26):start / set_slot / change_slot / cancel / clarify / chitchat / handoff;规则漏斗只产出 start/set_slot/clarify(deriveCommands
在 command.go:85)。能力目录 Snapshot.Catalog() / CatalogMarkdown()
(intent/catalog.go:36,68)把启用意图渲染为 Markdown,供小队规划者/Leader 提示词注入
(deps/agent-squad/adapters/intent/catalog.go 消费)。
5. 端到端调用链:用户输入一条消息后发生了什么
| # | 步骤 | 调用方 → 被调方 | 位置 |
|---|---|---|---|
| 1 | HTTP 入口 | 路由注册 POST /api/conversations/{id}/messages |
internal/server/server.go:168(注册)、:574 postMessage |
| 2 | slash 短路 | / 前缀非技能命令 → handleCommand(server.go:756)→ 命令总线,不进意图识别 |
server.go:623 |
| 3 | 附件摄取 | ingest.Parse(name, mime, content) → core.ParsedDocument(与意图识别并行、互不依赖) |
server.go:643;internal/ingest/ingest.go |
| 4 | 识别调用 | s.recognize(ctx, text)(端口 NLIntentGate,server.go:79-85) |
server.go:655 |
| 5 | 进入闸门 | intentgate.Gate.Recognize |
internal/intentgate/gate.go:137 |
| 6 | 四级漏斗 | FunnelRecognizer.Recognize(ctx, input, reg.Snapshot()) |
gate.go:144 附近 → deps/agent-intent/intent/recognizer.go:41 |
| 7 | 结果投影 | 库 Result → MatchResult{Branch, ID, Slots, Missing, Commands};guard/expectation 折叠为 fallback;缺槽位附 spec 的 Question |
gate.go:148-176 |
| 8 | 统一路由 | route.Decide(recognized, route.Env{HasCommand, WorkflowFor, SquadReady}) |
server.go:656;internal/route/route.go:60 |
| 9a | 命令分支 | KindCommand → s.Bus.Dispatch(Entrance="chat",槽位透传 Params) |
server.go:701-717 |
| 9b | 工作流分支 | KindWorkflow → mgr.StartClaimedWorkflowRun(缺槽位列表随行) |
server.go:725;internal/runmgr/runmgr.go:996 |
| 9c | 回合分支 | KindTurn → ClaimConversation 抢槽 → AppendMessageWithRequest 落库 → mgr.StartClaimedConversationRun |
server.go:679, 727;runmgr.go:980, 991 |
| 10 | 澄清/HITL | 工作流缺必填槽位 → 逐个发 HITLCard{Kind:Input, AllowFreeInput} 人工输入卡 |
internal/wfcompile/run.go:47 起 RunCompiled 槽位补齐段 |
| 11 | HITL 转轨 | POST /api/hitl/{id}/resolve → mgr.ResolveHITL:自由输入以 / 开头 → pivot=command;否则再过 gate.Recognize + route.Decide,命中其他意图则取消挂起 run 并 pivot |
server.go:883;runmgr.go:1261(转轨判定 :1293 附近) |
启动期装配链
| # | 内容 | 位置 |
|---|---|---|
| A1 | app.Run 建 gate := intentgate.New() 并 mgr.SetGate(gate) |
internal/app/app.go:309-310;runmgr.go:142 |
| A2 | 插件装载回调:pluginHost.OnIntents = overlay → gate.Rebuild(overlay)(fail-closed:解析失败保留上一快照) |
app.go:324;internal/plugins/host.go:216-220;gate.go:96 |
| A3 | 开关开启时接线慢通道:gate.SetSemantic(intentsem.EmbeddingScorer{…}) / gate.SetGenerator(intentsem.LightGenerator{…}) |
app.go:351,357;gate.go:66,75 |
6. intentgate 与 intentsem 的分工
- intentgate(
internal/intentgate/gate.go):宿主闸门适配器。持 Registry + FunnelRecognizer;负责插件 overlay 装载(Rebuild,:96)、分支折叠(非 matched/ambiguous 一律折叠为 fallback,gate.go:148-155)、补槽位 Question、 类型投影(让 runmgr/route 只依赖本包表面)、慢通道接线(:66,75)。 不做缓存——识别是纯函数 Query,快照不可变无锁读(intent/registry.go)。 - intentsem(
internal/intentsem/):慢通道实现包。EmbeddingScorer出错原样上抛 (库保持 Ambiguous);LightGenerator解析失败/模型不可达返回错误(库保持 fallback)。
7. 识别结果的去向与失败路径
- 识别结果
Result(intent/result.go)是纯内存 Query 结果,不落库(store 无 intent 表)。去向仅三处:① HTTP 响应;② logsys 结构化日志intent recognized(recognizer.go:62附近,不记原文);③ 作为 run 启动参数(workflow slots/missing、命令 ID)。 - ambiguous(重排后仍歧义)→ gate 折叠 →
route.Decide返回KindTurn(route.go:61-63)→ 走普通 LLM 回合自由对话兜底(不是强制澄清)。 - matched 但缺必填槽位 → Commands=[start, clarify](
intent/command.go:110附近)→ workflow 路径把 Missing(含建议 Question)传入 run → 发 HITL 输入卡澄清。 - guard_hit / expectation_hit → 折叠为 fallback → turn。
- 低置信度(未过 threshold)→ fallback → turn。
route 决策细节(internal/route/route.go:55-114):优先级
命令 > 工作流 > 普通回合;blueprint → KindSquad,但当前不可达——
server.go:664 明确注释 SquadReady 保持 false(per-run entry 覆盖未就位),
小队的实际启动走别的路径(见小队与子 Agent 启动)。
声明目标不可解析时不静默错配,回落 turn 并写 Reason。
8. 辅助工具与测试
deps/agent-intent/cmd/intentctl/:只读诊断 CLI(lint校验配置+词条冲突、explain单条输入裁决轨迹(recognizer.go:226Snapshot.Explain)、eval语料评测 Precision/Recall、catalog输出目录)。deps/agent-intent/conformance/conformance.go:识别器公开契约行为验收 (分支/槽位断言 + 确定性 + 并发冒烟)。deps/agent-intent/catalogcheck/align_test.go:断言库错误码与 Error-Code-System intent 域种子码逐个相等,防两套平行权威。
9. 关键文件清单
| 文件 | 职责 |
|---|---|
deps/agent-intent/intent/recognizer.go |
四级漏斗识别器、语义重排(:292)、生成兜底(:333)、Explain 诊断(:226) |
deps/agent-intent/intent/spec.go |
IntentSpec/MatchSpec/SlotSpec/ConfidenceSpec 定义与校验 |
deps/agent-intent/intent/registry.go |
编译、不可变 Snapshot、双源合并、原子 Reload |
deps/agent-intent/intent/loader.go |
JSON overlay 加载 |
deps/agent-intent/intent/command.go |
命令模型 + deriveCommands(:85)展开 |
deps/agent-intent/intent/catalog.go |
能力目录与 Markdown 渲染(:36/:68/:128) |
deps/agent-intent/intent/semantic.go generator.go classifier.go expectation.go |
四个可注入端口 |
deps/agent-intent/cmd/intentctl/main.go |
诊断 CLI |
modules/backend/internal/intentgate/gate.go |
闸门适配器(overlay :96、投影、慢通道接线 :66/:75) |
modules/backend/internal/intentsem/generator.go scorer.go |
LLM 命令生成(含 system prompt :100)/ embedding 重排 |
modules/backend/internal/route/route.go |
唯一 Router(Decide :60 三分支裁决) |
modules/backend/internal/server/server.go |
消息入口 postMessage(:574)、recognize(:655)、resolveHITL(:883)、SSE events(:275) |
modules/backend/internal/plugins/host.go |
插件 intents/*.json overlay 采集(:216-220) |
modules/backend/internal/app/app.go |
组合根装配(:309-310、:324、:351/:357) |