Commit Graph

17 Commits

Author SHA1 Message Date
Blizzard 31bf3e5907 feat(dispatcher): 熔断降级真三态状态机(弃用空桩)+ 单测
CircuitBreaker 此前是空桩(Allow 恒 true、Report 空操作),dispatcher 调 LLM/工具
无任何失败保护——今天就撞上 DeepSeek 流连接累积把报告卡死。改为真实三态熔断:

- Closed:正常放行;连续失败达阈值(默认5) → Open。
- Open:快速拒绝;冷却(默认10s)到点 → HalfOpen 放行少量探测(默认1)。
- HalfOpen:探测成功 → Closed 恢复;探测失败 → 重新 Open。
- sync.Mutex 并发安全(多任务 goroutine 共享);时钟可注入便于确定性测试。

orchestrator.Handle:熔断开启时不再静默丢弃任务,改为回流"服务繁忙"提示 +
CompleteStream 收尾,让客户端解阻不挂死。

测试(含 -race):达阈值断开、成功清零、半开恢复、探测失败重断、并发安全 —— 全过。
PROGRESS.md 勾掉熔断项。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:02:45 +08:00
Blizzard 9b0520e020 test(backend): 编排引擎/DSL/docx/报告导出 首批 Go 单测(19 用例,纯逻辑无依赖)
补齐核心后端逻辑的自动化测试,全部纯函数级、不依赖 docker/NATS/LLM,毫秒级跑完,
把"手动起全栈 curl 验证"变成 `go test`:

- dispatcher/internal/eino (graph_test.go)
  evalCondition(各运算符+refs/tools/answer/profile 关键字+兜底)、resolveOperand、
  aggregate(拼接/去重合并/默认/全空)、branchNode(真假边标签精确选路 + 无标签退回边序 +
  单边剪枝)、cstr/cbool/countLines/labelOf/targetsOf。
- dispatcher/internal/dsl (compile_test.go)
  Topo(线性序 + 有环不丢节点)、Compile(system/query/tools 抽取 + 无输入兜底 + 空图用原文)、
  ToolBinding(tool/retriever/非工具)、Parse(合法/非法)。
- mcp-go/internal/office (unioffice_test.go)
  RenderReport 产物为合法 docx(zip 三部件 + 标题/章节文本 + XML 转义)、escapeXML。
- mcp-go/internal/mcp (report_test.go)
  reportMarkdown(标题+多章 / 无标题)。
- gateway/internal/dsl (parser_test.go)
  ParseAndAssemble(task_ 前缀 + Graph 透传 + Meta 初始化 + 空/非法报错)。

`go test ./...` 各模块全绿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 14:13:13 +08:00
Blizzard 1dd6b0cce3 feat(report): 生成只出 Markdown 预览,导出时再渲染 Word/PDF/Markdown
把"渲染"从生成阶段解耦到导出阶段(导出时再处理):
- 生成阶段:报告正文按 Markdown 流式预览(前端 <Markdown> 已渲染),
  dispatcher 不再 eager 渲染 docx,改为经 mcp-go report_store 落盘报告源(title+sections JSON)。
- 导出阶段(按需现渲染):
  - GET /reports/:id/export?format=docx → mcp-go report_export 读源渲染 .docx;
  - ?format=md → 返回 Markdown 文本;
  - PDF → 前端把已渲染的 Markdown 送进打印视图出 PDF(CJK 零字体依赖)。
- 旧 /reports/:id/download 兼容保留(默认 docx)。

改动:
- contract: ReportSourcePath(id) = <id>.json。
- mcp-go: 新增 report_store / report_export 工具(report_render 保留给 Studio render 节点)。
- dispatcher: handleReport 末尾 renderReport → storeReport。
- gateway: DownloadReport → ExportReport(经 NATS 调 report_export)。
- 前端: ReportView 单个「Word」→「导出」组 Word/PDF/Markdown;
  desktop.printReportHtml 客户端打印;api.reportExportUrl。

