Tarko 的 Agent Hooks 系统在 Agent 生命周期的特定节点提供扩展点,允许你自定义行为、添加监控、实现自定义逻辑并与外部系统集成。
Agent Hooks 是在 Agent 运行特定节点执行的回调方法。所有 Hook 都在 Agent 类中定义,可以在自定义 Agent 实现中重写:
拦截和监控 LLM 请求和响应:
import { Agent } from '@tarko/agent';
class MonitoringAgent extends Agent {
// 在每个 LLM 请求之前调用
override async onLLMRequest(id: string, payload: LLMRequestHookPayload) {
console.log(`[${id}] 发送请求到 ${payload.model}`);
console.log('Messages count:', payload.messages.length);
// 记录 token 使用估算
const tokenEstimate = this.estimateTokens(payload.messages);
console.log('Estimated tokens:', tokenEstimate);
}
// 在每个 LLM 响应之后调用
override async onLLMResponse(id: string, payload: LLMResponseHookPayload) {
const response = payload.response;
console.log(`[${id}] 收到响应:`);
console.log('Usage:', response.usage);
console.log('Finish reason:', response.choices[0]?.finish_reason);
// 如果存在工具调用则记录
const toolCalls = response.choices[0]?.message?.tool_calls;
if (toolCalls?.length) {
console.log('Tool calls:', toolCalls.map(tc => tc.function.name));
}
}
// 在流式响应期间调用
override onLLMStreamingResponse(id: string, payload: LLMStreamingResponseHookPayload) {
// 实时监控 streaming chunks
const chunks = payload.chunks;
console.log(`[${id}] 收到 ${chunks.length} 个 streaming chunks`);
}
}监控和控制工具调用执行:
class ToolMonitoringAgent extends Agent {
private toolUsageStats = new Map<string, number>();
// 在每个工具执行之前调用
override async onBeforeToolCall(
id: string,
toolCall: { toolCallId: string; name: string },
args: any
) {
console.log(`[${id}] Executing tool: ${toolCall.name}`);
console.log('Arguments:', JSON.stringify(args, null, 2));
// 跟踪工具使用情况
const currentCount = this.toolUsageStats.get(toolCall.name) || 0;
this.toolUsageStats.set(toolCall.name, currentCount + 1);
// 验证参数或应用速率限制
if (toolCall.name === 'expensive_api' && currentCount >= 5) {
throw new Error('Rate limit exceeded for expensive_api');
}
// 返回可能修改的参数
return args;
}
// 在每个工具执行之后调用
override async onAfterToolCall(
id: string,
toolCall: { toolCallId: string; name: string },
result: any
) {
console.log(`[${id}] Tool ${toolCall.name} completed`);
console.log('Result type:', typeof result);
// 记录错误或成功结果
if (result?.error) {
console.error('Tool execution failed:', result.error);
} else {
console.log('Tool execution successful');
}
// 返回可能修改的结果
return result;
}
// 当工具执行失败时调用
override async onToolCallError(
id: string,
toolCall: { toolCallId: string; name: string },
error: any
) {
console.error(`[${id}] Tool ${toolCall.name} failed:`, error);
// 实现重试逻辑或错误转换
if (error.message?.includes('timeout')) {
return 'Tool execution timed out. Please try again later.';
}
return `Error: ${error.message || error}`;
}
// 完全覆盖工具调用处理
override async onProcessToolCalls(
id: string,
toolCalls: ChatCompletionMessageToolCall[]
) {
// 返回 undefined 正常执行工具
// 返回 ToolCallResult[] 跳过正常执行
// 示例:测试时模拟工具执行
if (process.env.NODE_ENV === 'test') {
return toolCalls.map(tc => ({
toolCallId: tc.id,
result: `Mocked result for ${tc.function.name}`,
success: true
}));
}
return undefined; // 正常执行工具
}
}控制 Agent 循环迭代和终止:
class LoopControlAgent extends Agent {
private iterationStartTimes = new Map<string, number>();
// 在每个循环迭代开始时调用
override async onEachAgentLoopStart(sessionId: string) {
this.iterationStartTimes.set(sessionId, Date.now());
console.log(`[${sessionId}] Starting iteration ${this.getCurrentLoopIteration()}`);
// 注入额外上下文或执行设置
const currentTime = new Date().toISOString();
console.log(`Current time: ${currentTime}`);
}
// 在每个循环迭代结束时调用
override async onEachAgentLoopEnd(context: EachAgentLoopEndContext) {
const startTime = this.iterationStartTimes.get(context.sessionId);
if (startTime) {
const duration = Date.now() - startTime;
console.log(`[${context.sessionId}] Iteration completed in ${duration}ms`);
}
// 记录迭代结果
console.log('Events in this iteration:', context.events?.length || 0);
console.log('Tool calls made:', context.toolCallResults?.length || 0);
}
// 当整个 Agent 循环结束时调用
override async onAgentLoopEnd(id: string) {
console.log(`[${id}] Agent loop completed`);
console.log('Total iterations:', this.getCurrentLoopIteration());
// 清理迭代跟踪
this.iterationStartTimes.delete(id);
// 调用父实现
await super.onAgentLoopEnd(id);
}
}使用 onBeforeLoopTermination 强制执行特定的完成要求:
class ValidatingAgent extends Agent {
private requiredToolsCalled = new Set<string>();
private requiredTools = ['gather_data', 'analyze_results', 'final_report'];
constructor(options: AgentOptions) {
super({
...options,
instructions: `${options.instructions || ''}
你必须按顺序调用这些工具: gather_data, analyze_results, final_report。
在所有必需的工具都被调用之前,不要提供最终答案。`,
});
}
override async onAfterToolCall(
id: string,
toolCall: { toolCallId: string; name: string },
result: any
) {
// 跟踪必需的工具调用
if (this.requiredTools.includes(toolCall.name)) {
this.requiredToolsCalled.add(toolCall.name);
console.log(`必需工具已调用: ${toolCall.name}`);
console.log('剩余:', this.requiredTools.filter(t => !this.requiredToolsCalled.has(t)));
}
return await super.onAfterToolCall(id, toolCall, result);
}
// 防止在所有必需工具调用之前终止
override async onBeforeLoopTermination(
id: string,
finalEvent: AgentEventStream.AssistantMessageEvent
): Promise<LoopTerminationCheckResult> {
const missingTools = this.requiredTools.filter(tool =>
!this.requiredToolsCalled.has(tool)
);
if (missingTools.length > 0) {
console.log(`[${id}] 阻止终止。缺少工具:`, missingTools);
// 注入提醒消息
const reminderEvent = this.getEventStream().createEvent('user_message', {
content: `请在提供最终答案之前调用以下必需工具: ${missingTools.join(', ')}`
});
this.getEventStream().sendEvent(reminderEvent);
return {
finished: false,
message: `必须调用必需工具: ${missingTools.join(', ')}`
};
}
console.log(`[${id}] 所有必需工具已调用。允许终止。`);
return { finished: true };
}
override async onAgentLoopEnd(id: string) {
// 为下次运行重置
this.requiredToolsCalled.clear();
await super.onAgentLoopEnd(id);
}
}使用 onPrepareRequest 动态修改系统提示和可用工具:
class AdaptiveAgent extends Agent {
private userExpertiseLevel: 'beginner' | 'intermediate' | 'expert' = 'intermediate';
override async onPrepareRequest(
context: PrepareRequestContext
): Promise<PrepareRequestResult> {
// 根据上下文修改系统提示
let systemPrompt = context.systemPrompt;
// 添加专业水平特定指令
switch (this.userExpertiseLevel) {
case 'beginner':
systemPrompt += '\n\n用简单的术语解释概念并提供逐步指导。';
break;
case 'expert':
systemPrompt += '\n\n提供技术细节并假设具有高级知识。';
break;
}
// 根据迭代次数过滤工具
let availableTools = context.tools;
const iteration = this.getCurrentLoopIteration();
if (iteration === 1) {
// 第一次迭代:只允许信息收集工具
availableTools = context.tools.filter(tool =>
tool.id.includes('search') || tool.id.includes('read')
);
} else if (iteration >= 5) {
// 后续迭代:添加分析和报告工具
availableTools = context.tools; // 所有工具可用
}
console.log(`迭代 ${iteration}: ${availableTools.length} 个工具可用`);
return {
systemPrompt,
tools: availableTools
};
}
// 更新用户专业水平的方法
setUserExpertiseLevel(level: 'beginner' | 'intermediate' | 'expert') {
this.userExpertiseLevel = level;
console.log(`用户专业水平设置为: ${level}`);
}
}class ResilientAgent extends Agent {
private errorCounts = new Map<string, number>();
private maxRetries = 3;
override async onToolCallError(
id: string,
toolCall: { toolCallId: string; name: string },
error: any
) {
const errorKey = `${id}-${toolCall.name}`;
const currentCount = this.errorCounts.get(errorKey) || 0;
console.error(`工具 ${toolCall.name} 失败 (尝试 ${currentCount + 1}):`, error);
// 实现重试逻辑
if (currentCount < this.maxRetries && this.isRetriableError(error)) {
this.errorCounts.set(errorKey, currentCount + 1);
// 重试前添加延迟
await new Promise(resolve => setTimeout(resolve, 1000 * (currentCount + 1)));
console.log(`重试 ${toolCall.name} (尝试 ${currentCount + 2})`);
return '由于临时错误正在重试...';
}
// 超过最大重试次数或不可重试错误
this.errorCounts.delete(errorKey);
return `工具 ${toolCall.name} 在 ${currentCount + 1} 次尝试后失败: ${error.message || error}`;
}
private isRetriableError(error: any): boolean {
const errorMessage = error.message || error.toString();
return (
errorMessage.includes('timeout') ||
errorMessage.includes('network') ||
errorMessage.includes('503') ||
errorMessage.includes('502')
);
}
override async onAgentLoopEnd(id: string) {
// 清除此会话的错误计数
for (const key of this.errorCounts.keys()) {
if (key.startsWith(id)) {
this.errorCounts.delete(key);
}
}
await super.onAgentLoopEnd(id);
}
}了解 hook 执行序列对于正确实现至关重要:
1. Agent.run() 调用
2. onEachAgentLoopStart() - 迭代开始
3. onPrepareRequest() - 准备 LLM 请求
4. onLLMRequest() - 发送到 LLM 之前
5. onLLMResponse() / onLLMStreamingResponse() - LLM 响应之后
6. [如果存在工具调用]
a. onProcessToolCalls() - 覆盖工具执行(可选)
b. 对于每个工具调用:
- onBeforeToolCall() - 工具执行之前
- [工具执行]
- onAfterToolCall() - 成功执行之后
- 或 onToolCallError() - 执行失败之后
7. onEachAgentLoopEnd() - 迭代结束
8. [如果最终答案准备就绪]
a. onBeforeLoopTermination() - 检查是否应该终止
b. [如果允许终止] onAgentLoopEnd() - Agent 循环结束
9. [否则从步骤 2 重复]WIP