feat: skills

This commit is contained in:
Acbox
2026-02-08 01:57:06 +08:00
parent 5b09c53a3b
commit 318bd87f65
12 changed files with 254 additions and 43 deletions
+25 -4
View File
@@ -1,5 +1,5 @@
import { generateText, ImagePart, LanguageModelUsage, ModelMessage, stepCountIs, streamText, UserModelMessage } from 'ai'
import { AgentInput, AgentParams, allActions, HTTPMCPConnection, MCPConnection, Schedule } from './types'
import { AgentInput, AgentParams, AgentSkill, allActions, HTTPMCPConnection, MCPConnection, Schedule } from './types'
import { system, schedule, user, subagentSystem } from './prompts'
import { AuthFetcher } from './index'
import { createModel } from './model'
@@ -22,6 +22,7 @@ export const createAgent = ({
allowedActions = allActions,
channels = [],
mcpConnections = [],
skills = [],
currentChannel = 'Unknown Channel',
identity = {
botId: '',
@@ -33,6 +34,18 @@ export const createAgent = ({
auth,
}: AgentParams, fetch: AuthFetcher) => {
const model = createModel(modelConfig)
const enabledSkills: AgentSkill[] = []
const enableSkill = (skill: string) => {
const agentSkill = skills.find(s => s.name === skill)
if (agentSkill) {
enabledSkills.push(agentSkill)
}
}
const getEnabledSkills = () => {
return enabledSkills.map(skill => skill.name)
}
const getDefaultMCPConnections = (): MCPConnection[] => {
const fs: HTTPMCPConnection = {
@@ -52,8 +65,8 @@ export const createAgent = ({
language,
maxContextLoadTime: activeContextTime,
channels,
skills: [],
enabledSkills: [],
skills,
enabledSkills,
})
}
@@ -63,6 +76,7 @@ export const createAgent = ({
model: modelConfig,
brave,
identity,
enableSkill,
})
const defaultMCPConnections = getDefaultMCPConnections()
const { tools: mcpTools, close: closeMCP } = await getMCPTools([
@@ -99,6 +113,7 @@ export const createAgent = ({
const ask = async (input: AgentInput) => {
const userPrompt = generateUserPrompt(input)
const messages = [...input.messages, userPrompt]
input.skills.forEach(skill => enableSkill(skill))
const systemPrompt = generateSystemPrompt()
const { tools, close } = await getAgentTools()
const { response, reasoning, text, usage } = await generateText({
@@ -125,6 +140,7 @@ export const createAgent = ({
usage,
text: cleanedText,
attachments: allAttachments,
skills: getEnabledSkills(),
}
}
@@ -169,12 +185,14 @@ export const createAgent = ({
reasoning: reasoning.map(part => part.text),
usage,
text,
skills: getEnabledSkills(),
}
}
const triggerSchedule = async (params: {
schedule: Schedule
messages: ModelMessage[]
skills: string[]
}) => {
const scheduleMessage: UserModelMessage = {
role: 'user',
@@ -183,6 +201,7 @@ export const createAgent = ({
]
}
const messages = [...params.messages, scheduleMessage]
params.skills.forEach(skill => enableSkill(skill))
const { tools, close } = await getAgentTools()
const { response, reasoning, text, usage } = await generateText({
model,
@@ -199,12 +218,14 @@ export const createAgent = ({
reasoning: reasoning.map(part => part.text),
usage,
text,
skills: getEnabledSkills(),
}
}
async function* stream(input: AgentInput): AsyncGenerator<AgentAction> {
const userPrompt = generateUserPrompt(input)
const messages = [...input.messages, userPrompt]
input.skills.forEach(skill => enableSkill(skill))
const systemPrompt = generateSystemPrompt()
const attachmentsExtractor = new AttachmentsStreamExtractor()
const result: {
@@ -320,9 +341,9 @@ export const createAgent = ({
yield {
type: 'agent_end',
messages: [userPrompt, ...strippedMessages],
skills: [],
reasoning: result.reasoning,
usage: result.usage!,
skills: getEnabledSkills(),
}
}
+6 -1
View File
@@ -4,7 +4,7 @@ import { createAgent } from '../agent'
import { createAuthFetcher, getBaseUrl, getBraveConfig } from '../index'
import { ModelConfig } from '../types'
import { bearerMiddleware } from '../middlewares/bearer'
import { AllowedActionModel, AttachmentModel, IdentityContextModel, MCPConnectionModel, ModelConfigModel, ScheduleModel } from '../models'
import { AgentSkillModel, AllowedActionModel, AttachmentModel, IdentityContextModel, MCPConnectionModel, ModelConfigModel, ScheduleModel } from '../models'
import { allActions } from '../types'
const AgentModel = z.object({
@@ -14,6 +14,7 @@ const AgentModel = z.object({
currentChannel: z.string(),
allowedActions: z.array(AllowedActionModel).optional().default(allActions),
messages: z.array(z.any()),
usableSkills: z.array(AgentSkillModel).optional().default([]),
skills: z.array(z.string()),
identity: IdentityContextModel,
attachments: z.array(AttachmentModel).optional().default([]),
@@ -37,6 +38,7 @@ export const chatModule = new Elysia({ prefix: '/chat' })
bearer: bearer!,
baseUrl: getBaseUrl(),
},
skills: body.usableSkills,
brave: getBraveConfig(),
}, authFetcher)
return ask({
@@ -66,6 +68,7 @@ export const chatModule = new Elysia({ prefix: '/chat' })
bearer: bearer!,
baseUrl: getBaseUrl(),
},
skills: body.usableSkills,
brave: getBraveConfig(),
}, authFetcher)
for await (const action of stream({
@@ -101,11 +104,13 @@ export const chatModule = new Elysia({ prefix: '/chat' })
bearer: bearer!,
baseUrl: getBaseUrl(),
},
skills: body.usableSkills,
brave: getBraveConfig(),
}, authFetcher)
return triggerSchedule({
schedule: body.schedule,
messages: body.messages,
skills: body.skills,
})
}, {
body: AgentModel.extend({
+2
View File
@@ -35,6 +35,8 @@ export const system = ({
'time-now': date.toISOString(),
}
console.log('enabledSkills', enabledSkills)
return `
---
${Bun.YAML.stringify(headers)}
+8 -2
View File
@@ -7,17 +7,19 @@ import { getMemoryTools } from './memory'
import { getSubagentTools } from './subagent'
import { getContactTools } from './contact'
import { getMessageTools } from './message'
import { getSkillTools } from './skill'
export interface ToolsParams {
fetch: AuthFetcher
model: ModelConfig
brave?: BraveConfig
identity: IdentityContext
enableSkill: (skill: string) => void
}
export const getTools = (
actions: AgentAction[],
{ fetch, model, brave, identity }: ToolsParams
{ fetch, model, brave, identity, enableSkill }: ToolsParams
) => {
const tools: ToolSet = {}
if (actions.includes(AgentAction.Web) && brave) {
@@ -44,5 +46,9 @@ export const getTools = (
const messageTools = getMessageTools({ fetch, identity })
Object.assign(tools, messageTools)
}
return tools
if (actions.includes(AgentAction.Skill)) {
const skillTools = getSkillTools({ useSkill: enableSkill })
Object.assign(tools, skillTools)
}
return tools
}
+3 -9
View File
@@ -1,13 +1,11 @@
import { AgentSkill } from '../types'
import { tool } from 'ai'
import { z } from 'zod'
interface SkillToolParams {
skills: AgentSkill[]
useSkill: (skill: AgentSkill, reason: string) => void
useSkill: (skill: string) => void
}
export const getSkillTools = ({ skills, useSkill }: SkillToolParams) => {
export const getSkillTools = ({ useSkill }: SkillToolParams) => {
const useSkillTool = tool({
description: 'Use a skill if you think it is relevant to the current task',
inputSchema: z.object({
@@ -15,11 +13,7 @@ export const getSkillTools = ({ skills, useSkill }: SkillToolParams) => {
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)
useSkill(skillName)
return {
success: true,
skillName,
+2 -2
View File
@@ -51,6 +51,7 @@ export interface AgentParams {
mcpConnections?: MCPConnection[]
identity?: IdentityContext
auth: AgentAuthContext
skills?: AgentSkill[]
}
export interface AgentInput {
@@ -64,6 +65,5 @@ export interface AgentSkill {
name: string
description: string
content: string
// eslint-disable-next-line @typescript-eslint/no-explicit-any
metadata: Record<string, any>
metadata?: Record<string, unknown>
}