# Prompt Builder 分层注入系统 实施计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 新建 prompt_builder + prompt_guidance 模块,将 5 个调用点的手动提示词拼接替换为统一的 8 层分层注入,并在前端提供预览面板。 **Architecture:** 纯函数 `buildSystemPrompt()` 接收 Agent 配置、模型名、可用工具列表等参数,按 8 层顺序组装系统提示词。所有指导文本常量集中在 `prompt_guidance.ts`。5 个调用点(gateway、agent_executor、crew_orchestrator、crew_delegate、controller preview)统一调用 builder,移除各自的手动拼接逻辑。 **Tech Stack:** TypeScript, Midway.js, Vue 3 + Element Plus **Spec:** `docs/superpowers/specs/2026-04-15-prompt-builder-design.md` --- ## 文件结构 ### 新建文件 | 文件 | 职责 | |------|------| | `packages/backend/src/modules/netaclaw/runtime/prompt_guidance.ts` | 所有指导文本常量(工具纪律、模型指导、工具策略、统一 MEMORY_SYSTEM_PROMPT) | | `packages/backend/src/modules/netaclaw/runtime/prompt_builder.ts` | `buildSystemPrompt()` 纯函数 + `collectAvailableToolNames()` 辅助函数 | | `packages/backend/test/prompt_builder.test.ts` | prompt_builder 纯函数单元测试 | ### 修改文件 | 文件 | 变更 | |------|------| | `packages/backend/src/modules/netaclaw/service/skill_loader.ts:143-161` | `buildSkillsPrompt()` 增强索引格式(category + tags + 强制措辞) | | `packages/backend/src/modules/netaclaw/gateway/server.ts:25-42,167-209` | 移除 MEMORY_SYSTEM_PROMPT,替换手动拼接为调用 builder | | `packages/backend/src/modules/netaclaw/service/agent_executor.ts:20-24,98-136` | 移除 MEMORY_SYSTEM_PROMPT,替换手动拼接为调用 builder | | `packages/backend/src/modules/netaclaw/runtime/agent.ts:64-66` | 移除 memoryContext 拼接(已由 builder 处理) | | `packages/backend/src/modules/netaclaw/service/crew_orchestrator.ts:224-257` | `buildEnhancedPrompt()` 改为调用 builder + crewContext | | `packages/backend/src/modules/netaclaw/service/crew_delegate.ts:47-53` | `buildSubAgentPrompt()` 改为调用 builder | | `packages/backend/src/modules/netaclaw/controller/agent.ts:6-41` | 新增 `previewPrompt` 接口 | | `packages/frontend/src/modules/agent/views/agent-edit.vue:94-102,211-480` | 系统提示词 Tab 下方新增折叠预览面板 | --- ## Task 1: 创建 prompt_guidance.ts — 指导文本常量 **Files:** - Create: `packages/backend/src/modules/netaclaw/runtime/prompt_guidance.ts` - [ ] **Step 1: 创建 prompt_guidance.ts 文件(第一部分:MEMORY + TOOL_USE_ENFORCEMENT + MODEL_GUIDANCE_RULES 前半)** ```typescript // packages/backend/src/modules/netaclaw/runtime/prompt_guidance.ts /** * Prompt Builder 指导文本常量 * 所有注入到系统提示词的指导文本集中管理于此文件。 */ // ─── 统一的 MEMORY_SYSTEM_PROMPT(合并 gateway 详细版 + agent_executor 简版) ─── export const MEMORY_SYSTEM_PROMPT = ` ## 记忆系统 你拥有长期记忆能力。使用 memory_save 工具存储重要信息,使用 memory_recall 工具检索过往记忆。 记忆类型: - user: 用户画像(偏好、角色、习惯) - project: 项目知识(进展、决策、约束) - feedback: 行为反馈(用户对你行为的纠正或确认) - reference: 引用(外部资源链接、文档地址) 存储原则: - 当用户透露个人偏好、角色、习惯时,存为 user 类型 - 当了解到项目进展、决策、约束时,存为 project 类型 - 当用户纠正或确认你的行为时,存为 feedback 类型 - 当提到外部资源链接时,存为 reference 类型 - 更新已有记忆而非创建重复条目 - 只存储对未来对话有价值的信息`; // ─── Layer 2: 工具使用纪律(通用) ─── export const TOOL_USE_ENFORCEMENT = `# 工具使用规范 你必须通过工具采取行动 - 不要只描述你打算做什么。当你说"我来检查一下"、"让我执行"时,必须在同一回复中立即发起对应的工具调用。不要以"下一步我会..."结束回复 - 现在就执行。 持续工作直到任务真正完成。不要停在计划阶段。如果你有可用的工具能完成任务,使用它们,而不是告诉用户你会怎么做。 每条回复要么 (a) 包含推进任务的工具调用,要么 (b) 向用户交付最终结果。仅描述意图而不行动的回复是不可接受的。`; // ─── Layer 3: 模型特定指导 ─── interface ModelGuidanceRule { patterns: string[]; guidance: string; } const MODEL_GUIDANCE_RULES: ModelGuidanceRule[] = [ { patterns: ['claude'], guidance: '', // Claude 工具调用能力强,不需要额外指导 }, { patterns: ['gpt', 'o1', 'o3', 'o4'], guidance: `# 执行纪律 - 工具能提升正确性时必须使用,不要提前停止 - 工具返回空结果时换一种查询策略重试 - 持续调用工具直到:(1) 任务完成 且 (2) 已验证结果 以下问题绝不能凭记忆回答,必须使用工具: - 算术计算 → bash - 文件内容、大小 → read_file / list_dir - 当前时间日期 → bash `, }, { patterns: ['deepseek'], guidance: `# 操作规范 - 支持并行工具调用:多个独立操作可在同一回复中发起 - 工具参数必须是合法 JSON,注意字符串转义 - 不要在工具调用的同时输出大段解释,先执行再说明`, }, { patterns: ['minimax', 'abab'], guidance: `# 操作规范 - 算术、日期、文件内容等事实性问题必须使用工具获取,不要凭记忆回答 - 调用工具时严格按照参数 schema 传参,不要添加 schema 中不存在的字段 - 一次只调用一个工具,等待结果后再决定下一步 - 如果工具返回错误,换一种方式重试,不要放弃`, }, { patterns: ['doubao', 'skylark'], guidance: `# 操作规范 - 工具参数必须严格匹配 schema 定义的类型 - 使用 bash 工具时加 -y 等非交互标志,避免命令挂起 - 先用 read_file/list_dir 了解现状,再做修改操作`, }, { patterns: ['qwen'], guidance: `# 操作规范 - 工具调用参数必须严格匹配 schema,不要添加额外字段 - 使用 bash 工具时加 -y 等非交互标志,避免命令挂起 - 先用 read_file/list_dir 了解现状,再做修改操作`, }, { patterns: ['glm', 'chatglm'], guidance: `# 操作规范 - 工具调用参数必须严格匹配 schema 定义的类型(string/number/boolean) - 文件路径使用绝对路径 - 每次工具调用后检查返回结果,确认操作成功后再继续`, }, ]; const DEFAULT_MODEL_GUIDANCE = `# 操作规范 - 事实性问题(算术、日期、文件内容)必须使用工具获取,不要凭记忆回答 - 调用工具时严格按照参数 schema 传参 - 一次只调用一个工具,等待结果后再决定下一步 - 如果工具返回错误,换一种方式重试`; export function getModelGuidance(modelId: string): string { const lower = modelId.toLowerCase(); for (const rule of MODEL_GUIDANCE_RULES) { if (rule.patterns.some(p => lower.includes(p))) { return rule.guidance; } } return DEFAULT_MODEL_GUIDANCE; } // ─── PLACEHOLDER_TOOL_BEHAVIOR ─── ``` - [ ] **Step 2: 追加 prompt_guidance.ts 第二部分(Layer 4 工具行为策略)** 在 `// ─── PLACEHOLDER_TOOL_BEHAVIOR ───` 处替换为: ```typescript // ─── Layer 4: 工具行为策略(条件注入) ─── const TOOL_BEHAVIOR_GUIDANCE: Record = { memory_save: `## 记忆使用策略 使用 memory_save 存储对未来对话有价值的持久信息:用户偏好、环境细节、项目约束。 保持记忆紧凑,聚焦于减少用户未来重复纠正的事实。 不要存储任务进度、会话结果、临时 TODO 状态。更新已有记忆而非创建重复条目。`, todo: `## 任务规划策略 面对3步以上的复杂任务或用户提供多个任务时,先用 todo 工具创建任务列表。 同一时间只标记一个任务为 in_progress。完成后立即标记 completed。`, skill_manage: `## 技能管理策略 完成复杂任务(5步以上工具调用)后,考虑用 skill_manage 将方法保存为技能以便复用。 使用技能时如发现过时或错误,立即修补,不要等用户要求。`, }; export function getToolBehaviorGuidance(toolNames: string[]): string { const parts: string[] = []; for (const [toolName, guidance] of Object.entries(TOOL_BEHAVIOR_GUIDANCE)) { if (toolNames.includes(toolName)) { parts.push(guidance); } } return parts.join('\n\n'); } ``` - [ ] **Step 3: 提交** ```bash git add packages/backend/src/modules/netaclaw/runtime/prompt_guidance.ts git commit -m "feat(netaclaw): 新建 prompt_guidance.ts — 集中管理所有指导文本常量" ``` --- ## Task 2: 创建 prompt_builder.ts — 核心构建函数 **Files:** - Create: `packages/backend/src/modules/netaclaw/runtime/prompt_builder.ts` - [ ] **Step 1: 创建 prompt_builder.ts 文件(接口定义 + collectAvailableToolNames)** ```typescript // packages/backend/src/modules/netaclaw/runtime/prompt_builder.ts import { SkillLoaderService } from '../service/skill_loader.js'; import { TOOL_USE_ENFORCEMENT, MEMORY_SYSTEM_PROMPT, getModelGuidance, getToolBehaviorGuidance, } from './prompt_guidance.js'; /** 基础内置工具(始终可用) */ const BASE_TOOLS = ['bash', 'read_file', 'write_file', 'list_dir', 'todo']; /** 构建完整的可用工具名列表 */ export function collectAvailableToolNames(opts: { memoryEnabled?: boolean; hasSkills?: boolean; crewRole?: 'master' | 'sub'; }): string[] { const names = [...BASE_TOOLS]; if (opts.memoryEnabled) names.push('memory_save', 'memory_recall'); if (opts.hasSkills) names.push('read_skill', 'read_skill_file', 'skill_manage'); if (opts.crewRole === 'master') names.push('delegate_task', 'delegate_parallel', 'escalate'); return names; } export interface BuildSystemPromptParams { agentSystemPrompt: string; modelId: string; availableToolNames: string[]; skills: string[]; skillLoader: SkillLoaderService; memoryEnabled: boolean; memoryContext?: string; crewContext?: string; } export interface PromptLayer { name: string; content: string; } export interface BuildResult { prompt: string; layers: PromptLayer[]; } // ─── PLACEHOLDER_BUILD_FN ─── ``` - [ ] **Step 2: 追加 buildSystemPrompt 函数实现** 在 `// ─── PLACEHOLDER_BUILD_FN ───` 处替换为: ```typescript /** 8 层分层组装系统提示词 */ export function buildSystemPrompt(params: BuildSystemPromptParams): BuildResult { const { agentSystemPrompt, modelId, availableToolNames, skills, skillLoader, memoryEnabled, memoryContext, crewContext, } = params; const layers: PromptLayer[] = []; // Layer 1: Agent 身份(用户写的 systemPrompt,原样保留) if (agentSystemPrompt) { layers.push({ name: 'Agent 身份', content: agentSystemPrompt }); } // Layer 2: 工具使用纪律(通用,所有有工具的 Agent 都注入) if (availableToolNames.length > 0) { layers.push({ name: '工具使用纪律', content: TOOL_USE_ENFORCEMENT }); } // Layer 3: 模型特定指导(按模型名称子串匹配) const modelGuidance = getModelGuidance(modelId); if (modelGuidance) { layers.push({ name: '模型特定指导', content: modelGuidance }); } // Layer 4: 工具行为策略(按可用工具条件注入) const toolBehavior = getToolBehaviorGuidance(availableToolNames); if (toolBehavior) { layers.push({ name: '工具行为策略', content: toolBehavior }); } // Layer 5: Skill 索引(增强版) if (skills.length > 0) { const skillPrompt = skillLoader.buildSkillsPrompt(skills, availableToolNames); if (skillPrompt) { layers.push({ name: 'Skill 索引', content: skillPrompt }); } } // Layer 6: 记忆系统 if (memoryEnabled) { let memoryContent = MEMORY_SYSTEM_PROMPT; if (memoryContext) { memoryContent += `\n\n\n${memoryContext}\n`; } layers.push({ name: '记忆系统', content: memoryContent }); } // Layer 7: Crew 编排上下文(仅 Crew 场景) if (crewContext) { layers.push({ name: 'Crew 编排上下文', content: crewContext }); } // Layer 8: 元信息 const meta = `\n---\n当前时间: ${new Date().toISOString()}\n模型: ${modelId}`; layers.push({ name: '元信息', content: meta }); const prompt = layers.map(l => l.content).join('\n\n'); return { prompt, layers }; } ``` - [ ] **Step 3: 提交** ```bash git add packages/backend/src/modules/netaclaw/runtime/prompt_builder.ts git commit -m "feat(netaclaw): 新建 prompt_builder.ts — 8层分层系统提示词组装引擎" ``` --- ## Task 3: 单元测试 prompt_builder + prompt_guidance **Files:** - Create: `packages/backend/test/prompt_builder.test.ts` 项目已有 Jest 基础设施(`jest.config.js` + `ts-jest`),但无实际测试文件。prompt_builder 和 prompt_guidance 是纯函数,不依赖 Midway 框架,可以直接用 Jest 测试。 - [ ] **Step 1: 编写测试文件(第一部分:collectAvailableToolNames + getModelGuidance)** ```typescript // packages/backend/test/prompt_builder.test.ts import { collectAvailableToolNames, buildSystemPrompt } from '../src/modules/netaclaw/runtime/prompt_builder.js'; import { getModelGuidance, getToolBehaviorGuidance, TOOL_USE_ENFORCEMENT, MEMORY_SYSTEM_PROMPT } from '../src/modules/netaclaw/runtime/prompt_guidance.js'; // ─── collectAvailableToolNames ─── describe('collectAvailableToolNames', () => { test('基础工具始终包含 todo', () => { const names = collectAvailableToolNames({}); expect(names).toContain('bash'); expect(names).toContain('read_file'); expect(names).toContain('write_file'); expect(names).toContain('list_dir'); expect(names).toContain('todo'); }); test('memoryEnabled 追加记忆工具', () => { const names = collectAvailableToolNames({ memoryEnabled: true }); expect(names).toContain('memory_save'); expect(names).toContain('memory_recall'); }); test('hasSkills 追加 skill 工具', () => { const names = collectAvailableToolNames({ hasSkills: true }); expect(names).toContain('read_skill'); expect(names).toContain('read_skill_file'); expect(names).toContain('skill_manage'); }); test('crewRole=master 追加委派工具', () => { const names = collectAvailableToolNames({ crewRole: 'master' }); expect(names).toContain('delegate_task'); expect(names).toContain('delegate_parallel'); expect(names).toContain('escalate'); }); test('crewRole=sub 不追加委派工具', () => { const names = collectAvailableToolNames({ crewRole: 'sub' }); expect(names).not.toContain('delegate_task'); }); }); // ─── getModelGuidance ─── describe('getModelGuidance', () => { test('Claude 返回空字符串', () => { expect(getModelGuidance('claude-sonnet-4-20250514')).toBe(''); }); test('MiniMax 匹配 minimax 规则', () => { const g = getModelGuidance('MiniMax-M2.7-highspeed'); expect(g).toContain('一次只调用一个工具'); }); test('GPT 匹配 gpt 规则', () => { const g = getModelGuidance('gpt-4o-2024-08-06'); expect(g).toContain('tool_persistence'); }); test('DeepSeek 匹配 deepseek 规则', () => { const g = getModelGuidance('deepseek-chat'); expect(g).toContain('并行工具调用'); }); test('未知模型返回默认指导', () => { const g = getModelGuidance('some-unknown-model-v1'); expect(g).toContain('事实性问题'); }); test('火山引擎跑 doubao 模型匹配 doubao 规则', () => { const g = getModelGuidance('doubao-pro-32k'); expect(g).toContain('非交互标志'); }); }); // ─── getToolBehaviorGuidance ─── describe('getToolBehaviorGuidance', () => { test('包含 todo 时注入任务规划策略', () => { const g = getToolBehaviorGuidance(['bash', 'todo']); expect(g).toContain('任务规划策略'); }); test('包含 memory_save 时注入记忆策略', () => { const g = getToolBehaviorGuidance(['memory_save', 'memory_recall']); expect(g).toContain('记忆使用策略'); }); test('无匹配工具时返回空字符串', () => { const g = getToolBehaviorGuidance(['bash', 'read_file']); expect(g).toBe(''); }); }); ``` - [ ] **Step 2: 追加测试文件第二部分(buildSystemPrompt 集成测试)** 在测试文件末尾追加: ```typescript // ─── buildSystemPrompt ─── // 模拟 SkillLoaderService(只需 buildSkillsPrompt 方法) function createMockSkillLoader(returnValue: string) { return { buildSkillsPrompt: jest.fn().mockReturnValue(returnValue), } as any; } describe('buildSystemPrompt', () => { test('基础场景:无 skill、无记忆、无 crew', () => { const result = buildSystemPrompt({ agentSystemPrompt: '你是电商助手', modelId: 'MiniMax-M2.7-highspeed', availableToolNames: ['bash', 'read_file', 'write_file', 'list_dir', 'todo'], skills: [], skillLoader: createMockSkillLoader(''), memoryEnabled: false, }); // Layer 1: Agent 身份 expect(result.prompt).toContain('你是电商助手'); // Layer 2: 工具纪律 expect(result.prompt).toContain('工具使用规范'); // Layer 3: MiniMax 模型指导 expect(result.prompt).toContain('一次只调用一个工具'); // Layer 4: todo 工具策略 expect(result.prompt).toContain('任务规划策略'); // Layer 8: 元信息 expect(result.prompt).toContain('模型: MiniMax-M2.7-highspeed'); // 不含记忆 expect(result.prompt).not.toContain('记忆系统'); // layers 数组正确 expect(result.layers.map(l => l.name)).toEqual([ 'Agent 身份', '工具使用纪律', '模型特定指导', '工具行为策略', '元信息', ]); }); test('完整场景:skill + 记忆 + memoryContext', () => { const mockLoader = createMockSkillLoader('...'); const result = buildSystemPrompt({ agentSystemPrompt: '你是运营助手', modelId: 'gpt-4o', availableToolNames: ['bash', 'todo', 'memory_save', 'memory_recall', 'read_skill', 'read_skill_file', 'skill_manage'], skills: ['playwright-cli'], skillLoader: mockLoader, memoryEnabled: true, memoryContext: '用户偏好:简洁回复', }); expect(result.prompt).toContain('你是运营助手'); expect(result.prompt).toContain('tool_persistence'); // GPT 指导 expect(result.prompt).toContain('available_skills'); expect(result.prompt).toContain('记忆系统'); expect(result.prompt).toContain(''); expect(result.prompt).toContain('用户偏好:简洁回复'); expect(mockLoader.buildSkillsPrompt).toHaveBeenCalledWith(['playwright-cli'], expect.any(Array)); }); test('Crew 场景:注入 crewContext', () => { const result = buildSystemPrompt({ agentSystemPrompt: '你是主 Agent', modelId: 'claude-sonnet-4-20250514', availableToolNames: ['bash', 'todo', 'delegate_task', 'delegate_parallel', 'escalate'], skills: [], skillLoader: createMockSkillLoader(''), memoryEnabled: false, crewContext: '## 你的团队成员\n- 小明: 数据分析', }); expect(result.prompt).toContain('你的团队成员'); expect(result.layers.find(l => l.name === 'Crew 编排上下文')).toBeTruthy(); // Claude 模型指导为空,不应有 Layer 3 expect(result.layers.find(l => l.name === '模型特定指导')).toBeFalsy(); }); test('空 agentSystemPrompt 不生成 Layer 1', () => { const result = buildSystemPrompt({ agentSystemPrompt: '', modelId: 'deepseek-chat', availableToolNames: ['bash', 'todo'], skills: [], skillLoader: createMockSkillLoader(''), memoryEnabled: false, }); expect(result.layers.find(l => l.name === 'Agent 身份')).toBeFalsy(); expect(result.layers[0].name).toBe('工具使用纪律'); }); }); ``` - [ ] **Step 3: 运行测试确认全部通过** ```bash cd packages/backend npx jest test/prompt_builder.test.ts --verbose ``` 预期输出:所有 test case PASS。如果 ts-jest 的 ESM 导入有问题(`.js` 后缀),需要在 jest.config.js 中添加 `moduleNameMapper` 将 `.js` 映射到 `.ts`。 如果遇到 ESM 问题,在 `jest.config.js` 中添加: ```javascript moduleNameMapper: { '^(\\.{1,2}/.*)\\.js$': '$1', }, ``` - [ ] **Step 4: 提交** ```bash git add packages/backend/test/prompt_builder.test.ts git add packages/backend/jest.config.js # 如果修改了 git commit -m "test(netaclaw): prompt_builder + prompt_guidance 纯函数单元测试" ``` --- ## Task 4: 增强 skill_loader.ts — 索引格式升级 **Files:** - Modify: `packages/backend/src/modules/netaclaw/service/skill_loader.ts:143-161` - [ ] **Step 1: 修改 buildSkillsPrompt() 方法** 打开 `packages/backend/src/modules/netaclaw/service/skill_loader.ts`,将 `buildSkillsPrompt()` 方法(第 143-161 行)替换为: ```typescript buildSkillsPrompt(skillNames: string[], availableTools: string[]): string { const filtered = this.filterByConditions(skillNames, availableTools); if (filtered.length === 0) return ''; const lines: string[] = []; let totalChars = 0; for (const s of filtered) { const category = (s.metadata?.category as string) || '通用'; const tags = Array.isArray(s.metadata?.tags) ? (s.metadata.tags as string[]).join(',') : ''; const line = ` \n ${s.description}\n `; if (totalChars + line.length > SkillLoaderService.MAX_SKILLS_PROMPT_CHARS) { this.logger.warn('[SkillLoader] Skill 索引超过 %d 字符限制,截断', SkillLoaderService.MAX_SKILLS_PROMPT_CHARS); break; } lines.push(line); totalChars += line.length; } if (lines.length === 0) return ''; return `\n\n## 技能(必须扫描)\n回复前,扫描以下技能列表。如果某个技能明确匹配当前任务,用 read_skill 工具加载完整指令后严格遵循。\n\n\n${lines.join('\n')}\n`; } ``` 变更点: 1. 每个 `` 标签增加 `category` 和 `tags` 属性 2. 索引头部从被动措辞改为强制措辞("必须扫描") 3. 尾部说明从 `当你需要使用某个 skill 时...` 改为 `回复前,扫描以下技能列表...` - [ ] **Step 2: 验证编译通过** ```bash cd packages/backend npx tsc --noEmit --pretty 2>&1 | head -20 ``` 预期:无新增编译错误。 - [ ] **Step 3: 提交** ```bash git add packages/backend/src/modules/netaclaw/service/skill_loader.ts git commit -m "feat(netaclaw): skill 索引增强 — category/tags 属性 + 强制扫描措辞" ``` --- ## Task 5: 改造 gateway/server.ts — 替换手动拼接 **Files:** - Modify: `packages/backend/src/modules/netaclaw/gateway/server.ts:1-42,131-135,167-177,192-210,226-229` 这是最核心的改造,WebSocket 对话入口。 - [ ] **Step 1: 添加 import 并移除 MEMORY_SYSTEM_PROMPT** 在文件顶部 import 区域添加: ```typescript import { buildSystemPrompt, collectAvailableToolNames } from '../runtime/prompt_builder.js'; ``` 删除第 25-42 行的 `MEMORY_SYSTEM_PROMPT` 常量(整个 const 声明)。 - [ ] **Step 2: 替换 handleChat() 中的 systemPrompt 组装逻辑** 找到 `handleChat()` 方法中构建 `agentConfig` 的部分。 当前代码(约第 131-135 行): ```typescript const { skillPrompt, skillTools: agentSkillTools } = buildSkillContext( this.skillLoader, agentInfo.skills, builtinToolNames, ); ``` 替换为: ```typescript const { skillTools: agentSkillTools } = buildSkillContext( this.skillLoader, agentInfo.skills, builtinToolNames, ); ``` 注意:仍然需要 `buildSkillContext` 来获取 `skillTools`(read_skill 等工具实例),但不再需要 `skillPrompt`(由 builder 内部处理)。 - [ ] **Step 3: 提前记忆系统代码 + 替换 agentConfig 构建** 当前 gateway 中记忆系统代码在第 192-210 行(agentConfig 构建之后),需要提前到 `buildSystemPrompt` 调用之前。同时消除冗余的 `agentInfo2` 重新查询。 找到第 192-210 行的记忆系统代码块,**整块删除**(包括 `agentInfo2` 查询)。 然后将第 167-177 行的 agentConfig 构建替换为以下代码(记忆处理 + builder 调用 + agentConfig 构建一体化): ```typescript // --- 记忆系统(从 cfg 直接取,不再重新查询 agentInfo2) --- const memoryConfig: AgentMemoryConfig | undefined = cfg?.memory; let memoryContext: string | undefined; let memoryTools: AnyAgentTool[] = []; if (memoryConfig?.enabled) { const provider = createMemoryProvider(memoryConfig, this.memoryRepo); const userId = 'anonymous'; memoryContext = await prefetchMemory(provider, content, agentInfo.name, userId, memoryConfig.prefetchLimit); memoryTools = [ createMemorySaveTool(provider, agentInfo.name, userId), createMemoryRecallTool(provider, agentInfo.name, userId), ]; } // --- 分层组装系统提示词 --- const toolNames = collectAvailableToolNames({ memoryEnabled: !!memoryConfig?.enabled, hasSkills: !!agentInfo.skills?.length, }); const { prompt: systemPrompt } = buildSystemPrompt({ agentSystemPrompt: agentInfo.systemPrompt || '', modelId: mc.modelId || '', // 纯模型名,不含 provider: 前缀 availableToolNames: toolNames, skills: agentInfo.skills || [], skillLoader: this.skillLoader, memoryEnabled: !!memoryConfig?.enabled, memoryContext, }); agentConfig = { name: agentInfo.name, systemPrompt, model, apiKey, baseUrl, channelId, channelName, channelSupplier, maxToolRounds: cfg?.middleware?.maxToolRounds ?? cfg?.middleware?.maxToolCalls ?? 20, temperature: cfg?.temperature, maxTokens: cfg?.maxTokens, defaultThinkLevel: cfg?.thinking?.defaultLevel, }; ``` 注意事项: - `modelId: mc.modelId || ''` 传纯模型名(如 `MiniMax-M2.7-highspeed`),不是 `model` 变量(`openai:MiniMax-M2.7-highspeed`) - `memoryConfig` 从 `cfg?.memory` 取(`cfg` 在第 129 行已有),不再重新查询 `agentInfo2` - 原第 193 行的 `const agentInfo2 = agentId ? await this.agentService.info(agentId) : null;` 删除(消除冗余数据库查询) - [ ] **Step 4: 移除 runAgent 调用中的 memoryContext 参数** 找到 `runAgent()` 调用(约第 229 行),移除 `memoryContext` 参数: ```typescript const result = await runAgent({ agentConfig, tools: [...this.defaultTools, ...memoryTools, ...agentSkillTools], userMessage: content, history: history.slice(0, -1), // memoryContext 已移除 — 由 builder 处理 sessionThinkLevel: sessionThinkLevel ?? undefined, onToken: (token) => { /* ... */ }, // ... }); ``` - [ ] **Step 5: 验证编译通过** ```bash cd packages/backend npx tsc --noEmit --pretty 2>&1 | head -30 ``` - [ ] **Step 6: 提交** ```bash git add packages/backend/src/modules/netaclaw/gateway/server.ts git commit -m "refactor(netaclaw): gateway handleChat() 替换手动拼接为 prompt_builder" ``` --- ## Task 6: 改造 agent_executor.ts — 替换手动拼接 **Files:** - Modify: `packages/backend/src/modules/netaclaw/service/agent_executor.ts:1-24,77-100,116-141` - [ ] **Step 1: 添加 import 并移除 MEMORY_SYSTEM_PROMPT** 在文件顶部 import 区域添加: ```typescript import { buildSystemPrompt, collectAvailableToolNames } from '../runtime/prompt_builder.js'; ``` 删除第 20-24 行的 `MEMORY_SYSTEM_PROMPT` 常量。 - [ ] **Step 2: 替换 execute() 中的 systemPrompt 组装** 找到 `execute()` 方法中构建 skillPrompt 的部分(约第 72-83 行)。 当前代码: ```typescript let skillTools: AnyAgentTool[] = []; let skillPrompt = ''; // ... const builtinToolNames = ['bash', 'read_file', 'write_file', 'list_dir']; const skillCtx = buildSkillContext(this.skillLoader, agentEntity.skills, builtinToolNames); skillPrompt = skillCtx.skillPrompt; skillTools = skillCtx.skillTools; ``` 替换为: ```typescript let skillTools: AnyAgentTool[] = []; // ... const builtinToolNames = ['bash', 'read_file', 'write_file', 'list_dir']; const skillCtx = buildSkillContext(this.skillLoader, agentEntity.skills, builtinToolNames); skillTools = skillCtx.skillTools; ``` 移除 `skillPrompt` 变量(不再需要)。 - [ ] **Step 3: 替换 agentConfig 构建和记忆拼接** 找到 agentConfig 构建(约第 98-105 行)和记忆拼接(约第 116-136 行)。 将 agentConfig 构建中的 `systemPrompt: (agentEntity.systemPrompt || '') + skillPrompt` 替换。 注意:`model` 变量在渠道解析后变成了 `provider:model` 格式(如 `openai:MiniMax-M2.7-highspeed`),但 builder 需要纯模型名。用 `mc.modelId` 而不是 `model`。 在记忆预取完成后,统一构建: ```typescript // 记忆预取(保持原有逻辑获取 memoryContext 和 memoryTools) let memoryContext: string | undefined; let memoryTools: AnyAgentTool[] = []; const memoryConfig: AgentMemoryConfig | undefined = cfg?.memory; if (memoryConfig?.enabled) { const provider = createMemoryProvider(memoryConfig, this.memoryEntity); memoryContext = await prefetchMemory(provider, params.message, agentEntity.name, params.userId, memoryConfig.prefetchLimit); memoryTools = [createMemorySaveTool(provider), createMemoryRecallTool(provider)]; } // 构建可用工具名列表 const toolNames = collectAvailableToolNames({ memoryEnabled: !!memoryConfig?.enabled, hasSkills: !!agentEntity.skills?.length, }); // 分层组装系统提示词 const { prompt: systemPrompt } = buildSystemPrompt({ agentSystemPrompt: agentEntity.systemPrompt || '', modelId: mc.modelId || '', // 纯模型名,不含 provider: 前缀 availableToolNames: toolNames, skills: agentEntity.skills || [], skillLoader: this.skillLoader, memoryEnabled: !!memoryConfig?.enabled, memoryContext, }); agentConfig = { name: agentEntity.name, systemPrompt, model, apiKey, baseUrl, maxToolRounds: cfg?.middleware?.maxToolRounds ?? cfg?.middleware?.maxToolCalls ?? 20, }; ``` 同时移除后面的 `agentConfig.systemPrompt += MEMORY_SYSTEM_PROMPT;` 行。 - [ ] **Step 4: 移除 runAgent 调用中的 memoryContext 参数** 找到 `runAgent()` 调用(约第 141 行),移除 `memoryContext` 参数。 - [ ] **Step 5: 验证编译通过** ```bash cd packages/backend npx tsc --noEmit --pretty 2>&1 | head -30 ``` - [ ] **Step 6: 提交** ```bash git add packages/backend/src/modules/netaclaw/service/agent_executor.ts git commit -m "refactor(netaclaw): agent_executor execute() 替换手动拼接为 prompt_builder" ``` --- ## Task 7: 改造 runtime/agent.ts — 移除 memoryContext 拼接 **Files:** - Modify: `packages/backend/src/modules/netaclaw/runtime/agent.ts:26-40,64-66` - [ ] **Step 1: 从 AgentRunParams 接口移除 memoryContext** 打开 `packages/backend/src/modules/netaclaw/runtime/agent.ts`。 找到 `AgentRunParams` 接口(第 26-40 行),移除 `memoryContext?: string;` 字段。 - [ ] **Step 2: 简化 systemContent 构建** 找到第 64-66 行: ```typescript const systemContent = memoryContext ? `${agentConfig.systemPrompt}\n\n\n${memoryContext}\n` : agentConfig.systemPrompt; ``` 替换为: ```typescript const systemContent = agentConfig.systemPrompt; ``` 同时从第 43 行的解构中移除 `memoryContext`: ```typescript // 之前 const { agentConfig, tools, userMessage, history = [], memoryContext, onToken, onThinking, onToolCall, onToolResult } = params; // 之后 const { agentConfig, tools, userMessage, history = [], onToken, onThinking, onToolCall, onToolResult } = params; ``` - [ ] **Step 3: 验证编译通过** ```bash cd packages/backend npx tsc --noEmit --pretty 2>&1 | head -30 ``` 注意:此步骤可能会在 gateway/server.ts 和 agent_executor.ts 中报错(如果它们还在传 memoryContext 参数)。确保 Task 5 和 Task 6 已完成。 - [ ] **Step 4: 提交** ```bash git add packages/backend/src/modules/netaclaw/runtime/agent.ts git commit -m "refactor(netaclaw): runAgent 移除 memoryContext — 已由 prompt_builder 统一处理" ``` --- ## Task 8: 改造 crew_orchestrator.ts — 主 Agent 提示词 **Files:** - Modify: `packages/backend/src/modules/netaclaw/service/crew_orchestrator.ts:1-23,224-257` - [ ] **Step 1: 添加 import** 在文件顶部 import 区域添加: ```typescript import { buildSystemPrompt, collectAvailableToolNames } from '../runtime/prompt_builder.js'; ``` - [ ] **Step 2: 替换 buildEnhancedPrompt() 方法** 找到 `buildEnhancedPrompt()` 方法(第 224-257 行),替换为: ```typescript private buildEnhancedPrompt( masterAgent: NetaClawAgentEntity, ctx: CrewRunContext, crew: NetaClawCrewEntity, resolvedModelId: string, ): string { // 构建 Crew 专属上下文(Layer 7) const crewParts: string[] = []; // 团队成员信息(含技能) const memberList = ctx.memberAgents .map(m => { const skills = m.agent?.skills?.length ? `,已具备技能: ${m.agent.skills.join('、')}` : ''; const desc = m.agent?.description ? `,简介: ${m.agent.description}` : ''; return `- ${m.name} (${m.label}): ${m.role || '无角色描述'}${desc}${skills}`; }) .join('\n'); crewParts.push(`## 你的团队成员\n${memberList}\n\n注意:委派任务时无需让成员安装工具或环境,他们已具备所列技能,直接描述要完成的目标即可。`); // 委派提示 if (crew.delegateHints?.hints) { crewParts.push(`## 调度建议\n${crew.delegateHints.hints}`); } // 工具说明 crewParts.push(`## 编排工具说明 - delegate_task: 将任务委派给指定子 Agent 串行执行 - delegate_parallel: 同时委派多个任务给不同子 Agent 并行执行 - escalate: 遇到无法解决的问题时升级给人工处理 请根据任务需求合理分配工作给团队成员,充分利用并行执行提高效率。`); // 通过 builder 统一组装 const toolNames = collectAvailableToolNames({ hasSkills: !!masterAgent.skills?.length, crewRole: 'master', }); const { prompt } = buildSystemPrompt({ agentSystemPrompt: masterAgent.systemPrompt || '', modelId: resolvedModelId, availableToolNames: toolNames, skills: masterAgent.skills || [], skillLoader: this.skillLoader, memoryEnabled: false, crewContext: crewParts.join('\n\n'), }); return prompt; } ``` - [ ] **Step 3: 更新 buildEnhancedPrompt 的调用处(第 139 行)** 当前代码(`crew_orchestrator.ts` 第 138-139 行): ```typescript // 构建增强系统提示词 const enhancedPrompt = this.buildEnhancedPrompt(masterAgent, ctx, crew); ``` 问题:模型解析在第 141-157 行,在 `buildEnhancedPrompt` 调用之后。需要将调用移到模型解析之后。 将第 138-161 行改为: ```typescript // 解析主 Agent 模型配置(移到 buildEnhancedPrompt 之前) const mc = (masterAgent.modelConfig || {}) as any; let model: string, apiKey: string, baseUrl: string | undefined; if (mc.apiKey && mc.modelId) { model = mc.modelId; apiKey = mc.apiKey; baseUrl = mc.apiUrl; } else if (mc.channelId) { const resolved = await this.modelChannelService.resolveForAgent(mc.channelId, mc.modelId || ''); model = `${resolved.provider}:${resolved.model}`; apiKey = resolved.apiKey; baseUrl = resolved.baseUrl; } else { model = mc.modelId || (process.env.NETACLAW_MODEL ?? 'anthropic:claude-sonnet-4-20250514'); apiKey = process.env.NETACLAW_API_KEY ?? ''; baseUrl = mc.apiUrl; } // 提取纯 modelId(去掉 provider: 前缀)用于 prompt_builder 模型匹配 const pureModelId = model.includes(':') ? model.split(':')[1] : model; // 构建增强系统提示词(现在有 modelId 了) const enhancedPrompt = this.buildEnhancedPrompt(masterAgent, ctx, crew, pureModelId); const agentConfig: AgentConfig = { name: masterAgent.name, systemPrompt: enhancedPrompt, model, apiKey, baseUrl, // ... 其余字段保持不变 ``` - [ ] **Step 4: 验证编译通过** ```bash cd packages/backend npx tsc --noEmit --pretty 2>&1 | head -30 ``` - [ ] **Step 5: 提交** ```bash git add packages/backend/src/modules/netaclaw/service/crew_orchestrator.ts git commit -m "refactor(netaclaw): crew_orchestrator buildEnhancedPrompt 改用 prompt_builder" ``` --- ## Task 9: 改造 crew_delegate.ts — 子 Agent 提示词 **Files:** - Modify: `packages/backend/src/modules/netaclaw/service/crew_delegate.ts:1-13,47-53` - [ ] **Step 1: 添加 import** 在文件顶部 import 区域添加: ```typescript import { buildSystemPrompt, collectAvailableToolNames } from '../runtime/prompt_builder.js'; ``` - [ ] **Step 2: 替换 buildSubAgentPrompt() 方法** 找到 `buildSubAgentPrompt()` 方法(第 47-53 行),替换为: ```typescript private buildSubAgentPrompt( systemPrompt: string, role: string, agentSkills?: string[], modelId?: string, ): string { const rolePrefix = role ? `${systemPrompt}\n\n你在团队中的角色: ${role}` : systemPrompt; const toolNames = collectAvailableToolNames({ hasSkills: !!agentSkills?.length, crewRole: 'sub', }); const { prompt } = buildSystemPrompt({ agentSystemPrompt: rolePrefix, modelId: modelId || '', availableToolNames: toolNames, skills: agentSkills || [], skillLoader: this.skillLoader, memoryEnabled: false, }); return prompt; } ``` - [ ] **Step 3: 更新 buildSubAgentPrompt 的调用处(第 111 行)** 当前代码(`crew_delegate.ts` 第 110-111 行): ```typescript const modelInfo = await this.resolveModelConfig(agent); const systemPrompt = this.buildSubAgentPrompt(agent.systemPrompt || '', member.role, agent.skills); ``` `modelInfo.model` 格式为 `provider:modelId`(如 `openai:MiniMax-M2.7-highspeed`),需要提取纯 modelId。 替换为: ```typescript const modelInfo = await this.resolveModelConfig(agent); // 提取纯 modelId(去掉 provider: 前缀)用于 prompt_builder 模型匹配 const pureModelId = modelInfo.model.includes(':') ? modelInfo.model.split(':')[1] : modelInfo.model; const systemPrompt = this.buildSubAgentPrompt(agent.systemPrompt || '', member.role, agent.skills, pureModelId); ``` - [ ] **Step 4: 验证编译通过** ```bash cd packages/backend npx tsc --noEmit --pretty 2>&1 | head -30 ``` - [ ] **Step 5: 提交** ```bash git add packages/backend/src/modules/netaclaw/service/crew_delegate.ts git commit -m "refactor(netaclaw): crew_delegate buildSubAgentPrompt 改用 prompt_builder" ``` --- ## Task 10: 后端预览接口 — controller/agent.ts **Files:** - Modify: `packages/backend/src/modules/netaclaw/controller/agent.ts:1-41` - [ ] **Step 1: 添加 import 和注入** 在文件顶部添加 import: ```typescript import { buildSystemPrompt, collectAvailableToolNames } from '../runtime/prompt_builder.js'; import { SkillLoaderService } from '../service/skill_loader.js'; import { NetaClawModelChannelService } from '../service/model_channel.js'; ``` 在 `NetaClawAgentAdminController` 类中添加注入: ```typescript @Inject() skillLoader: SkillLoaderService; @Inject() channelService: NetaClawModelChannelService; ``` - [ ] **Step 2: 添加 previewPrompt 接口** 在 `NetaClawAgentAdminController` 类中(info() 方法之后)添加: ```typescript @Post('/previewPrompt', { summary: '预览最终系统提示词' }) async previewPrompt(@Body() body: { systemPrompt: string; skills: string[]; modelConfig: { channelId?: number; modelId?: string }; }) { let modelId = body.modelConfig?.modelId || 'unknown'; // 如果有 channelId,解析渠道获取模型信息 if (body.modelConfig?.channelId) { const resolved = await this.channelService.resolveForAgent( body.modelConfig.channelId, modelId, ); modelId = resolved.model; } const toolNames = collectAvailableToolNames({ hasSkills: !!body.skills?.length, }); const result = buildSystemPrompt({ agentSystemPrompt: body.systemPrompt || '', modelId, availableToolNames: toolNames, skills: body.skills || [], skillLoader: this.skillLoader, memoryEnabled: false, // 预览时不含记忆上下文 }); return this.ok(result); } ``` - [ ] **Step 3: 验证编译通过** ```bash cd packages/backend npx tsc --noEmit --pretty 2>&1 | head -30 ``` - [ ] **Step 4: 提交** ```bash git add packages/backend/src/modules/netaclaw/controller/agent.ts git commit -m "feat(netaclaw): 新增 previewPrompt 接口 — 前端预览最终系统提示词" ``` --- ## Task 11: 前端预览面板 — agent-edit.vue **Files:** - Modify: `packages/frontend/src/modules/agent/views/agent-edit.vue:94-102,211-480` - [ ] **Step 1: 在系统提示词 Tab 下方添加折叠预览面板** 打开 `packages/frontend/src/modules/agent/views/agent-edit.vue`。 找到系统提示词 Tab(第 94-102 行),在 `` 闭合标签之前(第 102 行之前),添加折叠面板: ```vue 刷新预览
{{ layer.name }}
{{ layer.content }}
``` - [ ] **Step 2: 在 script 中添加预览相关的响应式变量和方法** 在 `