mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-26 13:19:34 +07:00
⚡️ perf: optimize tool system prompt — remove duplicate APIs, simplify XML tags (#13041)
* 💄 style: remove platform-specific Spotlight reference from searchLocalFiles Replace "using Spotlight (macOS) or native search" with "using native search" since the actual search implementation is platform-dependent and the LLM doesn't need to know the specific backend. Fixes LOBE-5778 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ⚡️ perf: remove duplicate API descriptions from tool system prompt API identifiers and descriptions are already in the tools schema passed via the API tools parameter. Repeating them in the system prompt wastes tokens. Now only tools with systemRole (usage instructions) are injected. Also rename XML tags: plugins→tools, collection→tool, collection.instructions→tool.instructions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * 💄 style: inject tool description when no systemRole instead of skipping Tools without systemRole now show their description as <tool> children. Tools with systemRole use <tool.instructions> wrapper as before. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * 💄 style: always emit <tool> tag, fallback to "no description" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * update tools * fix --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -34,7 +34,7 @@ You have access to a set of tools to interact with the user's local file system:
|
||||
9. **killCommand**: Terminate a running background shell command by its ID.
|
||||
|
||||
**Search & Find:**
|
||||
10. **searchLocalFiles**: Searches for files based on keywords and other criteria using Spotlight (macOS) or native search. Use this tool to find files if the user is unsure about the exact path.
|
||||
10. **searchLocalFiles**: Searches for files based on keywords and other criteria using native search. Use this tool to find files if the user is unsure about the exact path.
|
||||
11. **grepContent**: Search for content within files using regex patterns. Supports various output modes, filtering, and context lines.
|
||||
12. **globLocalFiles**: Find files matching glob patterns (e.g., "**/*.js", "*.{ts,tsx}").
|
||||
</core_capabilities>
|
||||
|
||||
@@ -23,7 +23,7 @@ You have access to a set of tools to interact with the user's local file system:
|
||||
9. **killCommand**: Terminate a running background shell command by its ID.
|
||||
|
||||
**Search & Find:**
|
||||
10. **searchLocalFiles**: Searches for files based on keywords and other criteria using Spotlight (macOS) or native search. Use this tool to find files if the user is unsure about the exact path.
|
||||
10. **searchLocalFiles**: Searches for files based on keywords and other criteria using native search. Use this tool to find files if the user is unsure about the exact path.
|
||||
11. **grepContent**: Search for content within files using regex patterns. Supports various output modes, filtering, and context lines.
|
||||
12. **globLocalFiles**: Find files matching glob patterns (e.g., "**/*.js", "*.{ts,tsx}").
|
||||
</core_capabilities>
|
||||
|
||||
@@ -369,7 +369,7 @@ describe('MessagesEngine', () => {
|
||||
// Should inject tool system role when manifests are provided
|
||||
const systemMessage = result.messages.find((msg) => msg.role === 'system');
|
||||
expect(systemMessage).toBeDefined();
|
||||
expect(systemMessage!.content).toContain('tool1');
|
||||
expect(systemMessage!.content).toContain('Tool 1');
|
||||
});
|
||||
|
||||
it('should skip tool system role provider when no tools', async () => {
|
||||
|
||||
@@ -107,6 +107,7 @@ export class ToolSystemRoleProvider extends BaseProvider {
|
||||
name: this.toolNameResolver.generate(manifest.identifier, api.name, manifest.type),
|
||||
}),
|
||||
),
|
||||
description: manifest.meta?.description,
|
||||
identifier: manifest.identifier,
|
||||
name: manifest.meta?.title || manifest.identifier,
|
||||
systemRole: manifest.systemRole,
|
||||
|
||||
@@ -16,11 +16,12 @@ const createMockManifests = (identifiers: string[]): LobeToolManifest[] =>
|
||||
identifier: id,
|
||||
api: [{ name: 'action', description: `${id} action`, parameters: {} }],
|
||||
meta: { title: id },
|
||||
systemRole: `Instructions for ${id}`,
|
||||
type: 'default' as const,
|
||||
}));
|
||||
|
||||
describe('ToolSystemRoleProvider', () => {
|
||||
it('should inject tool system role when manifests are provided and FC is supported', async () => {
|
||||
it('should inject tool system role when manifests with systemRole are provided and FC is supported', async () => {
|
||||
const mockIsCanUseFC = () => true;
|
||||
const manifests = createMockManifests(['calculator', 'weather']);
|
||||
|
||||
@@ -36,11 +37,12 @@ describe('ToolSystemRoleProvider', () => {
|
||||
const ctx = createContext(messages);
|
||||
const result = await provider.process(ctx);
|
||||
|
||||
// Should have system message with tool system role
|
||||
// Should have system message with tool instructions
|
||||
const systemMessage = result.messages.find((msg) => msg.role === 'system');
|
||||
expect(systemMessage).toBeDefined();
|
||||
expect(systemMessage!.content).toContain('calculator');
|
||||
expect(systemMessage!.content).toContain('weather');
|
||||
expect(systemMessage!.content).toContain('<tool name="calculator">');
|
||||
expect(systemMessage!.content).toContain('<tool name="weather">');
|
||||
expect(systemMessage!.content).toContain('Instructions for calculator');
|
||||
|
||||
// Should update metadata
|
||||
expect(result.metadata.toolSystemRole).toBeDefined();
|
||||
@@ -48,6 +50,34 @@ describe('ToolSystemRoleProvider', () => {
|
||||
expect(result.metadata.toolSystemRole!.supportsFunctionCall).toBe(true);
|
||||
});
|
||||
|
||||
it('should skip injection when manifests have apis but no systemRole', async () => {
|
||||
const mockIsCanUseFC = () => true;
|
||||
const manifests: LobeToolManifest[] = [
|
||||
{
|
||||
identifier: 'no-instructions',
|
||||
api: [{ name: 'action', description: 'some action', parameters: {} }],
|
||||
meta: { title: 'No Instructions' },
|
||||
type: 'default',
|
||||
},
|
||||
];
|
||||
|
||||
const provider = new ToolSystemRoleProvider({
|
||||
manifests,
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
isCanUseFC: mockIsCanUseFC,
|
||||
});
|
||||
|
||||
const messages = [{ id: 'u1', role: 'user', content: 'Do something' }];
|
||||
const ctx = createContext(messages);
|
||||
const result = await provider.process(ctx);
|
||||
|
||||
// Has apis → still injects tool info even without systemRole
|
||||
const systemMessage = result.messages.find((msg) => msg.role === 'system');
|
||||
expect(systemMessage).toBeDefined();
|
||||
expect(systemMessage!.content).toContain('No Instructions');
|
||||
});
|
||||
|
||||
it('should merge tool system role with existing system message', async () => {
|
||||
const mockIsCanUseFC = () => true;
|
||||
const manifests = createMockManifests(['calculator']);
|
||||
|
||||
@@ -18,12 +18,9 @@ describe('pluginPrompts', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const expected = `<plugins description="The plugins you can use below">
|
||||
<collection name="tool1">
|
||||
|
||||
<api identifier="api1">API 1</api>
|
||||
</collection>
|
||||
</plugins>`;
|
||||
const expected = `<tools description="The tools you can use below">
|
||||
<tool name="tool1">no description</tool>
|
||||
</tools>`;
|
||||
|
||||
expect(pluginPrompts({ tools })).toBe(expected);
|
||||
});
|
||||
@@ -31,10 +28,6 @@ describe('pluginPrompts', () => {
|
||||
it('should generate plugin prompts without tools', () => {
|
||||
const tools: Tool[] = [];
|
||||
|
||||
const expected = `<plugins description="The plugins you can use below">
|
||||
|
||||
</plugins>`;
|
||||
|
||||
expect(pluginPrompts({ tools })).toBe(expected);
|
||||
expect(pluginPrompts({ tools })).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,11 +2,12 @@ import type { Tool } from './tools';
|
||||
import { toolsPrompts } from './tools';
|
||||
|
||||
export const pluginPrompts = ({ tools }: { tools: Tool[] }) => {
|
||||
const prompt = `<plugins description="The plugins you can use below">
|
||||
${toolsPrompts(tools)}
|
||||
</plugins>`;
|
||||
const content = toolsPrompts(tools);
|
||||
if (!content) return '';
|
||||
|
||||
return prompt.trim();
|
||||
return `<tools description="The tools you can use below">
|
||||
${content}
|
||||
</tools>`;
|
||||
};
|
||||
|
||||
export { type API, apiPrompt, type Tool, toolPrompt, toolsPrompts } from './tools';
|
||||
|
||||
@@ -4,104 +4,83 @@ import type { Tool } from './tools';
|
||||
import { apiPrompt, toolPrompt, toolsPrompts } from './tools';
|
||||
|
||||
describe('Prompt Generation Utils', () => {
|
||||
// 测试 apiPrompt 函数
|
||||
describe('apiPrompt', () => {
|
||||
it('should generate correct api prompt', () => {
|
||||
const api = {
|
||||
name: 'testApi',
|
||||
desc: 'Test API Description',
|
||||
};
|
||||
|
||||
const api = { name: 'testApi', desc: 'Test API Description' };
|
||||
expect(apiPrompt(api)).toBe(`<api identifier="testApi">Test API Description</api>`);
|
||||
});
|
||||
});
|
||||
|
||||
// 测试 toolPrompt 函数
|
||||
describe('toolPrompt', () => {
|
||||
it('should generate tool prompt with system role', () => {
|
||||
it('should use tool.instructions when systemRole is present', () => {
|
||||
const tool: Tool = {
|
||||
name: 'testTool',
|
||||
identifier: 'test-id',
|
||||
systemRole: 'Test System Role',
|
||||
apis: [
|
||||
{
|
||||
name: 'api1',
|
||||
desc: 'API 1 Description',
|
||||
},
|
||||
],
|
||||
description: 'Short desc',
|
||||
systemRole: 'Detailed instructions',
|
||||
apis: [{ name: 'api1', desc: 'API 1' }],
|
||||
};
|
||||
|
||||
const expected = `<collection name="testTool">
|
||||
<collection.instructions>Test System Role</collection.instructions>
|
||||
<api identifier="api1">API 1 Description</api>
|
||||
</collection>`;
|
||||
|
||||
expect(toolPrompt(tool)).toBe(expected);
|
||||
expect(toolPrompt(tool)).toBe(`<tool name="testTool">
|
||||
<tool.instructions>Detailed instructions</tool.instructions>
|
||||
</tool>`);
|
||||
});
|
||||
|
||||
it('should generate tool prompt without system role', () => {
|
||||
it('should use description as children when no systemRole', () => {
|
||||
const tool: Tool = {
|
||||
name: 'testTool',
|
||||
identifier: 'test-id',
|
||||
apis: [
|
||||
{
|
||||
name: 'api1',
|
||||
desc: 'API 1 Description',
|
||||
},
|
||||
],
|
||||
description: 'A useful tool for testing',
|
||||
apis: [{ name: 'api1', desc: 'API 1' }],
|
||||
};
|
||||
|
||||
const expected = `<collection name="testTool">
|
||||
expect(toolPrompt(tool)).toBe(`<tool name="testTool">A useful tool for testing</tool>`);
|
||||
});
|
||||
|
||||
<api identifier="api1">API 1 Description</api>
|
||||
</collection>`;
|
||||
it('should fallback to "no description" when no systemRole and no description', () => {
|
||||
const tool: Tool = {
|
||||
name: 'testTool',
|
||||
identifier: 'test-id',
|
||||
apis: [{ name: 'api1', desc: 'API 1' }],
|
||||
};
|
||||
|
||||
expect(toolPrompt(tool)).toBe(expected);
|
||||
expect(toolPrompt(tool)).toBe('<tool name="testTool">no description</tool>');
|
||||
});
|
||||
});
|
||||
|
||||
// 测试 toolsPrompts 函数
|
||||
describe('toolsPrompts', () => {
|
||||
it('should generate tools prompts with multiple tools', () => {
|
||||
it('should include tools with systemRole and description', () => {
|
||||
const tools: Tool[] = [
|
||||
{
|
||||
name: 'tool1',
|
||||
identifier: 'id1',
|
||||
apis: [
|
||||
{
|
||||
name: 'api1',
|
||||
desc: 'API 1',
|
||||
},
|
||||
],
|
||||
systemRole: 'Instructions for tool1',
|
||||
apis: [{ name: 'api1', desc: 'API 1' }],
|
||||
},
|
||||
{
|
||||
name: 'tool2',
|
||||
identifier: 'id2',
|
||||
apis: [
|
||||
{
|
||||
name: 'api2',
|
||||
desc: 'API 2',
|
||||
},
|
||||
],
|
||||
description: 'Tool 2 description',
|
||||
apis: [{ name: 'api2', desc: 'API 2' }],
|
||||
},
|
||||
{
|
||||
name: 'tool3',
|
||||
identifier: 'id3',
|
||||
apis: [{ name: 'api3', desc: 'API 3' }],
|
||||
},
|
||||
];
|
||||
|
||||
const expected = `<collection name="tool1">
|
||||
|
||||
<api identifier="api1">API 1</api>
|
||||
</collection>
|
||||
<collection name="tool2">
|
||||
|
||||
<api identifier="api2">API 2</api>
|
||||
</collection>`;
|
||||
const expected = `<tool name="tool1">
|
||||
<tool.instructions>Instructions for tool1</tool.instructions>
|
||||
</tool>
|
||||
<tool name="tool2">Tool 2 description</tool>
|
||||
<tool name="tool3">no description</tool>`;
|
||||
|
||||
expect(toolsPrompts(tools)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should generate tools prompts with empty tools array', () => {
|
||||
const tools: Tool[] = [];
|
||||
|
||||
expect(toolsPrompts(tools)).toBe('');
|
||||
it('should return empty for empty tools array', () => {
|
||||
expect(toolsPrompts([])).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ export interface API {
|
||||
}
|
||||
export interface Tool {
|
||||
apis: API[];
|
||||
description?: string;
|
||||
identifier: string;
|
||||
name?: string;
|
||||
systemRole?: string;
|
||||
@@ -11,11 +12,15 @@ export interface Tool {
|
||||
|
||||
export const apiPrompt = (api: API) => `<api identifier="${api.name}">${api.desc}</api>`;
|
||||
|
||||
export const toolPrompt = (tool: Tool) =>
|
||||
`<collection name="${tool.name}">
|
||||
${tool.systemRole ? `<collection.instructions>${tool.systemRole}</collection.instructions>` : ''}
|
||||
${tool.apis.map((api) => apiPrompt(api)).join('\n')}
|
||||
</collection>`;
|
||||
export const toolPrompt = (tool: Tool) => {
|
||||
if (tool.systemRole) {
|
||||
return `<tool name="${tool.name}">
|
||||
<tool.instructions>${tool.systemRole}</tool.instructions>
|
||||
</tool>`;
|
||||
}
|
||||
|
||||
return `<tool name="${tool.name}">${tool.description || 'no description'}</tool>`;
|
||||
};
|
||||
|
||||
export const toolsPrompts = (tools: Tool[]) => {
|
||||
const hasTools = tools.length > 0;
|
||||
|
||||
Reference in New Issue
Block a user