feat: 添加钩子

This commit is contained in:
李岩岩 2026-06-10 17:11:56 +08:00
parent a0023c49f4
commit cc79596b1e
4 changed files with 88 additions and 4 deletions

23
src/hooks/index.ts Normal file
View File

@ -0,0 +1,23 @@
type EventName = 'process:start' | 'agent:start' | 'step:before' | 'tool:before' | 'tool:after' | 'agent:end';
type Listener = (data: any) => void | Promise<void>;
export class HookBus {
private listeners = new Map<EventName, Listener[]>();
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();

26
src/hooks/log.hooks.ts Normal file
View File

@ -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);
});
}

28
src/hooks/registry.ts Normal file
View File

@ -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);
}
}

View File

@ -3,6 +3,10 @@ import { chat } from './llm.js';
import { PROMPTS } from './prompts/system.js'; import { PROMPTS } from './prompts/system.js';
import { getOpenAITools, findTool } from './tools/registry.js'; import { getOpenAITools, findTool } from './tools/registry.js';
import * as readline from 'node:readline/promises'; 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 promptName = process.argv[2] || 'reAct';
const systemPrompt = PROMPTS[promptName as keyof typeof PROMPTS]; const systemPrompt = PROMPTS[promptName as keyof typeof PROMPTS];
@ -27,6 +31,8 @@ while (true) {
if (userInput.toLowerCase() === 'exit') break; if (userInput.toLowerCase() === 'exit') break;
context.add({ role: 'user', content: userInput }); context.add({ role: 'user', content: userInput });
hooks.emit('agent:start', { userInput });
// 第一轮:带工具定义调用 // 第一轮:带工具定义调用
const tools = getOpenAITools(); const tools = getOpenAITools();
@ -34,15 +40,16 @@ while (true) {
// 如果模型要求调用工具,执行并在上下文中构造 tool 消息 // 如果模型要求调用工具,执行并在上下文中构造 tool 消息
while (reply.tool_calls && reply.tool_calls.length > 0) { while (reply.tool_calls && reply.tool_calls.length > 0) {
hooks.emit('step:before', { userInput, reply });
// 先把 assistant 消息(含 tool_calls加入上下文 // 先把 assistant 消息(含 tool_calls加入上下文
context.add({ role: 'assistant', content: '', tool_calls: reply.tool_calls } as any); context.add({ role: 'assistant', content: '', tool_calls: reply.tool_calls } as any);
for (const tc of reply.tool_calls) { for (const tc of reply.tool_calls) {
hooks.emit('tool:before', { toolCall: tc });
const tool = findTool(tc.function.name); const tool = findTool(tc.function.name);
const args = JSON.parse(tc.function.arguments); const args = JSON.parse(tc.function.arguments);
const result = await tool!.execute(args); 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 // tool 消息:必须回传 tool_call_id
context.add({ context.add({
role: 'tool', role: 'tool',
@ -54,8 +61,8 @@ while (true) {
// 继续调用,看模型还需要不需要更多工具 // 继续调用,看模型还需要不需要更多工具
reply = await chat(context.getMessages(), tools); reply = await chat(context.getMessages(), tools);
} }
console.log('助手:', reply.content); hooks.emit('agent:end', { userInput, reply });
context.add({ role: 'assistant', content: reply.content! }); context.add({ role: 'assistant', content: reply.content! });
} }