playbook/outfitter-agents/plugins/outfitter/skills/ai-sdk/references/agents.md

3.7 KiB

ToolLoopAgent Patterns

Deep dive on v6 agent workflows.

When to Use ToolLoopAgent

Use Case Approach
Single tool call streamText with tools
Multi-step reasoning ToolLoopAgent
Autonomous workflows ToolLoopAgent with stopWhen
Complex orchestration ToolLoopAgent with custom stop conditions

Agent Configuration

import { ToolLoopAgent, stepCountIs } from 'ai';

const agent = new ToolLoopAgent({
  // Required
  model: 'anthropic/claude-sonnet-4.5',

  // Optional
  instructions: 'You are a research assistant.',
  tools: { search, calculate, summarize },

  // Stop conditions
  stopWhen: stepCountIs(10),
  // Or custom: async ({ steps }) => steps.length >= 10

  // Tool selection
  toolChoice: 'auto', // 'auto' | 'required' | 'none'

  // Token limits
  maxOutputTokens: 4096,
});

Execution Patterns

Non-Streaming (Simple)

const result = await agent.generate({
  prompt: 'Research quantum computing breakthroughs.',
});

console.log(result.text);
console.log(result.steps); // Array of all steps
console.log(result.steps.length, 'steps executed');

Streaming (Real-time)

const stream = agent.stream({
  prompt: 'Analyze this data and provide insights.',
});

for await (const chunk of stream.textStream) {
  process.stdout.write(chunk);
}

UI Streaming (React/Next.js)

import { createAgentUIStream } from 'ai';

const stream = await createAgentUIStream({
  agent,
  messages: [{ role: 'user', content: 'What is the weather?' }],
  abortSignal: controller.signal,
});

for await (const chunk of stream) {
  // Yield to client
}

Stop Conditions

Built-in: Step Count

import { stepCountIs } from 'ai';

stopWhen: stepCountIs(5) // Stop after 5 steps

Custom: Finish Reason

stopWhen: async ({ steps }) =>
  steps.at(-1)?.finishReason === 'stop'

Custom: Combined

stopWhen: async ({ steps }) =>
  steps.length >= 10 || steps.at(-1)?.finishReason === 'stop'

Tool Choice Control

const agent = new ToolLoopAgent({
  model: 'anthropic/claude-sonnet-4.5',
  tools: { search, calculate },

  // Force tool use every step
  toolChoice: 'required',

  // Disable tools (text only)
  toolChoice: 'none',

  // Let model decide (default)
  toolChoice: 'auto',
});

Agent with Constraints

const customerSupportAgent = new ToolLoopAgent({
  model: 'anthropic/claude-sonnet-4.5',
  instructions: `You are a customer support specialist.

Rules:
- Never promise refunds without checking policy
- Always be empathetic and professional
- If unsure, offer to escalate
- Keep responses concise
- Never share internal company information`,
  tools: {
    checkOrderStatus,
    lookupPolicy,
    createTicket,
  },
});

Accessing Step History

const result = await agent.generate({ prompt: '...' });

for (const step of result.steps) {
  if (step.type === 'tool-call') {
    console.log(`Called ${step.tool} with`, step.input);
  } else if (step.type === 'text-generation') {
    console.log('Generated:', step.output);
  }
}

Error Handling

try {
  const result = await agent.generate({ prompt: '...' });
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('Agent execution aborted');
  } else {
    console.error('Agent error:', error);
  }
}

Best Practices

DO:

  • Set reasonable stopWhen limits
  • Use typed tool schemas with Zod
  • Handle abort signals for long-running agents
  • Log step history for debugging

DON'T:

  • Leave agents unbounded (no stop condition)
  • Use synchronous blocking operations in tools
  • Ignore error states
  • Skip tool validation