Streaming Events

Stream tool calls, tool results, and text as structured events

Stream typed events (text, tool_call, tool_result, finish, error) instead of just token text, so a UI can show tool activity as it happens.

Diagram

streamEvents()

💬 Prompt

🤖 Agent

📝 text

🔧 tool_call

✅ tool_result

📝 text

🏁 finish

Quick Start

  1. See tools happening live

    Iterate the event stream and switch on event.type to render text, tool calls, and results as they arrive.

    typescript
    import { Agent } from 'praisonai';
    
    function getWeather(city: string): string {
      return `Weather in ${city}: 22°C, sunny`;
    }
    
    const agent = new Agent({
      instructions: 'You are a helpful assistant',
      tools: [getWeather],
    });
    
    for await (const event of agent.streamEvents('What is the weather in Paris?')) {
      if (event.type === 'text')        process.stdout.write(event.delta);
      else if (event.type === 'tool_call')   console.log(`\n[tool] ${event.name}(${JSON.stringify(event.args)})`);
      else if (event.type === 'tool_result') console.log(`[tool] ${event.name} -> ${event.ok ? 'ok' : 'fail'}: ${event.output}`);
      else if (event.type === 'finish')      console.log(`\n[done] ${event.text}`);
    }
  2. Cancel a turn

    Pass an AbortController signal to stop generation mid-turn — or just break the for await loop, which aborts the upstream provider request automatically.

    typescript
    import { Agent } from 'praisonai';
    
    const agent = new Agent({ instructions: 'You are a helpful assistant' });
    const controller = new AbortController();
    
    setTimeout(() => controller.abort(), 2000);  // Cancel after 2 seconds
    
    for await (const event of agent.streamEvents('Write a long essay', { signal: controller.signal })) {
      if (event.type === 'text') process.stdout.write(event.delta);
      if (event.type === 'finish') break;  // Breaking also aborts upstream generation
    }

How It Works

Structured events share the same internal queue as text deltas, so tool activity is reported in its true position in the response.

Diagram
ToolLLMAgentUserToolLLMAgentUserloop[Text deltas]loop[More text]streamEvents(“question”)Requesttokentexttool calltool_call (callId)executeoutputtool_result (same callId, ok)tokentextfinish

The Five Event Types

Every event is a member of the AgentEvent discriminated union — switch on type to narrow the fields.

EventFieldsMeaning
textdelta: stringA token chunk. Concatenate to build the reply.
tool_callcallId, name, argsA tool is about to run.
tool_resultcallId, name, ok, outputThe tool finished. ok is the only success signal — never infer success from a non-empty output.
finishtext: stringThe final full response.
errorerror: ErrorSomething threw. Terminal.

Choosing a Streaming API

Pick the smallest API that gives you what you need.

Diagram

Console output

Text tokens only

Text + tools + cancel

What do you need?

agent.chat with stream:true

agent.stream

agent.streamEvents

You wantUse
Console output that “just streams”agent.chat(...) with stream: true
Just the text tokens as stringsfor await (const tok of agent.stream(prompt))
Text plus tool activity, cancellation, custom UIfor await (const ev of agent.streamEvents(prompt))

Common Patterns

Chat UI that shows tools in progress

Track calls in a Map keyed by callId, then flip each card from running to ok or fail on the matching result.

typescript
import { Agent } from 'praisonai';

function getWeather(city: string): string {
  return `Weather in ${city}: 22°C, sunny`;
}

const agent = new Agent({ instructions: 'You are a helpful assistant', tools: [getWeather] });
const cards = new Map<string, { name: string; args: unknown; status: string }>();

for await (const event of agent.streamEvents('Weather in Paris and Tokyo?')) {
  if (event.type === 'tool_call') {
    cards.set(event.callId, { name: event.name, args: event.args, status: 'running' });
  } else if (event.type === 'tool_result') {
    const card = cards.get(event.callId);
    if (card) card.status = event.ok ? 'ok' : 'fail';
  }
  console.log([...cards.values()]);  // Render the tool cards
}

Progress spinner around a slow tool

Narrate tool_call.name in a status line, then clear it on the matching tool_result.

typescript
import { Agent } from 'praisonai';

function searchDocs(query: string): string {
  return `Found 3 results for ${query}`;
}

const agent = new Agent({ instructions: 'You are a research assistant', tools: [searchDocs] });

for await (const event of agent.streamEvents('Search the docs for streaming')) {
  if (event.type === 'tool_call')   process.stdout.write(`⏳ Running ${event.name}...`);
  if (event.type === 'tool_result') process.stdout.write(`\r✅ ${event.name} done       \n`);
}

Best Practices

Pair by callId, never by position

Order is preserved, but positional matching breaks the moment two tools run in one turn. Always key your state on event.callId so a tool_result finds its tool_call.

Treat ok === false as failure, always

A failed tool that returns a friendly output looks identical to a success — the output is the same shape either way. Only event.ok === true means the tool succeeded.

Break the for await loop to cancel

The iterator’s return() is the language’s own abort signal — breaking out of the loop stops upstream generation and billing. Pass opts.signal for external cancellation.

Return objects from tools that expect named args

JSON scalars, arrays, or null are rejected before execution with Invalid arguments for tool <name>: expected a JSON object. Pass an object so arguments map to named parameters.


Related