# Limitations of Combining Streaming with max_turns in AISuite: Why They Are Mutually Exclusive

> Discover why AISuite's streaming and max_turns parameters are mutually exclusive. Understand the limitations and avoid ValueErrors in your AI development.

- Repository: [Andrew Ng/aisuite](https://github.com/andrewyng/aisuite)
- Tags: deep-dive
- Published: 2026-07-27

---

**AISuite treats streaming and automatic tool execution via `max_turns` as mutually exclusive modes, raising a `ValueError` as soon as both parameters are supplied to `client.chat.completions.create()`.**

The `aisuite` library by Andrew Ng's team provides a unified interface for multiple LLM providers, but it imposes a strict architectural boundary between real-time streaming responses and iterative tool-calling loops. Understanding the limitations of combining streaming with max_turns in aisuite is essential when building applications that require both interactive output and autonomous function execution.

## Why Streaming and max_turns Cannot Be Combined

AISuite follows two distinct internal paths depending on whether `stream=True` or `max_turns` is provided. The client explicitly prevents their intersection because the streaming API returns an iterator of chunks directly from the provider, leaving no hook for the client-side loop that manages tool calls across multiple turns.

### The Streaming Path Disables Automatic Tool Execution

When `stream=True` is set, the client follows the manual tool-calling path. The request is routed directly to the provider's `chat_completions_create_stream` method, and the client **does not** run the internal tool-execution loop that `max_turns` would trigger. Because partial LLM responses are yielded as chunks in real time, there is no mechanism to pause the stream, execute a tool, append the result, and resume the conversation for another turn.

### The max_turns Path Requires Non-Streaming Mode

When `max_turns` is supplied, the client prepares a normal non-streaming chat request and runs the tool-execution loop for up to the specified number of turns. It returns only the final LLM response after all tool calls resolve. If `stream=True` is also present, the guard aborts early because the iterative tool-execution logic cannot interleave with a live chunk iterator.

## Where AISuite Enforces This Limitation

The enforcement happens in [`aisuite/client.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/client.py), which contains the core client logic validating the `stream` and `max_turns` combination. Before any network request is dispatched, the client checks for the incompatible pair and raises `ValueError`.

The provider-side streaming contract is defined in [`aisuite/provider.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/provider.py), where the base provider class establishes the streaming interface. This file delineates which features are unsupported when operating in streaming mode.

## How the Test Suite Locks In the Behavior

The AISuite test suite codifies this rule explicitly. In [`tests/client/test_streaming.py`](https://github.com/andrewyng/aisuite/blob/main/tests/client/test_streaming.py), the test `test_create_stream_with_max_turns_raises` confirms that a `ValueError` is thrown when both parameters are supplied:

```python
with pytest.raises(ValueError, match="max_turns"):
    client.chat.completions.create(
        model="openai:gpt-4o",
        messages=[{"role": "user", "content": "hi"}],
        tools=[{"type": "function", "function": {"name": "t", "parameters": {}}}],
        max_turns=3,
        stream=True,
    )

```

This validation covers lines 70‑82 of [`tests/client/test_streaming.py`](https://github.com/andrewyng/aisuite/blob/main/tests/client/test_streaming.py).

The asynchronous client enforces the identical restriction before iteration begins. The test `test_acreate_stream_with_max_turns_raises_eagerly` in [`tests/client/test_async_client.py`](https://github.com/andrewyng/aisuite/blob/main/tests/client/test_async_client.py) validates the guard on lines 44‑55, raising the same `ValueError` eagerly.

## Correct Usage Patterns

To avoid runtime errors, keep these two modes entirely separate.

### Streaming Without Automatic Tool Execution

Use `stream=True` when you need real-time output and will handle tool calls manually outside the client:

```python

# ✅ Correct usage – streaming without automatic tool execution

client.chat.completions.create(
    model="openai:gpt-4o",
    messages=[{"role": "user", "content": "Explain quantum tunneling"}],
    stream=True,
)

```

### Automatic Tool Execution Without Streaming

Use `max_turns` when you want AISuite to manage multi-turn tool execution autonomously:

```python

# ✅ Correct usage – automatic tool execution (max_turns) without streaming

client.chat.completions.create(
    model="openai:gpt-4o",
    messages=[{"role": "user", "content": "What's the weather?"}],
    tools=[weather_tool],
    max_turns=3,      # run up to 3 tool‑execution turns

)

```

### Invalid Combination

Passing both parameters triggers an immediate exception:

```python

# ❌ Invalid combination – raises ValueError

client.chat.completions.create(
    model="openai:gpt-4o",
    messages=[{"role": "user", "content": "Run a script"}],
    tools=[script_tool],
    max_turns=2,
    stream=True,      # ❌ cannot be combined

)

```

## Summary

- **Streaming and `max_turns` are mutually exclusive** in AISuite by design.
- When `stream=True`, the client routes to `chat_completions_create_stream` and skips the tool-execution loop.
- When `max_turns` is set, the client expects a non-streaming request to run its iterative tool loop.
- The guard lives in [`aisuite/client.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/client.py) and raises `ValueError` immediately.
- Tests in [`tests/client/test_streaming.py`](https://github.com/andrewyng/aisuite/blob/main/tests/client/test_streaming.py) (lines 70‑82) and [`tests/client/test_async_client.py`](https://github.com/andrewyng/aisuite/blob/main/tests/client/test_async_client.py) (lines 44‑55) lock in this behavior.

## Frequently Asked Questions

### What happens if I pass both `stream=True` and `max_turns` to AISuite?

AISuite raises a `ValueError` before sending any request. The guard in [`aisuite/client.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/client.py) detects the incompatible combination and aborts immediately.

### Can I still use tools when streaming is enabled?

Yes, but you must handle tool calling manually. When `stream=True`, AISuite follows the manual tool-calling path, so you are responsible for intercepting tool requests, executing functions, and re-sending the results in subsequent messages.

### Is there an async version of this limitation?

Yes. The async client enforces the same restriction eagerly. The test `test_acreate_stream_with_max_turns_raises_eagerly` in [`tests/client/test_async_client.py`](https://github.com/andrewyng/aisuite/blob/main/tests/client/test_async_client.py) (lines 44‑55) verifies that `ValueError` is raised before async iteration begins.

### Will AISuite support streaming with `max_turns` in the future?

The current architecture does not support this combination because streaming yields raw provider chunks without a client-side hook for multi-turn tool execution. Implementing it would require complex interleaving of partial responses with tool calls, which the codebase does not currently provide.