feat: add skills uses to agent gateway

This commit is contained in:
Acbox
2026-01-31 18:47:16 +08:00
parent 9fd15bfa6b
commit 87a9787877
5 changed files with 115 additions and 2 deletions
+39 -1
View File
@@ -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),
}
}
+14
View File
@@ -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({
+22 -1
View File
@@ -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()
}
+34
View File
@@ -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,
}
}
+6
View File
@@ -11,4 +11,10 @@ export interface Schedule {
pattern: string
maxCalls?: number
command: string
}
export interface AgentSkill {
name: string
description: string
content: string
}