Skip to content

Commit 08e68d0

Browse files
authored
docs(tarko): refactor agent hooks (#1688)
1 parent 4cab415 commit 08e68d0

2 files changed

Lines changed: 34 additions & 221 deletions

File tree

multimodal/websites/tarko/docs/en/guide/advanced/agent-hooks.mdx

Lines changed: 5 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ Agent Hooks are callback methods that execute at specific points during agent op
1717
- **Termination Hooks**: Custom completion criteria enforcement
1818
- **Request Preparation**: Dynamic system prompt and tool modification
1919

20-
## Core Hook Categories
20+
## Core Hooks
2121

22-
### LLM Communication Hooks
22+
### Hooks interacting with LLM
2323

2424
Intercept and monitor LLM requests and responses:
2525

@@ -401,106 +401,14 @@ Understanding the hook execution sequence is crucial for proper implementation:
401401

402402
## Testing Hooks
403403

404-
### Unit Testing Individual Hooks
405-
406-
```typescript
407-
import { Agent, AgentOptions } from '@tarko/agent';
408-
409-
class TestableAgent extends Agent {
410-
public hookCallLog: string[] = [];
411-
412-
override async onLLMRequest(id: string, payload: any) {
413-
this.hookCallLog.push(`onLLMRequest:${id}`);
414-
}
415-
416-
override async onBeforeToolCall(id: string, toolCall: any, args: any) {
417-
this.hookCallLog.push(`onBeforeToolCall:${toolCall.name}`);
418-
return args;
419-
}
420-
}
421-
422-
describe('Agent Hooks', () => {
423-
let agent: TestableAgent;
424-
425-
beforeEach(() => {
426-
agent = new TestableAgent({});
427-
});
428-
429-
it('should call hooks in correct order', async () => {
430-
// Test hook execution order
431-
await agent.onLLMRequest('test-session', { model: 'gpt-4', messages: [] });
432-
await agent.onBeforeToolCall('test-session', { name: 'test-tool' }, {});
433-
434-
expect(agent.hookCallLog).toEqual([
435-
'onLLMRequest:test-session',
436-
'onBeforeToolCall:test-tool'
437-
]);
438-
});
439-
});
440-
```
441-
442-
### Integration Testing
443-
444-
```typescript
445-
describe('Hook Integration', () => {
446-
it('should prevent termination when required', async () => {
447-
const agent = new ValidatingAgent({
448-
tools: [/* required tools */]
449-
});
450-
451-
// Mock a scenario where not all required tools are called
452-
const result = await agent.onBeforeLoopTermination(
453-
'test-session',
454-
{ content: 'Final answer' } as any
455-
);
456-
457-
expect(result.finished).toBe(false);
458-
expect(result.message).toContain('Must call required tools');
459-
});
460-
});
461-
```
462-
463-
## Best Practices
464-
465-
### 1. Hook Design
466-
- Keep hooks focused and lightweight
467-
- Always call `super.hookMethod()` when overriding
468-
- Handle errors gracefully without breaking agent execution
469-
- Use async/await for asynchronous operations
470-
471-
### 2. State Management
472-
- Store hook-specific state in instance variables
473-
- Clean up state in `onAgentLoopEnd`
474-
- Use session IDs to track per-conversation state
475-
476-
### 3. Performance
477-
- Avoid blocking operations in critical hooks
478-
- Use caching for expensive computations
479-
- Implement timeouts for external calls
480-
481-
### 4. Error Handling
482-
- Always handle hook errors gracefully
483-
- Provide fallback behavior when possible
484-
- Log errors for debugging without exposing sensitive data
485-
486-
### 5. Testing
487-
- Unit test hooks in isolation
488-
- Test hook composition and ordering
489-
- Mock external dependencies
490-
- Test error scenarios
404+
WIP
491405

492406
## Real-World Examples
493407

494-
See our [Examples](/examples/custom-hooks) section for complete implementations of:
495-
496-
- Analytics and metrics collection
497-
- User permission enforcement
498-
- Rate limiting and quota management
499-
- Multi-step workflow validation
500-
- Error recovery and retry mechanisms
408+
WIP
501409

502410
## Next Steps
503411

504412
- [Agent Protocol](/guide/advanced/agent-protocol) - Understand event handling in hooks
505413
- [Tool Management](/guide/basic/tools) - Learn about tool registration and execution
506-
- [Context Engineering](/guide/advanced/context-engineering) - Advanced context management
414+
- [Context Engineering](/guide/advanced/context-engineering) - Advanced context management

multimodal/websites/tarko/docs/zh/guide/advanced/agent-hooks.mdx

Lines changed: 29 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ Agent Hooks 是在 Agent 运行特定节点执行的回调方法。所有 Hook
1717
- **Termination Hooks**:自定义完成条件强制执行
1818
- **Request Preparation**:动态系统提示和工具修改
1919

20-
## 核心 Hook 类别
20+
## 核心 Hook
2121

22-
### LLM 通信 Hooks
22+
### LLM 交互 Hook
2323

2424
拦截和监控 LLM 请求和响应:
2525

@@ -30,32 +30,32 @@ class MonitoringAgent extends Agent {
3030
// 在每个 LLM 请求之前调用
3131
override async onLLMRequest(id: string, payload: LLMRequestHookPayload) {
3232
console.log(`[${id}] 发送请求到 ${payload.model}`);
33-
console.log('消息数量:', payload.messages.length);
33+
console.log('Messages count:', payload.messages.length);
3434

3535
// 记录 token 使用估算
3636
const tokenEstimate = this.estimateTokens(payload.messages);
37-
console.log('预估 tokens:', tokenEstimate);
37+
console.log('Estimated tokens:', tokenEstimate);
3838
}
3939

4040
// 在每个 LLM 响应之后调用
4141
override async onLLMResponse(id: string, payload: LLMResponseHookPayload) {
4242
const response = payload.response;
4343
console.log(`[${id}] 收到响应:`);
44-
console.log('使用情况:', response.usage);
45-
console.log('完成原因:', response.choices[0]?.finish_reason);
44+
console.log('Usage:', response.usage);
45+
console.log('Finish reason:', response.choices[0]?.finish_reason);
4646

4747
// 如果存在工具调用则记录
4848
const toolCalls = response.choices[0]?.message?.tool_calls;
4949
if (toolCalls?.length) {
50-
console.log('工具调用:', toolCalls.map(tc => tc.function.name));
50+
console.log('Tool calls:', toolCalls.map(tc => tc.function.name));
5151
}
5252
}
5353

5454
// 在流式响应期间调用
5555
override onLLMStreamingResponse(id: string, payload: LLMStreamingResponseHookPayload) {
56-
// 实时监控流式块
56+
// 实时监控 streaming chunks
5757
const chunks = payload.chunks;
58-
console.log(`[${id}] 收到 ${chunks.length} 个流式块`);
58+
console.log(`[${id}] 收到 ${chunks.length} 个 streaming chunks`);
5959
}
6060
}
6161
```
@@ -74,16 +74,16 @@ class ToolMonitoringAgent extends Agent {
7474
toolCall: { toolCallId: string; name: string },
7575
args: any
7676
) {
77-
console.log(`[${id}] 执行工具: ${toolCall.name}`);
78-
console.log('参数:', JSON.stringify(args, null, 2));
77+
console.log(`[${id}] Executing tool: ${toolCall.name}`);
78+
console.log('Arguments:', JSON.stringify(args, null, 2));
7979

8080
// 跟踪工具使用情况
8181
const currentCount = this.toolUsageStats.get(toolCall.name) || 0;
8282
this.toolUsageStats.set(toolCall.name, currentCount + 1);
8383

8484
// 验证参数或应用速率限制
8585
if (toolCall.name === 'expensive_api' && currentCount >= 5) {
86-
throw new Error('expensive_api 超出速率限制');
86+
throw new Error('Rate limit exceeded for expensive_api');
8787
}
8888

8989
// 返回可能修改的参数
@@ -96,14 +96,14 @@ class ToolMonitoringAgent extends Agent {
9696
toolCall: { toolCallId: string; name: string },
9797
result: any
9898
) {
99-
console.log(`[${id}] 工具 ${toolCall.name} 完成`);
100-
console.log('结果类型:', typeof result);
99+
console.log(`[${id}] Tool ${toolCall.name} completed`);
100+
console.log('Result type:', typeof result);
101101

102102
// 记录错误或成功结果
103103
if (result?.error) {
104-
console.error('工具执行失败:', result.error);
104+
console.error('Tool execution failed:', result.error);
105105
} else {
106-
console.log('工具执行成功');
106+
console.log('Tool execution successful');
107107
}
108108

109109
// 返回可能修改的结果
@@ -116,14 +116,14 @@ class ToolMonitoringAgent extends Agent {
116116
toolCall: { toolCallId: string; name: string },
117117
error: any
118118
) {
119-
console.error(`[${id}] 工具 ${toolCall.name} 失败:`, error);
119+
console.error(`[${id}] Tool ${toolCall.name} failed:`, error);
120120

121121
// 实现重试逻辑或错误转换
122122
if (error.message?.includes('timeout')) {
123-
return '工具执行超时。请稍后重试。';
123+
return 'Tool execution timed out. Please try again later.';
124124
}
125125

126-
return `错误: ${error.message || error}`;
126+
return `Error: ${error.message || error}`;
127127
}
128128

129129
// 完全覆盖工具调用处理
@@ -138,7 +138,7 @@ class ToolMonitoringAgent extends Agent {
138138
if (process.env.NODE_ENV === 'test') {
139139
return toolCalls.map(tc => ({
140140
toolCallId: tc.id,
141-
result: `${tc.function.name} 的模拟结果`,
141+
result: `Mocked result for ${tc.function.name}`,
142142
success: true
143143
}));
144144
}
@@ -148,7 +148,7 @@ class ToolMonitoringAgent extends Agent {
148148
}
149149
```
150150

151-
### 循环生命周期 Hooks
151+
### 循环生命周期 Hook
152152

153153
控制 Agent 循环迭代和终止:
154154

@@ -159,30 +159,30 @@ class LoopControlAgent extends Agent {
159159
// 在每个循环迭代开始时调用
160160
override async onEachAgentLoopStart(sessionId: string) {
161161
this.iterationStartTimes.set(sessionId, Date.now());
162-
console.log(`[${sessionId}] 开始迭代 ${this.getCurrentLoopIteration()}`);
162+
console.log(`[${sessionId}] Starting iteration ${this.getCurrentLoopIteration()}`);
163163

164164
// 注入额外上下文或执行设置
165165
const currentTime = new Date().toISOString();
166-
console.log(`当前时间: ${currentTime}`);
166+
console.log(`Current time: ${currentTime}`);
167167
}
168168

169169
// 在每个循环迭代结束时调用
170170
override async onEachAgentLoopEnd(context: EachAgentLoopEndContext) {
171171
const startTime = this.iterationStartTimes.get(context.sessionId);
172172
if (startTime) {
173173
const duration = Date.now() - startTime;
174-
console.log(`[${context.sessionId}] 迭代在 ${duration}ms 内完成`);
174+
console.log(`[${context.sessionId}] Iteration completed in ${duration}ms`);
175175
}
176176

177177
// 记录迭代结果
178-
console.log('此迭代中的事件:', context.events?.length || 0);
179-
console.log('进行的工具调用:', context.toolCallResults?.length || 0);
178+
console.log('Events in this iteration:', context.events?.length || 0);
179+
console.log('Tool calls made:', context.toolCallResults?.length || 0);
180180
}
181181

182182
// 当整个 Agent 循环结束时调用
183183
override async onAgentLoopEnd(id: string) {
184-
console.log(`[${id}] Agent 循环完成`);
185-
console.log('总迭代次数:', this.getCurrentLoopIteration());
184+
console.log(`[${id}] Agent loop completed`);
185+
console.log('Total iterations:', this.getCurrentLoopIteration());
186186

187187
// 清理迭代跟踪
188188
this.iterationStartTimes.delete(id);
@@ -399,105 +399,10 @@ class ResilientAgent extends Agent {
399399
9. [否则从步骤 2 重复]
400400
```
401401

402-
## 测试 Hooks
403-
404-
### 单元测试单个 Hooks
405-
406-
```typescript
407-
import { Agent, AgentOptions } from '@tarko/agent';
408-
409-
class TestableAgent extends Agent {
410-
public hookCallLog: string[] = [];
411-
412-
override async onLLMRequest(id: string, payload: any) {
413-
this.hookCallLog.push(`onLLMRequest:${id}`);
414-
}
415-
416-
override async onBeforeToolCall(id: string, toolCall: any, args: any) {
417-
this.hookCallLog.push(`onBeforeToolCall:${toolCall.name}`);
418-
return args;
419-
}
420-
}
421-
422-
describe('Agent Hooks', () => {
423-
let agent: TestableAgent;
424-
425-
beforeEach(() => {
426-
agent = new TestableAgent({});
427-
});
428-
429-
it('should call Hook in correct order', async () => {
430-
// 测试 hook 执行顺序
431-
await agent.onLLMRequest('test-session', { model: 'gpt-4', messages: [] });
432-
await agent.onBeforeToolCall('test-session', { name: 'test-tool' }, {});
433-
434-
expect(agent.hookCallLog).toEqual([
435-
'onLLMRequest:test-session',
436-
'onBeforeToolCall:test-tool'
437-
]);
438-
});
439-
});
440-
```
441-
442-
### 集成测试
443-
444-
```typescript
445-
describe('Hook Integration', () => {
446-
it('should prevent termination when required', async () => {
447-
const agent = new ValidatingAgent({
448-
tools: [/* 必需工具 */]
449-
});
450-
451-
// 模拟未调用所有必需工具的场景
452-
const result = await agent.onBeforeLoopTermination(
453-
'test-session',
454-
{ content: '最终答案' } as any
455-
);
456-
457-
expect(result.finished).toBe(false);
458-
expect(result.message).toContain('必须调用必需工具');
459-
});
460-
});
461-
```
462-
463-
## 最佳实践
464-
465-
### 1. Hook 设计
466-
- 保持 Hook 专注和轻量级
467-
- 重写时始终调用 `super.hookMethod()`
468-
- 优雅处理错误而不破坏 Agent 执行
469-
- 对异步操作使用 async/await
470-
471-
### 2. 状态管理
472-
- 在实例变量中存储 hook 特定状态
473-
-`onAgentLoopEnd` 中清理状态
474-
- 使用会话 ID 跟踪每个对话的状态
475-
476-
### 3. 性能
477-
- 避免在关键 Hook 中阻塞操作
478-
- 对昂贵的计算使用缓存
479-
- 为外部调用实现超时
480-
481-
### 4. 错误处理
482-
- 始终优雅处理 hook 错误
483-
- 在可能的情况下提供回退行为
484-
- 记录错误以便调试,但不暴露敏感数据
485-
486-
### 5. 测试
487-
- 单独单元测试 Hook
488-
- 测试 hook 组合和顺序
489-
- 模拟外部依赖
490-
- 测试错误场景
491402

492403
## 实际示例
493404

494-
查看我们的[示例](/examples/custom-hooks)部分以获取完整实现:
495-
496-
- 分析和指标收集
497-
- 用户权限强制执行
498-
- 速率限制和配额管理
499-
- 多步骤工作流验证
500-
- 错误恢复和重试机制
405+
WIP
501406

502407
## 下一步
503408

0 commit comments

Comments
 (0)