games-development-ai/.agents/skills/saa-graph-orchestration.md
lili affb507bc3 docs(.agents): 沉淀 003-U1 e2e 验收门工具化 + 「SAA 路无¥(token≠¥,折¥=F3)」坑
扩 saa-graph-orchestration「真·全图 e2e 测成功率」章一条:SaaFullGraphE2eTest 003-U1 工具化
(n≥10/外部 brief 文件、minSuccessRate 可配硬门默认不设、token 成本会计软警告、模型无关单测),
并记关键坑「SAA 路无 ¥ 数据(SaaGraphDispatcher cost 整段省略,折¥=F3)→ 别接¥门、别造假¥」。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 16:30:43 -07:00

14 KiB
Raw Blame History

SAA 图编排 playbook生成流水 · HJ-AGI-002

定位:用 Spring AI AlibabaSAA v1.1.2.2)裸 StateGraph 在 game-cloudJava/Boot 3.5.14)里搭"确定性多步生成编排"的标准配方。现行 agentic 基建HJ-AGI-002short-term SAA-only何时用:把"渲染→设计→生成代码→[校验/构建/真玩 九门]→修复回环→玩家顾问→出包"这类确定性多步流程落成图。不用 ReactAgent/asNode/subAgents(那是"LLM+工具自治",与"done 由九门确定性门定、不让 LLM 自评"冲突;自治节点延 Phase1.5,见 [saa-agentic-infra-decision 记忆] / docs/agent-specs/agentic编排-SAA.md §10)。 参考:能力/API 速查 docs/agent-specs/2026-06-15-SAA-能力API接入-dossier.md迁移设计9 步计划/拓扑/坑)docs/agent-specs/_archive/2026-06-15-python-to-SAA-migration-design.md;落地实现见 game-module-aigc/.../saa/SaaStudioGraph.java(唯一布线源)/SaaGraphDispatcher.java(生产派发)。


1. 依赖集(最小证成集,别贪"完整推荐集"

game-module-aigc-server/pom.xml3 个 BOM仅管版本不引 spring-boot-dependencies + 3 个直接依赖

<!-- BOMspring-ai-bom:1.1.2 / spring-ai-alibaba-bom:1.1.2.2 / spring-ai-alibaba-extensions-bom:1.1.2.2 -->
<dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-graph-core</artifactId></dependency>            <!-- 图引擎+MysqlSaver+observation 机芯 全在此 -->
<dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-starter-graph-observation</artifactId></dependency> <!-- 仅 Boot 自动配置层 -->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-openai</artifactId></dependency>        <!-- OpenAiApi/OpenAiChatModel接 new-api -->

别引agent-frameworkReactAgent/asNodePhase1.5 才要)、builtin-nodes(节点全自写 NodeAction且它脱 BOM 硬钉 1.1.2.2 拖 tika 全家桶=版本钉债)、SAA 的 Boot BOM(让项目 3.5.14 胜,同 minor 补丁前向兼容)、RedisSaver(避 Redisson 3.22↔4.4 冲突)。 铁律:动 pom 删依赖前,先 dependency:tree/读 jar 核类的模块归属——MysqlSaverGraphObservationLifecycleListener 都在 graph-core,删 agent-framework/builtin-nodes 不影响它们。

2. 拓扑(声明式条件边,非硬编码 while

SaaStudioGraph.assemble()唯一布线源(生产派发器 + 回归测试共用,杜绝两处漂移)。节点 11 个、条件边 4 处:

START→render→design→generate→validate ─┬(ok)→ scaffold→build ─┬(ok)→ play ─┬(ok)→ player ─┬(ok)→ emit→END
                                        ├(repair)→ repair ──────┘(repair)────┘(repair)──────┘(repair)→ generate回环
                                        └(giveup)→ giveup→END
  • 回环/分支用 addConditionalEdges("validate"|"build"|"play"|"player", router, mapping)router 是 EdgeAction 返回 ok|repair|giveup|emit 等字符串键。
  • 超步刹车CompileConfig.builder().recursionLimit(N)repair 成环时优雅终止)+ state 里的 repair 计数器,不靠 LoopAgent
  • 节点 = NodeAction lambda 经 node_async() 注册;状态 OverAllState + KeyStrategyFactory(多数键用 ReplaceStrategy)。

