From 87a97878778ea091f8960c1e42e37b9c75775c2f Mon Sep 17 00:00:00 2001 From: Acbox Date: Sat, 31 Jan 2026 18:47:16 +0800 Subject: [PATCH] feat: add skills uses to agent gateway --- agent/src/agent.ts | 40 ++++++++++++++++++++++++++++++++++++- agent/src/modules/chat.ts | 14 +++++++++++++ agent/src/prompts/system.ts | 23 ++++++++++++++++++++- agent/src/tools/skill.ts | 34 +++++++++++++++++++++++++++++++ agent/src/types.ts | 6 ++++++ 5 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 agent/src/tools/skill.ts diff --git a/agent/src/agent.ts b/agent/src/agent.ts index e0e547c2..44cd2cac 100644 --- a/agent/src/agent.ts +++ b/agent/src/agent.ts @@ -1,10 +1,11 @@ import { generateText, ModelMessage, stepCountIs, streamText, TextStreamPart, ToolSet } from 'ai' import { createChatGateway } from './gateway' -import { ClientType, Schedule } from './types' +import { AgentSkill, ClientType, Schedule } from './types' import { system, schedule } from './prompts' import { AuthFetcher } from './index' import { getScheduleTools } from './tools/schedule' import { getWebTools } from './tools/web' +import { getSkillTools } from './tools/skill' export interface AgentParams { apiKey: string @@ -19,6 +20,8 @@ export interface AgentParams { currentPlatform?: string braveApiKey?: string braveBaseUrl?: string + skills?: AgentSkill[] + useSkills?: string[] } export interface AgentInput { @@ -28,6 +31,7 @@ export interface AgentInput { export interface AgentResult { messages: ModelMessage[] + skills: string[] } export const createAgent = ( @@ -36,13 +40,27 @@ export const createAgent = ( ) => { const gateway = createChatGateway(params.clientType) const messages: ModelMessage[] = [] + const enabledSkills: AgentSkill[] = params.skills ?? [] + enabledSkills.push( + ...params.useSkills?.map((name) => params.skills?.find((s) => s.name === name) + ).filter((s) => s !== undefined) ?? []) const maxSteps = params.maxSteps ?? 50 const getTools = () => { const scheduleTools = getScheduleTools({ fetch: fetcher }) + const skillTools = getSkillTools({ + skills: params.skills ?? [], + useSkill: (skill) => { + if (enabledSkills.some((s) => s.name === skill.name)) { + return + } + enabledSkills.push(skill) + } + }) const tools: ToolSet = { ...scheduleTools, + ...skillTools, } // Add web search tools if Brave API key is provided @@ -65,6 +83,8 @@ export const createAgent = ( maxContextLoadTime: params.maxContextLoadTime, platforms: params.platforms ?? [], currentPlatform: params.currentPlatform, + skills: params.skills ?? [], + enabledSkills, }) } @@ -83,10 +103,16 @@ export const createAgent = ( system: generateSystem(), stopWhen: stepCountIs(maxSteps), messages, + prepareStep: () => { + return { + system: generateSystem(), + } + }, tools: getTools(), }) return { messages: [user, ...response.messages], + skills: enabledSkills.map((s) => s.name), } } @@ -105,6 +131,11 @@ export const createAgent = ( system: generateSystem(), stopWhen: stepCountIs(maxSteps), messages, + prepareStep: () => { + return { + system: generateSystem(), + } + }, tools: getTools(), }) for await (const event of fullStream) { @@ -112,6 +143,7 @@ export const createAgent = ( } return { messages: [user, ...(await response).messages], + skills: enabledSkills.map((s) => s.name), } } @@ -137,10 +169,16 @@ export const createAgent = ( system: generateSystem(), stopWhen: stepCountIs(maxSteps), messages, + prepareStep: () => { + return { + system: generateSystem(), + } + }, tools: getTools(), }) return { messages: [user, ...response.messages], + skills: enabledSkills.map((s) => s.name), } } diff --git a/agent/src/modules/chat.ts b/agent/src/modules/chat.ts index 5aaadef4..67e53d41 100644 --- a/agent/src/modules/chat.ts +++ b/agent/src/modules/chat.ts @@ -7,6 +7,12 @@ import { ModelMessage } from 'ai' import { bearerMiddleware } from '../middlewares/bearer' import { loadConfig } from '../config' +const Skill = z.object({ + name: z.string().min(1, 'Skill name is required'), + description: z.string().min(1, 'Skill description is required'), + content: z.string().min(1, 'Skill content is required'), +}) + const ChatBody = z.object({ apiKey: z.string().min(1, 'API key is required'), baseUrl: z.string().min(1, 'Base URL is required'), @@ -22,6 +28,8 @@ const ChatBody = z.object({ maxContextLoadTime: z.number().min(1, 'Max context load time is required'), platforms: z.array(z.string()).optional(), currentPlatform: z.string().optional(), + skills: z.array(Skill).optional(), + useSkills: z.array(z.string()).optional(), messages: z.array(z.any()), query: z.string().min(1, 'Query is required'), @@ -56,6 +64,8 @@ export const chatModule = new Elysia({ prefix: '/chat' }) currentPlatform: body.currentPlatform, braveApiKey: config.brave?.api_key, braveBaseUrl: config.brave?.base_url, + skills: body.skills, + useSkills: body.useSkills, }, createAuthFetcher(bearer)) try { const result = await ask({ @@ -98,6 +108,8 @@ export const chatModule = new Elysia({ prefix: '/chat' }) currentPlatform: body.currentPlatform, braveApiKey: config.brave?.api_key, braveBaseUrl: config.brave?.base_url, + skills: body.skills, + useSkills: body.useSkills, }, createAuthFetcher(bearer)) try { const streanGenerator = stream({ @@ -151,6 +163,8 @@ export const chatModule = new Elysia({ prefix: '/chat' }) currentPlatform: body.currentPlatform, braveApiKey: config.brave?.api_key, braveBaseUrl: config.brave?.base_url, + skills: body.skills, + useSkills: body.useSkills, }, createAuthFetcher(bearer)) try { return await triggerSchedule({ diff --git a/agent/src/prompts/system.ts b/agent/src/prompts/system.ts index 26a7d804..aec1b731 100644 --- a/agent/src/prompts/system.ts +++ b/agent/src/prompts/system.ts @@ -1,5 +1,6 @@ import { time } from './shared' import { quote } from './utils' +import { AgentSkill } from '../types' export interface SystemParams { date: Date @@ -8,9 +9,20 @@ export interface SystemParams { maxContextLoadTime: number platforms: string[] currentPlatform?: string + skills: AgentSkill[] + enabledSkills: AgentSkill[] } -export const system = ({ date, locale, language, maxContextLoadTime, platforms, currentPlatform }: SystemParams) => { +export const skillPrompt = (skill: AgentSkill) => { + return ` +### ${skill.name} +> ${skill.description} + +${skill.content} + `.trim() +} + +export const system = ({ date, locale, language, maxContextLoadTime, platforms, currentPlatform, skills, enabledSkills }: SystemParams) => { return ` --- ${time({ date, locale })} @@ -46,5 +58,14 @@ Your abilities: + The ${quote('message')} is the message to send. + IF: the problem is initiated by a user, regardless of the platform the user is using, the content should be directly output in the content. + IF: the issue is initiated by a non-user (such as a scheduled task reminder), then it should be sent using the appropriate tools on the platform specified in the requirements. + +**Skills** + +There are ${skills.length} skills available, you can use ${quote('use_skill')} to use a skill. +${skills.map(skill => `- ${skill.name}: ${skill.description}`).join('\n')} + +**Enabled Skills** + +${enabledSkills.map(skill => skillPrompt(skill)).join('\n\n---\n\n')} `.trim() } \ No newline at end of file diff --git a/agent/src/tools/skill.ts b/agent/src/tools/skill.ts new file mode 100644 index 00000000..f669d074 --- /dev/null +++ b/agent/src/tools/skill.ts @@ -0,0 +1,34 @@ +import { AgentSkill } from '../types' +import { tool } from 'ai' +import { z } from 'zod' + +interface SkillToolParams { + skills: AgentSkill[] + useSkill: (skill: AgentSkill, reason: string) => void +} + +export const getSkillTools = ({ skills, useSkill }: SkillToolParams) => { + const useSkillTool = tool({ + description: 'Use a skill if you think it is relevant to the current task', + inputSchema: z.object({ + skillName: z.string().describe('The name of the skill to use'), + reason: z.string().describe('The reason why you think this skill is relevant to the current task'), + }), + execute: async ({ skillName, reason }) => { + const skill = skills.find((s) => s.name === skillName) + if (!skill) { + return { error: 'Skill not found' } + } + await useSkill(skill, reason) + return { + success: true, + skillName, + reason, + } + }, + }) + + return { + 'use_skill': useSkillTool, + } +} \ No newline at end of file diff --git a/agent/src/types.ts b/agent/src/types.ts index 8d322549..e6ece32a 100644 --- a/agent/src/types.ts +++ b/agent/src/types.ts @@ -11,4 +11,10 @@ export interface Schedule { pattern: string maxCalls?: number command: string +} + +export interface AgentSkill { + name: string + description: string + content: string } \ No newline at end of file