结论先说:编排的价值在约束。场景用 YAML 声明、加载期编译成 LangGraph 子图;总控不查库、不发通知、不判审批;数字只从 SQL 来,模型只负责解释。这三句听起来像架构原则,实际每一条都是踩坑换来的。
一、场景不写代码
7 个对话场景 + 接单评审,一共 8 份 YAML:声明节点和依赖,加载期编译成 LangGraph 子图。节点类型只有五种——`agent` / `fan_out` / `fan_in` / `workflow` / `hitl`,每个节点都带 `timeout_s` 和 `on_error: degrade`。
好处不是「少写代码」,而是改流程不用动主干。加一个场景、调一个超时、把某个步骤从串行改并行,都是改声明。
坑点:流程一旦写死进代码,第三个场景就开始复制粘贴,第五个场景没人敢改。声明式的红利在第四个场景之后才显现。
二、总控的三条边界
总控(Orchestrator)只做编排,不做业务动作:
- 不直接查库——必须经 Text2SQLService;
- 不直接发通知——必须经 NotificationService;
- 不判审批——必须经 WorkflowEngine。
每条边界后面都对应一套能力:权限、脱敏、溯源、会签。总控「顺手」查一次库,这些就全绕过去了。
坑点:捷径都是从一个 `# 这里直接查一下更快` 开始的。
三、数字只能来自 SQL
问数链路有八道防线:仅 SELECT、多语句拦截、`sqlglot` AST 提取表名、表级 ACL fail-closed、行数上限、按角色脱敏、provenance 全溯源、人工兜底。数字从哪张表、哪条 SQL 来,全程可追溯。
LLM 掉线时走离线降级:规则路由 + 模板结论,响应里显式标 `degraded`。注意——降级的是表达,不是数据;数字仍然取自 SQL,不编造。
坑点:让模型「总结一下数据」,很容易变成「编一个看起来合理的数字」。演示时好看,生产里致命。
四、别让每次提问都过大模型
路由分两级:L0 规则快路径先拦,L1 小模型做闭集意图分类,模糊问题才落到长尾。L1 小模型同时负责意图分类和 SQL 初版,L2 大模型只写分析报告。
可用性探针带 TTL 缓存,LLM 不可用时自动降级,不让一次超时拖住整条链路。
坑点:自由路由看起来灵活,实际不可测。闭集分类 + 模糊兜底,才能把失败率压下来,也才敢谈成本。
五、协议接入不是后门
MCP 和 A2A 都是最小自实现:前者是 stdio JSON-RPC 2.0(MCP 2024-11-05 的工具子集),后者是 Agent Card + `message/send` / `tasks/get`。外部智能体拿到的是一致的 Task + Artifact。
关键是:走协议的调用和 HTTP 端点共用同一套 ACL、脱敏、会签;工具参数里的角色覆盖不了令牌身份,`role` 只能收窄,不能扩宽。
坑点:很多「Agent 互联」演示绕过了治理。接进企业时,第一个问题就是「这条调用算谁的身份」。
怎么验证
除了正常路径,更值钱的是坏路径的断言:依赖超时、LLM 掉线、越权调用、跨域详情、非法状态流转、判不出工段。项目用 1,896 条离线测试覆盖这些,CI 在三个 Python 版本上跑。
编排的正确性,不靠 demo 跑一遍,靠「出事的时候它会不会乱来」。
如果重来一次
- 先定节点契约,再写节点实现。否则每个 Agent 都会长出自己的参数格式。
- 每个节点都要有超时和降级。默认策略不该是「重试到成功」,而是「失败也可解释」。
- 让数字和解释分家。SQL 出数,模型出话;两者混在一起,就再也说不清是谁错了。
- 场景 YAML 进 CI 校验。加载期报错,永远比线上报错便宜。