GPU_GUARD_MONOREPO/packages/backend/test/prompt_builder.test.ts
2026-05-20 21:39:12 +08:00

196 lines
6.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
getModelGuidance,
getToolBehaviorGuidance,
} from '../src/modules/netaclaw/runtime/prompt_guidance.js';
import {
collectAvailableToolNames,
buildSystemPrompt,
BuildSystemPromptParams,
buildLLMMessages,
} from '../src/modules/netaclaw/runtime/prompt_builder.js';
// ─── Mock Helper ───
function createMockSkillLoader(returnValue: string) {
return { buildSkillsPrompt: jest.fn().mockReturnValue(returnValue) } as any;
}
// ─── collectAvailableToolNames ───
describe('collectAvailableToolNames', () => {
it('基础工具始终包含', () => {
const names = collectAvailableToolNames({});
expect(names).toEqual(expect.arrayContaining(['bash', 'read_file', 'write_file', 'list_dir', 'todo']));
});
it('memoryEnabled 追加记忆工具', () => {
const names = collectAvailableToolNames({ memoryEnabled: true });
expect(names).toContain('memory_save');
expect(names).toContain('memory_recall');
});
it('hasSkills 追加技能工具', () => {
const names = collectAvailableToolNames({ hasSkills: true });
expect(names).toContain('read_skill');
expect(names).toContain('read_skill_file');
expect(names).toContain('skill_manage');
});
it('crewRole=master 追加委派工具', () => {
const names = collectAvailableToolNames({ crewRole: 'master' });
expect(names).toContain('delegate_task');
expect(names).toContain('delegate_parallel');
expect(names).toContain('escalate');
});
it('crewRole=sub 不追加委派工具', () => {
const names = collectAvailableToolNames({ crewRole: 'sub' });
expect(names).not.toContain('delegate_task');
expect(names).not.toContain('delegate_parallel');
expect(names).not.toContain('escalate');
});
});
// ─── getModelGuidance ───
describe('getModelGuidance', () => {
it('Claude 返回空字符串', () => {
expect(getModelGuidance('claude-3-opus')).toBe('');
expect(getModelGuidance('Claude-Sonnet')).toBe('');
});
it('MiniMax 匹配 minimax 规则', () => {
const g = getModelGuidance('minimax-abab6');
expect(g).toContain('一个工具');
});
it('GPT 匹配 gpt 规则', () => {
const g = getModelGuidance('gpt-4o');
expect(g).toContain('tool_persistence');
});
it('DeepSeek 匹配 deepseek 规则', () => {
const g = getModelGuidance('deepseek-chat');
expect(g).toContain('并行');
});
it('doubao 匹配 doubao 规则', () => {
const g = getModelGuidance('doubao-pro-32k');
expect(g).toContain('non_interactive');
});
it('未知模型返回默认指导', () => {
const g = getModelGuidance('some-unknown-model-xyz');
expect(g).toContain('通用操作规范');
});
});
// ─── getToolBehaviorGuidance ───
describe('getToolBehaviorGuidance', () => {
it('包含 todo 时注入任务规划策略', () => {
const g = getToolBehaviorGuidance(['todo', 'bash']);
expect(g).toContain('任务规划策略');
});
it('包含 memory_save 时注入记忆策略', () => {
const g = getToolBehaviorGuidance(['memory_save']);
expect(g).toContain('记忆使用策略');
});
it('无匹配工具时返回空字符串', () => {
const g = getToolBehaviorGuidance(['bash', 'read_file']);
expect(g).toContain('命令执行策略');
expect(g).toContain('文件读取策略');
});
});
// ─── buildSystemPrompt ───
describe('buildSystemPrompt', () => {
const baseParams: BuildSystemPromptParams = {
agentSystemPrompt: '你是一个测试助手',
modelId: 'gpt-4o',
availableToolNames: ['bash', 'todo'],
skills: [],
skillLoader: createMockSkillLoader(''),
memoryEnabled: false,
};
it('基础场景:包含 Agent 身份、工具纪律、模型指导、工具行为策略、元信息', () => {
const { layers } = buildSystemPrompt(baseParams);
const names = layers.map(l => l.name);
expect(names).toContain('Agent 身份');
expect(names).toContain('工具使用纪律');
expect(names).toContain('模型特定指导');
expect(names).toContain('工具行为策略');
expect(names).toContain('元信息');
expect(names).not.toContain('Skill 索引');
expect(names).not.toContain('记忆系统');
expect(names).not.toContain('Crew 编排上下文');
});
it('完整场景skill + 记忆 + memoryContext → 包含所有层', () => {
const loader = createMockSkillLoader('技能索引内容');
const { layers } = buildSystemPrompt({
...baseParams,
skills: ['skill_a'],
skillLoader: loader,
memoryEnabled: true,
memoryContext: '用户偏好数据',
});
const names = layers.map(l => l.name);
expect(names).toContain('Skill 索引');
expect(names).toContain('记忆行为');
expect(loader.buildSkillsPrompt).toHaveBeenCalled();
});
it('Crew 场景:注入 crewContext → 包含 Crew 编排上下文层', () => {
const { layers } = buildSystemPrompt({
...baseParams,
crewContext: '你是主Agent负责分配任务',
});
const names = layers.map(l => l.name);
expect(names).toContain('Crew 编排上下文');
const crewLayer = layers.find(l => l.name === 'Crew 编排上下文');
expect(crewLayer!.content).toContain('你是主Agent');
});
it('空 agentSystemPrompt 不生成 Agent 身份层', () => {
const { layers } = buildSystemPrompt({ ...baseParams, agentSystemPrompt: '' });
const names = layers.map(l => l.name);
expect(names).not.toContain('Agent 身份');
});
it('Claude 模型不生成模型特定指导层', () => {
const { layers } = buildSystemPrompt({ ...baseParams, modelId: 'claude-3-opus' });
const names = layers.map(l => l.name);
expect(names).not.toContain('模型特定指导');
});
});
describe('buildLLMMessages', () => {
it('视频附件有旧伤检测 skill 时提示使用 execute_skill 而不是 image_recognize', () => {
const messages = buildLLMMessages(
'<skill name="vehicle-damage-inspection" type="compute-entry">汽车环车视频旧伤检测</skill>',
[],
{
content: '请分析',
metadata: {
attachments: [{
type: 'video',
name: 'car.mp4',
url: '/upload/20260507/car.mp4',
}],
},
},
['execute_skill', 'image_recognize'],
);
const hint = messages[messages.length - 1].content;
expect(hint).toContain('execute_skill');
expect(hint).toContain('vehicle-damage-inspection');
expect(hint).toContain('/upload/20260507/car.mp4');
expect(hint).toContain('不要用它直接分析视频文件');
});
});