# Connect Microsoft AutoGen to Router One with OpenAIChatCompletionClient

> Markdown mirror of https://router.one/integrations/autogen for AI assistants and crawlers. Router One is an OpenAI-compatible LLM API gateway.
> Last updated: 2026-09-23

To use Microsoft AutoGen with Router One, build autogen-ext's OpenAIChatCompletionClient with base_url https://api.router.one/v1, your Router One key and the exact catalog ID, and pass a model_info dictionary, because AutoGen does not recognize catalog IDs such as anthropic/claude-sonnet-5. Every model call is then one POST /v1/chat/completions with a cost and latency trace, while agents, tools, memory and team turns keep running in your process. AutoGen is Microsoft's Python framework for agents and multi-agent teams (autogen-agentchat provides AssistantAgent, RoundRobinGroupChat and SelectorGroupChat, autogen-ext the model clients); it is in maintenance mode with Microsoft Agent Framework as its successor, and its latest release is 0.7.5 from September 30, 2025. This guide was checked against autogen-agentchat and autogen-ext 0.7.5 with openai 3.17.0, explains what each model_info key changes, and ends with the matching settings for AG2 1.0.6, a separate project that describes itself as formerly AutoGen.

## Install autogen-agentchat and autogen-ext[openai], then set two variables

Use Python 3.10 or newer. The model client lives in autogen-ext and needs its openai extra, which adds the openai library (any version from 1.93 on; this guide ran with 3.17.0) and tiktoken, while autogen-agentchat provides the agents and teams. Pin both packages to 0.7.5, the release this guide was checked against. Two similarly named packages will not give you this setup: autogen on PyPI is now AG2 Classic, and pyautogen 0.10.0 is a Microsoft placeholder package that only pulls in autogen-agentchat, without autogen-ext[openai] and without the 0.7.5 pin, so install the two packages above directly; if your code says import autogen, see the FAQ below. Microsoft recommends Agent Framework for new projects, and if you are migrating, the Agent Framework guide on this site shows where the base URL goes there. The shell example is for macOS and Linux. The ROUTER_ONE_* names belong to this example, which reads them explicitly. When api_key or base_url is omitted, the openai library falls back to OPENAI_API_KEY and OPENAI_BASE_URL, and with no key at all the constructor raises OpenAIError before any request is sent (in openai 3.17.0 the message starts with Missing credentials; older releases such as 1.93 say The api_key client option must be set).

`terminal`

```bash
python -m pip install "autogen-agentchat==0.7.5" "autogen-ext[openai]==0.7.5"
export ROUTER_ONE_API_KEY="sk-your-router-one-key"
export ROUTER_ONE_MODEL_ID="anthropic/claude-sonnet-5"
```

## Configure AutoGen to use the Router One base URL

Save this as autogen_router_one.py and run python autogen_router_one.py. model is the catalog ID, sent unchanged: the client neither strips nor parses the provider prefix, so the model in each Dashboard → Logs entry should match /models character for character. base_url is the /v1 base URL and goes to the openai library's AsyncOpenAI client, which appends /chat/completions; without /v1 the request goes to /chat/completions instead of /v1/chat/completions. api_key is passed explicitly. model_info tells AutoGen what the model can do, because its built-in capability table only holds vendor model names such as gpt-5 or gemini-2.5-flash and no catalog ID matches it: vision, function_calling, json_output and family are required and a missing one raises ValueError; structured_output only warns when absent at construction but raises KeyError once structured output is used, so always set it; and multiple_system_messages is optional. Take vision and function_calling from the model page (its Vision and Tool calling labels); this example sends no images, so it leaves vision False. The page does not list JSON mode or structured output, so keep those False until one real request works. The flags decide what AutoGen agrees to send, and the next section lists what each key changes. family stays ModelFamily.UNKNOWN, the neutral value for catalog IDs. AutoGen's own reference notes that using this client for non-OpenAI models is not tested or guaranteed, so confirm tools, streaming and images on one real request each. The first agent gets one Python function as a tool: AutoGen sends its JSON schema in the tools field with tool_choice auto and runs the function in your process when the model calls it, and max_tool_iterations=2 lets it send the tool result back in a second request that carries the same tools. If the model answers that request in text, the reply ends the run; if it calls the tool again, that second round is the last and the run ends with a ToolCallSummaryMessage. With the defaults the run ends after the first request with a ToolCallSummaryMessage holding the tool's raw output. reflect_on_tool_use=True also adds a second request, but that one drops the tools field while keeping the tool-call history, so confirm it for your ID on one real request in Logs before relying on it. total_usage() sums the prompt and completion tokens the API reported across the client's calls. The second agent streams: model_client_stream=True sets stream to true, and stream_options include_usage asks for a final chunk with token usage. It gets its own client because stream_options passed to the constructor is sent on every request from that client, non-streaming create() calls included.