加一个节点:写 NodeAction(读 state→干活→Map.of(key,val) 写回)→ graph.addNode("x", node_async(xNode))addEdge/addConditionalEdges 接进拓扑 → 若新键,在 KeyStrategyFactory 注册其合并策略。

3. 模型接 new-apiOpenAI 兼容)

五角色design/code/fix/player_text/player_vision共用一个 OpenAiApi,仅 defaultOptions.model/temperature 异:

OpenAiApi api = OpenAiApi.builder().baseUrl(stripV1Suffix(newApiBase)).apiKey(key).build();
OpenAiChatModel m = OpenAiChatModel.builder().openAiApi(api).defaultOptions(opts).build();
  • ⚠️ baseUrl 坑Spring AI 自拼 completionsPath=/v1/chat/completions,故 baseUrl 必须是 host 根、不带 /v1http://100.64.0.8:3000);同一 .envNEWAPI_BASE_URL=.../v1)两侧通用须 Java 侧 stripV1Suffix 剥之,否则 404 /v1/v1/...
  • 视觉位M3走多模态 UserMessage.builder().media(Media(IMAGE_PNG, ...))。成本:全走 new-api 单一 key→自动入 newapi_cost(logs.quota) 计费平面,不内联折账。

4. checkpoint崩溃续跑 · 迁移 step5

MysqlSaver saver = MysqlSaver.builder().dataSource(yudaoDruid).createOption(CreateOption.CREATE_IF_NOT_EXISTS).build();
SaverConfig sc = SaverConfig.builder().register(saver).build();
// compile: CompileConfig.builder().saverConfig(sc).releaseThread(false)...   run: RunnableConfig.builder().threadId(traceId)
  • 一图一 saver(注册俩抛异常);releaseThread(false) 才能长期续跑DataSource 直接收 yudao DynamicRoutingDataSource(primary=master)。
  • Flyway 共存:表 GRAPH_CHECKPOINT/GRAPH_THREAD(大写引擎私有 DDL非 Flyway 管)与 yudao 迁移零撞名;测试隔离 saa_spike schema生产 ruoyi-vue-pro 零污染。生产懒建(仅 dispatcher=saa 且 DataSource 在席时首个 dispatch 触 DDLhttp 态零碰 DB
  • 🔴 必踩的框架坑(实证)MysqlSaver.saved_at秒级 TIMESTAMPload checkpoint 的 SQL 仅 ORDER BY saved_at DESC 无 tiebreaker。一次 interrupt 在同秒落多条 checkpoint__START__→n1n1→n2)→ 默认 resume()(取"最新"非确定地选其一→续跑点漂移(节点可能重放)。修法(不改框架):用 getStateHistory 按内容(StateSnapshot.node()=="n1")确定性选中断点、取其稳定 config().checkPointId(),用显式 checkPointId 续跑(走 saver by-id 精确分支,绕开 saved_at 排序)。验证见 SaaCheckpointResumeTest(真恢复:查库 GRAPH_CHECKPOINT 行 + 跨实例 N1_RUNS==1)。
  • ⚠️ resume 实例不要再挂 interruptBeforepreviousNodeId 非 null 会再次停在该点,后续节点永不执行)。

5. 可观测observation 埋点)

SaaStudioGraph.buildObservationRegistry 非空且非 NOOP 时挂 graph-core 的 GraphObservationLifecycleListener + CompileConfig.observationRegistry(..) → 每节点发 spring.ai.alibaba.graph.node.<id> observationtrace/耗时/失败→Micrometer。node id 编码在 observation 名 + 低基数键里(非自定义 context 类型,测试按名前缀捕获)。ObservationRegistryObjectProvider 软注入 yudao 共享 bean。

