# 用 Chat Completions 客户端把 Microsoft Agent Framework 接到 Router One

> https://router.one/zh/integrations/microsoft-agent-framework 的 Markdown 镜像，供 AI 助手与爬虫使用。Router One 是 OpenAI 兼容的统一 LLM API 网关。
> 最后更新：2026-09-19

Microsoft Agent Framework 是微软开源的 Agent 与工作流 SDK，支持 Python 和 .NET，也是 Semantic Kernel 与 AutoGen 的直接后继；两种语言都在 2026 年 4 月 2 日发布了 1.0。它的 OpenAI provider 可以连接任意 OpenAI 兼容端点，指向 Router One 后就能调用目录里在 /v1/chat/completions 上提供服务的所有聊天模型，每次请求都有成本和延迟 Trace；Agent 循环、函数工具、session、middleware 和工作流仍在你的进程里运行。有一个命名细节决定请求打到哪个端点：在 1.x 的 Python 包里，OpenAIChatCompletionClient 调用 Chat Completions，而 OpenAIChatClient（1.0 之前的代码用它表示 Chat Completions）现在调用的是 Responses API。本指南用 model、api_key 和 base_url 构造一个 OpenAIChatCompletionClient，运行带一个函数工具的 Agent，在同一个 session 上流式跑第二轮，给工具循环设上限，给出 .NET 里通过 OpenAIClientOptions.Endpoint 和 AsAIAgent 的等价写法，并列出 Semantic Kernel 与 AutoGen 里对应的设置。核对版本：agent-framework 1.19.0、agent-framework-openai 1.14.4、Microsoft.Agents.AI.OpenAI 1.22.0。

## 安装 agent-framework-openai 并设置凭证

使用 Python 3.10 或更新版本。OpenAI 客户端在 agent-framework-openai 包里，它会带上 agent-framework-core 和 openai Python 库；pip install agent-framework 则安装整套标准包。已正式发布的包不再需要 --pre。没装 provider 包就从 agent_framework.openai 导入客户端，会抛出点名 agent-framework-openai 的 ModuleNotFoundError。下面是 macOS/Linux 的 shell 示例，运行前替换两个占位符：一把 Router One Key，以及当前目录中某个聊天模型的精确 ID。ROUTER_ONE_* 是本示例自己定义并显式读取的变量名。参数省略时客户端有自己的回退变量：OPENAI_API_KEY、OPENAI_BASE_URL，模型则先读 OPENAI_CHAT_COMPLETION_MODEL，再读 OPENAI_MODEL。Agent Framework 不会自动加载 .env 文件；如果把这些值放在文件里，需要自己调用 load_dotenv() 或传入 env_file_path。运行 Python 文件时沿用同一环境。

`terminal`

```bash
python -m pip install agent-framework-openai
export ROUTER_ONE_API_KEY="sk-your-router-one-key"
export ROUTER_ONE_MODEL_ID="<exact-model-id-from-/models>"
```

## 把 Microsoft Agent Framework 配置到 Router One base URL

保存为 maf_router_one.py，再运行 python maf_router_one.py。OpenAIChatCompletionClient 的 model 原样填目录里的模型 ID，若 ID 自带 provider 前缀也要保留；base_url 填带 /v1 的 base URL，它会交给 openai 库的 AsyncOpenAI 客户端，客户端自行拼接 /chat/completions，所以一次运行里的每个模型来回都是一个 POST /v1/chat/completions；api_key 从 ROUTER_ONE_API_KEY 读取，显式传入还能保证即使 shell 里存在 AZURE_OPENAI_* 变量，客户端也走 OpenAI 路由。@tool 装饰器把带类型标注的 Python 函数变成函数工具：它的 JSON schema 放在请求的 tools 字段里，模型要求调用时，框架在你的进程里执行这个函数，并在下一次请求里把结果发回去。approval_mode 写出来是因为官方示例也这样写；never_require 是默认值，always_require 会在得到明确批准之前不执行该调用。function_invocation_configuration 给这个循环设上限，这里是每次运行最多 5 个模型来回、10 次工具执行。Agent(client=..., instructions=..., tools=[...]) 是当前的构造方式，client.as_agent(...) 构造的是同一个对象。agent.create_session() 返回 AgentSession；用这个客户端时，历史保存在你进程内的 session 里，每次运行都会作为 messages 重新发送。第一次运行返回 AgentResponse：text 是最终答案，usage_details 是 API 为这次运行的各个请求报告的输入、输出 token 之和。第二次运行传入 stream=True 并遍历 AgentResponseUpdate 分块；请求本身相同，只是带上 stream 和 stream_options include_usage。