实测(docker 全栈 + mcp-go + gateway + dispatcher + DeepSeek 真跑):
- 真实生成「绿茶的功效」18s 完成,report_store 落源(5章, 6280B) ✓
- export md 返回正确 Markdown(# 标题/## 小节/正文) ✓
- export docx 为合法「Microsoft Word 2007+」(含 document.xml/Content_Types) ✓
- 前端 tsc 干净 + 生产构建通过 ✓
(注:发现并修复一处环境问题——mcp-go 启动时若 Milvus 未起会阻塞在
 rag 初始化、永不订阅工具,导致所有 mcp-go 工具"no responders";起全栈后正常。
 报告生成在累积大量未完成 DeepSeek 流连接时会偶发卡顿,干净进程下正常。
 前端导出按钮的实时点击因 React 受控输入自动化限制未在预览中走通,非代码缺陷。)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 14:04:06 +08:00
Blizzard 1bd187874d feat(orchestration): Phase2 —— map 真并行 fan-out + branch 真/假边标签精确选路
Phase1 让引擎按图执行后,本轮补上两块:

1) map 并行 fan-out(dispatcher)
   - map 节点:planItems 把主题拆成 3–6 子项 → 复用 report 的 writeSections
     有界并发(4)逐项撰写 → 结构化 sections 存黑板 + 拼进 answer + 流式呈现。
   - 检索节点记下 owner 作用域库名(b.kb),供 map 各项并行检索复用。
   - render 节点优先用 map 产出的多章 sections 渲染,否则整段成稿当单章。

2) branch 真/假边标签(前端 + DSL + dispatcher)
   - TypedNode:分支节点渲染两个出口手柄 真(绿)/假(红),连线各带 sourceHandle。
   - exportDsl / TaskDsl:边导出携带 sourceHandle。
   - dispatcher dsl.Edge 增 SourceHandle;branchNode 优先按 true/false 标签精确
     选路,无标签的旧图退回"出边顺序"约定,向后兼容。

实测(gateway+dispatcher+DeepSeek 真跑):
- map:input→map→render,DeepSeek 拆出 5 章并行撰写(347–512字),trace 见
  section:0..4 并发 + 有界并发(section3 等槽);render 因 mcp-go 不在优雅降级 ✓
- branch 标签:把 true 边故意列第二位,条件真仍走 true 标签的分支A、假走分支B,
  证明按标签而非边序选路 ✓
- 桌面端:分支节点正确渲染 真/假 两手柄,无 console 报错 ✓

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:01:51 +08:00
Blizzard fd145b5852 feat(dispatcher): 编排引擎按图执行(拓扑+连线+分支剪枝),弃用线性拍平
旧 compileFlow 把 DSL 图拍平成线性 init→tool…→prompt→model,连线/分支/
memory/aggregate/render 节点全被忽略——"画得出、跑不全"。改为纯 Go 图解释器
(graph.go),按真实拓扑与连线执行,每种节点 kind 有真实行为:

- input     注入用户输入
- memory    按勾选注入画像/历史(无 memory 节点则沿用默认注入,不回归)
- retriever kb 按 owner 作用域 → kb_search 累计参考资料
- tool      调 MCP 工具,产出进黑板,失败降级不阻断
- agent     据黑板拼消息 → pool 流式回流 token,累计成稿
- aggregate 按策略合并参考资料(拼接/去重合并/摘要)
- render    把成稿经 report_render 渲染 docx
- branch    求值条件 + active-set 剪枝下游(边序约定 [true,false])
- map       占位(fan-out 暂串行,路线图 Phase 2)
- output    终端

全程逐节点点亮"运行·观测",token 流与记忆写回保持不变;报告 intent 走原专用
编排不动。compile.go 精简为只留 RunCtx/buildMessages/previewArgs。

实测(gateway+dispatcher+DeepSeek 实跑):
- input→agent→output 真实流式答复 ✓
- branch 条件 2>1 走分支A、1>2 走分支B(下游真被剪枝)✓
- memory 节点按勾选注入;exec 事件按新节点名(agent:a 等)回流 ✓
- 桌面端 Studio 载示例→运行:4节点3连线校验通过,检索节点 mcp-go 不在时
  优雅降级,agent 据空资料如实作答,输出/轨迹面板正常 ✓

