diff --git a/src/hooks/index.ts b/src/hooks/index.ts new file mode 100644 index 0000000..a8c0c21 --- /dev/null +++ b/src/hooks/index.ts @@ -0,0 +1,23 @@ +type EventName = 'process:start' | 'agent:start' | 'step:before' | 'tool:before' | 'tool:after' | 'agent:end'; +type Listener = (data: any) => void | Promise; + +export class HookBus { + private listeners = new Map(); + + on(event: EventName, fn: Listener) { + if (!this.listeners.has(event)) { + this.listeners.set(event, []); + } + this.listeners.get(event)!.push(fn); + } + + async emit(event: EventName, data: any) { + const fns = this.listeners.get(event); + if (!fns) return; + for (const fn of fns) { + await fn(data); + } + } +} + +export const hooks = new HookBus(); diff --git a/src/hooks/log.hooks.ts b/src/hooks/log.hooks.ts new file mode 100644 index 0000000..06429e0 --- /dev/null +++ b/src/hooks/log.hooks.ts @@ -0,0 +1,26 @@ +import type { HookBus } from './index.js'; + +export default (hooks: HookBus) => { + // hooks.on('agent:start', async (data) => { + // console.log('🚀 ~ agent:start ~ data:', data); + // }); + + // hooks.on('step:before', async (data) => { + // console.log('🚀 ~ step:before ~ data:', data); + // }); + + hooks.on('tool:before', async (data) => { + const { toolCall: tc, result } = data; + console.log(` [工具调用] ${tc.function.name}(${tc.function.arguments})`); + }); + + hooks.on('tool:after', async (data) => { + const { toolCall: tc, result } = data; + console.log(` [工具] ${tc.function.name}(${tc.function.arguments}) -> ${result}`); + }); + + hooks.on('agent:end', async (data) => { + const { reply } = data; + console.log('助手:', reply.content); + }); +} \ No newline at end of file diff --git a/src/hooks/registry.ts b/src/hooks/registry.ts new file mode 100644 index 0000000..1231fb9 --- /dev/null +++ b/src/hooks/registry.ts @@ -0,0 +1,28 @@ +// 从内置hooks目录中获取所有hooks并注册到HookBus + +import path from 'node:path'; +import fs from 'node:fs'; +import { pathToFileURL } from 'node:url'; +import { hooks } from './index.js'; + +// 这里可以自动扫描hooks目录下的所有文件并导入它们,假设每个文件都默认导出一个函数来注册hook + +const getAllHooks = async () => { + // 动态读取hooks目录下的所有 .hooks.ts 结尾的文件 + const hooksDir = path.resolve('src/hooks'); + const hookFiles = fs.readdirSync(hooksDir).filter(file => file.endsWith('.hooks.ts')); + return Promise.all( + hookFiles.map(async (file) => { + const mod = await import(pathToFileURL(path.join(hooksDir, file)).href); + return mod.default; + }) + ); +} + +export const registerHooks = async () => { + const hookModules = await getAllHooks(); + for (const hookModule of hookModules) { + // 每个hook模块默认导出一个函数,调用它并传入hooks实例 + hookModule(hooks); + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 13fc33a..9e94ad2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,10 @@ import { chat } from './llm.js'; import { PROMPTS } from './prompts/system.js'; import { getOpenAITools, findTool } from './tools/registry.js'; import * as readline from 'node:readline/promises'; +import { hooks } from './hooks/index.js'; +import { registerHooks } from './hooks/registry.js'; + +await registerHooks(); const promptName = process.argv[2] || 'reAct'; const systemPrompt = PROMPTS[promptName as keyof typeof PROMPTS]; @@ -27,6 +31,8 @@ while (true) { if (userInput.toLowerCase() === 'exit') break; context.add({ role: 'user', content: userInput }); + + hooks.emit('agent:start', { userInput }); // 第一轮:带工具定义调用 const tools = getOpenAITools(); @@ -34,15 +40,16 @@ while (true) { // 如果模型要求调用工具,执行并在上下文中构造 tool 消息 while (reply.tool_calls && reply.tool_calls.length > 0) { + hooks.emit('step:before', { userInput, reply }); // 先把 assistant 消息(含 tool_calls)加入上下文 context.add({ role: 'assistant', content: '', tool_calls: reply.tool_calls } as any); for (const tc of reply.tool_calls) { + hooks.emit('tool:before', { toolCall: tc }); const tool = findTool(tc.function.name); const args = JSON.parse(tc.function.arguments); const result = await tool!.execute(args); - console.log(` [工具] ${tc.function.name}(${tc.function.arguments}) -> ${result}`); - + hooks.emit('tool:after', { toolCall: tc, result }); // tool 消息:必须回传 tool_call_id context.add({ role: 'tool', @@ -54,8 +61,8 @@ while (true) { // 继续调用,看模型还需要不需要更多工具 reply = await chat(context.getMessages(), tools); } - - console.log('助手:', reply.content); + + hooks.emit('agent:end', { userInput, reply }); context.add({ role: 'assistant', content: reply.content! }); }