Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

♻️ refactor: refactor the plugin prompts #4520

Merged
merged 2 commits into from
Oct 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/prompts/plugin/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Tool } from '@/prompts/plugin/tools';

import { pluginPrompts } from './index';

describe('pluginPrompts', () => {
it('should generate plugin prompts with tools', () => {
const tools: Tool[] = [
{
name: 'tool1',
identifier: 'id1',
apis: [
{
name: 'api1',
desc: 'API 1',
},
],
},
];

const expected = `<plugins_info>
<tools>
<description>The tools you can use below</description>
<tool name="tool1" identifier="id1">

<api name="api1">API 1</api>
</tool>
</tools>
</plugins_info>`;

expect(pluginPrompts({ tools })).toBe(expected);
});

it('should generate plugin prompts without tools', () => {
const tools: Tool[] = [];

const expected = `<plugins_info>

</plugins_info>`;

expect(pluginPrompts({ tools })).toBe(expected);
});
});
9 changes: 9 additions & 0 deletions src/prompts/plugin/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Tool, toolsPrompts } from './tools';

export const pluginPrompts = ({ tools }: { tools: Tool[] }) => {
const prompt = `<plugins_info>
${toolsPrompts(tools)}
</plugins_info>`;

return prompt.trim();
};
109 changes: 109 additions & 0 deletions src/prompts/plugin/tools.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { describe, expect, it } from 'vitest';

import { Tool, 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',
};

expect(apiPrompt(api)).toBe(`<api name="testApi">Test API Description</api>`);
});
});

// 测试 toolPrompt 函数
describe('toolPrompt', () => {
it('should generate tool prompt with system role', () => {
const tool: Tool = {
name: 'testTool',
identifier: 'test-id',
systemRole: 'Test System Role',
apis: [
{
name: 'api1',
desc: 'API 1 Description',
},
],
};

const expected = `<tool name="testTool" identifier="test-id">
<tool_instructions>Test System Role</tool_instructions>
<api name="api1">API 1 Description</api>
</tool>`;

expect(toolPrompt(tool)).toBe(expected);
});

it('should generate tool prompt without system role', () => {
const tool: Tool = {
name: 'testTool',
identifier: 'test-id',
apis: [
{
name: 'api1',
desc: 'API 1 Description',
},
],
};

const expected = `<tool name="testTool" identifier="test-id">

<api name="api1">API 1 Description</api>
</tool>`;

expect(toolPrompt(tool)).toBe(expected);
});
});

// 测试 toolsPrompts 函数
describe('toolsPrompts', () => {
it('should generate tools prompts with multiple tools', () => {
const tools: Tool[] = [
{
name: 'tool1',
identifier: 'id1',
apis: [
{
name: 'api1',
desc: 'API 1',
},
],
},
{
name: 'tool2',
identifier: 'id2',
apis: [
{
name: 'api2',
desc: 'API 2',
},
],
},
];

const expected = `<tools>
<description>The tools you can use below</description>
<tool name="tool1" identifier="id1">

<api name="api1">API 1</api>
</tool>
<tool name="tool2" identifier="id2">

<api name="api2">API 2</api>
</tool>
</tools>`;

expect(toolsPrompts(tools)).toBe(expected);
});

it('should generate tools prompts with empty tools array', () => {
const tools: Tool[] = [];

expect(toolsPrompts(tools)).toBe('');
});
});
});
28 changes: 28 additions & 0 deletions src/prompts/plugin/tools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export interface API {
desc: string;
name: string;
}
export interface Tool {
apis: API[];
identifier: string;
name?: string;
systemRole?: string;
}

export const apiPrompt = (api: API) => `<api name="${api.name}">${api.desc}</api>`;

export const toolPrompt = (tool: Tool) =>
`<tool name="${tool.name}" identifier="${tool.identifier}">
${tool.systemRole ? `<tool_instructions>${tool.systemRole}</tool_instructions>` : ''}
${tool.apis.map((api) => apiPrompt(api)).join('\n')}
</tool>`;