路线图 Phase 2:map 真并行 fan-out + aggregate reduce 接上 report 那套;
前端给 branch 的边打 true/false 标签,使条件分支完全精确(当前靠出边顺序约定)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 11:42:29 +08:00
Blizzard 337d4d7619 feat(studio): 完善编排 —— 检索接本人知识库 + 真实模型下拉 + 模板/示例
把编排从"演示桩"接到真实平台:检索节点查本人 owner 隔离的知识库,节点下拉用真实数据。

- dispatcher:makeToolNode 用 task user_id 给检索类工具的 kb 加 owner 前缀("uid/kb"),
  编排里的「检索(RAG)」节点真正命中本人知识库(与隔离对齐)。
- 前端 StudioView:加 identity,载入 /kb/list 与 chat 模型作为「检索.kb」「Agent.model」下拉真值;
  Inspector 支持 dynamicOptions(无真值时提示去创建)。
- 编辑体验:示例(一键加载 输入→检索→Agent→输出 可运行图)/ 清空 / 模板名+保存(localStorage,
  含布局)/ 载入下拉;ReactFlow deleteKeyCode 支持 Del/Backspace 删节点。

验证:示例图运行 → gateway 发布任务 → dispatcher 编译 → mcp-go 日志 `tool=wiki_search
args=[kb:wt/default ...]`(kb 已按 owner 作用域)→ 命中本人库 → DeepSeek 流式作答;
底部抽屉 完成 ✓ · 工具调用 1。tsc+vite+dispatcher build 通过;重建 .app。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 16:14:54 +08:00
Blizzard cdc5b3a847 feat(observability): 执行可视化 — 节点级实时轨迹(运行·观测)
把任务执行做成可观测:Dispatcher 在每个节点/阶段发结构化 ExecEvent,
经独立 NATS 通道回流,前端逐节点点亮(状态/耗时/工具入参产出)。

- shared: contract.ExecEvent + ExecSubject(sundynix.exec.<id>,与 Token 流分流);
  bus.PublishExec/CompleteExec/SubscribeExec(core NATS,复用结束头)
- dispatcher: execTracer(自增 Seq 保序 + span 自动计耗时);
  Orchestrator 加 ExecSink;通用图(init 召回 / 各 tool 入参→产出 / prompt / model
  首token+token数)与报告编排(规划大纲 / 各章并行 start-end / 渲染)全程埋点
- gateway: SubscribeExec + GET /tasks/:id/exec SSE(与 token 流并行)
- desktop: streamExec + deriveNodes(按 node 归并 start/end/error/info);
  复用组件 ExecTrace(竖向轨道,按 kind 着色,运行中脉冲灯);
  新 RunsView(运行·观测:轨迹+输出双栏);BottomDrawer 轨迹/工具调用 tab 接真实数据;
  ReportView 加执行轨迹栏;左导航「运行」置就绪

实测:
- 报告任务 /exec:规划(2680ms,4章) → 4 章并行(seq 交错,各~7-8s 重叠=真并行,
  每章带 docs 知识库检索预览+成稿字数) → 渲染(docx 落盘)
- 通用图 /exec:tool:kb_search(678ms,入参→Milvus 产出) → prompt(2消息) →
  model(首token 860ms / 4 tokens)
- 浏览器(Preview):报告页执行轨迹逐节点点亮、章节带耗时/字数/检索片段,完成后下载 Word

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 14:29:28 +08:00
Blizzard ba8c6b3c43 feat(report): 报告生成端到端 — 规划→分章并行检索撰写→渲染真实 Word
- shared: 新增 intent=report 任务约定 + ReportPath(跨进程共享落盘目录,零配置对齐)
- dispatcher: handleReport 专用编排(DeepSeek 规划大纲 → 各章并行 RAG 检索+撰写
  → 汇聚 → report_render),Pool.Chat 非流式聚合;进度与正文经 Token 流实时回流