`maf_router_one.py`

```python
import asyncio
import os
from typing import Annotated

from agent_framework import Agent, tool
from agent_framework.openai import OpenAIChatCompletionClient


@tool(approval_mode="never_require")
def get_order_status(order_id: Annotated[str, "The order number to look up."]) -> str:
    """Look up the shipping status of an order."""
    return f"Order {order_id} shipped yesterday and arrives on Friday."


async def main() -> None:
    # Chat Completions client: every model round trip is one POST /v1/chat/completions
    client = OpenAIChatCompletionClient(
        model=os.environ["ROUTER_ONE_MODEL_ID"],
        api_key=os.environ["ROUTER_ONE_API_KEY"],
        base_url="https://api.router.one/v1",
    )
    # Bound the tool loop (defaults: max_iterations=40, max_function_calls=None)
    client.function_invocation_configuration.update({"max_iterations": 5, "max_function_calls": 10})

    agent = Agent(
        client=client,
        name="SupportAgent",
        instructions="You answer order questions. Call the tool when you need order data.",
        tools=[get_order_status],
    )
    session = agent.create_session()  # history stays in this process

    # 1. Non-streaming run: text is the answer, usage_details sums the reported tokens
    result = await agent.run("Where is order A-1042?", session=session)
    print(result.text)
    print(result.usage_details)

    # 2. Same session, streamed: same request with stream=true
    async for update in agent.run("When does it arrive?", session=session, stream=True):
        if update.text:
            print(update.text, end="", flush=True)
    print()


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

## Agent Framework 的哪个设置发出哪种请求

端点由客户端类决定，每个请求带什么内容由 Agent 决定。下表按本指南涉及的设置，给出该填的值、它产生的请求，以及使用前要确认的事项。同一个包里有一个类不在本指南范围内：OpenAIEmbeddingClient 请求的是 /v1/embeddings，Router One 不提供该端点，所以 RAG 流程的 embedding 要留在其他服务商或本地模型上。

| Agent Framework 设置 | 填什么 / 产生的请求 | 怎么验证 |
| --- | --- | --- |
| OpenAIChatCompletionClient → base_url（回退：OPENAI_BASE_URL） | https://api.router.one/v1 | 交给 openai 库的 AsyncOpenAI 客户端，由它在后面拼 /chat/completions；第一次运行就报 404 not_found，说明 /v1 少了或重复了 |
| OpenAIChatCompletionClient → model（回退：先 OPENAI_CHAT_COMPLETION_MODEL，再 OPENAI_MODEL） | 精确的目录 ID | Logs 里每条 Trace 的模型与 /models 逐字一致；关键字是 model，1.0 之前代码里的 model_id 会触发 TypeError |
| OpenAIChatCompletionClient → api_key（回退：OPENAI_API_KEY） | 为这个 Agent 单独创建、设了 maxSpend 的 Router One Key | 第一次请求出现在 Dashboard → Logs 里这把 Key 名下；显式传入 Key 时，即使设置了 AZURE_OPENAI_* 变量，客户端也走 OpenAI 路由 |
| 用同样三个参数构造 OpenAIChatClient（模型回退：OPENAI_CHAT_MODEL） | POST /v1/responses：在 1.x 的包里，这个类是 Responses API 客户端 | 只适用于详情页列出 /v1/responses 的 ID（当前在售的 GPT 系列与 DeepSeek ID），其他系列会返回 400 must be called via。带 session 时默认用 previous_response_id 续接，除非 options 里带 store=False，所以多轮对话要用一次真实请求确认 |
| Agent(tools=[...]) 配合 @tool 函数 | 带 tools 字段（JSON schema）的 Chat Completions；函数在你的进程里执行 | 模型详情页列出工具调用；get_web_search_tool() 加的是 web_search_options，属于服务商托管字段，依赖它之前先用一次真实请求确认 |
| client.function_invocation_configuration | max_iterations（默认 40）限制模型来回次数；max_function_calls 和 max_duration_seconds 默认为 None | max_iterations 用完后，还会再发一次 tool_choice 为 none 的请求来写出最终答案，所以不算重试时一次运行最多 max_iterations + 1 个请求 |
| agent.run(..., stream=True) | 同一个请求，改为 stream=true 并带 stream_options include_usage | 模型页的流式支持；中途断开的流记为 HTTP 499 client_cancelled |
| agent.create_session() | 历史由 InMemoryHistoryProvider 保存在 session 的 state 里，每次运行都作为 messages 重新发送 | 输入 token 逐轮增长；用 session.to_dict() 和 AgentSession.from_dict() 持久化。Chat Completions 没有服务端会话状态，session 就是这段历史的唯一副本 |

## .NET：设置 OpenAIClientOptions.Endpoint 并调用 AsAIAgent

在 .NET 里，连接由官方 OpenAI 库负责，Agent Framework 在它之上加一层 Agent。安装 Microsoft.Agents.AI.OpenAI；Learn 页面的命令仍带 --prerelease，但 NuGet 上已有稳定的 1.x 版本（核对本指南时为 1.22.0），所以这个参数可以不加。OpenAIClientOptions.Endpoint 要填带 /v1 的 URL：该库的默认端点是 https://api.openai.com/v1，并且会自己拼接 /chat/completions，所以只填 https://api.router.one 会得到 404 not_found。GetChatClient(model) 选的是 Chat Completions，AsAIAgent 扩展方法把它包装成 ChatClientAgent；调用 CreateAIAgent 的代码早于 2026 年 1 月改名为 AsAIAgent 的那次变更。GetResponsesClient().AsAIAgent(model: ...) 是 Responses 版本，模型系列规则与 Python 的 OpenAIChatClient 相同。函数工具通过 tools: [AIFunctionFactory.Create(Method)] 传入，经由 Microsoft.Extensions.AI 的 FunctionInvokingChatClient 在你的进程里执行，它的 MaximumIterationsPerRequest 默认是 40。RunAsync 返回完整响应，RunStreamingAsync 以流式返回更新。

`Program.cs`

```csharp
// dotnet add package Microsoft.Agents.AI.OpenAI
using System.ClientModel;
using Microsoft.Agents.AI;
using OpenAI;
using OpenAI.Chat;

