Streaming

Display AI responses in real-time as they are generated

Stream agent responses token-by-token — print to the terminal, or iterate tokens yourself to render them in a browser, webview, mobile app, or server.

To stop a stream mid-flight, pass an AbortSignal — see Cancellation.

Diagram

TS Agent Streaming

CLI default

for await token

for await event

💬 User

🤖 Agent

🔀 How?

📺 Terminal

⚡ Async Iterable

📨 Structured Events

Which Mode Do I Want?

Pick the surface that matches where the answer renders.

Diagram

Terminal / CLI

Browser, webview, mobile, server

No, just tokens

Yes, structured

I want to stream…

Where does the answer render?

Just call agent.start / agent.chat

Do I need tool / error / finish events?

agent.stream — async iterable of strings

agent.streamEvents — async iterable of AgentEvent

Quick Start

  1. Iterate tokens with stream()

    Render tokens live anywhere — the for await loop pulls them at its own pace.

    typescript
    import { Agent } from 'praisonai';
    
    const agent = new Agent({
      instructions: 'You are a storyteller'
    });
    
    for await (const token of agent.stream('Tell me a short story about a robot')) {
      process.stdout.write(token);  // or send to a WebSocket, React state, etc.
    }
  2. Terminal default (start / chat)

    On the CLI, streaming is already on — tokens print to the terminal.

    typescript
    import { Agent } from 'praisonai';
    
    const agent = new Agent({
      instructions: 'You are a helpful assistant'
    });
    
    await agent.chat('Tell me a story about a robot');
    // Response streams to the console word by word
  3. Structured events with streamEvents()

    Same agent, structured events — handle text, finish, and error.

    typescript
    import { Agent } from 'praisonai';
    
    const agent = new Agent({ instructions: 'You are helpful' });
    
    for await (const event of agent.streamEvents('Hi')) {
      if (event.type === 'text') {
        process.stdout.write(event.delta);
      } else if (event.type === 'finish') {
        console.log('\n\nDone. Full text:', event.text);
      } else if (event.type === 'error') {
        console.error('Stream failed:', event.error);
      }
    }
  4. Streaming with Tools

    Tool calls are woven into the same stream — text the model speaks before the tool ("Let me check."), the tool call itself, and the answer after it all arrive as they happen. This holds on every provider: OpenAI, Anthropic, Google, xAI, Groq, and any AI-SDK backend. Pre-tool commentary is delivered as tokens, not held back until the tool finishes.

    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 weather assistant',
      llm: 'anthropic/claude-3-5-sonnet-latest',  // non-OpenAI provider
      stream: true,
      tools: [getWeather]
    });
    
    await agent.chat('What is the weather in Paris?');
    // Console:
    //   Let me check the weather.     <-- streamed reasoning
    //   [getWeather("Paris") runs]     <-- tool executes
    //   It is 22°C in Paris.           <-- streamed final answer

    Iterate agent.stream() to observe the pre-tool text in a non-console caller — each chunk is a token string.

    typescript
    for await (const chunk of agent.stream('What is the weather in Paris?')) {
      process.stdout.write(chunk);
    }
  5. Consume Tokens Yourself

    Consume tokens yourself with agent.stream() — an async iterator over chunks.

    typescript
    import { Agent } from 'praisonai';
    
    const agent = new Agent({ instructions: 'You are a helpful assistant' });
    
    for await (const chunk of agent.stream('Tell me a story about a robot')) {
      process.stdout.write(chunk);
    }
  6. Stop a Stream

    Break the loop to stop the request — the provider stops generating and billing immediately.

    typescript
    import { Agent } from 'praisonai';
    
    const agent = new Agent({ instructions: 'You are a helpful assistant' });
    
    let tokens = 0;
    for await (const chunk of agent.stream('Write a very long essay')) {
      process.stdout.write(chunk);
      if (++tokens === 50) break;  // Auto-aborts the underlying request
    }
    
    console.log(agent.lastStopReason);  // 'cancelled'

    For a UI Stop button, pass an AbortSignal:

    typescript
    const controller = new AbortController();
    document.getElementById('stop')!.onclick = () => controller.abort();
    
    try {
      for await (const chunk of agent.stream('Write a long essay', { signal: controller.signal })) {
        process.stdout.write(chunk);
      }
    } catch (err) {
      if (agent.lastStopReason === 'cancelled') {
        console.log('User stopped the stream');
      } else {
        throw err;  // Real error
      }
    }

Cancellation