- mcp-go: 用标准库 archive/zip + OOXML 拼出真实可打开的 .docx(零额外依赖),
  report_render 工具落盘到共享目录;附 docx 有效性测试
- gateway: POST /reports 触发;GET /reports/:id/download 下发 Word
- desktop: 新增「报告」页(主题→实时编排进度→下载 Word),左导航置为就绪

实测:DeepSeek 生成 5 章报告 → 渲染 5KB docx → file 识别为 Microsoft Word 2007+
→ textutil 提取标题/各章正文完整。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 14:02:21 +08:00
Blizzard 21e5f6620d chore: fresh-clone 可直接运行审查与修复
确保 git clone 后零配置(除 LLM key)即可跑通后端链路。

- go.work: 移出 sundynix-desktop(Wails,//go:embed frontend/dist 需先 npm build,
  且 main 为桩),避免污染后端工作区构建;保留后端 4 模块
- mcp-py pyproject: 注释未 import 的 mcp/docker 重依赖(对应功能仍为桩),
  fresh pip install 只装实际用到的 nats-py + docx/openpyxl/pypdf,更快更稳
- README: 完整环境改为 fresh-clone 零配置运行指引(infra→4后端→2前端→控制台配模型)
- 审查结论: 无硬编码绝对路径; 各服务 env 默认全对齐 docker-compose; go.sum/
  package-lock 齐全; /tmp clone 实测: 4 后端模块 go build ✓、admin npm ci+build ✓、
  mcp-py 全新 venv 安装+导入 ✓; e2e PASS

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 17:13:55 +08:00
Blizzard 3b54e59ecf feat: embedding 配置搬上控制面 — 数据源页可视化配置 + 热更新
embedding 从 env 改为控制面驱动(持久化+可视化),复用 chat 模型同套范式:
配置控制面泛化为按 kind(chat/embedding),加 embedding kind。

- shared: 配置 subjects 泛化 sundynix.config.<kind>.get/.updated;bus 方法改 kind 参数
  (RequestConfig/ServeConfig/PublishConfigUpdated/SubscribeConfigUpdated)
- gateway: sundynix_model 加 kind 列(每 kind 唯一激活)+旧行回填 chat;admin 按 kind
  增删改/激活/列表,测试连接 embedding 走 POST /embeddings;main 按 kind ServeConfig;
  变更广播各 kind
- dispatcher: 取 chat 配置(kind 化)
- mcp-go: rag.Engine.SetEmbedding 热更新(RWMutex);main 取/订阅 embedding 控制面配置
  (覆盖 env)
- admin 控制台: api 按 kind;抽出复用 ModelManager;ModelsPage(chat)+新 DatasourcesPage
  (embedding + 向量/图库占位);routes 数据源页就绪
- 验证: 全模块 build✓ + e2e PASS + 控制台 npm build✓;live 全跑通——chat(DeepSeek 回填
  kind 仍工作);mcp-go 不带 EMBED env 启动→控制台配 embedding(百炼)→测试连接✓→激活
  →NATS 热更新 mcp-go→入库+语义检索'存向量的数据库'→Milvus;浏览器数据源页拉到激活配置

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 17:25:54 +08:00
Blizzard 71db0e295f feat: compose.NewGraph 全图编译 — 工具节点在 Eino 图里真实执行
dispatcher 按每个任务的 DSL 动态编译 Eino 图:工具/检索节点按拓扑序作为真实图
节点经 NATS 调 MCP,产出注入模型上下文。不再是固定的 recall→prompt→model。

- dsl: 加 Parse(图结构) + (Flow)Topo(Kahn 拓扑序,环退化声明序) + ToolBinding(tool/
  retriever 节点→工具名+参数)
- eino/compile.go: 逐任务 compileFlow —— START→init(身份+记忆召回)→tool_n(真调 MCP,
  失败降级)→prompt(黑板 RunCtx 组装 system+画像+工具产出+历史+输入)→model→END
