Merge pull request #1375 from trheyi/main
Enhance JSAPI documentation for hooks and context management
This commit is contained in:
commit
b107ccd0b8
1 changed files with 325 additions and 120 deletions
|
|
@ -915,16 +915,19 @@ function Create(ctx, messages) {
|
||||||
|
|
||||||
#### `ctx.Release()`
|
#### `ctx.Release()`
|
||||||
|
|
||||||
Manually releases Context resources. This is optional as cleanup happens automatically via garbage collection.
|
Manually releases Context resources.
|
||||||
|
|
||||||
**Example:**
|
> **Note:** In Hook functions (`Create`, `Next`), you do **NOT** need to call `Release()` - the system handles cleanup automatically. Only call `Release()` when you create a new Context manually (e.g., via `new Context()`).
|
||||||
|
|
||||||
|
**Example (only for manually created Context):**
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
// Only needed when creating Context manually, NOT in hooks
|
||||||
|
const ctx = new Context(options);
|
||||||
try {
|
try {
|
||||||
// Use context
|
|
||||||
ctx.Send("Processing...");
|
ctx.Send("Processing...");
|
||||||
} finally {
|
} finally {
|
||||||
ctx.Release(); // Manual cleanup
|
ctx.Release(); // Required for manually created Context
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -1197,6 +1200,12 @@ Marks the entire trace as complete.
|
||||||
ctx.trace.MarkComplete();
|
ctx.trace.MarkComplete();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### `ctx.trace.Release()`
|
||||||
|
|
||||||
|
Releases trace resources.
|
||||||
|
|
||||||
|
> **Note:** In Hook functions, you do **NOT** need to call `Release()` - the system handles cleanup automatically. Only call this when you create a Trace manually (e.g., via `new Trace()`).
|
||||||
|
|
||||||
### Trace Space Operations
|
### Trace Space Operations
|
||||||
|
|
||||||
Trace spaces are visual containers for organizing trace nodes in the frontend UI. They help group related operations together for better presentation to users.
|
Trace spaces are visual containers for organizing trace nodes in the frontend UI. They help group related operations together for better presentation to users.
|
||||||
|
|
@ -1554,22 +1563,78 @@ console.log(sample.name, sample.input); // Sample name and input data
|
||||||
|
|
||||||
## Hooks
|
## Hooks
|
||||||
|
|
||||||
The Agent system supports two hooks that can be defined in the assistant's `index.ts` file:
|
The Agent system supports two hooks that can be defined in the assistant's `index.ts` file: `Create` and `Next`.
|
||||||
|
|
||||||
|
### Agent Execution Lifecycle
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[User Input] --> B[Load History]
|
||||||
|
B --> C{Create Hook?}
|
||||||
|
C -->|Yes| D[Execute Create Hook]
|
||||||
|
C -->|No| E{Has Prompts/MCP?}
|
||||||
|
D --> E
|
||||||
|
E -->|Yes| F[Build LLM Request]
|
||||||
|
E -->|No| K
|
||||||
|
F --> G[LLM Stream Call]
|
||||||
|
G --> H{Tool Calls?}
|
||||||
|
H -->|Yes| I[Execute Tools]
|
||||||
|
I --> J{Tool Errors?}
|
||||||
|
J -->|Yes, Retry| G
|
||||||
|
J -->|No| K
|
||||||
|
H -->|No| K
|
||||||
|
K{Next Hook?}
|
||||||
|
K -->|Yes| L[Execute Next Hook]
|
||||||
|
K -->|No| M[Return Response]
|
||||||
|
L --> N{Delegate?}
|
||||||
|
N -->|Yes| O[Call Target Agent]
|
||||||
|
O --> M
|
||||||
|
N -->|No| M
|
||||||
|
M --> P[End]
|
||||||
|
|
||||||
|
style D fill:#e1f5fe
|
||||||
|
style L fill:#e1f5fe
|
||||||
|
style G fill:#fff3e0
|
||||||
|
style I fill:#f3e5f5
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** LLM call is optional. If the assistant has no prompts and no MCP servers configured, the LLM call is skipped. Hooks can be used independently to implement custom logic without LLM involvement.
|
||||||
|
|
||||||
### Create Hook
|
### Create Hook
|
||||||
|
|
||||||
Called before the LLM call. Use this to preprocess messages, add context, or configure the LLM request.
|
Called at the beginning of agent execution, before any LLM call. Use this to preprocess messages, add context, configure the request, or implement custom logic.
|
||||||
|
|
||||||
**Signature:**
|
**Signature:**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
function Create(ctx: Context, messages: Message[]): HookCreateResponse | null;
|
function Create(
|
||||||
|
ctx: Context,
|
||||||
|
messages: Message[],
|
||||||
|
options?: Record<string, any>
|
||||||
|
): HookCreateResponse | null;
|
||||||
```
|
```
|
||||||
|
|
||||||
**Parameters:**
|
**Parameters:**
|
||||||
|
|
||||||
- `ctx`: Context object
|
- `ctx`: Context object
|
||||||
- `messages`: Array of input messages (including chat history if enabled)
|
- `messages`: Array of input messages (including chat history if enabled)
|
||||||
|
- `options`: Optional call-level options (see below)
|
||||||
|
|
||||||
|
**Options Structure:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface Options {
|
||||||
|
skip?: {
|
||||||
|
history?: boolean; // Skip loading/saving chat history
|
||||||
|
trace?: boolean; // Skip trace recording
|
||||||
|
output?: boolean; // Skip output to client (for internal A2A calls that only need response data)
|
||||||
|
};
|
||||||
|
connector?: string; // Override LLM connector ID
|
||||||
|
disable_global_prompts?: boolean; // Disable global prompts for this request
|
||||||
|
search?: boolean; // Enable/disable search mode
|
||||||
|
mode?: string; // Agent mode (default: "chat")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
**Return Value (`HookCreateResponse`):**
|
**Return Value (`HookCreateResponse`):**
|
||||||
|
|
||||||
|
|
@ -1590,12 +1655,40 @@ interface HookCreateResponse {
|
||||||
mcp_servers?: MCPServerConfig[];
|
mcp_servers?: MCPServerConfig[];
|
||||||
|
|
||||||
// Prompt configuration
|
// Prompt configuration
|
||||||
prompts?: string; // Prompt preset key to use
|
prompt_preset?: string; // Select prompt preset (e.g., "chat.friendly", "task.analysis")
|
||||||
disable_global_prompts?: boolean; // Disable global prompts
|
disable_global_prompts?: boolean; // Temporarily disable global prompts for this request
|
||||||
|
|
||||||
// Tool configuration
|
// Context adjustments - allow hook to modify context fields
|
||||||
tools?: ToolConfig[]; // Override tools for this request
|
connector?: string; // Override connector (call-level)
|
||||||
disable_tools?: boolean; // Disable all tools
|
locale?: string; // Override locale (session-level)
|
||||||
|
theme?: string; // Override theme (session-level)
|
||||||
|
route?: string; // Override route (session-level)
|
||||||
|
metadata?: Record<string, any>; // Override or merge metadata (session-level)
|
||||||
|
|
||||||
|
// Uses configuration - allow hook to override wrapper configurations
|
||||||
|
uses?: UsesConfig; // Override wrapper configurations for vision, audio, search, and fetch
|
||||||
|
force_uses?: boolean; // Force using Uses tools regardless of model capabilities
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audio output configuration
|
||||||
|
interface AudioConfig {
|
||||||
|
voice: string; // Voice to use (e.g., "alloy", "echo", "fable", "onyx", "nova", "shimmer")
|
||||||
|
format: string; // Audio format (e.g., "wav", "mp3", "flac", "opus", "pcm16")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MCP server configuration
|
||||||
|
interface MCPServerConfig {
|
||||||
|
server_id: string; // MCP server ID (required)
|
||||||
|
tools?: string[]; // Tool name filter (empty = all tools)
|
||||||
|
resources?: string[]; // Resource URI filter (empty = all resources)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uses wrapper configuration
|
||||||
|
interface UsesConfig {
|
||||||
|
vision?: string; // Vision processing tool. Format: "agent" or "mcp:server_id"
|
||||||
|
audio?: string; // Audio processing tool. Format: "agent" or "mcp:server_id"
|
||||||
|
search?: string; // Search tool. Format: "agent" or "mcp:server_id"
|
||||||
|
fetch?: string; // Fetch/retrieval tool. Format: "agent" or "mcp:server_id"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -1623,18 +1716,23 @@ function Create(ctx, messages) {
|
||||||
|
|
||||||
### Next Hook
|
### Next Hook
|
||||||
|
|
||||||
Called after the LLM response (and tool calls if any). Use this to post-process the response, send custom messages, or delegate to another agent.
|
Called after the LLM response and tool calls (if any), or directly after Create Hook if no LLM call is configured. Use this to post-process the response, send custom messages, delegate to another agent, or implement custom response logic.
|
||||||
|
|
||||||
**Signature:**
|
**Signature:**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
function Next(ctx: Context, payload: NextHookPayload): NextHookResponse | null;
|
function Next(
|
||||||
|
ctx: Context,
|
||||||
|
payload: NextHookPayload,
|
||||||
|
options?: Record<string, any>
|
||||||
|
): NextHookResponse | null;
|
||||||
```
|
```
|
||||||
|
|
||||||
**Parameters:**
|
**Parameters:**
|
||||||
|
|
||||||
- `ctx`: Context object
|
- `ctx`: Context object
|
||||||
- `payload`: Object containing:
|
- `payload`: Object containing:
|
||||||
|
- `options`: Optional call-level options (same structure as Create Hook options)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
interface NextHookPayload {
|
interface NextHookPayload {
|
||||||
|
|
@ -1665,174 +1763,277 @@ interface ToolCallResponse {
|
||||||
```typescript
|
```typescript
|
||||||
interface NextHookResponse {
|
interface NextHookResponse {
|
||||||
// Delegate to another agent (recursive call)
|
// Delegate to another agent (recursive call)
|
||||||
|
// If provided, the current agent will call the target agent
|
||||||
delegate?: {
|
delegate?: {
|
||||||
agent_id: string; // Target agent ID
|
agent_id: string; // Required: target agent ID
|
||||||
messages: Message[]; // Messages to send
|
messages: Message[]; // Messages to send to target agent
|
||||||
|
options?: Record<string, any>; // Optional: call-level options for delegation
|
||||||
};
|
};
|
||||||
|
|
||||||
// Custom response data (returned to user)
|
// Custom response data
|
||||||
|
// Will be placed in Response.next field and returned to user
|
||||||
|
// If both delegate and data are null/undefined, standard Response is returned
|
||||||
data?: any;
|
data?: any;
|
||||||
|
|
||||||
// Metadata for debugging
|
// Metadata for debugging and logging
|
||||||
metadata?: Record<string, any>;
|
metadata?: Record<string, any>;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Agent Response Structure:**
|
||||||
|
|
||||||
|
The agent's `Stream()` method returns a `Response` object:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface Response {
|
||||||
|
request_id: string; // Request ID
|
||||||
|
context_id: string; // Context ID
|
||||||
|
trace_id: string; // Trace ID
|
||||||
|
chat_id: string; // Chat ID
|
||||||
|
assistant_id: string; // Assistant ID
|
||||||
|
create?: HookCreateResponse; // Create hook response
|
||||||
|
next?: any; // See below for what this contains
|
||||||
|
completion?: CompletionResponse; // LLM completion response
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response.next field logic:**
|
||||||
|
|
||||||
|
- If `NextHookResponse.data` is provided → `Response.next` = custom data
|
||||||
|
- If `NextHookResponse.data` is null/undefined → `Response.next` = entire `NextHookResponse` object
|
||||||
|
- If no Next hook defined → `Response.next` = null
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
/**
|
||||||
|
* Next Hook - Process LLM response
|
||||||
|
* @param {Context} ctx - Agent context
|
||||||
|
* @param {NextHookPayload} payload - Contains messages, completion, tools, error
|
||||||
|
* @returns {NextHookResponse | null} - Return null for standard response
|
||||||
|
*/
|
||||||
function Next(ctx, payload) {
|
function Next(ctx, payload) {
|
||||||
const { messages, completion, tools, error } = payload;
|
const { messages, completion, tools, error } = payload;
|
||||||
|
|
||||||
|
// Handle errors gracefully
|
||||||
if (error) {
|
if (error) {
|
||||||
ctx.Send({
|
|
||||||
type: "error",
|
|
||||||
props: { message: error },
|
|
||||||
});
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process tool results
|
|
||||||
if (tools && tools.length > 0) {
|
|
||||||
const results = tools.map((t) => t.result);
|
|
||||||
ctx.Send(`Tool results: ${JSON.stringify(results)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return custom data
|
|
||||||
return {
|
return {
|
||||||
data: {
|
data: {
|
||||||
response: completion?.content,
|
status: "error",
|
||||||
|
message: error,
|
||||||
|
recovery: "Please try again",
|
||||||
|
},
|
||||||
|
metadata: { error_handled: true },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process tool results if any
|
||||||
|
if (tools && tools.length > 0) {
|
||||||
|
const successful = tools.filter((t) => !t.error);
|
||||||
|
const failed = tools.filter((t) => t.error);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
status: "tools_processed",
|
||||||
|
total: tools.length,
|
||||||
|
successful: successful.length,
|
||||||
|
failed: failed.length,
|
||||||
|
results: successful.map((t) => t.result),
|
||||||
|
},
|
||||||
|
metadata: { has_failures: failed.length > 0 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return custom data based on completion
|
||||||
|
if (completion && completion.content) {
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
status: "success",
|
||||||
|
response: completion.content,
|
||||||
processed: true,
|
processed: true,
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: { source: "next_hook" },
|
||||||
tool_count: tools?.length || 0,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return null to use standard response
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Hook Execution Flow
|
### Hook Execution Flow
|
||||||
|
|
||||||
```
|
See the [Agent Execution Lifecycle](#agent-execution-lifecycle) diagram above for a visual representation.
|
||||||
User Input
|
|
||||||
↓
|
|
||||||
[Create Hook] → Preprocess messages, configure LLM
|
|
||||||
↓
|
|
||||||
[LLM Call] → Get completion from language model
|
|
||||||
↓
|
|
||||||
[Tool Calls] → Execute any tool calls (if requested by LLM)
|
|
||||||
↓
|
|
||||||
[Next Hook] → Post-process response, send messages
|
|
||||||
↓
|
|
||||||
Response to User
|
|
||||||
```
|
|
||||||
|
|
||||||
**Notes:**
|
**Key Points:**
|
||||||
|
|
||||||
- Hooks are optional - if not defined, the agent uses default behavior
|
- **Hooks are optional** - if not defined, the agent uses default behavior
|
||||||
- Return `null` or `undefined` from hooks to use default behavior
|
- **LLM call is optional** - only executed if the assistant has prompts or MCP servers configured
|
||||||
- Hooks can send messages directly via `ctx.Send()`, `ctx.SendStream()`, etc.
|
- **Return `null` or `undefined`** from hooks to use default behavior
|
||||||
|
- **Hooks can send messages directly** via `ctx.Send()`, `ctx.SendStream()`, etc.
|
||||||
|
- **Create Hook** runs before LLM call (if any), can modify messages and configure the request
|
||||||
|
- **Next Hook** runs after LLM call and tool execution (if any), can post-process or delegate
|
||||||
- Use `ctx.space` to pass data between Create and Next hooks
|
- Use `ctx.space` to pass data between Create and Next hooks
|
||||||
|
|
||||||
## Complete Example
|
## Complete Example
|
||||||
|
|
||||||
Here's a comprehensive example using various Context API features:
|
Here's a comprehensive example demonstrating Create and Next hooks with various Context API features:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
/**
|
/**
|
||||||
* Create Hook - Initialize and prepare for LLM call
|
* Create Hook - Preprocess messages and configure the request
|
||||||
* @param {Context} ctx - Agent context
|
*
|
||||||
* @param {Array} messages - Input messages
|
* @param {Context} ctx - Agent context object
|
||||||
|
* @param {Message[]} messages - Input messages (including history if enabled)
|
||||||
|
* @returns {HookCreateResponse | null} - Configuration for LLM call, or null for defaults
|
||||||
*/
|
*/
|
||||||
function Create(ctx, messages) {
|
function Create(ctx, messages) {
|
||||||
// Store original query in space for later use
|
// Extract user query from the last message
|
||||||
ctx.space.Set("original_query", messages[0]?.content || "");
|
const user_query = messages[messages.length - 1]?.content || "";
|
||||||
|
|
||||||
// Add trace node
|
// Store data in space for use in Next hook
|
||||||
ctx.trace.Add(
|
ctx.space.Set("original_query", user_query);
|
||||||
{ messages },
|
ctx.space.Set("request_time", Date.now());
|
||||||
|
|
||||||
|
// Add trace node to show processing in UI
|
||||||
|
const create_node = ctx.trace.Add(
|
||||||
|
{ query: user_query },
|
||||||
{
|
{
|
||||||
label: "Create Hook",
|
label: "Create Hook",
|
||||||
type: "hook",
|
type: "preprocessing",
|
||||||
icon: "play",
|
icon: "play",
|
||||||
description: "Preparing messages for LLM",
|
description: "Analyzing user request",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return { messages };
|
// Check if user needs search functionality
|
||||||
|
const needs_search =
|
||||||
|
user_query.toLowerCase().includes("search") ||
|
||||||
|
user_query.toLowerCase().includes("find");
|
||||||
|
|
||||||
|
if (needs_search) {
|
||||||
|
create_node.Info("Search mode enabled");
|
||||||
|
|
||||||
|
// Configure MCP servers for search
|
||||||
|
return {
|
||||||
|
messages: messages,
|
||||||
|
mcp_servers: [{ server_id: "search_engine" }],
|
||||||
|
prompt_preset: "search.assistant",
|
||||||
|
metadata: { mode: "search" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
create_node.Complete({ mode: "standard" });
|
||||||
|
|
||||||
|
// Return modified messages or configuration
|
||||||
|
return {
|
||||||
|
messages: messages,
|
||||||
|
temperature: 0.7,
|
||||||
|
max_tokens: 2000,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Next Hook - Process LLM response and enhance with tools
|
* Next Hook - Process LLM response and optionally customize output
|
||||||
* @param {Context} ctx - Agent context
|
*
|
||||||
* @param {Object} payload - Hook payload
|
* @param {Context} ctx - Agent context object
|
||||||
* @param {Array} payload.messages - Messages sent to the assistant
|
* @param {NextHookPayload} payload - Contains messages, completion, tools, error
|
||||||
* @param {Object} payload.completion - Completion response from LLM
|
* @returns {NextHookResponse | null} - Custom response, delegation, or null for standard
|
||||||
* @param {Array} payload.tools - Tool call results
|
|
||||||
* @param {string} payload.error - Error message if failed
|
|
||||||
*/
|
*/
|
||||||
function Next(ctx, payload) {
|
function Next(ctx, payload) {
|
||||||
try {
|
|
||||||
const { messages, completion, tools, error } = payload;
|
const { messages, completion, tools, error } = payload;
|
||||||
|
|
||||||
// Retrieve data from Create hook
|
// Retrieve data from Create hook via space
|
||||||
const original_query = ctx.space.Get("original_query");
|
const original_query = ctx.space.Get("original_query");
|
||||||
|
const request_time = ctx.space.Get("request_time");
|
||||||
|
const duration = Date.now() - request_time;
|
||||||
|
|
||||||
// Create trace node for custom processing
|
// Create trace node for Next hook processing
|
||||||
const process_node = ctx.trace.Add(
|
const next_node = ctx.trace.Add(
|
||||||
{ completion, tools },
|
{ completion_length: completion?.content?.length || 0 },
|
||||||
{
|
{
|
||||||
label: "Custom Processing",
|
label: "Next Hook",
|
||||||
type: "custom",
|
type: "postprocessing",
|
||||||
icon: "settings",
|
icon: "check",
|
||||||
description: "Enhancing response with external data",
|
description: "Processing LLM response",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.trace.Info("Starting custom processing", {
|
// Handle errors
|
||||||
original_query: original_query,
|
if (error) {
|
||||||
tool_count: tools?.length || 0,
|
next_node.Fail(error);
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
status: "error",
|
||||||
|
message: "An error occurred while processing your request",
|
||||||
|
error: error,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process tool call results
|
||||||
|
if (tools && tools.length > 0) {
|
||||||
|
next_node.Info(`Processing ${tools.length} tool results`);
|
||||||
|
|
||||||
|
const successful = tools.filter((t) => !t.error);
|
||||||
|
const results = successful.map((t) => ({
|
||||||
|
tool: t.tool,
|
||||||
|
server: t.server,
|
||||||
|
result: t.result,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Send streaming message with results
|
||||||
|
const msg_id = ctx.SendStream("## Tool Results\n\n");
|
||||||
|
results.forEach((r, i) => {
|
||||||
|
ctx.Append(msg_id, `**${i + 1}. ${r.tool}**\n`);
|
||||||
|
ctx.Append(msg_id, `${JSON.stringify(r.result, null, 2)}\n\n`);
|
||||||
});
|
});
|
||||||
|
ctx.End(msg_id);
|
||||||
|
|
||||||
// Start streaming output
|
next_node.SetMetadata("tools_processed", tools.length);
|
||||||
const msg_id = ctx.SendStream("# Search Results\n\n");
|
next_node.Complete({ status: "tools_processed" });
|
||||||
|
|
||||||
// Call MCP tool for additional data
|
|
||||||
const search_results = ctx.mcp.CallTool("search_engine", "search", {
|
|
||||||
query: "latest AI news",
|
|
||||||
limit: 5,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Stream results as they come
|
|
||||||
ctx.Append(msg_id, `Found ${search_results.length} articles:\n\n`);
|
|
||||||
|
|
||||||
search_results.forEach((result, i) => {
|
|
||||||
ctx.Append(msg_id, `${i + 1}. **${result.title}**\n`);
|
|
||||||
ctx.Append(msg_id, ` ${result.summary}\n\n`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Finalize the streaming message
|
|
||||||
ctx.End(msg_id, "---\n*Search complete*");
|
|
||||||
|
|
||||||
// Update trace
|
|
||||||
process_node.SetMetadata("search_results_count", search_results.length);
|
|
||||||
process_node.Complete({ status: "success" });
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: { sources: search_results },
|
data: {
|
||||||
metadata: { processed: true },
|
status: "success",
|
||||||
|
tool_results: results,
|
||||||
|
duration_ms: duration,
|
||||||
|
},
|
||||||
|
metadata: { processed_by: "next_hook" },
|
||||||
};
|
};
|
||||||
} catch (error) {
|
|
||||||
ctx.trace.Error("Processing failed", { error: error.message });
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if delegation is needed based on completion content
|
||||||
|
if (completion?.content?.toLowerCase().includes("delegate to specialist")) {
|
||||||
|
next_node.Info("Delegating to specialist agent");
|
||||||
|
|
||||||
|
return {
|
||||||
|
delegate: {
|
||||||
|
agent_id: "specialist.agent",
|
||||||
|
messages: [
|
||||||
|
{ role: "system", content: "Handle this specialized request" },
|
||||||
|
{ role: "user", content: original_query },
|
||||||
|
],
|
||||||
|
options: { priority: "high" },
|
||||||
|
},
|
||||||
|
metadata: { reason: "specialist_needed" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standard processing - add metadata and return
|
||||||
|
next_node.SetMetadata("duration_ms", duration);
|
||||||
|
next_node.Complete({ status: "success" });
|
||||||
|
|
||||||
|
// Return null to use standard LLM response
|
||||||
|
// Or return custom data to override
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Best Practices
|
## Best Practices
|
||||||
|
|
||||||
1. **Error Handling**: Always wrap Context operations in try-catch blocks
|
1. **Error Handling**: Always wrap Context operations in try-catch blocks
|
||||||
2. **Resource Cleanup**: Use try-finally pattern for manual cleanup if needed
|
2. **Resource Cleanup**: Only call `ctx.Release()` for manually created Context, not in hooks
|
||||||
3. **Trace Organization**: Create meaningful trace nodes with descriptive labels
|
3. **Trace Organization**: Create meaningful trace nodes with descriptive labels
|
||||||
4. **Logging Levels**: Use appropriate log levels (Debug for development, Info for progress, Error for failures)
|
4. **Logging Levels**: Use appropriate log levels (Debug for development, Info for progress, Error for failures)
|
||||||
5. **Message IDs**: Let the system auto-generate message IDs unless you need specific tracking
|
5. **Message IDs**: Let the system auto-generate message IDs unless you need specific tracking
|
||||||
|
|
@ -1859,7 +2060,7 @@ try {
|
||||||
For TypeScript projects, the Context types are automatically inferred. You can also import explicit types:
|
For TypeScript projects, the Context types are automatically inferred. You can also import explicit types:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Context, Message, TraceNodeOption } from "@yaoapps/types";
|
import { Context, Message, TraceNodeOption } from "@yao/runtime";
|
||||||
|
|
||||||
interface NextPayload {
|
interface NextPayload {
|
||||||
messages: Message[];
|
messages: Message[];
|
||||||
|
|
@ -1868,7 +2069,11 @@ interface NextPayload {
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Next(ctx: Context, payload: NextPayload): any {
|
function Next(
|
||||||
|
ctx: Context,
|
||||||
|
payload: NextHookPayload,
|
||||||
|
options?: Record<string, any>
|
||||||
|
): NextHookResponse | null {
|
||||||
// Your code with full type checking
|
// Your code with full type checking
|
||||||
const { messages, completion, tools, error } = payload;
|
const { messages, completion, tools, error } = payload;
|
||||||
// ...
|
// ...
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue