diff --git a/ARCHITECTURE_REVIEW.md b/ARCHITECTURE_REVIEW.md new file mode 100644 index 0000000..4baee13 --- /dev/null +++ b/ARCHITECTURE_REVIEW.md @@ -0,0 +1,172 @@ +# sundynix-agentix · 架构评审报告 + +> 评审日期:2026-07-02 | 评审范围:全栈架构(gateway / dispatcher / mcp-go / mcp-py / shared / 双前端 / 基建) +> 评审方式:通读代码 + 真机运行验证(本次会话中实跑并踩到若干真实问题,见文末「证据索引」) +> 定性:这不是「一个应用」,而是一套 **事件驱动的 Agent 平台**。请按「平台」而非「应用」的标准衡量它。 + +--- + +## 0. 摘要(一句话结论) + +**架构 factored 得相当好、也相当有野心,但当前是「分布式系统的复杂度,由接近单人的规模在扛」。** +解耦干净、控制面热切换、韧性分层——这些是真资产;代价是运维面大、一致性靠自觉、单租户假设焊死。 +复杂度**是否值得,取决于目标**:多租户平台产品 → 值;单用户工具/demo → 偏重。 + +| | Top 3 | +|---|---| +| 最强 | ① 契约+NATS 解耦(加工具/换模型/改 prompt 热生效,零重编译)② 分层韧性(failover+熔断+预算+护栏+评测)③ 流式原生 | +| 最痛 | ① NATS 万物单点 + 运维尖角(双 NATS 脑裂真踩到)② 一致性靠 best-effort(非事务级联删等)③ 单租户假设焊死 | + +--- + +## 1. 系统全景 + +``` + ┌─────────────┐ ┌─────────────┐ + │ 桌面端 Wails │ │ 管理端 React │ ← 两个独立产品面(绝不合并) + └──────┬──────┘ └──────┬──────┘ + │ HTTP/SSE │ HTTP + ┌──────┴──────────────────┴──────┐ + │ gateway (gin) │ 接入层:JWT鉴权/路由/护栏/限流/审计/SSE回流/导出 + └───────────────┬────────────────┘ + │ NATS(RPC + 流 + 控制面广播 + 心跳)——万物总线 + ┌──────────────────┼───────────────────────────────┐ + │ │ │ +┌────┴─────┐ ┌──────┴──────┐ ┌──────┴──────┐ +│dispatcher│ │ mcp-go │ │ mcp-py │ +│ 编排核心 │ │ Go 工具服务 │ │ Python 算法 │ +│ (Eino) │ │ RAG/记忆/ │ │ 沙箱/解析 │ +│ +harness │ │ 历史/报告 │ │ │ +└────┬─────┘ └──────┬──────┘ └─────────────┘ + │ │ + └── LLM providers └── PG / Redis / Milvus / Neo4j / MinIO + (OpenAI 兼容) 关系 / 缓存 / 向量 / 图谱 / 对象 +``` + +**模块职责边界(清晰、无循环 import)** +- **gateway**:HTTP 接入、JWT、路由、输入护栏、限流、审计、SSE 回流、报告导出服务。不含业务编排。 +- **dispatcher**:Eino 编排引擎(DSL→compose 图)+ harness 治理层(评测/护栏/预算/熔断/纠偏/脱敏)+ LLM 池(failover+缓存)+ 多智能体协调。 +- **mcp-go / mcp-py**:MCP 工具服务,经 NATS 被调。真正的 I/O 与算法执行在这里。 +- **shared**:bus(NATS/JetStream)、contract(跨服务契约)、prompts 注册表、secrets(AES-GCM)、otel。**服务间只认 contract + bus,互不 import。** + +--- + +## 2. 核心架构决策与取舍 + +| 决策 | 好处 | 代价 | +|---|---|---| +| **NATS 为统一总线**(RPC/流/控制面/心跳全在上) | 极致解耦;广播式控制面热更新 | 万物单点;一旦异常以诡异方式全面降级;单点 NATS(集群是 T3) | +| **MCP 工具作为独立服务** | 加工具只改注册表、dispatcher 自动发现;多语言(Go+Py) | 每次 tool call 是网络 RPC(多 `no responders`/超时故障类);要整链在线 | +| **Eino 做编排核心** | DSL→compose、ReAct、checkpoint(HITL) 白嫖框架 | 框架耦合/塑形;限制传导(如流式中途失败不切) | +| **控制面热切换**(模型/prompt 经 NATS 广播) | 不重启改配置/换 provider/改提示词 | 权威态在 DB、下发靠广播,广播失败需兜底 | +| **契约解耦**(shared/contract) | 服务独立演进、独立部署 | 契约变更需跨服务协调 | +| **多存储专用化**(PG/Redis/Milvus/Neo4j/MinIO) | 各司其职、检索质量高 | 5 个存储要起/连/备份/保一致 | +| **双前端**(desktop/admin 独立) | 用户产品与运维面互不干扰 | 两套前端 + 两套构建要维护 | + +--- + +## 3. 关键数据流(架构最能说明问题的四条) + +**A. 任务生命周期**:桌面端 POST DSL → gateway 解析+拓扑校验 → NATS 发布 → dispatcher 编译为 Eino compose 图执行 → token 流 + 执行轨迹经 NATS→Redis Stream→SSE 回流 → 状态机 FSM 落库。 + +**B. 工具调用(两条路,易混淆)** +- **编排直连**:dispatcher 编排代码 `o.tools.CallTool()` 直接 NATS→mcp-go。**报告写文件(report_render)走这条,绕开 Eino。** +- **Eino ReAct 自主调用**:仅 `agent:true` 工具。模型 function-call → Eino ToolsNode → `mcpTool.InvokableRun`(react_agent.go)→ NATS→mcp-go。 +- **共同点**:真正执行永远在 mcp-go;**Eino 把工具当黑盒(吃 JSON→吐字符串),从不碰文件系统**。 + +**C. 报告导出/写文件**:dispatcher 生成 → mcp-go `report_store` 写源 JSON → 用户导出 → gateway `report_export` → mcp-go 渲染 docx 并 `os.WriteFile` 到 `SUNDYNIX_REPORTS_DIR`(默认 `$TMPDIR/sundynix-reports/{id}.docx`)→ gateway `c.File(路径)` 流回 → 桌面端 Wails `SaveReportAs` 原生另存为落盘。**⚠️ mcp-go 写、gateway 按路径读,二者必须共享该目录。** + +**D. 控制面热切换**:admin 改配置 → gateway 写 DB → NATS 广播激活集 → dispatcher/mcp-go `ApplyOverrides` 热更新(不重启)。本次会话 live 验证 prompt/模型 均即时生效。 + +--- + +## 4. 优点(有据,非纸面) + +1. **解耦干净、扩展成本低 —— 最大亮点。** 服务互不 import,只认契约。加工具只改 mcp-go 注册表、dispatcher 经 `list_tools` 自动发现;换 provider/改 prompt 控制面热下发、零重编译。本次会话亲见 live 生效。 +2. **韧性分层叠进架构,非事后补。** model 层 failover + 编排层熔断 + 预算 + 护栏 + 评测纠偏(「恒温器」)。单 provider 挂平台不宕——本次真机演示过完整 failover+熔断+半开恢复。 +3. **流式原生。** token 流 + 执行轨迹 NATS→SSE + Redis Stream 断点重放,实时体验是架构级支持。 +4. **Eino 省造轮子。** 编排图/ReAct/checkpoint(HITL) 白嫖框架,自研 graph.go 已退役。 +5. **合理的多语言/多存储。** Go 走热路径,Python 做算法/沙箱;存储各司其职。 +6. **可观测与水平扩展底子在。** OTel 全链路 + 结构化日志 + 轨迹;NATS queue group + JetStream 持久消费,worker 无状态可扩;入库走 JetStream 持久队列(崩溃重投/幂等/背压,已 live 验证)。 + +--- + +## 5. 缺点 / 风险 / 技术债(诚实的一半) + +### 5.1 运维面大、故障以诡异方式出现 +- **NATS 万物单点。** 它是 RPC/流/控制面/心跳,一旦异常全面降级。本次真踩到**双 NATS 脑裂**(`make devnats` 与 docker NATS 都占 4222,客户端被劈成两半、静默半失败)。当前是**单点 NATS**(集群为 T3,未上)。 +- **分布式跑/调都难。** 4 服务 + NATS + 5 存储要全起对连对。`local-run-gotchas` 那条 memory 长不是没原因(mcp-go 必须在 Milvus 后、ReportsDir 共享、token 过期静默 401、JetStream 重投…)。 + +### 5.2 一致性/正确性靠自觉,非机制强制 +- 大量 **best-effort**:审计失败吞掉、部分关键 DB 写失败仍返 200、**KB 级联删跨三库非事务**(删一半失败仍删 PG → 不一致)。 +- 预算/护栏是「计量+兜底」式,非硬约束(不过已有默认顶,见「审计误报」)。 + +### 5.3 与「干净解耦」自相矛盾的隐性耦合 +- **共享本地文件系统**(ReportsDir):mcp-go 写、gateway 按路径读——单机能对上,一旦容器化拆开即断,除非挂共享卷。这是整套 NATS 解耦里一处**潜在耦合**。 + +### 5.4 扩展性/多租户 +- **单租户假设焊死。** 无 RBAC/tenant_id,`owner_id` 当隔离键。变多租户是**动表结构 + 鉴权边界的手术**(T4.A,L 级)。 +- **RequireAdmin 开发期放行任意登录用户**(生产靠 `ADMIN_USER_IDS` 白名单,无角色矩阵)。 + +### 5.5 框架耦合 / 取舍不一致 +- 编排核心 Eino-native,框架抽象塑形代码,限制会传导(流式中途失败不切,部分是 Eino stream 语义)。 +- 又有「该用库却手搓」的地方:`office/unioffice.go` 其实是零依赖手搓 docx(非 unioffice 库);`mcp-py` 算法层目前是**桩**(解析未接 OCR)。 + +### 5.6 可观测的盲区 +- failover/熔断的**运行时态只在 dispatcher 日志,admin UI 看不到**(本次 demo 暴露,已记 T4.F 待办)。分层韧性在运维上偏不透明。 + +--- + +## 6. 「税单」:复杂度的代价何时到期(映射路线图) + +这套架构的痛点,本质是「分布式平台迟早要付的税」。项目已把它们编入路线图,且**克制地把纯部署硬化推迟**(T3 标 ⏸「等有真实流量再上」——这是对的判断,别在没用户前先付税): + +| 债 | 何时爆发 | 对应项 | +|---|---|---| +| 单点 NATS / DB 无 HA | 第一次真实并发/宕机 | T3(NATS 集群 + DB HA)⏸ | +| 单租户 → 多租户 | 第一个多租户客户 | T4.A(RBAC/tenant_id,L)| +| 共享文件系统耦合 | 第一次容器化/多机 | 见 §7 必修清单 | +| 非事务级联删 / best-effort 写 | 数据量上来后的静默不一致 | T4.F(级联删事务化/关键写上浮 5xx)| +| 无审计/安全溯源 | 合规/事故复盘 | ✅ T4.B(已做:审计+护栏事件+审计页)| +| 运行时韧性态不可见 | 生产排障 | T4.F(🔴 模型健康/熔断态 surface 到 admin,本次新增)| + +**已还的债(本次会话)**:T4.B 审计可溯源整组 ✅、T4.E 编排边角整组 ✅(Map 错误传播/专家超时/Branch else/DSL 校验/熔断接回 failover)、T4.F 部分(拆 search 残骸/CORS+限流收紧)。 + +--- + +## 7. 「上生产前必修清单」(当你要容器化 / 多机部署时,这些会先断) + +1. **报告文件落盘 → 去本地盘耦合。** mcp-go 写/gateway 读同一本地目录在多机即断。改:报告直传 MinIO(正文已走 MinIO,导出产物同理),gateway 从对象存储取,而非 `c.File(本地路径)`。 +2. **单点 NATS → 集群。** 3 节点 + JetStream Raft,否则总线宕=平台宕。(T3) +3. **多进程共享配置对齐。** `SUNDYNIX_SECRET_KEY`(三服务须一致)、`SUNDYNIX_REPORTS_DIR`、`NATS_URL` 等,容器化时用统一 env/secret,别靠 `$TMPDIR` 默认对齐(会不一致)。 +4. **DB HA + 备份/DR 演练。**(T3) +5. **CORS/TLS 严格化。** CORS 生产已收紧(本次做);TLS 待上(T3)。 +6. **RBAC/多租户**若面向多客户则前置(T4.A)。 +7. **一致性收口**:KB 级联删事务化、关键 DB 写失败上浮 5xx(T4.F)——多机下静默不一致更难查。 +8. **可观测补齐**:模型/熔断运行时态、各服务指标上报 admin(T4.F)。 + +--- + +## 8. 总体判断与建议 + +**判断**:一套 factored 良好、有平台野心的架构。解耦与控制面是真正的差异化资产;当前阶段(单租户、无真实流量)复杂度相对偏重,但**方向自洽**——痛点都已被识别并编入路线图,且硬化被克制地推迟。 + +**最该警惕的一条**:解耦很干净,但**一致性与运维的「暗债」在攒**(非事务级联删、best-effort 写、双 NATS 这类环境坑、共享文件系统)。这些不会在 demo 里咬你,会在「第一个真实多用户 / 容器化部署」时集中爆发。 + +**建议(按优先级)**: +1. **继续还 T4.F 的正确性债**(级联删事务化、关键写上浮)——低成本、防未来静默 bug。 +2. **补运行时可观测**(模型/熔断态上 admin)——让分层韧性在生产可见。 +3. **在容器化之前,先解「共享文件系统 + 配置对齐」两处**(§7 #1、#3)——这是"看起来能跑、一拆就断"的典型。 +4. **T3(NATS 集群/HA/TLS)维持 ⏸,直到有真实流量**——不提前付税是对的。 +5. **多租户(T4.A)按商业化节奏推**——不确定要多租户就先别动这个大手术。 + +--- + +## 附:证据索引(本次评审的一手依据) + +- **控制面热切换 live 有效**:prompt 建版→激活→图谱抽取即时变;模型配置热下发(dispatcher 日志 `model config set`)。 +- **failover+熔断 live**:配坏主模型→任务经备用出答案;连败 3 次熔断→跳过坏主;冷却半开探测→重试→重熔断(完整三态)。 +- **双 NATS 脑裂真踩到**:`devnats`(127.0.0.1:4222) 与 docker NATS(*:4222) 并存,客户端劈成两半、`/admin/status` 误报服务离线;`curl :8222/connz` 定位、`pkill devnats` 收敛。 +- **审计清单需逐项核实**(原始审计有噪声):ListModels「O(N²)」实为 O(N) 单次;budget「无限烧」实为 `taskBudget` 已默认 20 万顶;hybrid「删文件」实为拆残骸接线;failover「卡在备用」实为每次都重试主。**教训:改前必读码核实。** +- **关键文件**:`gateway/internal/{router,handler,middleware,store}`;`dispatcher/internal/{eino,llm,harness}`;`mcp-go/internal/{mcp,rag,office}`;`shared/{bus,contract,prompts,secrets}`。 +- **配套文档**:`DEPTH_ROADMAP.md`(T0–T4 路线)、`EINO_ADOPTION.md`(Eino 采纳 A/B/C✅ D 基本)、`production_readiness.md`、`architecture.md`。