- eino/orchestrator: 去掉启动期静态图,Handle 内按 DSL 动态编译;删旧 graph.go/state.go
- 工具节点产出作为参考资料注入 system,模型据此作答
- 验证: 全模块 build✓ + e2e PASS; 真实 DeepSeek 双证——回归(input+agent)→'蓝色';
  工具节点(echo 注入事实)→mcp-go 日志证明图里真调 echo→模型据参考资料答'…Milvus…'

注: 分支/并行节点(compose.Branch/fan-out)暂未编译,是更大 TODO。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 16:45:33 +08:00
Blizzard aa574a8cb2 feat: DSL→对话编译 — Eino 图用节点字段而非整段 JSON 喂模型
dispatcher 真正解析 DSL 图:input 节点文本=用户消息,agent 节点 system=系统提示词,
不再把整段 DSL JSON 当 prompt 丢给模型。

- dispatcher/internal/dsl: Compile(graph)→Plan{System,Query,Tools}
  (input.text/agent.prompt→query, agent.system→system, tool.tool→tools, 兜底默认)
- eino/graph: recall 调 dsl.Compile,模板加 {system}(Agent 系统提示词+画像注入)
- eino/orchestrator: 写回历史落真实 query 而非 DSL 原文
- frontend nodeCatalog: input 节点改 text 字段(用户输入,必填),检查器可编辑
- 验证: 全模块+前端 build✓; 真实 DeepSeek——curl DSL(input '中国首都?')→'北京';
  真实浏览器——加 input 节点输入'NATS是什么'→运行→DeepSeek 简洁正确作答

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 16:34:38 +08:00
Blizzard 3c65189f30 feat: 配置控制面 + LLM Pool 接第三方在线 API (OpenAI 兼容)
后端从占位回显变为真实生成:管理员经控制面登记/激活模型,Gateway 经 NATS
下发,Dispatcher 热更新 LLM Pool,Eino 图用 OpenAI 兼容流式真实推理。

- shared: contract.ModelConfig(provider/base_url/api_key/model) + 配置 subjects;
  bus.RequestModelConfig/ServeModelConfig/Publish/Subscribe ModelConfigUpdated
- gateway: store.LLMModel→sundynix_model(AutoMigrate,唯一激活) + admin REST
  (GET/POST/active/delete/test models, api_key 脱敏) + main ServeModelConfig +
  变更广播; 路由 /api/v1/admin/models*
- dispatcher: llm.Pool OpenAI 兼容 SSE 流式客户端(ChatStream) + 热更新配置 +
  未配置则降级桩; poolModel.Ready()?真实流式:注入记忆的桩; main 取配置+订阅
- 开发期接在线 API 不拉本地模型(见 llm-provider-strategy memory)
- 验证: 4 模块 build✓ + e2e PASS; mock OpenAI 服务 live 跑通——登记/测试连接✓/
  激活→NATS 热更新→提交→真实 SSE 流出 mock 回复, mock 日志证明端点被调用且
  注入画像(老王)进了模型上下文

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:41:39 +08:00
Blizzard 4928ffc0f7 feat: 短期多轮历史接入 Eino 图 MessagesPlaceholder (⑨)
会话历史(Redis,易失,与长期画像分开)经 MCP 工具进出 Eino 图:
recall 召回历史填 MessagesPlaceholder,写回把本轮 user/assistant 落历史。

- mcp-go: internal/history(go-redis, sundynix:history:<session>, LPUSH+LTRIM 保留近20条,
  24h TTL) + 工具 history_get(返回JSON turns)/history_append; main 开 Redis(降级)
- dispatcher Eino: 模板加 MessagesPlaceholder('history'); recall 调 history_get→转 schema.Message;
  Handle 累积 answer; memorize 异步 history_append(user+assistant)