`autogen_router_one.py`

```python
import asyncio
import os

from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.ui import Console
from autogen_core.models import ModelFamily
from autogen_ext.models.openai import OpenAIChatCompletionClient

# AutoGen cannot look up capabilities for any catalog ID, openai/ ones included.
# vision / function_calling: True only if the model's page lists Vision / Tool calling.
# json_output / structured_output: True only after one real request works.
MODEL_INFO = {
    "vision": False,
    "function_calling": True,
    "json_output": False,
    "structured_output": False,
    "family": ModelFamily.UNKNOWN,
    "multiple_system_messages": False,
}


def router_one_client(**extra) -> OpenAIChatCompletionClient:
    # Every model call is one POST /v1/chat/completions
    return OpenAIChatCompletionClient(
        model=os.environ["ROUTER_ONE_MODEL_ID"],
        api_key=os.environ["ROUTER_ONE_API_KEY"],
        base_url="https://api.router.one/v1",
        model_info=MODEL_INFO,
        **extra,
    )


async def get_order_status(order_id: str) -> str:
    """Look up the shipping status of an order."""
    return f"Order {order_id} shipped yesterday and arrives on Friday."


async def main() -> None:
    # 1. One tool call, then a second request (with the same tools) that writes the answer
    client = router_one_client()
    agent = AssistantAgent(
        "support_agent",
        model_client=client,
        tools=[get_order_status],
        system_message="You answer order questions. Call the tool for order data.",
        max_tool_iterations=2,
    )
    result = await agent.run(task="Where is order A-1042?")
    print(result.messages[-1].to_text())
    print(client.total_usage())  # usage the API reported, summed
    await client.close()

    # 2. Streaming: without stream_options the reported usage stays at 0
    stream_client = router_one_client(stream_options={"include_usage": True})
    streamer = AssistantAgent("streamer", model_client=stream_client, model_client_stream=True)
    await Console(streamer.run_stream(task="Say hello in one sentence."))
    await stream_client.close()


if __name__ == "__main__":
    asyncio.run(main())
```

## What each model_info key changes in AutoGen 0.7.5

AutoGen checks model_info before it builds a request and refuses features the flags rule out, so a wrong flag either blocks a feature the model has or lets through a request the model cannot serve. A True flag adds no capability: the model page on /models decides, and one real request confirms it. Two constructor options shape messages independently of model_info: user messages carry a name field set to their source (user for the task), which include_name_in_message=False removes if a model rejects the field; add_name_prefixes=True additionally writes "<source> said:" at the start of the text but keeps the name field, so combine it with include_name_in_message=False when you need the speaker in the text without the field. The rows below were checked in the 0.7.5 source and against a local mock server.

