execution-log.md 14 KB

变更日志 — AI 模型路由与调用治理

本文件持续记录提案、实现、验证和审查证据。变更已于 2026-07-11 归档。

时间线

时间 阶段 事件 备注
2026-07-11 Research 读取根 AGENTS、code-copilot 规则、相关知识、战略方案和已归档 Provider Adapter Spec 以实际代码和 Spring AI 1.1.2 本地发布 Jar 为事实来源
2026-07-11 Research 检查 AiModel、AiAgent、AiInvocationResolver、AiClientImpl、CircuitBreaker、模型/Agent 管理页面 确认当前只有固定优先级,无能力目录、策略路由和完整调用审计
2026-07-11 Research 核对 Spring AI 1.1.2 ChatResponse/Metadata/Usage API CallResponseSpec#chatResponseChatResponseMetadata#getUsage 可提供统一 Token 数据
2026-07-11 Decision 用户选择显式候选切换 调用前跳过已知 OPEN 候选;调用发出后禁止自动重试
2026-07-11 Decision 用户选择后端闭环 + 最小管理界面 不建设治理大屏,交付模型能力、策略、Agent 绑定、调用记录
2026-07-11 Decision 用户选择真实调用被动统计 + 手动测试 默认不执行定时付费探测,预留 HealthRegistry/Probe 扩展点
2026-07-11 Proposal 创建 spec.md、tasks.md、test-spec.md、execution-log.md 等待静态自审和 Reader Test
2026-07-11 Reader Test 1 未通过:显式 provider/model 决策表和调用观察契约存在阻断 补齐四种显式组合、UNKNOWN dispatched 边界、Observation 可空性与安全字段
2026-07-11 Reader Test 2 未通过:默认供应商和 HALF_OPEN 试探权契约存在阻断 固定 requireEnabledDefaultProvider 恰好一条契约,引入幂等 AiModelHealthLease
2026-07-11 Reader Test 3 未通过:路由原因未落审计,RouteDecision 与 Lease 耦合 增加 source/reason 与 route_source/route_reason;拆分纯数据 RouteDecision 和 RoutedInvocation
2026-07-11 Reader Test 4 PASS 独立读者确认最新四份文档无阻断级歧义、矛盾或占位符
2026-07-11 Proposal Validation 完成静态一致性与空白检查 P95 统一为 nearest-rank;四份文档 whitespace check 无错误;占位符扫描无输出
2026-07-11 Apply 完成模型能力、显式策略路由、模型级健康状态、调用审计、Agent 模式与最小管理端 未引入 Nacos/MCP/Agent Framework,未增加调用失败后换模型
2026-07-11 Apply Fix 修复策略分页 N+1、策略候选 tenantId 丢失、HALF_OPEN 准备阶段 Lease 泄漏与流式异步审计上下文 增加跨租户候选回归测试;删除旧 agentCode CircuitBreaker
2026-07-11 Apply Validation AI 插件 63 tests、Admin package、前端 ESLint/build、XML/SQL/安全扫描通过 实库 Flyway、真实模型和浏览器主路径按条件跳过,状态进入 review
2026-07-11 Review Fix 修复内容安全/取消分类、治理组件可选装配、管理页功能缺口和 P0 测试缺失 Resolver 只走 Router;核心依赖强制构造器注入;页面补齐完整目录、候选状态、跳过原因和筛选权限
2026-07-11 Review Fix Validation AI 插件 84 tests、Admin 35 modules、前端 ESLint/build、XML/安全/空白扫描通过 不调用公网模型;实库 Flyway 与浏览器主路径继续保留为人工条件项
2026-07-11 /archive 归档 核对四份变更文档、长期记忆、Review Fix 结论、验证证据和跳过项 状态更新为 done;知识已沉淀到 decisions 17、pitfalls 105/106;条件验证项如实保留,未推送

Research 证据

结论 代码出处 影响
模型解析只有请求、Agent、供应商默认三级 client/AiInvocationResolver.java#resolve/#resolveModel 路由需要独立 Router,不应继续扩张 Resolver 条件分支
模型没有能力和价格字段 model/domain/AiModel.java 新增能力关系表,主表只加数值治理字段
Agent 只能固定 provider/model agent/domain/AiAgent.javaviews/ai/agent.vue 增加 PINNED/POLICY 模式,历史 null 兼容 PINNED
熔断按 agentCode 且只在本机内存 client/CircuitBreaker.javaAiClientImpl#call/#stream 改为 tenantId/providerPk/modelPk HealthRegistry SPI
同步调用只取 content AiClientImpl#call 改取 ChatResponse 才能得到 Usage
流式已消费 ChatResponse 但未聚合 Usage AiClientImpl#stream 捕获最后一个有效 Usage,保证只审计一次
聊天记录不足以承载治理审计 chat/domain/AiChatRecord.java 新建 append-only 调用日志,不把业务消息表继续扩张
当前 INFO 日志打印 Prompt/响应内容 AiClientImpl#call/#stream 本变更删除内容日志,只保留长度和 requestId
Provider Adapter 已统一模型创建 provider/adapter/AiProviderAdapterRegistry.java#createChatModel Router 只选择目标,不侵入 Adapter
战略路线要求 Nacos/MCP 分阶段 docs/Forge-AI中枢战略与技术选型方案.mdmemory/decisions.md#16 本变更不引入 Nacos、MCP 或 Agent Framework