- shared: contract.MetaSessionID; gateway: SubmitTask 注入 Meta[session_id](X-Session-ID 头,缺省 default)
- demo.sh: 同会话两轮提交,验证第2轮召回第1轮历史
- 验证: 4 模块 build✓ + 3 e2e PASS; live 跑通——轮1=0轮历史→落库, 轮2 history_get 命中→注入

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 14:18:45 +08:00
Blizzard cbd130ecae feat: 第一张真实 Eino 图 + 偏好记忆(让模型知道是我)
dispatcher 不再手搓 pool.Stream,改用编译好的 Eino 图驱动;接入用户常驻画像,
推理前召回并注入 system prompt,实现个性化(架构'心脏'首次真跳)。

Eino 图(dispatcher/internal/eino): START→recall→prompt→model→END + 全局 State
- recall(Lambda): 取 Meta[user_id] → 调 MCP memory_get → ProcessState 写画像
- prompt(ChatTemplate): {profile} 注入 system,{query} 作 user
- model: poolModel 适配 LLM Pool 为 model.BaseChatModel(Generate+Stream, schema.Pipe)
- 写回: 流排空后异步 memorize(流式节点走 OnEndWithStreamOutput 非 OnEndFn)

记忆存储(mcp-go owns): GORM Profile→sundynix_user_profile(复合主键, AutoMigrate,
遵守前缀约定), 新工具 memory_get/memory_upsert, 连不上降级
Gateway: SubmitTask 注入 Meta[user_id](X-User-ID 头), PUT /api/v1/memory→memory_upsert
shared: contract.MetaUserID; llm.Pool 拆出 StreamText

验证: 4 模块 build✓ + 3 e2e PASS; live 跑通——PUT 偏好落 sundynix_user_profile,
带 X-User-ID 提交→Eino recall 召回→注入→SSE 流出含画像的个性化回答, writeback 触发

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 14:06:18 +08:00
Blizzard adc521f94d feat: 打通 Dispatcher→MCP 工具调用链路 (core NATS request-reply)
第 4 层 Dispatcher 经 NATS request-reply + 队列组同步调用第 5 层 MCP 工具,
工具不可用/超时即降级,不阻断主流程。

- shared/contract: ToolCall/ToolResult + sundynix.tools.go.* subject 约定 + ToolSubjectGo/Py
- shared/bus: CallTool(发起) / ServeTool(队列组订阅+应答)
- mcp-go: 接共享 bus,gateway 通配订阅按工具名分发(wiki_search/echo),main 优雅退出
- dispatcher: ToolCaller 接口 + Orchestrator.retrieveContext(调 wiki_search,超时3s降级)
- e2e: TestToolCallRoundTrip(PASS);demo.sh 加 mcp-go(就绪门避免启动竞态),live 跑通

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 11:31:58 +08:00
Blizzard c7a02c3905 feat: 初始化 sundynix-agentix 分层式 AI Agent 平台脚手架
5 层 + 1 条 NATS 零拷贝消息总线的 monorepo(Monolith First → Microservices Morph B)。
纵向主干(任务流 + Token 流回流)已真实跑通,横向各层能力为带注释的桩。

已贯通(real code):
- sundynix-shared: 共享契约 + JetStream/core NATS 真实收发(bus) + 内嵌 NATS(devnats) + e2e 测试
- sundynix-gateway: Gin 接入 + DSL 解析组装 + NATS Publish + SSE 流式输出
- sundynix-dispatcher: NATS 消费 + Eino Orchestrator 流式回流 + 熔断器 + LLM Pool 占位流式
- 链路: HTTP POST → DSL → sundynix.tasks.* → Dispatcher → Token 经 sundynix.streams.<id> 回流 → SSE
- 基础设施: docker-compose(nats/postgres/redis/neo4j/milvus) + Makefile(make demo/e2e)

待填(桩):
- Eino 图编排 compose.NewGraph、LLM Pool 接 vLLM/Ollama
- Gateway store 换真实 pgx/redis
- sundynix-mcp-go: Bleve+Milvus+Neo4j 混合检索 / UniOffice / 外部 API
- sundynix-mcp-py: gVisor 沙箱 / MinerU(PaddleOCR) / Docker 解释器
- sundynix-desktop: React Flow 画布 → DSL 导出 → SSE 展示
2026-06-10 11:00:29 +08:00