6. 生产派发契约(框架可换、无 split-brain

  • GenerationDispatcher.dispatch(job) 接口 → 两实现 WorkerDispatchClient(http) | SaaGraphDispatcher(进程内 SAA 图) 经 aigc.executor.dispatcher(默认 http 非破坏) 二选一单写
  • SaaGraphDispatcherjob→state→后台单线程 compiledGraph.invoke(threadId=traceId)→出口组 DifyCallbackReqVO 进程内直调 difyCallbackService.handleCallback(消灭 HTTP+HMAC复用唯一三表同事务写链串行真玩门 Semaphore(1) 守端口;图执行不包大事务(落库走 handleCallback 内短事务);失败统一归 llm_error(图内真因不在契约#6 七值枚举内)。
  • 🔴 铁律「多派发路·回调可观测字段必须每路都 set」回调 trace split-brain与下方发布裁决 split-brain 是两回事)存在多条派发路http worker / SAA 进程内 / executor 旧路)时,任一落库可观测字段(trace_json/readiness_score)若只在部分回调构造点 setdispatcher静默丢字段。实证:SaaGraphDispatcher.buildCallbackReqVO 原只 set status/engineBundle/failureReason、漏 setTracedispatcher=saa(未来主力路)时 trace_json 恒 NULL、组B 9d/D11/D9 在 SAA 路静默失效HTTP 路因 worker _extract_trace 不漏)。修法:派发器回调构造点对称镜像 worker 的 _extract_trace——从图终态 OverAllState 抽与之字节兼容的 camelCase 子集 setTrace,严格 additive + best-effort整段 try-catch 返 null抽不到绝不打断生成+ 复用同一 aigc.trace.enabled flag落库走现成 persistTraceQuietly,零改契约/消费侧。字节兼容红线:抽出键集必须=worker _extract_trace camelCase 子集(stage_fail→stageFail 等 snake→camelattempts 不含 per-attempt guards——SAA 源端三处 appendAttempt 从不写,照 K_ATTEMPTS 注释抽会逼实现造假readiness 读顶层 sevenGateVerdict.guards.H_progress 而非 attempts[].guards否则审核台/D11 两路读不一致。闭合Round3 703e462cHJ-AGI-002 线),真库验 task141 trace_json 真非空/readiness=74、task145开关关NULLverdict agentic编排-SAA.md。坑readiness 真值随真跑质量浮动repairs=5→stability 降、cost 缺→efficiency 中性,故 74<HTTP 路 96firstPlayH_progress 是嵌套对象喂 asBool(Map) 恒中性 0.5 = 组B 既存 bugHTTP/SAA 同病、字节兼容仍成立),修评分器另立 F5 票,严禁为过测把 SAA 端 H_progress 改裸 bool破 HTTP 字节兼容)。
  • 🔴 铁律「worker succeeded ≠ 准予发布」split-brain 防线)SAA worker 跑通九门(runnableOk = 机制可玩:能加载/不崩/有终态)不等于准予发布。发布裁决归治理层——对抗 P0 安全审查(注入/越权/恶意资源)、金丝雀完玩率、人工二审,是与"机制可玩"正交的另一维。生成路生产化前,安全门须由 W-G1 GP9 合规段接管10 负例 staging 真验worker 自身只产"机制可玩"信号、无权置位 published。两者混为一谈 = split-brainworker 自评准予上架。来源agent-loop-v1 退役登记Lane B