Break out of the loop to stop the stream cleanly — no more tokens are queued and the in-flight provider request is aborted.

typescript
let received = '';
for await (const token of agent.stream('Write a very long essay')) {
  received += token;
  if (received.length > 500) break;  // stops the stream cleanly
}

You can also pass an AbortSignal to cancel a turn from outside the loop.

typescript
const controller = new AbortController();
setTimeout(() => controller.abort(), 2000);  // stop after 2s

for await (const token of agent.stream('Long essay', { signal: controller.signal })) {
  process.stdout.write(token);
}

How It Works

Diagram
LLMAgentServerBrowserLLMAgentServerBrowserloop[each token]POST /chat “question”for await (token of agent.stream(…))streaming requestchunkyield tokenSSE / WebSocket frameappend to DOM[DONE]finish eventclose stream

When tools are configured, the round routes through streamText on every provider — text spoken before the tool call streams as tokens, then the tool runs, then the answer after it streams too.

Diagram
ToolLLMAgentUserToolLLMAgentUserstream(“weather in Paris?”)streamText(tools=[getWeather])“Let me “text delta“check. “text deltatool_call getWeather(Paris)getWeather(“Paris”)“22°C, sunny”streamText(next round)“It is 22°C.”text deltafinish[done]

Streaming with Tools

Text deltas, tool calls, and tool results interleave on a single loop — the stream never stops just because the agent needs a tool.

Diagram
ToolLLMAgentUserToolLLMAgentUserloop[Round 1 (streams both text and a toolcall)]loop[Round 2 (final answer streams)]chat(“weather in Paris?”)Request (stream=true, tools=[getWeather])text deltadisplay tokentool_call: getWeather(“Paris”)getWeather(“Paris”)“22°C, sunny”Request (stream=true, tool result)text deltadisplay token

Bounding the loop

Two knobs bound the run when the model chains many tools.

OptionTypeDefaultDescription
maxIterationsnumber20Maximum tool-call round-trips before the run aborts.
maxToolCallsPerTurnnumber10Maximum tool calls executed in a single round; extras in one round are dropped with a warning.
typescript
const agent = new Agent({
  instructions: 'Research assistant',
  stream: true,
  tools: [search, fetchUrl, summarise],
  maxIterations: 30,          // more room for long research chains
  maxToolCallsPerTurn: 5,     // but at most 5 parallel tools per turn
});

Stop a stream

Two ways to cancel a streaming turn — both stop the upstream provider request so no further tokens are generated or billed.

Diagram

Cancellation paths

break the loop

abort a signal

👤 User clicks Stop

Which pattern?

🔚 for-await break

🛑 controller.abort

⏹️ Upstream request aborts

📌 lastStopReason = ‘cancelled’

Which one should I use?

You want to…UseWhy
Stop reading (loop-driven)break in the for awaitNatural JS pattern — no plumbing needed
Wire a UI Stop button{ signal: controller.signal }External event fires abort(), cancels the turn
Cancel the whole agent (not just this turn)SimpleAgentConfig.signalAgent-lifetime — outlives one call

Tell a Stop from a real error

agent.lastStopReason reports why the last run ended:

ValueMeaning
'completed'Ran to the end
'cancelled'User-initiated abort (Stop button, break, or opts.signal)
'error'Genuine failure (network, provider error, thrown tool)
'max_steps'Hit the tool-loop step cap
nullHasn’t run yet
typescript
try {
  for await (const chunk of agent.stream('...', { signal })) { /* ... */ }
} catch (err) {
  if (agent.lastStopReason === 'cancelled') {
    // Silent — user asked for it
  } else {
    reportError(err);  // Real failure
  }
}

AgentEvent Reference

streamEvents() yields a discriminated union. Switch on event.type.

EventFieldsWhen
text{ type: 'text'; delta: string }Each incremental token
tool_call{ type: 'tool_call'; callId: string; name: string; args: Record<string, unknown> }A tool is about to run
tool_result{ type: 'tool_result'; callId: string; name: string; ok: boolean; output: string }A tool finished (ok signals success)
finish{ type: 'finish'; text: string }Once, at the end of a successful run — carries the full response
error{ type: 'error'; error: Error }Once, if the run threw

AgentStreamOptions Reference

Both stream() and streamEvents() accept an optional second argument.

OptionTypeDefaultDescription
previousResultstringundefinedSubstituted for the {{previous}} placeholder in instructions (same semantics as the second arg to start())
signalAbortSignalundefinedTurn-scoped cancellation. Aborting it stops the provider request so no further tokens are generated or billed

Configuration Options

The stream config option controls the terminal default and whether token deltas are produced.

OptionTypeDefaultDescription
streambooleantrueEnable streaming responses
verbosebooleanfalseShow formatted output while streaming
toolsFunction[]undefinedTools available during streaming; text and tool calls interleave
maxIterationsnumber20Cap tool-call round-trips in a streaming+tools run
maxToolCallsPerTurnnumber10Cap tool calls executed in a single round
agent.stream(prompt, { signal })AbortSignalundefinedTurn-scoped abort signal for the streaming call; aborting it stops the underlying provider request

Common Patterns

Stream to a Server-Sent Events endpoint

typescript
import { Agent } from 'praisonai';
import express from 'express';

const agent = new Agent({ instructions: 'You are helpful' });
const app = express();

app.get('/chat', async (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  for await (const token of agent.stream(String(req.query.q))) {
    res.write(`data: ${token}\n\n`);
  }
  res.end();
});

app.listen(3000);

Log the full text once with streamEvents()

typescript
import { Agent } from 'praisonai';

const agent = new Agent({ instructions: 'You are helpful' });

for await (const event of agent.streamEvents('Explain streaming in one line')) {
  if (event.type === 'text') process.stdout.write(event.delta);
  if (event.type === 'finish') console.log('\n[full]', event.text);
}

Terminal default (unchanged)

typescript
import { Agent } from 'praisonai';

const writer = new Agent({
  instructions: 'You are a creative writer',
  stream: true
});

// See the story unfold in the terminal as it is written
await writer.chat('Write a 500-word story about a time-traveling chef');

Seeing Tool Activity

If your UI needs to render tool calls and their results — not just the streamed text — use agent.streamEvents() instead of agent.chat(). It yields a typed union (text, tool_call, tool_result, finish, error) in the order the agent produced them, so a call is announced before it runs and its result carries an explicit ok field.


Best Practices

Use stream() for UI hosts

Anywhere tokens must land in a DOM, SSE, WebSocket, or React Native view, for await (const token of agent.stream(...)) gives you each token to route yourself.

Use streamEvents() when you need finish/error semantics

Switch to streamEvents() to close a socket, log the full text once, react to tool activity, or fall back on error — the finish, error, tool_call, and tool_result events carry that context.

Break the loop to cancel

No separate AbortController is needed for the streaming surface itself — breaking out of the for await loop detaches the token sink and aborts the in-flight request. Pass opts.signal only when you need to cancel from outside the loop.

Streaming works with all providers — including with tools

Every supported provider streams — OpenAI, Anthropic, Google, xAI, Groq, and any AI-SDK backend. That includes streaming while tools are running: text the model speaks before a tool_call is delivered as tokens, not held back until the tool finishes. Setting stream: false still works and collapses the round into a single non-streaming call.

Keep stream: true when you use tools on non-OpenAI providers

On Anthropic, Google, xAI, Groq, and any AI-SDK backend, stream: true gives you pre-tool commentary as tokens. Setting stream: false collapses the round into one non-streaming call and delivers the same text at the end in one lump.

Break or abort — both stop billing

Agent.stream() owns the upstream request and cancels it in the iterator’s finally. Breaking a for await loop, or aborting a signal you passed via agent.stream(prompt, { signal }), both stop provider tokens from being generated. Use agent.lastStopReason === 'cancelled' to distinguish a user Stop from a real failure in your error handler.

Non-streaming paths still work with stream()

Even when tools or outputSchema disable token deltas, stream() yields the full response as a single item, so callers always receive an answer.

Keep the CLI default alone

Omitting the token sink (i.e. calling start() or chat()) still writes to process.stdout, so existing CLI scripts don’t change.


Beyond Text: Structured Events

For UIs that need to show tool activity live, use streamEvents() — it yields typed AgentEvents instead of just tokens.

typescript
import { Agent } from 'praisonai';

const agent = new Agent({ instructions: 'You are a helpful assistant' });

for await (const event of agent.streamEvents('What is the weather in Paris?')) {
  switch (event.type) {
    case 'text':        process.stdout.write(event.delta); break;
    case 'tool_call':   console.log(`\n[tool] ${event.name}`); break;
    case 'tool_result': console.log(`[tool] ${event.ok ? 'ok' : 'fail'}`); break;
    case 'finish':      console.log(`\n[done]`); break;
  }
}
Streaming Events

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


Related