| model_info key | What AutoGen 0.7.5 does with it | Value for a Router One ID |
| --- | --- | --- |
| vision | False: AssistantAgent replaces each image with the text <image> before sending, and a direct create() call with an image raises ValueError. True: images go out as image_url content parts | True only if the model page lists Vision |
| function_calling | False: an AssistantAgent built with tools raises ValueError before any request. True: each tool goes out in the tools field as a function schema, with strict false unless the tool sets it | True only if the model page lists Tool calling |
| json_output | Gates JSON mode: json_output=True adds response_format json_object, and with the flag False it raises ValueError before sending | True only after JSON mode works on one real request |
| structured_output | Gates Pydantic output: output_content_type, or json_output set to a model class, sends response_format json_schema with strict true through the openai library's parse helper; False raises ValueError, and a missing key only warns at construction, then raises KeyError: 'structured_output' the first time structured output is requested | Set it explicitly; True only after structured output works for this model |
| family | Required. Among other things it selects message shaping and one team behavior: a Claude constant drops user and assistant text messages that are empty or whitespace-only, a Gemini constant replaces empty content with a space, an OpenAI constant makes SelectorGroupChat send its speaker prompt as a system message instead of a user message, and R1 parses <think> tags. The constants stop at gpt-5, claude-4 and gemini-2.5 | ModelFamily.UNKNOWN ("unknown"): AutoGen then tries a prefix match on the model name, which no current catalog ID matches, prefixed or bare, so the default shaping applies |
| multiple_system_messages | Missing or False: adjacent system messages are merged into one, and a second system message that is not adjacent to the first raises ValueError before sending. True: each system message is sent where it sits | Leave it False unless memory or another component appends system messages; then set True and confirm one request |

## Which AutoGen class sends which request

OpenAIChatCompletionClient is the class this guide configures, and it only ever calls Chat Completions. Other AutoGen components bring clients of their own and call other endpoints, some of which Router One does not serve, so check the component before pointing it at the gateway.

| AutoGen component | Request it sends | What to check |
| --- | --- | --- |
| OpenAIChatCompletionClient (create and create_stream) | POST /v1/chat/completions, with stream true when streaming | Any current chat model; the Logs entry shows the exact model and status |
| ChatCompletionClient.load_component(config) with provider autogen_ext.models.openai.OpenAIChatCompletionClient | The same client and requests, built from a component config, the declarative format AutoGen uses for configuration-based tools such as AutoGen Studio | config takes the same keys, model_info included. dump_component() keeps api_key as a secret value, but its JSON form (dump_component().model_dump_json(), or a file written from it) writes api_key as **********, so a config loaded from that JSON sends that string as the key, which the gateway rejects as an invalid key with 401; leave api_key out and set OPENAI_API_KEY, or add the key when loading |
| autogen_ext.agents.openai.OpenAIAgent | POST /v1/responses through the client you pass it, with store true by default | Only IDs whose model page lists /v1/responses (currently listed GPT-family, DeepSeek and Grok chat models); any other ID returns 400 must be called via. It takes only built-in Responses tools, not your Python functions: the image_generation tool is rejected with 400 (generate images through /v1/images/generations), and file_search needs vector_store_ids, and Router One serves no Files or Vector Stores API to create them. From the second turn on it continues by previous_response_id, so confirm multi-turn and web_search_preview or any other hosted tool on one real request before relying on it |
| autogen_ext.agents.openai.OpenAIAssistantAgent | The Assistants API: assistants, threads and files | Not served by Router One; use AssistantAgent with OpenAIChatCompletionClient |
| ChromaDBVectorMemory with OpenAIEmbeddingFunctionConfig | Embedding requests to /v1/embeddings | Not served by Router One; keep ChromaDB's default embedding function (all-MiniLM-L6-v2) or use another embedding provider |

## Count the requests behind one run or one team

