# Real Agent 实施计划:从 Mockup → 真正 AI Agent ## 进行中记录(2026-06-04 01:40 CST) ### 当前本地验收清单 | 用户需求 | 当前状态 | 证据 | |------|------|------| | 合并本线程约定的 `@` / `/` / detail chat 能力 | ✅ 本地已落实 | [components/AppShell.tsx](/Users/tigersmacbookair/Documents/MySite/CoDEVX/components/AppShell.tsx), [components/ChatPanel.tsx](/Users/tigersmacbookair/Documents/MySite/CoDEVX/components/ChatPanel.tsx), [components/WorkPackageDetail.tsx](/Users/tigersmacbookair/Documents/MySite/CoDEVX/components/WorkPackageDetail.tsx) | | `@` / `/` 不再因为 invalid JSON 全面失效 | ✅ 本地已落实 | [app/api/chat/route.ts](/Users/tigersmacbookair/Documents/MySite/CoDEVX/app/api/chat/route.ts), [lib/llm-response.ts](/Users/tigersmacbookair/Documents/MySite/CoDEVX/lib/llm-response.ts), [app/api/chat/route.test.ts](/Users/tigersmacbookair/Documents/MySite/CoDEVX/app/api/chat/route.test.ts) | | 只修改当前 work package,而不是误改整板 | ✅ 本地已落实 | [lib/chat-request-context.ts](/Users/tigersmacbookair/Documents/MySite/CoDEVX/lib/chat-request-context.ts), [app/api/live-integration.test.ts](/Users/tigersmacbookair/Documents/MySite/CoDEVX/app/api/live-integration.test.ts) | | Agent Log 可完整记录并上下滚动 | ✅ 本地已落实 | [components/AgentLogPanel.tsx](/Users/tigersmacbookair/Documents/MySite/CoDEVX/components/AgentLogPanel.tsx), [components/AgentLogPanel.test.tsx](/Users/tigersmacbookair/Documents/MySite/CoDEVX/components/AgentLogPanel.test.tsx) | | detail 里可对具体句子提问 | ✅ 本地已落实 | [components/MarkdownContent.tsx](/Users/tigersmacbookair/Documents/MySite/CoDEVX/components/MarkdownContent.tsx), [components/WorkPackageDetail.tsx](/Users/tigersmacbookair/Documents/MySite/CoDEVX/components/WorkPackageDetail.tsx), [components/MarkdownContent.test.tsx](/Users/tigersmacbookair/Documents/MySite/CoDEVX/components/MarkdownContent.test.tsx) | | model settings 可明确测试连接 | ✅ 本地已落实 | [components/ChatPanel.tsx](/Users/tigersmacbookair/Documents/MySite/CoDEVX/components/ChatPanel.tsx), [components/ChatPanel.test.tsx](/Users/tigersmacbookair/Documents/MySite/CoDEVX/components/ChatPanel.test.tsx), [app/api/test-connection/route.ts](/Users/tigersmacbookair/Documents/MySite/CoDEVX/app/api/test-connection/route.ts) | | chat 不再把 JSON 原样吐给用户 | ✅ 本地已落实 | [lib/llm-response.ts](/Users/tigersmacbookair/Documents/MySite/CoDEVX/lib/llm-response.ts), [lib/llm-response.test.ts](/Users/tigersmacbookair/Documents/MySite/CoDEVX/lib/llm-response.test.ts) | | 做完整集成测试,不只靠局部手测 | ✅ 本地已落实 | [app/api/live-integration.test.ts](/Users/tigersmacbookair/Documents/MySite/CoDEVX/app/api/live-integration.test.ts) | ### 本轮正在推进 - 收紧 work package detail 里的局部聊天体验 - 让 `@` / `/` 指令稳定落到当前选中的 package 上 - 让 latest output 里的具体段落和列表项可以直接发起 scoped ask - 保持 Agent Log 为完整、可滚动、按时间顺序的纯文本进度区 ### 本轮已落实 1. detail 视图里的普通问题会自动按当前 package 的 `ask` 处理,不再误触发整板自动化。 2. 直接发送 `/ask`、`/plan`、`/change`、`/execute` 时,会命中当前 package,而不是依赖先点自动补全。 3. LLM 返回非 JSON 时,`ask` 会尽量保留文本答复,`plan` / `change` 会走本地 structured fallback,不再只报 invalid JSON。 4. latest output 区域现在支持更细粒度提问: - `Ask about latest output` - `Ask about this paragraph` - `Ask about this item` 5. Agent Log 会持续累积关键动作,按时间顺序展示,并自动滚到最新记录。 ### 本轮验证结果 - `npm test`:18 个测试文件,44 个测试通过 - `npm run lint`:通过 - `npm run build`:通过 - 路由级 fake-live 集成验证通过: - `/api/test-connection` 可返回 `Connected to the configured model.` - `@SRS ask ...` 返回人类可读说明,不改 board - `@SRS plan ...` 只更新 `wp-srs` - `@SRS change ...` 只更新 `wp-srs` - `/plan ...` 通过选中 package 的客户端路由链路后,仍只更新 `wp-srs` ## 一、当前状态 | 项目 | 状态 | |------|------| | UI 两栏布局 + 12 个种子包 | ✅ 已完成 | | 命令解析器(@包名 模式 指令) | ✅ 已完成,新旧两版并存 | | 编辑器自动补全(@package / /mode) | ✅ 已完成 | | Mock Agent(硬编码返回) | ✅ 已完成,但依赖旧版 parser | | 系统提示词合约 | ✅ 已完成(见 prompts/work-package-system-prompt.md) | | LLM API 路由 | ❌ 不存在 | | 根目录 package.json | ❌ 不存在 | | 真实 LLM 集成 | ❌ 不存在 | | 加载/错误/连接状态 | ❌ 不存在 | ## 二、目标架构 ``` 用户输入 "SRS plan 拆成组件模块并定义接口" │ ▼ parseCommand() ← 解析出 { mode: "plan", matchedWorkPackageId, instruction } │ ▼ Agent.orchestrate() ← 统一入口 │ ├── LLM_API_KEY 存在? │ │ │ YES → POST /api/chat ──→ DeepSeek API │ │ │ │ │ 返回 { assistantMessage, boardAction } │ │ │ │ │ 前端执行 boardAction │ │ (新增 task / 修改字段 / 追加 output) │ │ │ NO → Mock Agent 回退 │ ▼ 更新 WorkPackage[] + ChatMessage[] │ ▼ UI 重新渲染 ``` 关键变化:**Agent 不再自己 mutation WorkPackage,而是返回一个 `boardAction`**。由前端统一执行这个 action。Mock Agent 和 LLM Agent 共用同一个接口签名。 ## 三、实施步骤(共 8 步,约 4-6 小时) --- ### 第 0 步:修复项目结构,使代码可构建(前置条件,30 分钟) **问题**:当前代码无法构建。根目录缺少 package.json,新旧 parser 并存,mock-agent 未迁移。 **操作**: 1. 在根目录创建 `package.json`,包含所有依赖 2. 将 `data/work-packages.seed.json` 复制到根目录 `data/` 3. 创建根目录 `lib/mock-agent.ts`,适配新 `parseCommand()` API 4. 迁移 `app/` 和 `components/` 到根目录 5. 确保 `npm run dev` 可正常启动 --- ### 第 1 步:统一 Agent 接口(30 分钟) 当前 mock-agent 直接返回变更后的 WorkPackage[]。真实 LLM Agent 应该返回一个**变更指令**,由调用方执行。 ```typescript // lib/agent-types.ts —— 新增文件 export type BoardActionType = "none" | "update_task" | "add_tasks" | "append_output" | "update_section" | "change_status"; export type BoardAction = { type: BoardActionType; workPackageId: string; payload: Record; }; export type AgentTurnResult = { assistantMessage: string; boardActions: BoardAction[]; }; export type AgentContext = { messages: ChatMessage[]; workPackages: WorkPackage[]; parsedCommand: ParsedCommand & { matchedWorkPackageId?: string }; }; ``` **为什么这样设计**: - Mock Agent 和 LLM Agent 返回同一种结构,切换时无需改 UI 代码 - `boardActions` 是个数组——允许一次对话执行多个操作(例如:plan + change 同时发生) - 把"生成指令"和"执行指令"拆开,便于测试 --- ### 第 2 步:创建 DeepSeek API 路由(45 分钟) **文件**:`app/api/chat/route.ts` **路由逻辑**: ``` POST /api/chat Body: { messages: ChatMessage[], workPackages: WorkPackage[], parsedCommand: ParsedCommand } 1. 从 process.env 读取 LLM_API_KEY, LLM_API_BASE_URL, LLM_MODEL 2. 构造 messages 数组: - system: work-package-system-prompt.md 内容 - system: 当前所有 workPackages 的 JSON(供 LLM 参考) - user: 当前用户消息 3. 调用 DeepSeek API(POST {baseUrl}/chat/completions) 4. 解析响应中的 JSON(要求 response_format: { type: "json_object" }) 5. 返回 { assistantMessage, boardActions } ``` **环境变量**(`.env.local`): ```env LLM_API_BASE_URL=https://api.deepseek.com LLM_API_KEY=sk-your-deepseek-key LLM_MODEL=deepseek-chat ``` **错误处理**: - API key 缺失 → 返回 503,附带 `fallback: true` 标记 - DeepSeek 返回非 JSON → 返回 502,附带原始文本 - 超时(15 秒)→ 返回 504,前端回退到 mock --- ### 第 3 步:实现 BoardAction 执行器(30 分钟) **文件**:`lib/board-reducer.ts` 这是一个纯函数:接收 `WorkPackage[]` + `BoardAction[]`,返回新的 `WorkPackage[]`。 ```typescript export function applyBoardActions( workPackages: WorkPackage[], actions: BoardAction[] ): WorkPackage[] { return workPackages.map((wp) => { const action = actions.find((a) => a.workPackageId === wp.id); if (!action) return wp; switch (action.type) { case "add_tasks": return { ...wp, tasks: [...wp.tasks, ...(action.payload.tasks as WorkPackageTask[])] }; case "append_output": return { ...wp, outputs: [action.payload.output as WorkPackageOutput, ...wp.outputs] }; case "update_section": return { ...wp, coreSections: [...wp.coreSections, action.payload.section as string] }; case "change_status": return { ...wp, status: action.payload.status as WorkPackageStatus }; case "update_task": return { ...wp, tasks: wp.tasks.map((t) => t.id === action.payload.taskId ? { ...t, ...action.payload.patch } : t )}; default: return wp; } }); } ``` **为什么需要这个**: - Mock Agent 当前直接在内部 mutation WorkPackage——这污染了 mock agent 的逻辑 - 拆出执行器后,mock agent 只负责生成 action,不碰数据 - LLM 返回的 `boardAction` JSON 也走同一个执行器——一条路径,两种来源 --- ### 第 4 步:重构 Mock Agent 使其返回 BoardAction(20 分钟) 当前 mock-agent 直接返回突变后的 WorkPackage[]。改为返回 `AgentTurnResult`: ```typescript // 旧接口 function runMockAgentTurn(input, workPackages): { assistantMessage, workPackages } // 新接口 function runMockAgentTurn(context: AgentContext): AgentTurnResult ``` Mock Agent 内部不再直接修改 WorkPackage,而是构建 `BoardAction[]`。例如: ``` mode === "plan" → boardActions = [ { type: "add_tasks", workPackageId: "wp-srs", payload: { tasks: [...] } }, { type: "change_status", workPackageId: "wp-srs", payload: { status: "in_progress" } } ] ``` --- ### 第 5 步:创建 Agent 编排器(30 分钟) **文件**:`lib/agent.ts` ```typescript export async function runAgentTurn(context: AgentContext): Promise { // 1. 检查是否有 API 配置 const hasLLMConfig = !!process.env.NEXT_PUBLIC_LLM_ENABLED; if (!hasLLMConfig) { // Mock 回退 return runMockAgentTurn(context); } // 2. 调用 LLM API try { const response = await fetch("/api/chat", { method: "POST", body: JSON.stringify(context), }); if (!response.ok) { // API 失败,回退到 mock return runMockAgentTurn(context); } return await response.json(); } catch { // 网络错误,回退到 mock return runMockAgentTurn(context); } } ``` **兜底策略**:三层保护 1. 无 API key → mock 2. API 返回错误 → mock 3. 网络错误 → mock 任何时候 UI 都不会崩溃,只是功能降级。 --- ### 第 6 步:在 UI 层接入真实 Agent(30 分钟) 修改 `AgenticPmWorkbench.tsx` 中的 `handleSend()`: ```typescript // 旧代码 function handleSend() { const result = runMockAgentTurn(content, workPackages); setMessages(...) setWorkPackages(result.workPackages) } // 新代码 async function handleSend() { setIsLoading(true); const context: AgentContext = { messages, workPackages, parsedCommand: parseCommand(content, workPackages), }; const result = await runAgentTurn(context); // 统一的 board action 执行 const nextPackages = applyBoardActions(workPackages, result.boardActions); setMessages(...) setWorkPackages(nextPackages) setIsLoading(false); } ``` **注意**:新 UI 需要的功能: - `isLoading` 状态 —— 在 LLM 调用期间显示旋转动画或 "Agent is thinking..." - `ConnectionStatus` 指示器 —— 类型已定义(idle/checking/connected/error),需接入 UI - 错误提示 —— API 失败时 toast 通知 --- ### 第 7 步:UX 打磨(45 分钟) 1. **连接状态指示器** - 顶部栏显示一个圆点:绿色 = connected,灰色 = mock mode,红色 = error - 实现 `ConnectionStatus` 类型的状态机 2. **加载动画** - 等待 LLM 回复时,聊天区显示波浪动画 - 禁用发送按钮防止重复提交 3. **Mock Mode 标识** - 未配置 API key 时,在标题栏旁显示 "MOCK MODE" 标签 4. **错误 Toast** - API 调用失败时弹出短暂提示,自动消失 - 显示回退到 mock 的提示 --- ## 四、调用链最终形态 ``` 用户输入 "SRS plan 拆成组件模块并定义接口" │ ▼ parseCommand(text, packages) ← 纯同步 │ ▼ AgentContext { messages, workPackages, parsedCommand } │ ▼ runAgentTurn(context) ← 异步 │ ├── LLM 可用? │ │ │ YES──→ fetch("/api/chat", { body: context }) │ │ │ │ │ ▼ │ │ DeepSeek API │ │ POST /chat/completions │ │ model: deepseek-chat │ │ │ │ │ ▼ │ │ JSON: { assistantMessage, boardActions } │ │ │ NO / ERROR ──→ runMockAgentTurn(context) │ │ │ ▼ │ AgentTurnResult { assistantMessage, boardActions } │ ▼ AgentTurnResult │ ▼ applyBoardActions(workPackages, boardActions) ← 纯函数 │ ▼ setState({ messages: [...], workPackages: [...] }) │ ▼ UI 重新渲染 ``` ## 五、实施优先级和预估 | 步骤 | 内容 | 预估 | 优先级 | |------|------|------|--------| | 0 | 修复项目结构,使可构建 | 30 分钟 | 🔴 阻塞 | | 1 | 统一 Agent 接口(AgentContext, AgentTurnResult, BoardAction) | 30 分钟 | 🔴 基础 | | 2 | 创建 `/api/chat` 路由(DeepSeek) | 45 分钟 | 🔴 核心 | | 3 | 实现 `board-reducer.ts`(执行 BoardAction) | 30 分钟 | 🟡 基础 | | 4 | 重构 Mock Agent 返回 BoardAction | 20 分钟 | 🟡 基础 | | 5 | 创建 Agent 编排器(LLM + Mock 回退) | 30 分钟 | 🔴 核心 | | 6 | UI 接入真实 Agent | 30 分钟 | 🟡 关键 | | 7 | UX 打磨(加载态/状态指示/mock标识) | 45 分钟 | 🟢 重要 | **总预估**:约 4-5 小时(含测试和调试) ## 六、关键设计决策 ### 为什么不使用流式输出(SSE)? MVP 阶段不实现流式。原因: - 结构化 JSON 响应不适合流式输出(必须等完整 JSON 才能 parse) - 简化错误处理和重试逻辑 - 后续可升级,在 `/api/chat` 路由添加 `stream: true` 参数 ### 为什么 BoardAction 走前端执行而不是 API 路由直接执行? - **透明性**:用户可以在 agent log 中看到每个 action 的执行结果 - **可撤销**:未来可以支持 undo(因为 state 变化是可追溯的) - **一致性**:Mock Agent 和 LLM Agent 走同一条执行路径 - **安全性**:API 路由不需要访问 React state ### 为什么 system prompt 里保留模拟免责声明? 真 Agent 也可以生成模拟输出。MVP 阶段即使接了 DeepSeek,所有 execute 仍然是模拟的——不会真正去跑测试、查专利、生成图像。免责声明是产品特性而非 mock 专用的。 ## 七、DeepSeek 接入要点 - **Base URL**: `https://api.deepseek.com` - **Model**: `deepseek-chat`(性价比最优,支持 JSON mode) - **特殊配置**: 无需特殊配置,标准 OpenAI SDK 直接兼容 - **JSON mode**: DeepSeek 支持 `response_format: { type: "json_object" }` - **Token 限制**: 上下文 64K tokens,足够塞 12 个 WorkPackage + 对话历史 - **提示**: system prompt 里写清楚 JSON 格式要求,user message 里加上 "Respond with JSON only." --- > **下一步**:确认此计划后,从第 0 步开始执行。