7. 验证门(无 harness 端口也能验大半)

  • 门 E依赖/启动)dependency:tree 断言 Boot 全 3.5.14 / redisson 4.4.0 / 零 3.22 泄漏 / 删的依赖消失;test-compile(main+test) 绿;最小 Context 共存不破 BeanCreation。
  • 门 Bcheckpoint 真恢复):纯 Java 2 节点小图(不调 LLM、不碰端口跑一半 interrupt→查 MySQL 断 checkpoint 落库→全新 CompiledGraph 实例 + 显式 checkPointId resume→抵 END 且节点不重放。gated 在 -Dsaa.checkpoint.mysql=1 -Dsaa.mysql.pass=…(密码经 -D 注入不入库)。
  • 真玩 e2e图真跑九门→进程内 handleCallback→建版本/组包/入 feed须占 4320/9222与生成批跑串行flock 单实例锁。harness-gated 测试无 -Dsaa.harness=1 时正确 skip。

真·全图 e2e 测成功率U7 集成顶石,SaaFullGraphE2eTest,2026-06-18 首证)

  • 测法(零 DB 跑真全图)new SaaGraphDispatcher(props, stubCallback, null, NOOP, null)——props 填 llmBase/apiKey/dispatcher=saa/saaGameRuntimeDirsaaCheckpointEnabled=false+DataSource=null内存 checkpoint,零 DBstub DifyCallbackService 只捕获 reqVO 不写库。门控 -Dsaa.e2e=1+env NEWAPI_KEY+-DGAME_RUNTIME_DIR
  • 异步派发测试坑(实翻)dispatch异步+单线程执行器+Semaphore(1) 串行真玩门,且单款墙钟 3.5-16min(九门真玩循环+最多 8 救场轮固有,非 SAA 特有=同 Python W-G1"每条 dispatch 后 arm 新 latch 等 240s"(串行+回调错配=归因全错);正解=一次性投递全 N 条→共享 CountDownLatch(N)→stub 按 reqVO.getTraceId() 精确归位并发 map,每条超时 900s。
  • 首证基线:真全图(render→…→真九门 play→…→emit/giveup)真 LLM+真九门跑通、零 prod bug;成功率 3/5=60% < 80%。成功路 engineBundle≈211KB含引擎,经 __GameBundle 全局名校验才置 succeeded难门 I_control/F_wiring/H_progress 卡 60%——根因非"模型天花板"2026-06-18 根因分析 + Codex 复核证伪:无样本确认真硬天花板;scale-20 无"人工介入后仍不过"的款)。主因 = 无人值守产线的"喂不全 + 救场机制不足 + 门判据偏":命门 = 注入的 few-shot 探针 _goldenpath-probe/factory.js_forensicsView/latch、而 prompt 硬要求它(模型抄没见过的实现);I_control 平滑跟手 harness 查、prompt 零提及;failCount 跨档不重置 + repair 不回喂源码。详见 docs/brainstorms/2026-06-18-生成失败根因分析。便宜模型不稳→救场耗尽 giveup真落盘,非崩溃)。SAA 机制 ≠ 生成质量:转默认产线(U7)的 ≥80% 门由 W-G1 生成质量域把守,非 SAA 编排 bug。
  • 003-U1 验收门工具化plan 2026-06-18-003,可作 flip 前置硬门):把本测从「内置 5 条 / 只断 succeeded>=1(留人判 80%=不可作硬门)」升成客观可运行产线门——① brief 内置扩 12 条多品类 + -Dsaa.e2e.briefFile 外部行式文件(去空白/跳空行/跳 #,空则回退内置);② -Dsaa.e2e.minSuccessRate 可配硬断言默认不设=仅存在性(保 003-U1「先实测真基线再收敛」、不破普通构建显式 0.8 即 flip(003-U2)前置硬门;③ 成本会计 = token 代理,非¥——从回调 trace.tokens.{prompt,completion} + modelTier(升 stage2 计数)汇总,-Dsaa.e2e.maxTokensPerGame软警告不硬阻断(创始人裁「质量优先、允许临时破价、记账对账」)。🔴SAA 路无 ¥ 数据——SaaGraphDispatcher cost v0 整段省略token≠¥,挂 cost 语义错)、折¥=follow-up F3 → 别接 ¥ 门、别造假¥,只记 token。纯逻辑抽静态方法 + 模型无关单测 SaaFullGraphE2eGateLogicTest(普通 mvn test 即跑,不需模型/key,12/12 过)。

关联contract-first-development.mdjob/callback 即契约#6cheap-model-game-generation.md(被编排的便宜模型生成)|staging-ops.mdmini-desktop 构建/门)|决策与坑全集见记忆 saa-agentic-infra-decision