An AutoGen run is a series of model calls, and every call is a separate request in Dashboard → Logs with its own request_id, tokens and cost. An AssistantAgent answers in one request when it calls no tool. With a tool and the defaults, max_tool_iterations 1 and reflect_on_tool_use False, it is still one request: the tool runs and its raw output becomes the reply. reflect_on_tool_use=True adds one request to write the answer, and a higher max_tool_iterations allows that many tool rounds, each resending the conversation so far, so input tokens grow per round. Teams multiply this. In RoundRobinGroupChat and SelectorGroupChat each turn by an agent that has a model client, such as AssistantAgent, is at least one request, and max_turns defaults to None, meaning no limit, so set max_turns or a termination condition such as MaxMessageTermination. SelectorGroupChat also asks its own model_client to pick the next speaker whenever more than one participant is eligible and no selector_func decides; a reply that names no valid participant is retried up to max_selector_attempts, 3 by default, before the team falls back to the previous speaker or the first participant. In a local test against a mock server whose replies named no agent, a two-agent SelectorGroupChat with max_turns=2 sent five requests, three of them selection attempts. Below the framework, the openai library retries connection errors and 408, 409, 429 and 5xx responses twice by default, so one failing call can be three requests; pass max_retries=0 to the client while testing. A 402 is not retried. Give each agent process a dedicated key with maxSpend: a runaway loop then stops with HTTP 402 at the cap, so it cannot spend past that amount and your other keys keep working. models_usage on each message and client.total_usage() add up the usage the API reported, not the charge, and a streamed call without include_usage reports 0; the charge is what Logs records. One context class needs a number AutoGen cannot look up: TokenLimitedChatCompletionContext without token_limit calls remaining_tokens, which raises KeyError for catalog IDs, so pass token_limit explicitly, at or below the context window on the model's page, and treat its counts as local tiktoken cl100k_base estimates. Router One records the model calls; it does not run AutoGen's agents, tools, memory or team turns.

## AG2: a separate project with its own config classes

AG2 describes itself as formerly AutoGen but is a separate project with its own maintainers, packages and classes, and it needs no model_info. AG2 1.x (pip install "ag2[openai]", import ag2; this guide checked 1.0.6) configures a model with OpenAIConfig: model takes the catalog ID unchanged, base_url the /v1 base URL, and api_key the key, which falls back to OPENAI_API_KEY when omitted. Every call is POST /v1/chat/completions. streaming=True adds stream_options include_usage by itself, and after a tool call the agent sends the result back in a second request without further settings. OpenAIResponsesConfig in the same module calls /v1/responses instead, with store defaulting to true, so it fits only IDs whose model page lists that endpoint. Code that imports autogen and builds ConversableAgent or LLMConfig is AG2 Classic, covered in the FAQ below. AG2 1.x (OpenAIConfig) and AG2 Classic were both checked against a local mock server.

`ag2_router_one.py`

```python
# AG2 1.x: python -m pip install "ag2[openai]"
import asyncio
import os

from ag2 import Agent
from ag2.config import OpenAIConfig

agent = Agent(
    "assistant",
    prompt="You answer order questions.",
    config=OpenAIConfig(
        model=os.environ["ROUTER_ONE_MODEL_ID"],
        api_key=os.environ["ROUTER_ONE_API_KEY"],
        base_url="https://api.router.one/v1",
        streaming=True,  # also requests stream_options include_usage
    ),
)


async def main() -> None:
    reply = await agent.ask("Say hello in one sentence.")
    print(reply.body)


asyncio.run(main())
```

## Which model ID should AutoGen send?

Copy the exact model ID from /models, preserving case, hyphens, and version suffixes; do not substitute a display name. Open its detail page and match the supported API endpoints, context window, and capabilities such as tool calling to the provider and features selected in AutoGen. A catalog listing does not mean the client can use every feature of that model. Give each client or application a dedicated API key with a maxSpend cap.

## Which API protocol is AutoGen using?

