mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-27 07:16:19 +09:00
Merge branch 'main' into feat/skills
This commit is contained in:
+84
-22
@@ -1,27 +1,34 @@
|
||||
import { generateText, ModelMessage, stepCountIs, streamText, TextStreamPart, ToolSet } from 'ai'
|
||||
import { createChatGateway } from './gateway'
|
||||
import { AgentSkill, ClientType, Schedule } from './types'
|
||||
import { AgentSkill, BaseModelConfig, Schedule } from './types'
|
||||
import { system, schedule } from './prompts'
|
||||
import { AuthFetcher } from './index'
|
||||
import { getScheduleTools } from './tools/schedule'
|
||||
import { getWebTools } from './tools/web'
|
||||
import { subagentSystem } from './prompts/subagent'
|
||||
import { getSubagentTools } from './tools/subagent'
|
||||
import { getSkillTools } from './tools/skill'
|
||||
|
||||
export interface AgentParams {
|
||||
apiKey: string
|
||||
baseUrl: string
|
||||
model: string
|
||||
clientType: ClientType
|
||||
export enum AgentAction {
|
||||
WebSearch = 'web_search',
|
||||
Message = 'message',
|
||||
Subagent = 'subagent',
|
||||
Schedule = 'schedule',
|
||||
Skill = 'skill',
|
||||
}
|
||||
|
||||
export interface AgentParams extends BaseModelConfig {
|
||||
locale?: Intl.LocalesArgument
|
||||
language?: string
|
||||
maxSteps?: number
|
||||
maxContextLoadTime: number
|
||||
maxContextLoadTime?: number
|
||||
platforms?: string[]
|
||||
currentPlatform?: string
|
||||
braveApiKey?: string
|
||||
braveBaseUrl?: string
|
||||
skills?: AgentSkill[]
|
||||
useSkills?: string[]
|
||||
allowed?: AgentAction[]
|
||||
}
|
||||
|
||||
export interface AgentInput {
|
||||
@@ -45,26 +52,33 @@ export const createAgent = (
|
||||
...params.useSkills?.map((name) => params.skills?.find((s) => s.name === name)
|
||||
).filter((s) => s !== undefined) ?? [])
|
||||
|
||||
const allowedActions = params.allowed
|
||||
?? Object.values(AgentAction)
|
||||
|
||||
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
|
||||
const tools: ToolSet = {}
|
||||
|
||||
if (allowedActions.includes(AgentAction.Skill)) {
|
||||
const skillTools = getSkillTools({
|
||||
skills: params.skills ?? [],
|
||||
useSkill: (skill) => {
|
||||
if (enabledSkills.some((s) => s.name === skill.name)) {
|
||||
return
|
||||
}
|
||||
enabledSkills.push(skill)
|
||||
}
|
||||
enabledSkills.push(skill)
|
||||
}
|
||||
})
|
||||
const tools: ToolSet = {
|
||||
...scheduleTools,
|
||||
...skillTools,
|
||||
})
|
||||
Object.assign(tools, skillTools)
|
||||
}
|
||||
|
||||
// Add web search tools if Brave API key is provided
|
||||
if (params.braveApiKey) {
|
||||
if (allowedActions.includes(AgentAction.Schedule)) {
|
||||
const scheduleTools = getScheduleTools({ fetch: fetcher })
|
||||
Object.assign(tools, scheduleTools)
|
||||
}
|
||||
|
||||
if (params.braveApiKey && allowedActions.includes(AgentAction.WebSearch)) {
|
||||
const webTools = getWebTools({
|
||||
braveApiKey: params.braveApiKey,
|
||||
braveBaseUrl: params.braveBaseUrl,
|
||||
@@ -72,6 +86,19 @@ export const createAgent = (
|
||||
Object.assign(tools, webTools)
|
||||
}
|
||||
|
||||
if (allowedActions.includes(AgentAction.Subagent)) {
|
||||
const subagentTools = getSubagentTools({
|
||||
fetch: fetcher,
|
||||
apiKey: params.apiKey,
|
||||
baseUrl: params.baseUrl,
|
||||
model: params.model,
|
||||
clientType: params.clientType,
|
||||
braveApiKey: params.braveApiKey,
|
||||
braveBaseUrl: params.braveBaseUrl,
|
||||
})
|
||||
Object.assign(tools, subagentTools)
|
||||
}
|
||||
|
||||
return tools
|
||||
}
|
||||
|
||||
@@ -80,7 +107,7 @@ export const createAgent = (
|
||||
date: new Date(),
|
||||
locale: params.locale,
|
||||
language: params.language,
|
||||
maxContextLoadTime: params.maxContextLoadTime,
|
||||
maxContextLoadTime: params.maxContextLoadTime ?? 1550,
|
||||
platforms: params.platforms ?? [],
|
||||
currentPlatform: params.currentPlatform,
|
||||
skills: params.skills ?? [],
|
||||
@@ -116,6 +143,40 @@ export const createAgent = (
|
||||
}
|
||||
}
|
||||
|
||||
const askAsSubagent = async (
|
||||
input: AgentInput,
|
||||
options: {
|
||||
name: string
|
||||
description?: string
|
||||
}
|
||||
): Promise<AgentResult> => {
|
||||
messages.push(...input.messages)
|
||||
const user: ModelMessage = {
|
||||
role: 'user',
|
||||
content: input.query,
|
||||
}
|
||||
messages.push(user)
|
||||
const { response } = await generateText({
|
||||
model: gateway({
|
||||
apiKey: params.apiKey,
|
||||
baseURL: params.baseUrl,
|
||||
})(params.model),
|
||||
system: subagentSystem({ date: new Date(), name: options.name, description: options.description }),
|
||||
stopWhen: stepCountIs(maxSteps),
|
||||
messages,
|
||||
prepareStep: () => {
|
||||
return {
|
||||
system: subagentSystem({ date: new Date(), name: options.name, description: options.description }),
|
||||
}
|
||||
},
|
||||
tools: getTools(),
|
||||
})
|
||||
return {
|
||||
messages: [user, ...response.messages],
|
||||
skills: enabledSkills.map((s) => s.name),
|
||||
}
|
||||
}
|
||||
|
||||
async function* stream(input: AgentInput): AsyncGenerator<TextStreamPart<ToolSet>, AgentResult> {
|
||||
messages.push(...input.messages)
|
||||
const user: ModelMessage = {
|
||||
@@ -186,5 +247,6 @@ export const createAgent = (
|
||||
ask,
|
||||
stream,
|
||||
triggerSchedule,
|
||||
askAsSubagent,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { time } from './shared'
|
||||
|
||||
export interface SubagentParams {
|
||||
date: Date
|
||||
name: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export const subagentSystem = ({ date, name, description }: SubagentParams) => {
|
||||
return `
|
||||
---
|
||||
${time({ date })}
|
||||
name: ${name}
|
||||
description: ${description}
|
||||
---
|
||||
|
||||
You are a subagent, which is a specialized assistant for a specific task.
|
||||
|
||||
Your task is communicated with the master agent to complete a task.
|
||||
`
|
||||
}
|
||||
@@ -59,6 +59,17 @@ Your abilities:
|
||||
+ 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.
|
||||
|
||||
**Subagent**
|
||||
When a task is large, you can create a Subagent to help you complete some tasks in order to save your own context.
|
||||
|
||||
- You can use ${quote('create_subagent')} to create a new subagent.
|
||||
- You can use ${quote('list_subagents')} to list subagents you have created.
|
||||
- You can use ${quote('delete_subagent')} to delete a subagent by id.
|
||||
- You can use ${quote('query_subagent')} to ask a subagent to complete a task.
|
||||
+ The ${quote('name')} is the name of the subagent to ask.
|
||||
+ The ${quote('query')} is the prompt to ask the subagent to complete the task.
|
||||
Before asking a subagent, you should first create a subagent if it does not exist.
|
||||
|
||||
**Skills**
|
||||
|
||||
There are ${skills.length} skills available, you can use ${quote('use_skill')} to use a skill.
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { tool } from 'ai'
|
||||
import { z } from 'zod'
|
||||
import { AgentAction, createAgent } from '../agent'
|
||||
import { BaseModelConfig } from '../types'
|
||||
import { AuthFetcher } from '..'
|
||||
|
||||
export interface SubagentToolParams extends BaseModelConfig {
|
||||
fetch: AuthFetcher
|
||||
braveApiKey?: string
|
||||
braveBaseUrl?: string
|
||||
}
|
||||
|
||||
export const getSubagentTools = ({ fetch, apiKey, baseUrl, model, clientType, braveApiKey, braveBaseUrl }: SubagentToolParams) => {
|
||||
const listSubagents = tool({
|
||||
description: 'List subagents for current user',
|
||||
inputSchema: z.object({}),
|
||||
execute: async () => {
|
||||
const response = await fetch('/subagents', { method: 'GET' })
|
||||
return response.json()
|
||||
},
|
||||
})
|
||||
|
||||
const createSubagent = tool({
|
||||
description: 'Create a new subagent',
|
||||
inputSchema: z.object({
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
}),
|
||||
execute: async ({ name, description }) => {
|
||||
const response = await fetch('/subagents', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name, description }),
|
||||
})
|
||||
return response.json()
|
||||
},
|
||||
})
|
||||
|
||||
const deleteSubagent = tool({
|
||||
description: 'Delete a subagent by id',
|
||||
inputSchema: z.object({
|
||||
id: z.string().describe('Subagent ID'),
|
||||
}),
|
||||
execute: async ({ id }) => {
|
||||
const response = await fetch(`/subagents/${id}`, { method: 'DELETE' })
|
||||
return response.status === 204 ? { success: true } : response.json()
|
||||
},
|
||||
})
|
||||
|
||||
const querySubagent = tool({
|
||||
description: 'Query a subagent',
|
||||
inputSchema: z.object({
|
||||
name: z.string(),
|
||||
query: z.string().describe('The prompt to ask the subagent to do.'),
|
||||
}),
|
||||
execute: async ({ name, query }) => {
|
||||
const listResponse = await fetch('/subagents', { method: 'GET' })
|
||||
const listPayload = await listResponse.json()
|
||||
const items = Array.isArray(listPayload?.items) ? listPayload.items : []
|
||||
const target = items.find((item: { name?: string }) => item?.name === name)
|
||||
if (!target?.id) {
|
||||
throw new Error(`subagent not found: ${name}`)
|
||||
}
|
||||
const contextResponse = await fetch(`/subagents/${target.id}/context`, { method: 'GET' })
|
||||
const contextPayload = await contextResponse.json()
|
||||
const contextMessages = Array.isArray(contextPayload?.messages) ? contextPayload.messages : []
|
||||
const { askAsSubagent } = createAgent({
|
||||
apiKey,
|
||||
baseUrl,
|
||||
model,
|
||||
clientType,
|
||||
braveApiKey,
|
||||
braveBaseUrl,
|
||||
allowed: [
|
||||
AgentAction.WebSearch,
|
||||
]
|
||||
})
|
||||
const result = await askAsSubagent({
|
||||
messages: contextMessages,
|
||||
query,
|
||||
}, { name, description: target.description })
|
||||
const updatedMessages = [...contextMessages, ...result.messages]
|
||||
await fetch(`/subagents/${target.id}/context`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ messages: updatedMessages }),
|
||||
})
|
||||
return {
|
||||
success: true,
|
||||
result: result.messages[result.messages.length - 1].content,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
'list_subagents': listSubagents,
|
||||
'create_subagent': createSubagent,
|
||||
'delete_subagent': deleteSubagent,
|
||||
'query_subagent': querySubagent,
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,13 @@ export enum ClientType {
|
||||
GOOGLE = 'google',
|
||||
}
|
||||
|
||||
export interface BaseModelConfig {
|
||||
apiKey: string
|
||||
baseUrl: string
|
||||
model: string
|
||||
clientType: ClientType
|
||||
}
|
||||
|
||||
export interface Schedule {
|
||||
id: string
|
||||
name: string
|
||||
|
||||
Reference in New Issue
Block a user