var key = Environment.GetEnvironmentVariable("ROUTER_ONE_API_KEY")
    ?? throw new InvalidOperationException("ROUTER_ONE_API_KEY is not set.");
var model = Environment.GetEnvironmentVariable("ROUTER_ONE_MODEL_ID")
    ?? throw new InvalidOperationException("ROUTER_ONE_MODEL_ID is not set.");

AIAgent agent = new OpenAIClient(
        new ApiKeyCredential(key),
        new OpenAIClientOptions { Endpoint = new Uri("https://api.router.one/v1") })
    .GetChatClient(model)
    .AsAIAgent(instructions: "You answer order questions.", name: "SupportAgent");

Console.WriteLine(await agent.RunAsync("Where is order A-1042?"));
```

## 给一次 Agent 运行定预算，并逐次核对请求

一次 agent.run() 是一个循环，不是一个请求。直接回答只有一个请求；每一批工具调用都会多一个来回，因为工具结果要在新的请求里发回模型，同时把到目前为止的对话重新发送，所以输入 token 每轮都在增长。max_iterations（默认 40）限制来回次数，用完之后框架还会再发一次 tool_choice 为 none 的请求来拿到最终答案；max_function_calls 和 max_duration_seconds 默认不设限，并且文档说明它们只在每一批并行工具调用结束后才检查，所以某一批可能超出限制。它们都不是花费上限。在框架之下，openai 库默认对连接错误和 408、409、429、5xx 响应重试 2 次。框架构造这个客户端时没有传 max_retries，所以要调整就传入自己的客户端：async_client=AsyncOpenAI(base_url=..., api_key=..., max_retries=0)。每一次真正到达网关的尝试在 Dashboard → Logs 里都是独立请求，有各自的 request_id、Trace 和费用。给每个 Agent 单独一把设了 maxSpend 的 Key：失控的循环会在你设的上限处收到 HTTP 402，openai 库不会重试 402，钱包和其他 Key 不受影响。用 WorkflowBuilder 搭的工作流同样在你的进程里运行，每个 Agent executor 的模型调用都是同一把 Key 上的又一个请求。Agent Framework 的 OpenTelemetry 层默认开启，但在你配置 exporter（例如调用 configure_otel_providers()）之前不会导出任何数据；它会产生 invoke_agent、chat 和 execute_tool 三类 span，只有开启敏感数据记录（ENABLE_SENSITIVE_DATA，默认关闭）时才记录提示词。这些 span 是你应用自己的 trace，导出到哪里就留在哪里。result.usage_details 是框架对 API 报告用量的求和；费用以 Logs 记录为准。按 Key、时间、模型和 token 数对账，报障时保留 request_id。Router One 只记录模型调用元数据，不运行 Agent 循环，也不运行你的工具、middleware 或工作流。

## Microsoft Agent Framework 该填哪个模型 ID？

从 /models 页复制精确的模型 ID，保留大小写、连字符和版本后缀，不要用展示名称代替。打开该模型的详情页，核对支持的 API 端点、上下文窗口和工具调用等能力，再与 Microsoft Agent Framework 当前选择的 provider 和功能对应。模型出现在目录里，不等于当前客户端能调用它的所有功能。建议为每个工具单独建 API Key，并设置 maxSpend 消费上限。

## Microsoft Agent Framework 用的是哪种 API 协议？

OpenAI 兼容描述的是接口格式，不能据此推断 Chat Completions（/v1/chat/completions）、Responses（/v1/responses）和 Anthropic Messages（/v1/messages）可以互换。先核对工具当前版本、provider 配置和实际请求路径，再查模型详情与 API 兼容性事实页。一次普通对话成功，也不能证明服务端工具、历史状态或文件编辑功能都受支持。

## 在 trace 里验证 Microsoft Agent Framework 的调用

先在 Microsoft Agent Framework 发出一次简单文本请求，再到 Dashboard → Logs 按时间、模型和 request_id 核对 trace：tokens、花费、延迟和状态码。成功后再分别验证流式输出、工具调用和多轮历史。失败时保留实际请求路径、完整错误消息及 request_id；没有对应日志时，先查客户端配置和网络，不能仅凭客户端报错认定是网关或上游故障。

## 常见问题

### 第一次运行就抛出 ChatClientException: service failed to complete the prompt，里面包着的错误是什么意思？

客户端把 openai 库抛出的异常包了一层：消息以 service failed to complete the prompt: 结尾，后面跟着库自己的文本，例如 Error code: 404 和响应体，原始异常可以从 __cause__ 取到。看状态码。404 not_found 说明 base_url 少了 /v1 或写了两次：openai 库会在你传入的地址后面拼 /chat/completions，只填 https://api.router.one 会打到 /chat/completions，网关的消息会直接说明 base URL 必须以 /v1 结尾。401 AUTH_INVALID_API_KEY 是网关没有接受这把 Key，核对运行该文件的环境里 ROUTER_ONE_API_KEY 的值。400 且带 must be called via，说明模型 ID 被发到了不提供它的端点；在这个框架里，原因几乎总是代码构造的是 OpenAIChatClient：在 1.x 的包里这个类调用 /v1/responses，Chat Completions 对应的类是 OpenAIChatCompletionClient。还有两个错误在发出任何请求之前就会出现，类型都是 SettingNotFoundError。Model must be specified via the 'model' parameter or the 'OPENAI_CHAT_COMPLETION_MODEL', 'OPENAI_MODEL' environment variable 表示构造函数既没拿到参数，也没找到环境变量。Azure OpenAI client requires either an API key or an Azure AD token provider 表示没有找到 Key：既没有 api_key 也没有 OPENAI_API_KEY 时，构造函数会落到 Azure 路由分支，所以消息里提到 Azure，而真正要做的是提供 Router One Key。

### 我的代码导入了 OpenAIResponsesClient、ChatAgent 或 create_agent，或者传了 model_id。哪里变了？

这些都是预览阶段的名字，Microsoft Learn 上的 Python significant-changes 页面记录了这些改名。在 python-1.0.0rc6 里，OpenAI 客户端移到了 agent-framework-openai 包，OpenAIResponsesClient 改名为 OpenAIChatClient，原来的 OpenAIChatClient 改名为 OpenAIChatCompletionClient，所有地方的 model_id 改为 model，预览阶段的环境变量 OPENAI_CHAT_MODEL_ID 和 OPENAI_RESPONSES_MODEL_ID 换成了 OPENAI_CHAT_MODEL、OPENAI_CHAT_COMPLETION_MODEL 和 OPENAI_MODEL。更早的 beta 版本把 ChatAgent 改为 Agent（参数 chat_client= 改为 client=），create_agent 改为 as_agent，run_stream(...) 改为 run(..., stream=True)，AgentThread 和 get_new_thread() 改为 AgentSession 和 create_session()，@ai_function 改为 @tool。Assistants 客户端在 python-1.0.0 中被移除，而 Router One 本来也不提供 Assistants API。对网关用户来说，容易踩的是第二条改名：1.0 之前写着 OpenAIChatClient(base_url=...) 的代码当时走 Chat Completions，现在走 /v1/responses，所以原来可用的 Claude、Gemini 或 Grok ID 在升级后会返回 400 must be called via。把类换成 OpenAIChatCompletionClient，把关键字换成 model 即可。

### 我从 Semantic Kernel 或 AutoGen 迁移过来，原来的 base URL 是在哪个设置里？

在 .NET 版 Semantic Kernel 里是 endpoint 参数：AddOpenAIChatCompletion(modelId: ..., apiKey: ..., endpoint: new Uri(...))，URI 填 https://api.router.one/v1，文档把它标为实验特性，需要 #pragma warning disable SKEXP0010；它最终设置的是上文同一个 OpenAI 库选项，所以 URL 要带 /v1。在 Python 版 Semantic Kernel 里，OpenAIChatCompletion 没有 base URL 参数，要传入一个客户端：OpenAIChatCompletion(ai_model_id=..., async_client=AsyncOpenAI(base_url=..., api_key=...))。在 AutoGen 里是 autogen_ext.models.openai 的 OpenAIChatCompletionClient(model=..., base_url=..., api_key=..., model_info={...})，参考文档写明：模型名不是有效的 OpenAI 模型时必须提供 model_info，带前缀的目录 ID 正属于这种情况。Agent Framework 里同名的类在 agent_framework.openai 下，不需要 model_info：model、api_key 和 base_url 就够了。有一个行为差异会影响预算：AutoGen 迁移指南指出，AssistantAgent 在不调高 max_tool_iterations 时只跑单轮，而 Agent Framework 的 Agent 会持续调用工具直到得出最终答案，所以把工具调用多的 Agent 迁过来之前，先设好 function_invocation_configuration 和按 Key 的 maxSpend。

### Microsoft Agent Framework 能通过网关用哪些模型？

选用当前目录中同时支持 Microsoft Agent Framework 所用端点和所需功能的模型。精确 ID、当前单价与能力以 /models 及模型详情为准；不要仅按 GPT、Claude 等系列名判断兼容性。客户端能列出模型，只证明模型发现成功，仍需验证实际调用。

### 能列出模型，但调用报 400 或 404，怎么办？

先记录实际请求路径和错误消息，再核对精确模型 ID。400 可能是参数、工具类型或模型与端点不匹配；404 可能是请求路径或资源不存在，不能直接判定模型下线。若错误提示 must be called via，按它指明的端点调整客户端 provider，或换用支持当前端点的模型。不要在所有工具里统一增删 /v1 或 /chat/completions。

### 中国大陆能直连吗？

能。网关在大陆可直连、无需 VPN，配置与全球环境完全一致。

### 报 401/402/403/429 怎么排查？

先到 Dashboard → Logs 核对请求及错误消息。401 查 Key 是否传入和有效；402 查钱包余额与 maxSpend；403 查 Key 权限和访问限制；429 查请求频率、token 限额及上游限流，按错误来源处理。保留 request_id，再按错误码速查页逐项排查。

## 相关页面

- 全部接入指南：https://router.one/zh/integrations
- Microsoft Agent Framework 的 API 错误排查：https://router.one/zh/llm-api-error-codes
- API 兼容性：端点与功能对照：https://router.one/zh/facts/api-compatibility.md
- Responses API 配置与限制：https://router.one/zh/codex-responses-api
- Flowise 接入：https://router.one/zh/integrations/flowise
- Langflow 接入：https://router.one/zh/integrations/langflow
- OpenAI Agents SDK 接入：https://router.one/zh/integrations/openai-agents-sdk
- 工具调用的 API 要求：https://router.one/zh/llm-tool-calling
- 按 Key 追踪模型调用成本：https://router.one/zh/llm-cost-tracking
- Agent Framework 官方文档：OpenAI provider：https://learn.microsoft.com/en-us/agent-framework/integrations/by-component/model-providers/openai
- Agent Framework 官方文档：函数工具：https://learn.microsoft.com/en-us/agent-framework/agents/tools/function-tools
- Agent Framework 官方文档：Python 重大变更：https://learn.microsoft.com/en-us/agent-framework/support/upgrade/python-2026-significant-changes
- 网关层负责什么：https://router.one/zh/llm-api-gateway
- OpenAI 兼容 API：https://router.one/zh/openai-compatible-api
- API 文档：https://router.one/zh/docs
- 本页规范地址：https://router.one/zh/integrations/microsoft-agent-framework
- 模型与每模型 token 价格：https://router.one/zh/models （markdown：https://router.one/zh/models.md ）
- 定价：https://router.one/zh/pricing
- API 文档（markdown）：https://router.one/zh/docs.md
- 公司事实（markdown）：https://router.one/zh/facts/company.md