OpenAI-compatible describes an interface format; it does not make Chat Completions (/v1/chat/completions), Responses (/v1/responses), and Anthropic Messages (/v1/messages) interchangeable. Check the installed client version, provider configuration, and actual request path against the model detail page and API compatibility fact sheet. A successful plain-text chat does not establish support for hosted tools, conversation state, or file-editing features.

## Verify the AutoGen call in your request trace

Send a simple text request from AutoGen, then match its trace in Dashboard → Logs by time, model, and request_id: tokens, cost, latency, and status. Next, test streaming, tool calls, and multi-turn history separately. For failures, retain the actual request path, full error message, and request_id. If there is no matching log, check client configuration and connectivity before attributing the error to the gateway or upstream.

## FAQ

### Why does OpenAIChatCompletionClient raise ValueError: model_info is required when model name is not a valid OpenAI model?

Because AutoGen looks up capabilities in a table of vendor model names bundled with autogen-ext, and no Router One catalog ID is in it: anthropic/claude-sonnet-5 and openai/gpt-5.5 carry a prefix, and grok-4.7 or deepseek-v4.1-flash are simply not listed. The error is raised in the constructor, so nothing reaches Router One and there is no Logs entry. Pass model_info with at least vision, function_calling, json_output and family; without family the message reads Missing required field 'family' in ModelInfo, followed by a note that the required fields have been enforced since v0.4.7. Add structured_output too: leaving it out emits a UserWarning at construction, which says the field will be required in a future version, and a KeyError as soon as you request structured output. model_capabilities is the deprecated predecessor and cannot be combined with model_info. The values are statements your code makes to AutoGen, not settings the gateway reads: a True flag lets AutoGen send that feature, and the model decides whether it works, so take vision and function_calling from the model page's Vision and Tool calling labels and confirm tools, images and structured output with one real request each.

### Streaming works, but models_usage and total_usage() report 0 tokens. Was the call free?

No. A streamed Chat Completions response carries token counts only when the request asks for them with stream_options include_usage, and AssistantAgent calls create_stream without that option, so AutoGen records RequestUsage(prompt_tokens=0, completion_tokens=0); the official agents tutorial shows the same zeros for a streamed reply. Pass stream_options={"include_usage": True} to the OpenAIChatCompletionClient that the streaming agent uses, as in the example above, and the last chunk then carries the usage AutoGen adds up. When you call the client yourself, create_stream(..., include_usage=True) does the same for one call. Keep that client for streaming: the constructor value is also sent with non-streaming create() calls, as stream_options next to stream false, and the openai library's own parameter documentation says to set stream_options only when stream is true. Whatever AutoGen reports, the charge for each request is its entry in Dashboard → Logs, and a zero or missing usage figure in your application means unknown, not unbilled.

### Adding memory to an AssistantAgent raises ValueError: Multiple and Not continuous system messages are not supported. How do I fix it?

Memory classes such as ListMemory add the retrieved content to the model context as a system message after the conversation so far, while the agent's own system message stays first; AssistantAgent adds a default system message when you omit system_message, and only system_message=None removes it. With multiple_system_messages missing or False, the client merges adjacent system messages but refuses a second one that is not adjacent to the first, so the error appears before any request is sent. Set "multiple_system_messages": True in model_info and the memory's system message goes out where it sits, after the user turn; confirm on one real request that the selected model accepts a system message in that position. The alternative is system_message=None on the agent, which leaves the memory's system message as the only one; move your instructions into the memory content or the task in that case. Any component that appends a system message can raise the same error, not only memory.

### My code says import autogen and uses ConversableAgent or config_list. Does this guide apply?

