review 暴露生成域正处于 gameDefinition→src/ 终态迁移 + 产线化(plan 2026-06-18-001 U1–U4)。给 11 份子树档插「🚧 架构演进中」横幅 + L1 总索引/架构 README 导航处加注,提示现行结论可能随 plan 推进变化、以最新裁定为准。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
24 KiB
SAA 能力 / API / 接入 · 逐键源码证据底座
🚧 架构演进中 —— 生成引擎子树正处于 gameDefinition→
src/终态迁移 + 产线化(plan2026-06-18-001U1–U4)中,文中标注的现行结论可能随推进变化;以子树 README 与最新裁定 / plan 为准。
这是什么:绘境AI 生成引擎采用 SAA(Spring AI Alibaba,阿里在 Spring 生态里的 AI 编排框架)作为编排基建——这份文档是那一决策背后的逐键源码证据底座。它不重复"为什么这样分层、有哪些不能破的不变量"那套架构论证(那在SAA 编排),而是回答更下沉的一层:照着写代码时,该调哪些 API、本期到底哪些能力能用哪些不能、引进 game-cloud 会撞哪些依赖、按什么范式落节点、最容易踩哪几个坑——每一条都钉在 SAA 某个类的某一行源码上。 给谁看:真正动手把 SAA 图落进
game-module-aigc-server的后端工程师、做依赖冲突评审的人、复核"这条结论有没有源码支撑"的架构评审。 怎么读:这是一份技术参考册,不必线性通读。先扫第 0 节那五条决策级结论建立锚点,然后按需跳——要照着写代码翻第 1 节 API 速查,要判断"这个能力本期能不能用"翻第 2 节能力矩阵,要落 Maven / 解依赖冲突翻第 3 节,要照范式骨架写节点翻第 4 节,动手前务必先过一遍第 5 节那六个坑。
这份文档在生成引擎子树里的定位,是SAA 编排那篇架构文档的证据附录。编排文档讲"这台编排机器长什么样、为什么这么搭";本文档讲"那篇文档里每一条工程结论,在 SAA 源码里能不能落地、落在哪一行"。两者一上一下:看设计读编排文档,照代码写实现、或要复核某条结论的源码出处,读本文档。
调查方法与基线(先讲清证据从哪来)
本文档的全部结论,来自一轮多路独立源码取证:四个 Claude / Opus 子代理(子代理 = 并行派出去独立干活的 AI 调查员)分别从四个视角逐行读 SAA 源码——API 面、能力盘点、接入部署、用法范式——再彼此交叉验证,结论一致。原计划还有 Codex(另一个模型)跑第 5 路,在 51 分钟时卡死被取消;因为前四路已互相印证,这不影响任何结论。
所有取证都钉在同一个代码基线上,引用结论时请认准这个版本:
- 被调查代码库:
/root/oss/spring-ai-alibaba,checkout 到 tag v1.1.2.2(HEAD commit7405a7d)——这是 GA 正式版(GA = General Availability,正式发布版,区别于预览 / SNAPSHOT 快照版),不是预览版。 - 底层依赖:SAA v1.1.2.2 之下压着 spring-ai 1.1.2(Spring 官方的 AI 抽象层,SAA 在它之上做编排扩展)。
- 关联文档:本调查服务于编号 HJ-AGI-002 的那条决策(用 SAA 裸
StateGraph做确定性编排;裁定与不变量见SAA 编排);spike(spike = 一次性的技术验证小实验)执行稿与目标架构 review 在docs/agent-specs/留痕层。
一个口径要先点明:本文档里凡出现具体类名 / 方法名 / 文件行号,都是真实读到的源码事实,不是设计设想。带"⚠️"标记的是框架已知缺陷或反直觉行为——这些正是最容易让人想当然写错的地方,务必当心。
0. 五条决策级结论(锚点)
下面五条是整轮调查蒸馏出的最高层结论。它们既是后续各节的总纲,也是落地时反复要回看的定盘星。
第一,我们的生成流水是确定性多步编排,因此用「裸 StateGraph + 自定义 NodeAction」,不用 agent 自治那套。 StateGraph 是 SAA 提供的"有向状态图"原语——你用它声明一个个节点和节点之间的条件边;NodeAction 是每个节点里那段实打实的业务代码(你自己实现的一个接口)。"裸着用"指我们直接拿这层图原语手写编排,而不套 ReactAgent / flow agent——那一类是给"LLM 自己挑工具、自由决定下一步"的自治场景准备的,与我们"步骤预先固定"的形态不匹配。生成里的 repair(修复重试)环,靠 addConditionalEdges(添加条件边)画一条回边,再配一个放在状态里的计数器来控制轮次,不用 SAA 的 LoopAgent。
第二,模型这一层 SAA 没有自己的封装,直接复用上游 Spring AI 的 OpenAiChatModel / OpenAiApi。 也就是说 SAA 圈里凡调 OpenAI 兼容接口的,都是 Spring AI 原生那两个类。具体接法是:baseUrl 指向 new-api(new-api = 绘境AI 内部统一的模型成本 / 凭证网关,负责 API key 管理与用量计费),每个图节点配一个 OpenAiChatModel bean,这些 bean 共用同一个 OpenAiApi 实例、仅 defaultOptions.model(默认用哪个模型)不同,节点用 @Qualifier 注解取到对应那个。便宜模型(如 DeepSeek)结构完全一样,还有官方原生 starter(starter = Spring Boot 的一键依赖包)可用。
第三,状态持久化用 MysqlSaver 作引擎唯一权威 saver,Redis 只作业务旁路缓存。 saver(保存器)就是把图执行到一半的状态存盘、以便续跑的组件;MysqlSaver 直接收一个 DataSource(数据源),可以把项目现成的 yudao Druid 连接池注进去(yudao 是 game-cloud 后端所基于的那套脚手架,Druid 是它用的数据库连接池)。配合 RunnableConfig.threadId(任务id)(把每个生成任务的 id 当线程标识),就能精确续跑。关键约束:一张图只能注册一个 saver,注册两个会直接抛异常——所以 MySQL 当权威、Redis 当旁路缓存,而不是两个都注册成 saver。另外必须设 releaseThread(false),图才能长期续跑而不被框架回收线程。
第四,把 SAA 引进 game-cloud 的依赖冲突风险是「低到中、可控」。 只引 graph-core(图引擎核心)加三个 BOM(BOM = Bill of Materials,依赖版本管理清单,统一锁定一组相关依赖的版本);绝不引 SAA 自带的 Boot BOM(那会让项目自己的 Spring Boot 3.5.14 输给 SAA 内置的 3.5.8);只要不用 RedisSaver,就避开了 Redisson(一个 Redis 的 Java 客户端)3.x 与 4.x 的版本冲突。其余依赖双方完全一致:fastjson、okhttp 版本都对齐;而且 graph-core 不引入任何 web 框架(只依赖 reactor-core),不会和 yudao 的 spring-mvc 抢运行栈。
第五,运行形态是「进程内嵌 + 后台线程池跑图」,而不是独立 worker。 我们抽一个 GenerationDispatcher(生成派发器)接口,挂在现有 AigcTaskService.submitGenerate → completeWithVersion 这条契约后面;它有两个实现——HttpWorkerDispatcher(走外部 HTTP worker)和 SaaGraphDispatcher(进程内跑 SAA 图),灰度切换、框架可换。图用 SAA 自带的阻塞式 invoke() 在后台线程池里跑(禁止占用 Tomcat 请求线程或 Reactor 事件循环线程),而且图的执行过程不包大事务。独立 worker 进程那条路留到 Phase 2(第二阶段)再说。
1. 关键 API(照着写代码那一层)
这一节是"打开 IDE 就能照抄"的 API 速查。按图怎么搭、状态怎么读写、怎么执行、模型怎么配、怎么持久化、怎么流式输出六块组织。每一行括号里的注解,是这个 API 在我们场景下的用法要点或坑。
搭图(StateGraph 的骨架 API)
new StateGraph(name, KeyStrategyFactory)—— 新建一张图;KeyStrategyFactory(键策略工厂)声明状态里每个 key 在合并时用什么策略,见下一块。addNode(id, node_async(NodeAction))—— 加一个节点;node_async(...)是把你写的NodeAction包装成框架内部的异步节点。addEdge(START, ..)/addEdge(.., END)—— 加一条无条件边;START/END是框架预定义的起止虚拟节点。addConditionalEdges(from, edge_async(EdgeAction→String), Map<key,目标节点>)—— 加条件边:EdgeAction返回一个字符串 key,框架按Map把这个 key 映射到下一个节点。生成里的 repair 回边就靠它;⚠️ 那个Map(mappings)不能为空,空了会报错。compile(CompileConfig) → CompiledGraph—— 把声明好的图编译成可执行的CompiledGraph;CompileConfig里挂 saver、中断点等编译期配置。
状态读写契约(最反直觉、最容易写错的一块)
节点里读写状态不是用 setter / getter,而是:
- 读:在
NodeAction.apply(OverAllState)方法里,用state.value("k", T.class)按 key 取值(OverAllState是贯穿整张图的那个全局状态对象)。 - 写:靠
return Map.of("k", v)—— 你返回一个 Map,框架按每个 key 配置的KeyStrategy(键策略)去合并进全局状态,而不是你直接改对象。ReplaceStrategy= 覆盖旧值,AppendStrategy= 追加(比如把新消息追加进 messages 列表)。 - ⚠️ 记住这条:写状态 = 返回 Map,不是调 set。习惯了命令式写法的人在这里几乎必踩。
执行图
invoke(inputs, RunnableConfig) → Optional<OverAllState>—— 同步阻塞执行,跑完返回最终状态。它内部其实就是stream().last().block(),自带阻塞——这正是第 3 节要强调"必须丢后台线程池跑"的原因。stream(...) → Flux<NodeOutput>—— 流式执行,返回一个Flux(reactor 的响应式流),逐个吐出节点输出。
模型(上游 Spring AI 三件套,照此装配)
OpenAiApi.builder()
.baseUrl(NEW_API) // 指向 new-api 网关
.apiKey(KEY)
.build();
OpenAiChatModel.builder()
.openAiApi(api) // 共用同一个 OpenAiApi
.defaultOptions(
OpenAiChatOptions.builder()
.model("deepseek-chat") // 各节点仅此处不同
.build())
.build();
持久化(saver 的装配链)
MysqlSaver.builder().dataSource(ds).createOption(CREATE_IF_NOT_EXISTS).build()—— MySQL 保存器,CREATE_IF_NOT_EXISTS表示表不存在就自动建。RedisSaver.builder().redisson(redissonClient).build()—— Redis 保存器(⚠️ 引它就拖进 Redisson,见第 3 节冲突 C2;我们不用它当 saver)。- 装配链:
SaverConfig.builder().register(saver).build()→CompileConfig.builder().saverConfig(..).releaseThread(false)→RunnableConfig.builder().threadId("..")。releaseThread(false)是长期续跑的开关,threadId是续跑的定位键。
流式输出(往前端推 token 增量)
stream() → Flux<NodeOutput>—— 拿到节点输出流。- 用
instanceof StreamingOutput区分哪个输出是 LLM 吐出的 token chunk(增量片段),再.message().getText()取这一段增量文本。 - ⚠️ SAA 没有
streamEvents这个 API(有些框架有,SAA 没有)——只能走stream()+instanceof这条路判类型,别去找streamEvents。
2. 能力盘点:本期(只有 MySQL + Redis)能用 vs 要等 Phase 2
这一节回答一个很实际的问题:在只部署了 MySQL + Redis 的现状下,SAA 的哪些能力当下就能用,哪些必须等更多基建到位? 误判这条边界,要么白白等基建、要么起了个根本起不来的重组件。下面这张图先给全景,再逐条列。
flowchart TB
subgraph NOW["✅ 本期即可落 — 纯 JVM 或仅需 MySQL+Redis"]
direction TB
C1["图编排全集<br/>条件 / 并行 / 嵌套子图 / 循环"]
C2["agentic ReAct + 工具调用"]
C3["多 agent<br/>sequential / parallel / routing / conditional / loop"]
C4["持久化 checkpoint / resume<br/>MysqlSaver(仅 MySQL) · RedisSaver(仅 Redis)<br/>均已验证零 Nacos / 零 MQ import"]
C5["流式 · HITL 中断恢复"]
C6["Hook ×10 / 拦截器 ×13"]
C7["OpenAI 兼容(new-api) + DeepSeek"]
C8["A2A 点对点 · 可观测埋点"]
end
subgraph LATER["⛔ 需 Phase 2 基建"]
direction TB
P1["完整 Admin 平台<br/>MySQL+Redis+ES+RocketMQ+Nacos 五件套全硬<br/>缺一启动失败"]
P2["A2A-Nacos 服务发现 · config-nacos · MCP-Nacos 网关"]
P3["sandbox(Docker 容器隔离)"]
P4["RAG 走 ES · Prompt 线上热更(写 Nacos)"]
end
style NOW fill:#eafaf1
style LATER fill:#fdedec
✅ 本期即可落(纯 JVM,或仅依赖 MySQL + Redis,覆盖我们的核心用例):
图编排全集(条件分支 / 并行 / 嵌套子图 / 循环都有);agentic ReAct(ReAct = Reason+Act,让模型"边推理边调工具"的经典范式);工具调用;多 agent 的全部组合(sequential 顺序 / parallel 并行 / routing 路由 / conditional 条件 / loop 循环);持久化的 checkpoint(检查点存盘)与 resume(续跑)——MysqlSaver 只需要 MySQL、RedisSaver 只需要 Redis,二者都已逐行验证零 Nacos、零 MQ(消息队列)的 import(Nacos = 服务注册与配置中心,MQ = 消息中间件);流式输出;HITL(Human-In-The-Loop,人工介入环节)的中断与恢复;Hook(钩子)10 个、拦截器 13 个;OpenAI 兼容接口(经 new-api)加 DeepSeek;A2A(Agent-to-Agent,智能体间通信)的点对点形态;可观测埋点。
⛔ 需要 Phase 2 基建(现状起不来):
完整的 Admin 平台——它把 MySQL + Redis + ES(Elasticsearch,搜索 / 向量库)+ RocketMQ(消息队列)+ Nacos 五件套全部硬依赖,缺一个就启动失败;A2A 的 Nacos 服务发现;config-nacos(配置走 Nacos);MCP-Nacos 网关(MCP = Model Context Protocol,模型上下文协议,给模型挂外部工具 / 数据的标准接口);sandbox(基于 Docker 的容器沙箱隔离);RAG(检索增强生成)走 ES;以及 Prompt 的线上热更新(它要写 Nacos)。
两条最容易误判的边界,单独拎出来强调:
- 「Admin 整包」≠「有 MySQL + Redis 就够」。 Admin 是个重组件,要五件套。如果只是想用编排引擎,千万别起 admin,直接用
graph-core+agent-framework的库依赖即可——这是本期能落地的关键前提。 - 向量库在这份 clone 里只有 ES 一个具体实现。 想做 RAG / 向量检索,本期实质上被 ES 这一依赖绑住,属于 Phase 2。
3. 接入 game-cloud(依赖冲突解法放最前)
这一节给真正动手把 SAA 引进后端的人。最该先解决的是依赖冲突——所以把它放在最前。整轮调查的结论是:真正的冲突只有两处,其余全是零冲突。
3.1 两处真冲突及解法
flowchart LR
subgraph C1["C1 · Spring Boot 版本"]
direction TB
C1a["项目:3.5.14<br/>(huijing-dependencies BOM 实际生效)"]
C1b["SAA 要:3.5.8"]
C1c["✅ 解:不引 SAA 的 Boot BOM<br/>让 3.5.14 胜<br/>(同 minor 补丁前向兼容)"]
C1a --- C1b --- C1c
end
subgraph C2["C2 · Redisson 版本"]
direction TB
C2a["项目:4.4.0"]
C2b["SAA graph-core:3.22.0<br/>(但 optional=true)"]
C2c["✅ 解:不用 RedisSaver<br/>→ 零冲突"]
C2a --- C2b --- C2c
end
- C1 — Spring Boot 版本:项目实际生效的是 3.5.14(由
huijing-dependencies这个 BOM 锁定),SAA 想要 3.5.8。解法:不引 SAA 自带的 Boot BOM,让项目的 3.5.14 胜出。 二者是同一个 minor 版本(3.5.x)下的补丁差异,前向兼容——jackson / reactor / spring 都只差补丁号,实测安全。 - C2 — Redisson 版本:项目用 4.4.0,SAA 的
graph-core里写的是 3.22.0——但它标了optional=true(可选依赖,不主动传递)。解法:只要不用RedisSaver,就零冲突。 如果将来非用不可,需要实测 Redisson 4.x 对RMap/RBucket/RLock这三个 API 与 3.22.0 的兼容性。 - 零冲突的部分:fastjson(1.2.83)、okhttp(4.12.0)双方版本完全一致;
graph-core不引任何 web 框架,不会与 yudao 的 spring-mvc 抢栈。
3.2 Maven 怎么加(只动一个 pom)
所有依赖只加到 game-module-aigc-server/pom.xml,不动根 POM / 根 BOM:
- import 三个 BOM:
spring-ai-bom:1.1.2+spring-ai-alibaba-bom:1.1.2.2+spring-ai-alibaba-extensions-bom:1.1.2.2。 - 依赖只加
spring-ai-alibaba-graph-core(图引擎,唯一必需的)+ 一个模型 starter(走 new-api 就用spring-ai-openai)。
3.3 reactive↔blocking 桥接(防死锁的命门)
CompiledGraph.invoke() 自带阻塞(内部调了 .block()),所以必须在后台 ExecutorService(线程池)里调它,绝不能在 Web 线程或 Reactor 线程上调——否则就是经典的"在 reactor 线程里 block"死锁。超时控制用 Future.get(timeout) 从外面包一层;取消接现有的 cancelTask 逻辑。图的执行不要包在大 @Transactional 事务里,跑完之后单独开一个短事务落库即可。
3.4 最小验证门 E:6 步,在 mini-desktop 上跑
门 E(门 = gate,一道必须通过才算数的验收检查;mini-desktop 是内网那台用来跑验证的桌面机)的设计哲学是先验最便宜的那个命题——"SAA 这套库到底能不能干净地进 game-cloud 这个进程",而把模型、saver、job 链全部隔离在外、暂不接入。六步如下:
- 加三个 BOM +
graph-core。 - 跑
mvn -pl …aigc-server -am dependency:tree并断言:Spring Boot 全是 3.5.14、Redisson 是 4.4.0、没有 3.22.0 泄漏进来、jackson 是 2.21.x。 - 写一个纯 Java 节点(不调任何 LLM)的 "hello"
StateGraph。 mvn package过编译门。- 单体启动门:验证 SAA 与 yudao 的自动配置能共存,没有
BeanCreation失败。 - 调
invoke跑那一个节点,断言返回了预期 state。
门 E 的精髓:这一步先不接模型、不接
MysqlSaver、不接 job 链——只隔离验证"SAA 库能进 game-cloud 进程"这个最便宜、最该先确认的命题,把昂贵的集成验证留到后面。
4. 面向我们生成流水的用法范式(7 条)
这一节是"该怎么用 SAA 把我们的生成流水搭出来"的范式手册——七条,每条都对应生成主线的一个实际需求。完整的节点骨架代码在各子代理报告里,这里给的是范式要点与对应 API。
-
主干 =
StateGraph+ 每步一个node_async(NodeAction)。 生成主线按 render → generate → validate → scaffold → build → play → emit 一步步串(解析意图 → 生成 → 校验 → 脚手架 → 构建 → 真玩 → 发布),每一步是图里一个节点。 -
repair 环 = 条件回边 + 状态计数器。 写法:
addConditionalEdges("validate", edge_async(s → ok ? "done" : cnt >= N ? "giveup" : "repair"), …)——校验通过就走 done,重试超过 N 次就 giveup,否则回到 repair。轮次靠状态里的repair_count计数器控制。另有一个recursionLimit(递归上限,默认 100)作硬刹车——注意它触发时是优雅终止、不是抛异常,而且它是兜底,不替代业务自己的 max-iters(最大迭代次数)上限。 -
build / play 节点 = 自定义
NodeAction里用ProcessBuilder调子进程。 build 步在节点里new ProcessBuilder(...)起一个 shell 调 node / esbuild(esbuild = 一个极快的 JS 打包工具);play 步同样起子进程调 CDP 九门 harness(CDP = Chrome DevTools Protocol,Chrome 的调试协议,可用程序操控真实浏览器;"九门 harness" = 一套在真浏览器里跑九道确定性验收检测的测试夹具,详见验收门 W-G1)。用mapper.readTree收子进程吐回的 JSON,用waitFor(timeout)/destroyForcibly控超时与强杀。⚠️ 别用 SAA 的 sandbox 模块——那是给 AgentScope 远程容器用的,与我们本地子进程形态不符;节点骨架可以照抄LocalFilesystemBackend.java的第 457–507 行。 -
进度推前端 = 节点返回的 Map 里塞一个
Flux。 节点apply返回的 Map 里放一个Flux,框架会自动在stream()上把它展开;出口侧用@PostMapping(produces = TEXT_EVENT_STREAM_VALUE) Flux<ServerSentEvent<String>>以 SSE(Server-Sent Events,服务器推送事件)往前端吐进度。 -
checkpoint = MySQL 单权威 saver +
releaseThread(false)。 Redis 作业务旁路;同一个threadId第二次invoke即自动续跑;要做 time-travel(回到历史某个状态点)用getStateHistory配checkPointId。 -
可选 HITL = 节点实现
InterruptableAction。 在interrupt()里按状态里的review_required开关决定停下等人、还是放行(条件式,默认自动放行);恢复时用updateState(...)+withResume()+stream(null, cfg)。⚠️HumanNode已废弃,而且 HITL 必须配 saver 才能用(没有 saver 中断后没法续)。 -
模型 = N 个
OpenAiChatModelbean 全指 new-api、仅 model 不同,节点@Qualifier取。 ⚠️ 一个反直觉点:AGENT_MODEL_NAME不是"选哪个模型"的机制——它只是节点 ID 加流式事件的前缀,别误以为改它能换模型。
5. 六个最容易踩的坑(四路交叉验证确认)
这一节是整份文档里最该先读的部分。下面六个坑,每一个都是框架的已知缺陷或反直觉行为,而且都经四路独立取证交叉确认。动手前过一遍,能省掉绝大多数返工。
| # | 坑(框架的反直觉 / 缺陷) | 正确做法 |
|---|---|---|
| 1 | HumanNode 整个文件被注释掉、不可用 —— 想当然去 new 一个 HumanNode 会找不到。 |
用 CompileConfig.interruptBefore / interruptAfter 设中断点 + resume() 恢复。 |
| 2 | 没有 streamEvents 这个 API —— 别去找。 |
走 stream() → Flux<NodeOutput> + instanceof StreamingOutput 判类型取增量。 |
| 3 | ReAct 循环默认无上限(上限是 Integer.MAX_VALUE,等于没上限)—— 用 ReactAgent 时不挂限制会无限烧。 |
用 ReactAgent 时生产环境必须挂 ModelCallLimitHook / ToolCallLimitHook;我们走裸 StateGraph,则靠状态计数器 + recursionLimit 兜底。 |
| 4 | supervisor / handoff 没真正实现(只有枚举占位,不能用)—— 以为能直接用多 agent 的 supervisor 模式会落空。 | 用 routing(路由)模式替代。 |
| 5 | 一张图只能注册一个 saver(注册第二个直接抛 IllegalStateException)。 |
MySQL 当权威 saver + Redis 当业务旁路缓存;真要双写就自己写一个双写装饰器,而不是注册两个 saver。 |
| 6 | Admin 整包硬依赖五件套(MySQL + Redis + ES + RocketMQ + Nacos,缺一启动失败)—— 只想用引擎却起了 admin 会卡在启动。 | 只要引擎就别起 admin,直接用 graph-core + agent-framework 的库依赖。 |
6. 深度参考
本文档是决策级蒸馏——把四路源码取证的结论收成一份可照着落地的参考册。再往下两个方向的细节,通过指针跳转,不在此展开:
| 主题 | 位置 |
|---|---|
全量逐行证据(每个类的 文件:行号,含 API 速查 / 能力矩阵 / 接入部署 / 用法范式四份子代理报告原文) |
本轮四个子代理报告(docs/agent-specs/ 留痕层) |
| 上层架构论证(为什么是 SAA-only、16 节点编排拓扑、六条不变量、split-brain 防线、与旧编排器边界) | SAA 编排 |
落地坑清单的扩充版(Semaphore(1) 串行守端口 4320 / 9222、checkpoint saved_at 无 tiebreaker 的显式 checkPointId 修法等) |
.agents/skills/saa-graph-orchestration.md |
| 这套编排在生成主线里生成什么、生成产物长什么样 | 固定游戏架构 · 生成引擎主文档 |
本文档定位:这是生成引擎子树里的技术参考底座——SAA 的 API / 能力 / 接入 / 坑,逐键钉在 v1.1.2.2(HEAD
7405a7d)源码上。它是SAA 编排那篇架构文档的证据附录:看设计读编排篇,照代码写实现、或复核某条结论的源码出处读本篇。承重事实(基线版本、三 BOM +graph-core、C1/C2 两处冲突、门 E 六步、六个坑、MysqlSaver单权威 +releaseThread(false)、AGENT_MODEL_NAME非选模型机制等)均为四路独立取证交叉验证的源码事实;品牌统一为"绘境AI"。