技术决策

决策 选择 放弃方案 原因
路由范围 策略显式候选 扫描所有启用模型 防止不可解释跨供应商
路由算法 能力全包含 + priority/id 排序 权重随机/动态评分 确定性、可复跑
失败后处理 不重试 请求失败后换模型 避免重复计费和 Tool 副作用
健康实现 SPI + 默认内存状态机 当前直接接 Redis/Nacos 控制当前依赖并保留替换点
健康采集 被动调用 + 手动测试 定时付费探测 无额外费用
能力存储 关系表 + 字典代码 boolean/单 JSON 新能力可扩展且可查询
Token Spring AI Usage 字符数估算 不伪造精度
成本 分/百万 Token + 价格快照 double/小数货币字段 遵循 Forge 金额规则
管理端 最小配置和调用记录 完整可观测大屏 YAGNI,不延误后续 MCP

踩坑记录

问题 原因 解决方案 沉淀?
失败后切换候选容易被误称为普通重试 LLM 调用可能计费且 Tool 调用可能有副作用 只允许调用前跳过已知 OPEN;请求发出后禁止切换 待实现验证后沉淀
用 AiChatRecord 兼做治理日志会混淆消息与调用 一次调用包含两条消息且缺少路由/错误元数据 独立 invocation log 待实现验证后沉淀
字符数不能替代供应商 Token Usage 编码和 tokenizer 不同 Usage 缺失时明确标记不可用 待实现验证后沉淀
agentCode 熔断不能表达模型健康 一个 Agent 可换模型、多个 Agent 可共享模型 健康键改为 tenantId/providerPk/modelPk 待实现验证后沉淀

知识发现

  • 确定性模型路由边界:调用前健康跳过与调用后自动重试必须区分,后者会带来费用和副作用风险。
  • 模型能力目录:能力代码应独立于供应商品牌和 Adapter,供 Agent/MCP/路由共同消费。
  • 调用治理数据:聊天内容、框架 Micrometer 指标和租户调用审计是三种不同数据,不应复用一张表。
  • 控制面可替换性:Router/Health SPI 是以后接 Redis/Nacos 的稳定接缝,业务调用不应读取注册中心 SDK。

Spec-Code 偏差记录

偏差点 Spec 预期 实际情况 处理方式
自动化场景覆盖 Test Spec 计划完整 P0 场景 Review Fix 已补齐点名缺失的四个测试类及失败分类、OPEN 跳过、默认供应商、健康隔离等高风险边界;仍未逐条穷举 Test Spec 全矩阵 已覆盖本轮阻断,未执行项继续保持未勾选,不把条件项标记为已验证
条件验证 Flyway 实库与浏览器响应式主路径 本轮未启动数据库、后端或前端服务 保留为 review/人工验收项,不阻断代码进入审查

验证记录