Not directly. That is the AutoGen 0.2 API, which Microsoft replaced in 0.4 with the autogen-agentchat and autogen-ext packages used above. The same API continues as AG2 Classic, which its maintainers publish on PyPI as autogen (0.14.1 when this guide was checked, in maintenance mode), so pip install autogen installs AG2 Classic rather than Microsoft's packages; install it as pip install "autogen[openai]" so the openai library that api_type openai needs is present. In AG2 Classic, put the Router One values in LLMConfig({"api_type": "openai", "model": "<exact-model-id>", "api_key": ..., "base_url": "https://api.router.one/v1"}), or give each entry of an OAI_CONFIG_LIST file the same keys and load it with LLMConfig.from_json(path="OAI_CONFIG_LIST"). Pass the dictionary positionally: keyword arguments such as LLMConfig(api_type=...) or LLMConfig(config_list=...) raise TypeError in 0.14.1. AG2 1.x (import ag2) is a different API again and uses the OpenAIConfig shown above. None of them needs model_info, and in a local test AG2 1.x and AG2 Classic both sent the catalog ID unchanged to /v1/chat/completions. Moving to Microsoft's 0.4+ API or to Agent Framework is a code migration, not a configuration change.

### Which models can AutoGen use through the gateway?

Choose a current catalog model that supports both the endpoint and the features AutoGen uses. Check /models and the model detail page for the exact ID, current rates, and capabilities; a family name such as GPT or Claude is not a compatibility guarantee. A client that lists a model has only read the ID, from its own configuration or from GET /v1/models; verify an actual request too.

### Models are listed, but requests fail with 400 or 404. What should I check?

Record the actual request path and error message, then check the exact model ID. A 400 can indicate invalid parameters, unsupported tools, or a model/endpoint mismatch; a 404 can indicate an incorrect path or missing resource, so it does not by itself establish that a model was retired. If the error says must be called via, use the named endpoint or select a model supported on the current endpoint. Do not add or remove /v1 or /chat/completions across all clients indiscriminately.

### Does this work from Mainland China?

Yes. The gateway is reachable from Mainland China without a VPN, and the configuration is identical to the global setup.

### How do I debug a 401/402/403/429?

Match the request and error message in Dashboard → Logs. For 401, check whether the key was sent and is valid; for 402, check wallet balance and maxSpend; for 403, check key permissions and access restrictions. For 429, distinguish request/token limits from upstream throttling using the error details. Keep the request_id and follow the error-codes reference.

## See also

- All integration guides: https://router.one/integrations
- Debug API errors in AutoGen: https://router.one/llm-api-error-codes
- API compatibility: endpoints and supported features: https://router.one/facts/api-compatibility.md
- Responses API setup and limits: https://router.one/codex-responses-api
- Crush setup: https://router.one/integrations/crush
- JetBrains AI Assistant setup: https://router.one/integrations/jetbrains-ai-assistant
- Microsoft Agent Framework setup: AutoGen's successor: https://router.one/integrations/microsoft-agent-framework
- Streaming responses and usage chunks: https://router.one/llm-streaming
- Structured outputs on the gateway: https://router.one/llm-structured-outputs
- AutoGen: model clients overview: https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/tutorial/models.html
- AutoGen: autogen_ext.models.openai reference: https://microsoft.github.io/autogen/stable/reference/python/autogen_ext.models.openai.html
- AutoGen 0.7.5 source: OpenAI chat completion client: https://github.com/microsoft/autogen/blob/python-v0.7.5/python/packages/autogen-ext/src/autogen_ext/models/openai/_openai_client.py
- Migration guide: AutoGen to Microsoft Agent Framework: https://learn.microsoft.com/en-us/agent-framework/migration-guide/from-autogen/
- AG2: model configuration: https://docs.ag2.ai/docs/user-guide/model_configuration/
- What the gateway layer does: https://router.one/llm-api-gateway
- OpenAI-compatible API: https://router.one/openai-compatible-api
- API docs: https://router.one/docs
- Canonical page: https://router.one/integrations/autogen
- Models and per-model token rates: https://router.one/models (markdown: https://router.one/models.md)
- Pricing: https://router.one/pricing
- API docs (markdown): https://router.one/docs.md
- Company facts: https://router.one/facts/company.md
