yao/agent/docs/hooks.md
Max bdfe7e83e8 Revise Agent and SUI Documentation for Clarity and Consistency
- Updated the Agent API documentation to reflect a new structure, emphasizing quick start instructions and reorganizing content for better readability.
- Renamed the main documentation title to "Yao Agent" and streamlined sections, including API endpoints and file management.
- Adjusted the SUI documentation to align with the Yao App Engine license, ensuring consistency across project documentation.
2026-01-01 11:08:30 +08:00

6.9 KiB

Hooks

Hooks allow you to customize agent behavior at key points in the execution lifecycle.

Lifecycle

User Input → Create Hook → LLM Call → Tool Execution → Next Hook → Response

Create Hook

Called before LLM call. Use to preprocess messages, configure request, or delegate.

function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
  // Return null for default behavior
  return null;

  // Or return configuration
  return {
    messages,                    // Modified messages
    temperature: 0.7,            // Override temperature
    max_tokens: 2000,            // Override max tokens
    connector: "gpt-4o-mini",    // Override connector
    prompt_preset: "task",       // Select prompt preset
    disable_global_prompts: true,// Skip global prompts
    mcp_servers: [               // Add MCP servers
      { server_id: "tools", tools: ["search"] }
    ],
    uses: {                      // Override wrapper tools
      vision: "vision-agent",
      search: "disabled"
    },
    force_uses: true,            // Force use wrapper tools
    locale: "zh-cn",             // Override locale
    metadata: { key: "value" },  // Pass data to context
  };
}

Delegation (Skip LLM)

Route to another agent immediately:

function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
  if (shouldDelegate(messages)) {
    return {
      delegate: {
        agent_id: "specialist.agent",
        messages: messages,
        options: { metadata: { source: "main" } }
      }
    };
  }
  return { messages };
}

Next Hook

Called after LLM response and tool execution. Use to post-process or delegate.

function Next(ctx: agent.Context, payload: agent.Payload): agent.Next {
  const { messages, completion, tools, error } = payload;

  // Handle errors
  if (error) {
    return { data: { status: "error", message: error } };
  }

  // Process tool results
  if (tools?.length > 0) {
    const results = tools.map(t => t.result);
    return { data: { status: "success", results } };
  }

  // Delegate based on response
  if (completion?.content?.includes("transfer")) {
    return {
      delegate: {
        agent_id: "transfer.agent",
        messages: payload.messages
      }
    };
  }

  // Return null for standard response
  return null;
}

Payload Structure

interface Payload {
  messages: Message[];           // Messages sent to LLM
  completion?: {
    content: string;             // LLM text response
    tool_calls?: ToolCall[];     // Tool calls from LLM
    usage?: UsageInfo;           // Token usage
  };
  tools?: ToolCallResponse[];    // Tool execution results
  error?: string;                // Error message
}

interface ToolCallResponse {
  toolcall_id: string;
  server: string;                // MCP server ID
  tool: string;                  // Tool name
  arguments?: any;               // Tool arguments
  result?: any;                  // Tool result
  error?: string;                // Tool error
}

Return Values

interface NextResponse {
  delegate?: {                   // Route to another agent
    agent_id: string;
    messages: Message[];
    options?: Record<string, any>;
  };
  data?: any;                    // Custom response data
  metadata?: Record<string, any>;// Debug metadata
}

Sending Messages

Use ctx to send messages to the client:

function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
  // Send complete message
  ctx.Send({ type: "text", props: { content: "Processing..." } });

  // Streaming message
  const msgId = ctx.SendStream("Starting...");
  ctx.Append(msgId, " step 1...");
  ctx.Append(msgId, " step 2...");
  ctx.End(msgId);

  return { messages };
}

Memory

Share data between hooks:

function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
  // Store in request-scoped memory
  ctx.memory.context.Set("start_time", Date.now());
  ctx.memory.context.Set("query", messages[0]?.content);

  return { messages };
}

function Next(ctx: agent.Context, payload: agent.Payload): agent.Next {
  // Retrieve data
  const startTime = ctx.memory.context.Get("start_time");
  const duration = Date.now() - startTime;

  return { data: { duration_ms: duration } };
}

Tracing

Add trace nodes for debugging and UI:

function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
  const node = ctx.trace.Add(
    { query: messages[0]?.content },
    { label: "Preprocessing", type: "process", icon: "play" }
  );

  node.Info("Starting analysis");
  // ... processing ...
  node.Complete({ status: "done" });

  return { messages };
}

Error Handling

function Next(ctx: agent.Context, payload: agent.Payload): agent.Next {
  try {
    if (payload.error) {
      ctx.trace.Error(payload.error);
      return {
        data: { status: "error", message: "Something went wrong" }
      };
    }
    // ... normal processing
  } catch (e) {
    ctx.trace.Error(e.message);
    return { data: { status: "error", message: e.message } };
  }
}

Multi-Agent Orchestration

// Main agent delegates based on intent
function Next(ctx: agent.Context, payload: agent.Payload): agent.Next {
  const { tools } = payload;

  // Route based on tool result
  const intent = tools?.[0]?.result?.intent;
  const agentMap = {
    "search": "search.agent",
    "calculate": "calc.agent",
    "translate": "translate.agent"
  };

  if (intent && agentMap[intent]) {
    return {
      delegate: {
        agent_id: agentMap[intent],
        messages: payload.messages
      }
    };
  }

  return null;
}

Complete Example

import { agent } from "@yao/runtime";

function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
  const query = messages[messages.length - 1]?.content || "";
  
  // Store for Next hook
  ctx.memory.context.Set("query", query);
  ctx.memory.context.Set("start", Date.now());

  // Add trace
  ctx.trace.Add({ query }, { label: "Create", type: "hook" });

  // Check if needs special handling
  if (query.toLowerCase().includes("urgent")) {
    return {
      messages,
      temperature: 0,
      prompt_preset: "task"
    };
  }

  return { messages };
}

function Next(ctx: agent.Context, payload: agent.Payload): agent.Next {
  const { completion, tools, error } = payload;
  const start = ctx.memory.context.Get("start");
  const duration = Date.now() - start;

  ctx.trace.Add(
    { duration },
    { label: "Next", type: "hook" }
  ).Complete();

  if (error) {
    return { data: { status: "error", error } };
  }

  if (tools?.length > 0) {
    return {
      data: {
        status: "success",
        response: completion?.content,
        tools: tools.map(t => ({ name: t.tool, result: t.result })),
        duration_ms: duration
      }
    };
  }

  return null;
}