时间 变更范围 命令 结果 警告/跳过 服务清理
2026-07-11 Research rg/sed 检查 AI 插件、管理端、战略文档;javap 检查 Spring AI 1.1.2 ChatResponse/Usage 已确认现状与可用 API 本阶段不运行 Maven/前端构建,不调用模型 无服务启动
2026-07-11 Proposal 静态校验 git diff --no-index --check /dev/null <spec/tasks/test-spec/execution-log> 四个文件均无 whitespace 错误输出;exit 1 仅表示 no-index 存在预期差异 本轮仅文档,不运行 Maven、前端构建或公网模型测试 无服务启动
2026-07-11 Proposal 占位符/契约扫描 rg -n -e 'T[B]D' -e 'TO[D]O' -e '待[补]充' -e '稍后[实]现' -e 'fill[ ]in' -e 'implement[ ]later' -e '归一化或明确[拒]绝' -e 'null[/]0' -e '近似[ ]P95' code-copilot/changes/ai-model-routing-governance;RouteDecision/RoutedInvocation/routeReason/Retention/P95 交叉扫描 禁止占位符无输出;关键契约在 Spec/Tasks/Test Spec 命名一致 未执行业务测试,因为 /apply 尚未开始且无业务代码变更 无服务启动
2026-07-11 最终 Reader Test 独立无上下文读者复核最新 spec.md、tasks.md、test-spec.md、execution-log.md PASS HARD-GATE 仍等待用户通过 /apply ai-model-routing-governance 明确进入编码 无服务启动
2026-07-11 Apply 前基线 Java 17 执行 mvn -Penable-tests -pl forge-framework/forge-plugin-parent/forge-plugin-ai -am test 51 tests,0 failure/error/skip,BUILD SUCCESS 不调用公网模型 无服务启动
2026-07-11 Apply 后 AI 插件回归 同一 Java 17 命令复跑 AI 插件及依赖测试 最终 63 tests,0 failure/error/skip,BUILD SUCCESS;基线 51 tests 全部保留 Spring JCL/Commons Logging 提示为既有非阻断警告 无服务启动
2026-07-11 主应用装配 mvn -pl forge-admin-server -am package -DskipTests 35/35 reactor modules SUCCESS,生成 forge-admin-server.jar generator/admin 既有 deprecated/unchecked 警告;本命令按设计跳过测试 无服务启动
2026-07-11 前端静态与生产构建 Node v20.19.0 执行目标 ESLint --fixpnpm build ESLint 通过;8487 modules transformed,约 1m40s 构建成功 既有 UserSelectModal 命名、动静态 import、CSS //、bundle size 警告,不阻断 无服务启动
2026-07-11 XML/Flyway 静态检查 xmllint --noout .../mapper/*.xmlrg -n '\$\{[^}]+\}' forge-server/db/migration;V1.0.18 tenantId/版本扫描 XML 解析通过;placeholder 与 tenantId=0 扫描无输出;V1.0.18 版本唯一 未连接隔离 MySQL,未写 forge_schema_history 无服务启动
2026-07-11 安全与重试扫描 扫描 gpt-3.5-turbo、Prompt/响应正文日志、API Key/Header/nativeUsage、retry/CircuitBreaker;git diff --check 与行尾空白扫描 禁止项无输出;旧 agentCode CircuitBreaker 已删除;空白检查通过 治理实体仍保存 adapterCode/providerModelId 等允许的非敏感元数据 无服务启动
2026-07-11 条件项 未执行 Flyway 实库、真实供应商模型测试、浏览器响应式/键盘主路径 明确跳过 避免污染用户数据库、产生模型费用;用户此前要求 UI 自测 无服务启动
2026-07-11 Review Fix 定向回归 AiModelFailureClassifierTest,AiInvocationResolverTest,AiClientRoutingGovernanceTest,AiModelRoutePolicyServiceTest,AiAgentServiceTest,AiInvocationLogRetentionJobTest,PolicyBasedAiModelRouterTest 22 tests,0 failure/error/skip,BUILD SUCCESS Fake/Mock ChatModel,不访问公网、不产生费用 无服务启动
2026-07-11 Review Fix AI 插件完整回归 Java 17 执行 mvn -Penable-tests -pl forge-framework/forge-plugin-parent/forge-plugin-ai -am test 84 tests,0 failure/error/skip,BUILD SUCCESS 流式错误测试会输出预期异常栈;Spring JCL/Commons Logging 为既有提示 无服务启动
2026-07-11 Review Fix 主应用装配 mvn -pl forge-admin-server -am package -DskipTests 35/35 reactor modules SUCCESS,生成可执行 Jar generator/admin 既有 deprecated/unchecked 警告;测试按命令跳过 无服务启动
2026-07-11 Review Fix 静态检查 目标 ESLint --fixxmllint --noout;扫描 optional injection、gpt-3.5-turbo、Flyway placeholder;tracked/untracked whitespace check 全部通过,禁止项无输出 未启动浏览器与数据库 无服务启动
2026-07-11 Review Fix 前端最终回归 Node v20.19.0 执行目标 ESLint --fixNODE_OPTIONS=--max-old-space-size=8192 pnpm build ESLint 通过;8487 modules transformed;生产构建约 1m38s 成功 既有组件命名、动静态 import、CSS // 和 bundle size 警告,不阻断 无服务启动

代码质量备忘

  • Router 不得循环查询候选模型、供应商和能力;使用 Mapper XML 批量/关联查询;
  • Service 不得互相注入形成循环依赖;协调逻辑使用单向依赖或 Manager;
  • RouteDecision 必须携带 source/policy/reason,不能只返回模型字符串;
  • 健康状态机使用 Clock 测试,禁止 sleep;
  • 同步/流式必须保证一次模型调用、一次最终审计;
  • 调用日志和普通日志均禁止 Prompt、响应正文、密钥、Header、nativeUsage;
  • Nacos、MCP、Agent Framework 不得因“预留扩展点”被顺带引入。

Review 结论

  • Spec Compliance:PASS;
  • Code Quality:PASS,Review 阻断修复后 AI 插件 84 tests 全部通过;
  • 安全与依赖:PASS,无 Prompt/响应/API Key 日志,无跨模型失败后补发,治理核心组件不再可选装配;
  • 保留警告:JVM CDS、commons-logging、既有 deprecated/unchecked Java 警告和既有 Vite 组件/import/CSS/bundle 警告均不阻断;
  • 保留跳过项:Flyway 实库、真实供应商模型、浏览器响应式与键盘主路径未执行;
  • 服务清理:归档轮次未启动服务;
  • 当前状态:done,已归档、未推送。