Skip to content

Commit 8343182

Browse files
authored
docs(agent-tars): agent hooks (#1277)
1 parent 50c6923 commit 8343182

File tree

4 files changed

+778
-0
lines changed

4 files changed

+778
-0
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[
2+
{
3+
"type": "file",
4+
"name": "core",
5+
"label": "Core"
6+
},
7+
{
8+
"type": "file",
9+
"name": "hooks",
10+
"label": "Hooks"
11+
},
12+
{
13+
"type": "file",
14+
"name": "snapshot",
15+
"label": "Snapshot"
16+
}
17+
]
Lines changed: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,372 @@
1+
# Agent Hooks
2+
3+
## Introduction
4+
5+
Agent Hooks provide a powerful way to extend and customize the behavior of your Agents throughout their execution lifecycle. The `BaseAgent` class exposes a comprehensive set of hooks that allow you to intercept, modify, and react to various events during Agent execution.
6+
7+
## Overview
8+
9+
| Hook | Description |
10+
|------|-------------|
11+
| [`initialize()`](#initialize) | Called during Agent initialization |
12+
| [`onDispose()`](#ondispose) | Called during Agent disposal |
13+
| [`onPrepareRequest()`](#onpreparerequest) | Before preparing LLM request |
14+
| [`onLLMRequest()`](#onllmrequest) | Before sending request to LLM |
15+
| [`onLLMResponse()`](#onllmresponse) | After receiving response from LLM |
16+
| [`onLLMStreamingResponse()`](#onllmstreamingresponse) | For streaming responses from LLM |
17+
| [`onProcessToolCalls()`](#onprocesstoolcalls) | Intercept tool call processing |
18+
| [`onBeforeToolCall()`](#onbeforetoolcall) | Before executing a tool |
19+
| [`onAfterToolCall()`](#onaftertoolcall) | After executing a tool |
20+
| [`onToolCallError()`](#ontooolcallerror) | When tool execution fails |
21+
| [`onEachAgentLoopStart()`](#oneachagentloopstart) | Start of each Agent loop iteration |
22+
| [`onEachAgentLoopEnd()`](#oneachagentloopend) | End of each Agent loop iteration |
23+
| [`onBeforeLoopTermination()`](#onbeforelooptermination) | Before Agent loop terminates |
24+
| [`onAgentLoopEnd()`](#onagentloopend) | When entire Agent loop completes |
25+
26+
## Hooks API
27+
28+
### `initialize()`
29+
30+
Called during Agent initialization to perform setup operations.
31+
32+
```ts
33+
class CustomAgent extends BaseAgent {
34+
async initialize(): Promise<void> {
35+
// Perform time-consuming setup operations
36+
await this.connectToDatabase();
37+
await this.loadConfiguration();
38+
console.log('Agent initialized successfully');
39+
}
40+
}
41+
```
42+
43+
### `onDispose()`
44+
45+
Called during Agent disposal to clean up resources.
46+
47+
```ts
48+
class CustomAgent extends BaseAgent {
49+
protected async onDispose(): Promise<void> {
50+
// Clean up resources
51+
await this.closeConnections();
52+
this.clearTimers();
53+
console.log('Agent disposed successfully');
54+
}
55+
}
56+
```
57+
58+
### `onPrepareRequest()`
59+
60+
Called before preparing the LLM request, allowing dynamic modification of system prompt and tools.
61+
62+
```ts
63+
class CustomAgent extends BaseAgent {
64+
onPrepareRequest(context: PrepareRequestContext): PrepareRequestResult {
65+
let { systemPrompt, tools } = context;
66+
67+
// Modify system prompt based on context
68+
if (context.iteration > 3) {
69+
systemPrompt += '\n\nNote: You are in iteration ' + context.iteration +
70+
'. Please focus on providing a concise final answer.';
71+
}
72+
73+
// Filter tools based on current state
74+
const filteredTools = tools.filter(tool => {
75+
// Disable expensive tools in later iterations
76+
if (context.iteration > 5 && tool.name.includes('search')) {
77+
return false;
78+
}
79+
return true;
80+
});
81+
82+
return {
83+
systemPrompt,
84+
tools: filteredTools,
85+
};
86+
}
87+
}
88+
```
89+
90+
### `onLLMRequest()`
91+
92+
Triggered before sending a request to the LLM, allowing you to inspect or log the request payload.
93+
94+
```ts
95+
class CustomAgent extends BaseAgent {
96+
async onLLMRequest(id: string, payload: LLMRequestHookPayload): Promise<void> {
97+
console.log(`Sending request to LLM for session ${id}`);
98+
console.log(`Model: ${payload.model}`);
99+
console.log(`Messages: ${payload.messages.length}`);
100+
101+
// Log token usage for monitoring
102+
this.logTokenUsage(payload);
103+
}
104+
}
105+
```
106+
107+
### `onLLMResponse()`
108+
109+
Triggered after receiving a response from the LLM, allowing you to process the response.
110+
111+
```ts
112+
class CustomAgent extends BaseAgent {
113+
async onLLMResponse(id: string, payload: LLMResponseHookPayload): Promise<void> {
114+
console.log(`Received response for session ${id}`);
115+
116+
// Track response metrics
117+
this.trackResponseTime(payload.elapsedMs);
118+
this.trackTokenUsage(payload.usage);
119+
120+
// Custom response processing
121+
if (payload.response.choices[0]?.finish_reason === 'length') {
122+
console.warn('Response truncated due to length limit');
123+
}
124+
}
125+
}
126+
```
127+
128+
### `onLLMStreamingResponse()`
129+
130+
Triggered for streaming responses from the LLM.
131+
132+
```ts
133+
class CustomAgent extends BaseAgent {
134+
onLLMStreamingResponse(id: string, payload: LLMStreamingResponseHookPayload): void {
135+
// Process streaming chunks
136+
console.log(`Streaming chunk for session ${id}: ${payload.chunk}`);
137+
138+
// Update UI or send real-time updates
139+
this.updateStreamingUI(payload.chunk);
140+
}
141+
}
142+
```
143+
144+
### `onProcessToolCalls()`
145+
146+
Intercepts tool call processing, essential for testing and mocking.
147+
148+
```ts
149+
class TestAgent extends BaseAgent {
150+
onProcessToolCalls(
151+
id: string,
152+
toolCalls: ChatCompletionMessageToolCall[]
153+
): ToolCallResult[] | undefined {
154+
// Mock tool calls for testing
155+
if (this.isTestMode) {
156+
return toolCalls.map(call => ({
157+
toolCallId: call.id,
158+
content: this.getMockResult(call.function.name),
159+
}));
160+
}
161+
162+
// Return undefined for normal execution
163+
return undefined;
164+
}
165+
}
166+
```
167+
168+
### `onBeforeToolCall()`
169+
170+
Called before executing a tool, allowing you to modify arguments or add validation.
171+
172+
```ts
173+
class CustomAgent extends BaseAgent {
174+
async onBeforeToolCall(
175+
id: string,
176+
toolCall: { toolCallId: string; name: string },
177+
args: any
178+
): Promise<any> {
179+
console.log(`Executing tool: ${toolCall.name}`);
180+
181+
// Add validation
182+
if (toolCall.name === 'fileOperation' && !this.hasFilePermission()) {
183+
throw new Error('Insufficient permissions for file operations');
184+
}
185+
186+
// Modify arguments
187+
if (toolCall.name === 'searchWeb') {
188+
args.maxResults = Math.min(args.maxResults || 10, 5);
189+
}
190+
191+
return args;
192+
}
193+
}
194+
```
195+
196+
### `onAfterToolCall()`
197+
198+
Called after executing a tool, allowing you to modify results or add post-processing.
199+
200+
```ts
201+
class CustomAgent extends BaseAgent {
202+
async onAfterToolCall(
203+
id: string,
204+
toolCall: { toolCallId: string; name: string },
205+
result: any
206+
): Promise<any> {
207+
console.log(`Tool ${toolCall.name} completed`);
208+
209+
// Post-process results
210+
if (toolCall.name === 'imageAnalysis') {
211+
result.confidence = this.calculateConfidence(result);
212+
}
213+
214+
// Log tool usage
215+
this.logToolUsage(toolCall.name, result);
216+
217+
return result;
218+
}
219+
}
220+
```
221+
222+
### `onToolCallError()`
223+
224+
Called when a tool execution results in an error, allowing you to handle or transform errors.
225+
226+
```ts
227+
class CustomAgent extends BaseAgent {
228+
async onToolCallError(
229+
id: string,
230+
toolCall: { toolCallId: string; name: string },
231+
error: any
232+
): Promise<any> {
233+
console.error(`Tool ${toolCall.name} failed:`, error);
234+
235+
// Provide fallback responses
236+
if (toolCall.name === 'weatherAPI') {
237+
return 'Weather information is currently unavailable. Please try again later.';
238+
}
239+
240+
// Transform error messages
241+
if (error.code === 'RATE_LIMIT') {
242+
return 'Service is temporarily busy. Please wait a moment and try again.';
243+
}
244+
245+
return `Error: ${error.message || error}`;
246+
}
247+
}
248+
```
249+
250+
### `onEachAgentLoopStart()`
251+
252+
Called at the beginning of each Agent loop iteration.
253+
254+
```ts
255+
class CustomAgent extends BaseAgent {
256+
async onEachAgentLoopStart(sessionId: string): Promise<void> {
257+
console.log(`Starting loop iteration ${this.getCurrentLoopIteration()} for session ${sessionId}`);
258+
259+
// Inject dynamic context
260+
this.updateContextForIteration();
261+
262+
// Check resource limits
263+
if (this.getCurrentLoopIteration() > this.maxIterations * 0.8) {
264+
console.warn('Approaching maximum iteration limit');
265+
}
266+
}
267+
}
268+
```
269+
270+
### `onEachAgentLoopEnd()`
271+
272+
Called at the end of each Agent loop iteration.
273+
274+
```ts
275+
class CustomAgent extends BaseAgent {
276+
async onEachAgentLoopEnd(context: EachAgentLoopEndContext): Promise<void> {
277+
console.log(`Completed iteration ${context.iteration} for session ${context.sessionId}`);
278+
279+
// Log iteration metrics
280+
this.logIterationMetrics(context);
281+
282+
// Check if we should continue
283+
if (this.shouldTerminateEarly(context)) {
284+
this.requestLoopTermination();
285+
}
286+
}
287+
}
288+
```
289+
290+
### `onBeforeLoopTermination()`
291+
292+
Called before the Agent loop terminates, allowing you to control termination conditions.
293+
294+
```ts
295+
class CustomAgent extends BaseAgent {
296+
onBeforeLoopTermination(
297+
id: string,
298+
finalEvent: AgentEventStream.AssistantMessageEvent
299+
): LoopTerminationCheckResult {
300+
// Ensure specific tools were called
301+
const requiredTools = ['validateResult', 'saveToDatabase'];
302+
const calledTools = this.getCalledToolsInSession(id);
303+
304+
const allRequiredToolsCalled = requiredTools.every(tool =>
305+
calledTools.includes(tool)
306+
);
307+
308+
if (!allRequiredToolsCalled) {
309+
console.log('Required tools not called, continuing loop');
310+
return { finished: false };
311+
}
312+
313+
// Check response quality
314+
if (finalEvent.content.length < 50) {
315+
console.log('Response too short, requesting more detail');
316+
return { finished: false };
317+
}
318+
319+
return { finished: true };
320+
}
321+
}
322+
```
323+
324+
### `onAgentLoopEnd()`
325+
326+
Called when the entire Agent loop completes.
327+
328+
```ts
329+
class CustomAgent extends BaseAgent {
330+
async onAgentLoopEnd(id: string): Promise<void> {
331+
console.log(`Agent loop completed for session ${id}`);
332+
333+
// Cleanup session resources
334+
this.cleanupSession(id);
335+
336+
// Send completion notifications
337+
await this.notifyCompletion(id);
338+
}
339+
}
340+
```
341+
342+
## Hook Execution Order
343+
344+
```mermaid
345+
flowchart TD
346+
A[Agent Start] --> B[initialize]
347+
B --> C[Agent Loop Start]
348+
C --> D[onEachAgentLoopStart]
349+
D --> E[onPrepareRequest]
350+
E --> F[onLLMRequest]
351+
F --> G[LLM Call]
352+
G --> H[onLLMResponse / onLLMStreamingResponse]
353+
H --> I{Tools Called?}
354+
I -->|Yes| J[onProcessToolCalls]
355+
J --> K[onBeforeToolCall]
356+
K --> L[Tool Execution]
357+
L --> M{Tool Success?}
358+
M -->|Yes| N[onAfterToolCall]
359+
M -->|No| O[onToolCallError]
360+
N --> P[onEachAgentLoopEnd]
361+
O --> P
362+
I -->|No| P
363+
P --> Q{Continue Loop?}
364+
Q -->|Yes| D
365+
Q -->|No| R[onBeforeLoopTermination]
366+
R --> S{Should Terminate?}
367+
S -->|Yes| T[onAgentLoopEnd]
368+
S -->|No| D
369+
T --> U[Agent End]
370+
U --> V[onDispose]
371+
V --> W[Complete]
372+
```

0 commit comments

Comments
 (0)