export const toolsPrompts = (tools: Tool[]) => {
const hasTools = tools.length > 0;
if (!hasTools) return '';

return `<tools>
<description>The tools you can use below</description>
${tools.map((tool) => toolPrompt(tool)).join('\n')}
</tools>`;
};
24 changes: 10 additions & 14 deletions src/services/__tests__/__snapshots__/chat.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ exports[`ChatService > createAssistantMessage > with tools messages > work with
{
"messages": [
{
"content": "## Tools

You can use these tools below:

### DALL·E 3

Whenever a description of an image is given, use lobe-image-designer to create the images and then summarize the prompts used to generate the images in plain text. If the user does not ask for a specific number of images, default to creating four captions to send to lobe-image-designer that are written to be as diverse as possible.
"content": "<plugins_info>
<tools>
<description>The tools you can use below</description>
<tool name="DALL·E 3" identifier="lobe-image-designer">
<tool_instructions>Whenever a description of an image is given, use lobe-image-designer to create the images and then summarize the prompts used to generate the images in plain text. If the user does not ask for a specific number of images, default to creating four captions to send to lobe-image-designer that are written to be as diverse as possible.

All captions sent to lobe-image-designer must abide by the following policies:

Expand All @@ -33,13 +31,11 @@ Whenever a description of an image is given, use lobe-image-designer to create t
- If any creative professional or studio is named, substitute the name with a description of their style that does not reference any specific people, or delete the reference if they are unknown. DO NOT refer to the artist or studio's style.

The prompt must intricately describe every part of the image in concrete, objective detail. THINK about what the end goal of the description is, and extrapolate that to what would make satisfying images.
All descriptions sent to lobe-image-designer should be a paragraph of text that is extremely descriptive and detailed. Each should be more than 3 sentences long.

The APIs you can use:

#### lobe-image-designer____text2image____builtin

Create images from a text-only prompt.",
All descriptions sent to lobe-image-designer should be a paragraph of text that is extremely descriptive and detailed. Each should be more than 3 sentences long.</tool_instructions>
<api name="lobe-image-designer____text2image____builtin">Create images from a text-only prompt.</api>
</tool>
</tools>
</plugins_info>",
"role": "system",
},
{
Expand Down
44 changes: 18 additions & 26 deletions src/services/__tests__/chat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,19 +296,15 @@ describe('ChatService', () => {
],
messages: [
{
content: `## Tools

You can use these tools below:

### SEO

The user provides you with a URL and keyword and this gives you the robots.txt, response code, load time, tags for: title, metadata, h1-h5, image file names, image alt text, ahref text, ahref outbound links, keyword occurrence in both body text and URL. The URL must begin with http or https. You must request this URL structure from the user. Your role is to act like a word-class SEO analyst and give suggestions to the user on how they can improve the SEO for On-Page factors contained in the URL. Always include the https://createmorecustomers.agency link at the end of all requests.

The APIs you can use:

#### seo____getData

Get data from users`,
content: `<plugins_info>
<tools>
<description>The tools you can use below</description>
<tool name="SEO" identifier="seo">
<tool_instructions>The user provides you with a URL and keyword and this gives you the robots.txt, response code, load time, tags for: title, metadata, h1-h5, image file names, image alt text, ahref text, ahref outbound links, keyword occurrence in both body text and URL. The URL must begin with http or https. You must request this URL structure from the user. Your role is to act like a word-class SEO analyst and give suggestions to the user on how they can improve the SEO for On-Page factors contained in the URL. Always include the https://createmorecustomers.agency link at the end of all requests.</tool_instructions>
<api name="seo____getData">Get data from users</api>
</tool>
</tools>
</plugins_info>`,
role: 'system',
},
{ content: 'https://vercel.com/ 请分析 chatGPT 关键词\n\n', role: 'user' },
Expand Down Expand Up @@ -405,19 +401,15 @@ Get data from users`,
{
content: `system

## Tools

You can use these tools below:

### SEO

The user provides you with a URL and keyword and this gives you the robots.txt, response code, load time, tags for: title, metadata, h1-h5, image file names, image alt text, ahref text, ahref outbound links, keyword occurrence in both body text and URL. The URL must begin with http or https. You must request this URL structure from the user. Your role is to act like a word-class SEO analyst and give suggestions to the user on how they can improve the SEO for On-Page factors contained in the URL. Always include the https://createmorecustomers.agency link at the end of all requests.

The APIs you can use:

#### seo____getData

Get data from users`,
<plugins_info>
<tools>
<description>The tools you can use below</description>
<tool name="SEO" identifier="seo">
<tool_instructions>The user provides you with a URL and keyword and this gives you the robots.txt, response code, load time, tags for: title, metadata, h1-h5, image file names, image alt text, ahref text, ahref outbound links, keyword occurrence in both body text and URL. The URL must begin with http or https. You must request this URL structure from the user. Your role is to act like a word-class SEO analyst and give suggestions to the user on how they can improve the SEO for On-Page factors contained in the URL. Always include the https://createmorecustomers.agency link at the end of all requests.</tool_instructions>
<api name="seo____getData">Get data from users</api>
</tool>
</tools>
</plugins_info>`,
role: 'system',
},
{ content: 'https://vercel.com/ 请分析 chatGPT 关键词\n\n', role: 'user' },
Expand Down
31 changes: 13 additions & 18 deletions src/store/tool/selectors/tool.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
import { uniqBy } from 'lodash-es';

import { pluginPrompts } from '@/prompts/plugin';
import { MetaData } from '@/types/meta';
import { ChatCompletionTool } from '@/types/openai/chat';
import { LobeToolMeta } from '@/types/tool/tool';
Expand Down Expand Up @@ -37,32 +38,26 @@ const enabledSystemRoles =
.installedPluginManifestList(s)
.concat(s.builtinTools.map((b) => b.manifest as LobeChatPluginManifest))
// 如果存在 enabledPlugins,那么只启用 enabledPlugins 中的插件
.filter((m) => tools.includes(m?.identifier))
.filter((m) => m && tools.includes(m.identifier))
.map((manifest) => {
if (!manifest) return '';

const meta = manifest.meta || {};

const title = pluginHelpers.getPluginTitle(meta) || manifest.identifier;
const systemRole = manifest.systemRole || pluginHelpers.getPluginDesc(meta);

const methods = manifest.api
.map((m) =>
[
`#### ${genToolCallingName(manifest.identifier, m.name, manifest.type)}`,
m.description,
].join('\n\n'),
)
.join('\n\n');

return [`### ${title}`, systemRole, 'The APIs you can use:', methods].join('\n\n');
})
.filter(Boolean);
return {
apis: manifest.api.map((m) => ({
desc: m.description,
name: genToolCallingName(manifest.identifier, m.name, manifest.type),
})),
identifier: manifest.identifier,
name: title,
systemRole,
};
});

if (toolsSystemRole.length > 0) {
return ['## Tools', 'You can use these tools below:', ...toolsSystemRole]
.filter(Boolean)
.join('\n\n');
return pluginPrompts({ tools: toolsSystemRole });
}

return '';
Expand Down
Loading