504 lines
19 KiB
Markdown
504 lines
19 KiB
Markdown
# Prompt Builder 分层注入系统设计
|
||
|
||
## 背景
|
||
|
||
当前 Neta Agent 的系统提示词组装非常简单(gateway/server.ts 中 4 层拼接),导致:
|
||
1. 模型使用工具时频繁犯基础错误(不调工具只描述、参数格式错误、跳过 read_skill)
|
||
2. 不同模型的工具调用能力差异大,但系统提示词完全相同
|
||
3. 用户手写的 systemPrompt 缺乏工具使用指导,质量参差不齐
|
||
|
||
参考 Hermes Agent 的 prompt_builder.py(989 行,10 层分层架构),在保持 Neta 渐进披露架构不变的前提下,新建 prompt_builder.ts 模块实现分层注入。
|
||
|
||
## 核心原则
|
||
|
||
- **渐进披露不变**:Skill 仍然只在索引中展示 name + description + category + tags,模型需要调 read_skill 加载完整指令
|
||
- **运行时注入**:用户的 systemPrompt 保持干净存数据库,增强部分在运行时动态拼接
|
||
- **按模型名称匹配**:不依赖 supplier 字段,用模型名子串匹配决定注入哪套指导(解决火山引擎跑多种模型的问题)
|
||
- **条件注入**:只有当工具实际可用时才注入对应的行为策略
|
||
|
||
## 分层结构
|
||
|
||
```
|
||
Layer 1: Agent 身份(用户写的 systemPrompt,原样保留)
|
||
Layer 2: 工具使用纪律(通用,所有有工具的 Agent 都注入)
|
||
Layer 3: 模型特定指导(按模型名称子串匹配)
|
||
Layer 4: 工具行为策略(按可用工具条件注入)
|
||
Layer 5: Skill 索引(增强版:分类 + 标签 + 强制措辞)
|
||
Layer 6: 记忆系统(MEMORY_SYSTEM_PROMPT + memory-context)
|
||
Layer 7: Crew 编排上下文(仅 Crew 场景:团队成员、委派提示、工具说明)
|
||
Layer 8: 元信息(时间戳、模型身份)
|
||
```
|
||
|
||
Layer 7 仅在 Crew 场景下注入(通过 crewContext 参数传入)。普通对话和 REST API 调用时此层为空。
|
||
|
||
## 关键文件变更
|
||
|
||
### 新建文件
|
||
|
||
| 文件 | 职责 |
|
||
|------|------|
|
||
| `packages/backend/src/modules/netaclaw/runtime/prompt_builder.ts` | 系统提示词分层组装引擎 |
|
||
| `packages/backend/src/modules/netaclaw/runtime/prompt_guidance.ts` | 所有指导文本常量(工具纪律、模型指导、工具策略) |
|
||
|
||
### 修改文件
|
||
|
||
| 文件 | 变更 |
|
||
|------|------|
|
||
| `packages/backend/src/modules/netaclaw/gateway/server.ts` | handleChat() 中替换手动拼接为调用 prompt_builder |
|
||
| `packages/backend/src/modules/netaclaw/service/agent_executor.ts` | execute() 中同样替换 |
|
||
| `packages/backend/src/modules/netaclaw/service/crew_orchestrator.ts` | buildEnhancedPrompt() 改为调用 builder + 追加 Crew 专属层 |
|
||
| `packages/backend/src/modules/netaclaw/service/crew_delegate.ts` | buildSubAgentPrompt() 改为调用 builder |
|
||
| `packages/backend/src/modules/netaclaw/service/skill_loader.ts` | buildSkillsPrompt() 增强索引格式 |
|
||
| `packages/backend/src/modules/netaclaw/runtime/agent.ts` | runAgent() 简化,移除 memoryContext 拼接(已由 builder 处理) |
|
||
| `packages/backend/src/modules/netaclaw/controller/agent.ts` | 新增 previewPrompt 接口,注入 skillLoader + channelService |
|
||
| `packages/frontend/src/modules/agent/views/agent-edit.vue` | 新增折叠面板预览最终提示词 |
|
||
|
||
## 详细设计
|
||
|
||
### 0. availableToolNames 构建辅助函数
|
||
|
||
todo 工具在 runAgent() 中动态创建,不在 defaultTools 里,但它始终可用。为避免各调用点硬编码不完整的工具列表,提供辅助函数:
|
||
|
||
```typescript
|
||
// prompt_builder.ts
|
||
|
||
/** 基础内置工具(始终可用) */
|
||
const BASE_TOOLS = ['bash', 'read_file', 'write_file', 'list_dir', 'todo'];
|
||
|
||
/** 构建完整的可用工具名列表 */
|
||
export function collectAvailableToolNames(opts: {
|
||
memoryEnabled?: boolean;
|
||
hasSkills?: boolean;
|
||
crewRole?: 'master' | 'sub'; // Crew 场景
|
||
}): 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;
|
||
}
|
||
```
|
||
|
||
### 1. prompt_builder.ts — 核心接口
|
||
|
||
```typescript
|
||
interface BuildSystemPromptParams {
|
||
agentSystemPrompt: string; // 用户写的系统提示词
|
||
modelId: string; // 模型名称(如 'MiniMax-M2.7-highspeed')
|
||
availableToolNames: string[]; // 当前可用工具名列表(用 collectAvailableToolNames 构建)
|
||
skills: string[]; // 已选 skill 名称列表
|
||
skillLoader: SkillLoaderService; // Skill 加载器实例
|
||
memoryEnabled: boolean;
|
||
memoryContext?: string; // 召回的记忆内容
|
||
crewContext?: string; // Crew 编排专属上下文(团队成员、委派提示等)
|
||
}
|
||
|
||
interface BuildResult {
|
||
prompt: string; // 拼装后的完整系统提示词
|
||
layers: Array<{ // 各层明细(供前端预览)
|
||
name: string;
|
||
content: string;
|
||
}>;
|
||
}
|
||
|
||
function buildSystemPrompt(params: BuildSystemPromptParams): BuildResult;
|
||
```
|
||
|
||
调用位置(共 5 处,全部覆盖):
|
||
- `gateway/server.ts` handleChat() — WebSocket 对话入口
|
||
- `service/agent_executor.ts` execute() — REST API / 渠道触发入口
|
||
- `service/crew_orchestrator.ts` buildEnhancedPrompt() — Crew 主 Agent 编排入口
|
||
- `service/crew_delegate.ts` buildSubAgentPrompt() — Crew 子 Agent 委派入口
|
||
- `controller/agent.ts` previewPrompt() — 前端预览接口(新增)
|
||
|
||
### 2. prompt_guidance.ts — 指导文本常量
|
||
|
||
#### Layer 2: 工具使用纪律(通用)
|
||
|
||
```typescript
|
||
export const TOOL_USE_ENFORCEMENT = `# 工具使用规范
|
||
你必须通过工具采取行动 - 不要只描述你打算做什么。当你说"我来检查一下"、"让我执行"时,必须在同一回复中立即发起对应的工具调用。不要以"下一步我会..."结束回复 - 现在就执行。
|
||
|
||
持续工作直到任务真正完成。不要停在计划阶段。如果你有可用的工具能完成任务,使用它们,而不是告诉用户你会怎么做。
|
||
|
||
每条回复要么 (a) 包含推进任务的工具调用,要么 (b) 向用户交付最终结果。仅描述意图而不行动的回复是不可接受的。`;
|
||
```
|
||
|
||
#### Layer 3: 模型特定指导(按模型名匹配)
|
||
|
||
```typescript
|
||
const MODEL_GUIDANCE_RULES: Array<{
|
||
patterns: string[];
|
||
guidance: string;
|
||
}> = [
|
||
{
|
||
patterns: ['claude'],
|
||
guidance: '', // Claude 工具调用能力强,不需要额外指导
|
||
},
|
||
{
|
||
patterns: ['gpt', 'o1', 'o3', 'o4'],
|
||
guidance: `# 执行纪律
|
||
<tool_persistence>
|
||
- 工具能提升正确性时必须使用,不要提前停止
|
||
- 工具返回空结果时换一种查询策略重试
|
||
- 持续调用工具直到:(1) 任务完成 且 (2) 已验证结果
|
||
</tool_persistence>
|
||
<mandatory_tool_use>
|
||
以下问题绝不能凭记忆回答,必须使用工具:
|
||
- 算术计算 → bash
|
||
- 文件内容、大小 → read_file / list_dir
|
||
- 当前时间日期 → bash
|
||
</mandatory_tool_use>`,
|
||
},
|
||
{
|
||
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)
|
||
- 文件路径使用绝对路径
|
||
- 每次工具调用后检查返回结果,确认操作成功后再继续`,
|
||
},
|
||
];
|
||
|
||
// 未匹配到的模型 → 通用保守版指导
|
||
export 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;
|
||
}
|
||
```
|
||
|
||
#### Layer 4: 工具行为策略(条件注入)
|
||
|
||
```typescript
|
||
// 工具名 → 行为策略的映射
|
||
export const TOOL_BEHAVIOR_GUIDANCE: Record<string, string> = {
|
||
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');
|
||
}
|
||
```
|
||
|
||
### 3. skill_loader.ts — 索引格式增强
|
||
|
||
改造 `buildSkillsPrompt()` 方法:
|
||
|
||
```typescript
|
||
// 之前
|
||
`<skill>\n <name>${s.name}</name>\n <description>${s.description}</description>\n</skill>`
|
||
|
||
// 之后
|
||
const category = s.metadata?.category || '通用';
|
||
const tags = (s.metadata?.tags || []).join(',');
|
||
`<skill name="${s.name}" category="${category}" tags="${tags}">\n ${s.description}\n</skill>`
|
||
```
|
||
|
||
索引头部措辞从被动改为强制:
|
||
|
||
```typescript
|
||
// 之前
|
||
`当你需要使用某个 skill 时,调用 read_skill 工具读取完整指令后再执行。`
|
||
|
||
// 之后
|
||
`## 技能(必须扫描)
|
||
回复前,扫描以下技能列表。如果某个技能明确匹配当前任务,用 read_skill 工具加载完整指令后严格遵循。`
|
||
```
|
||
|
||
### 4. 后端预览接口
|
||
|
||
```typescript
|
||
// controller/admin/agent.ts
|
||
@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 result = buildSystemPrompt({
|
||
agentSystemPrompt: body.systemPrompt || '',
|
||
modelId,
|
||
availableToolNames: ['bash', 'read_file', 'write_file', 'list_dir', 'todo'],
|
||
skills: body.skills || [],
|
||
skillLoader: this.skillLoader,
|
||
memoryEnabled: false, // 预览时不含记忆上下文
|
||
});
|
||
|
||
return this.ok(result); // { prompt, layers }
|
||
}
|
||
```
|
||
|
||
### 5. 前端预览组件
|
||
|
||
在 `agent-edit.vue` 的系统提示词 Tab 下方新增折叠面板:
|
||
|
||
```vue
|
||
<el-collapse v-if="form.modelConfig?.channelId">
|
||
<el-collapse-item>
|
||
<template #title>
|
||
<span>预览最终提示词</span>
|
||
<el-tag size="small" type="info" style="margin-left: 8px">
|
||
运行时实际发送给模型的内容
|
||
</el-tag>
|
||
</template>
|
||
<el-button size="small" @click="loadPreview" :loading="previewLoading">
|
||
刷新预览
|
||
</el-button>
|
||
<div v-if="previewLayers.length" class="prompt-preview">
|
||
<div v-for="layer in previewLayers" :key="layer.name" class="layer-block">
|
||
<div class="layer-label">{{ layer.name }}</div>
|
||
<pre class="layer-content">{{ layer.content }}</pre>
|
||
</div>
|
||
</div>
|
||
</el-collapse-item>
|
||
</el-collapse>
|
||
```
|
||
|
||
调用逻辑:
|
||
```typescript
|
||
const previewLoading = ref(false);
|
||
const previewLayers = ref<Array<{ name: string; content: string }>>([]);
|
||
|
||
async function loadPreview() {
|
||
previewLoading.value = true;
|
||
try {
|
||
const res = await service.request({
|
||
url: '/admin/netaclaw/agent/previewPrompt',
|
||
method: 'POST',
|
||
data: {
|
||
systemPrompt: form.systemPrompt,
|
||
skills: form.skills,
|
||
modelConfig: form.modelConfig,
|
||
},
|
||
});
|
||
previewLayers.value = res.layers || [];
|
||
} finally {
|
||
previewLoading.value = false;
|
||
}
|
||
}
|
||
```
|
||
|
||
## 改造影响范围
|
||
|
||
### MEMORY_SYSTEM_PROMPT 统一
|
||
|
||
当前 gateway/server.ts(第 25-42 行)和 agent_executor.ts(第 20-24 行)各有一份 MEMORY_SYSTEM_PROMPT,内容不一致(gateway 版本更详细)。改造后统一到 prompt_guidance.ts 中,两处都删除本地常量。
|
||
|
||
### gateway/server.ts 变更
|
||
|
||
**改造要点:**
|
||
|
||
1. 移除 `MEMORY_SYSTEM_PROMPT` 常量(第 25-42 行)
|
||
2. `buildSkillContext()` 调用保留但只取 `skillTools`(不再取 `skillPrompt`),skill 索引由 builder 内部处理
|
||
3. 记忆系统代码(第 192-210 行)需要提前到 `buildSystemPrompt` 调用之前,因为 builder 需要 `memoryEnabled` 和 `memoryContext`
|
||
4. `memoryConfig` 直接从 `cfg`(`agentInfo.config`)提取,不再重新查询 `agentInfo2`(消除冗余数据库查询)
|
||
5. 传给 builder 的 `modelId` 必须是纯模型名(`mc.modelId`),不是 `provider:model` 格式
|
||
|
||
```typescript
|
||
// 之前(分散在多处,第 167-177 行 + 第 192-210 行)
|
||
agentConfig = {
|
||
systemPrompt: (agentInfo.systemPrompt || '') + skillPrompt,
|
||
...
|
||
};
|
||
// ... 中间隔了十几行 ...
|
||
const agentInfo2 = agentId ? await this.agentService.info(agentId) : null;
|
||
const memoryConfig = (agentInfo2?.config as any)?.memory;
|
||
if (memoryConfig?.enabled) {
|
||
// ... prefetch ...
|
||
agentConfig.systemPrompt += MEMORY_SYSTEM_PROMPT;
|
||
}
|
||
|
||
// 之后(统一到一处)
|
||
// 1. 记忆系统提前处理(从 cfg 直接取,不再重新查询)
|
||
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, agentConfig.name, userId, memoryConfig.prefetchLimit);
|
||
memoryTools = [createMemorySaveTool(provider, agentConfig.name, userId), createMemoryRecallTool(provider, agentConfig.name, userId)];
|
||
}
|
||
|
||
// 2. 统一调用 builder
|
||
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 = { systemPrompt, ... };
|
||
```
|
||
|
||
### service/agent_executor.ts 变更
|
||
|
||
execute() 方法中同样替换手动拼接(第 74-137 行),改为调用 buildSystemPrompt()。移除该文件中的 MEMORY_SYSTEM_PROMPT 常量和 memoryContext 拼接逻辑。
|
||
|
||
### service/crew_orchestrator.ts 变更
|
||
|
||
buildEnhancedPrompt() 方法(第 224-257 行)改造:
|
||
- 先用 buildSystemPrompt() 构建基础 prompt(Layer 1-6, 8)
|
||
- 将现有的团队成员信息、委派提示、工具说明作为 crewContext 参数传入(Layer 7)
|
||
|
||
```typescript
|
||
// 之前
|
||
private buildEnhancedPrompt(masterAgent, ctx, crew): string {
|
||
const parts = [masterAgent.systemPrompt || ''];
|
||
parts.push(`\n## 你的团队成员\n${memberList}...`);
|
||
parts.push(`\n## 调度建议\n${crew.delegateHints?.hints}`);
|
||
parts.push(`\n## 编排工具说明\n...`);
|
||
return parts.join('\n');
|
||
}
|
||
|
||
// 之后
|
||
private buildEnhancedPrompt(masterAgent, ctx, crew): string {
|
||
// 构建 Crew 专属上下文
|
||
const crewParts = [];
|
||
crewParts.push(`## 你的团队成员\n${memberList}...`);
|
||
if (crew.delegateHints?.hints) crewParts.push(`## 调度建议\n${crew.delegateHints.hints}`);
|
||
crewParts.push(`## 编排工具说明\n...`);
|
||
|
||
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;
|
||
}
|
||
```
|
||
|
||
### service/crew_delegate.ts 变更
|
||
|
||
buildSubAgentPrompt() 方法(第 47-53 行)改造:
|
||
|
||
```typescript
|
||
// 之前
|
||
private buildSubAgentPrompt(systemPrompt, role, agentSkills?): string {
|
||
const skillCtx = buildSkillContext(this.skillLoader, agentSkills, BUILTIN_TOOL_NAMES);
|
||
const parts = [systemPrompt];
|
||
if (role) parts.push(`\n你在团队中的角色: ${role}`);
|
||
if (skillCtx.skillPrompt) parts.push(`\n${skillCtx.skillPrompt}`);
|
||
return parts.join('\n');
|
||
}
|
||
|
||
// 之后
|
||
private buildSubAgentPrompt(systemPrompt, role, agentSkills?, modelId?): 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;
|
||
}
|
||
```
|
||
|
||
### runtime/agent.ts 变更
|
||
|
||
第 64-66 行的 memoryContext 拼接移除(已由 builder 处理):
|
||
|
||
```typescript
|
||
// 之前
|
||
const systemContent = memoryContext
|
||
? `${agentConfig.systemPrompt}\n\n<memory-context>\n${memoryContext}\n</memory-context>`
|
||
: agentConfig.systemPrompt;
|
||
|
||
// 之后
|
||
const systemContent = agentConfig.systemPrompt; // builder 已处理所有层
|
||
```
|
||
|
||
## 验证方案
|
||
|
||
1. **单元测试**:对 prompt_builder.ts 写测试,验证不同模型名、不同工具组合下的输出
|
||
2. **预览接口测试**:在前端编辑 Agent 时点击预览,确认各层内容正确
|
||
3. **端到端测试**:用 MiniMax-M2.7-highspeed 模型对话,观察工具调用行为是否改善
|
||
4. **回归测试**:用 Claude/GPT 模型对话,确认不受负面影响(Claude 的指导为空字符串)
|