andrewyng/aisuite 架构拆解:Python 端 LLM 统一接口的两层抽象(Chat Completions + Agents)和它背后的工程取舍
posts posts 2026-06-13T21:03:20+08:00拆解 andrewyng/aisuite 的设计:Chat Completions API 统一多 provider,Agents API 提供工具治理与状态持久化。技术笔记Python, LLM, MCP, AI Agent, OpenAIandrewyng/aisuite 架构拆解:Python 端 LLM 统一接口的两层抽象(Chat Completions + Agents)和它背后的工程取舍
核心判断:aisuite 在 Chat Completions 统一接口之上加了一层 Agents API(Toolkits / MCP / Tool Policies / State Stores),并把"如何用这些能力搭一个生产级 Agent harness"做成参考实现 OpenWorker 一起发布。仓库的设计重心是让作者本人快速搭 Agent,切换不同 LLM 只是底层能力之一。
目录
- 一、项目坐标
- 二、与 LiteLLM 的分叉点
- 三、系统地图:两层抽象
- 四、Chat Completions API:统一接口的细节
- 五、Tool calling:max_turns 自动循环
- 六、Agents API:Agent + Runner
- 七、Toolkits:预制工具集
- 八、Tool Policies:审批与拦截
- 九、State Stores:断点续跑
- 十、MCP:作为一等公民
- 十一、安装与依赖管理
- 十二、和同类方案的对比
- 十三、采用建议
- 十四、常见问题 FAQ
- 十五、故障排查
- 十六、自测题
- 十七、练习
- 十八、进阶路径
- 十九、资料口径说明
学习目标
通过本文,你会了解:
- aisuite 的两层抽象(Chat Completions API + Agents API)分别解决什么问题
- Toolkits、MCP、Tool Policies、State Stores 四块拼图如何协同
- 和 LiteLLM、OpenAI Agents SDK、LangChain 的差异化取舍
- 什么场景该选 aisuite,什么场景该用别的方案
- 如何快速上手并评估 aisuite 是否适合你的项目
一、项目坐标
| 字段 | 值 |
|---|---|
| 仓库 | andrewyng/aisuite |
| 主语言 | Python(包名 aisuite,仓库内另有 aisuite-js/ 目录与 cli/ 命令行工具) |
| Stars | 约 16.2k(2026-09-07 经 GitHub API 核实的快照,会随时间变化) |
| License | MIT |
| 配套产物 | OpenWorker(macOS / Windows 桌面 AI 助手,开发已迁移到独立仓库 andrewyng/openworker) |
| Provider 支持 | README 主推 OpenAI、Anthropic、Google、Mistral、Hugging Face、AWS、Cohere、Ollama、OpenRouter、Requesty;aisuite/providers/ 目录另含 Groq、Cerebras、DeepSeek、xAI、Together、通义(tongyi)、SambaNova、Nebius、Fireworks、Watsonx、LM Studio、Deepgram(语音)等约 30 个适配文件 |
README 第一屏就是 OpenWorker 的下载链接,这透露出仓库的发布形态:pip install aisuite 拿到库,去 OpenWorker 独立仓库下载 dmg/exe 拿到参考实现,二者共用一套核心抽象。Andrew Ng 把自己的名字放进仓库名,意味着这个项目是他本人搭 Agent 的工作台,顺便开源给社区用。
二、与 LiteLLM 的分叉点
LiteLLM 的卖点是"一行切换 provider"。aisuite 表面上看起来一样(model="openai:gpt-4o" / model="anthropic:claude-3-5-sonnet-20240620"),但读完整套设计后会发现重心完全不同:
| 维度 | LiteLLM | aisuite |
|---|---|---|
| 主抽象 | Provider router | Provider router + Agent runtime |
| Tool calling | 直通 OpenAI 格式 | max_turns 自动循环 + 手动两条路径 |
| 内建工具集 | 无 | files / git / shell 三个预制 toolkit |
| MCP 支持 | Proxy 网关已原生支持;SDK 侧为实验性桥接 | 库内一等公民,内联配置 / 显式 MCPClient 两种方式 |
| Agent 运行态持久化 | 无内建 | in-memory / file / Postgres 三选 |
| 工具级策略 | SDK 无;Proxy 层有 guardrails 与工具过滤 | Runner 级 tool policy(策略类 + 自定义回调) |
| 参考应用 | 无 | OpenWorker 桌面端(独立仓库,macOS / Windows 安装包) |
| 安装门槛 | 低 | 低(opt-in extras) |
LiteLLM 后来补上了 MCP——它的 Proxy 走的是"网关"路线:把 MCP server 挂在代理后面统一管控,SDK 侧的 experimental_mcp_client 还标着实验性。aisuite 则把 MCP 直接做进库内。真正的分界线在 Agent 运行时:工具治理、状态持久化、参考应用这三件事,LiteLLM 至今没有对位物。aisuite 在"路由"之上多盖了一层 Agent harness——Toolkits、MCP、Policies、State Stores 四块拼图,这层是它和 LiteLLM 拉开差距的地方。
三、系统地图:两层抽象
整个仓库分两层。下层是 Chat Completions API,负责把请求路由到不同 provider 并处理 tool calling 循环;上层是 Agents API,负责把 Agent 定义、工具集、审批策略、状态持久化组装成一个可运行的 harness。
┌───────────────────────────────────────────────────────────┐
│ 用户代码 │
├─────────────────────────┬─────────────────────────────────┤
│ Chat Completions API │ Agents API │
│ (单轮 / 多轮对话) │ (Agent + Runner) │
│ │ │
│ client.chat │ Agent(name, model, tools, │
│ .completions │ instructions) │
│ .create() │ Runner.run / run_sync / │
│ │ continue_sync │
├─────────────────────────┴─────────────────────────────────┤
│ 共享能力层 │
│ - Provider Router (openai: / anthropic: / ollama: ...) │
│ - Tool calling (max_turns 自动循环) │
│ - Toolkits (files / git / shell) │
│ - MCP (内联配置 / 显式 MCPClient) │
│ - Tool Policies (策略类 / 自定义回调) │
│ - State Stores (in-memory / file / Postgres) │
├───────────────────────────────────────────────────────────┤
│ Provider SDKs (opt-in extras) │
│ openai | anthropic | google | mistral | cohere | ... │
└───────────────────────────────────────────────────────────┘两层之间的关系是:Agents API 内部调用 Chat Completions API 完成实际的模型推理和 tool calling 循环。你可以只用下层(Chat Completions),也可以用上层(Agents)拿到完整的 harness。
任务如何流过系统
以仓库文档里的 repo-helper agent 为例,追踪一个任务从入口到输出的完整路径:
- 定义 Agent:
Agent(name="repo-helper", model="anthropic:claude-sonnet-4-6", tools=[files, git], instructions=...) - 启动 Runner:
Runner.run_sync(agent, "What changed in the last commit?") - Runner 内部:把 task 字符串包装成 user message,调用
chat.completions.create(model, messages, tools, max_turns) - 第一轮推理:模型返回 tool_call(比如调用 git toolkit 的
git_diff) - 策略检查:如果 Runner 挂了 tool_policy,框架在每次工具执行前评估策略,通过才执行
- 工具执行:框架调用 git toolkit 里对应的函数,拿到 diff
- 结果回填:把 tool 结果作为 tool result message 喂回模型
- 第二轮推理:模型基于 diff 生成三条总结
- 返回:
result.final_output包含最终回答;如果传了state_store和thread_id,运行状态已持久化,后续可Runner.continue_sync(agent, 新任务, state_store=..., thread_id=...)续跑
这条路径穿过了 Agents API → Chat Completions API → Provider SDK 三层,每一层都有明确的职责边界。
下面逐层拆。
四、Chat Completions API:统一接口的细节
最基础的接口:
import aisuite as ai
client = ai.Client()
models = ["openai:gpt-4o", "anthropic:claude-3-5-sonnet-20240620"]
messages = [
{"role": "system", "content": "Respond in Pirate English."},
{"role": "user", "content": "Tell me a joke."},
]
for model in models:
response = client.chat.completions.create(
model=model,
messages=messages,
temperature=0.75,
)
print(response.choices[0].message.content)model 参数用 provider:model 格式(比如 openai:gpt-4o)。一个字符串同时携带 provider 信息和模型名,路由器解析后直接分发到对应 SDK,调用方不需要为每个 provider 实例化不同的 client。OpenAI 的 Chat Completions 格式被当作 lingua franca——所有 provider 的响应都被归一化成 response.choices[0].message.content 结构,换 provider 时上层代码不动。
API key 通过环境变量传入(OPENAI_API_KEY、ANTHROPIC_API_KEY 等),官方 quickstart 里有完整的配置说明。这和 LiteLLM、OpenAI SDK 的惯例一致。
Streaming 流式输出
需要 token 边生成边返回时,传 stream=True,同一个循环在所有支持流式的 provider(OpenAI、Anthropic、Ollama 及 OpenAI 兼容端点)上通用:
for chunk in client.chat.completions.create(model=model, messages=messages, stream=True):
print(chunk.choices[0].delta.content or "", end="", flush=True)异步变体是 await client.chat.completions.acreate(..., stream=True),配合 async for 迭代。注意流式下的 tool calling 需要手动处理:chunk 里携带的是增量 delta.tool_calls,要自己拼装并执行;因此它不能和 max_turns 自动循环组合使用,两者是两种模式。
五、Tool calling:max_turns 自动循环
Chat Completions API 内建了 tool calling 自动循环。传入 tools 和 max_turns 后,框架会自动处理"模型调用工具 → 执行工具 → 把结果喂回模型 → 模型继续"的循环:
def will_it_rain(location: str, time_of_day: str):
"""Check if it will rain in a location at a given time today.
Args:
location (str): Name of the city
time_of_day (str): Time of the day in HH:MM format.
"""
return "YES"
client = ai.Client()
response = client.chat.completions.create(
model="openai:gpt-4o",
messages=[{
"role": "user",
"content": "Can you check the weather and plan an outdoor picnic for me at 2pm in SF?"
}],
tools=[will_it_rain],
max_turns=2,
)
print(response.choices[0].message.content)max_turns=2 意味着框架最多自动执行两轮 tool calling。Python 函数直接传给 tools 参数,框架从函数签名和 docstring 推断出 JSON schema(底层用 docstring-parser),再转成模型需要的 tool definition。这比手写 OpenAI 的 tool definition JSON 省事,代价是你得信任框架的 schema 推断。
自动循环之外还有一条手动路径:省略 max_turns,并传 OpenAI 格式的 JSON tool spec。此时框架只把模型的 tool-call 请求原样返回,执行、校验、过滤都归你。两种风格在仓库的 examples/tool_calling_abstraction.ipynb 里都有对照。Runner 那一层的 max_turns 默认值是 5。
为什么用 max_turns 控制循环上限?因为 tool calling 循环可能失控——模型反复调用工具却不收敛,或者工具一直返回错误导致死循环。设一个硬上限是最直接的防护。工具调用的完整中间历史在 response.choices[0].intermediate_messages 里,追加到 messages 就能接着对话。
六、Agents API:Agent + Runner
Agents API 把"定义 Agent"和"运行 Agent"拆成两个对象:
import aisuite as ai
from aisuite import Agent, Runner
agent = Agent(
name="repo-helper",
model="anthropic:claude-sonnet-4-6",
instructions="You are a careful repo assistant. Use your tools to answer from the code.",
tools=[*ai.toolkits.files(root="."), *ai.toolkits.git(root=".")],
)
result = Runner.run_sync(agent, "What changed in the last commit? Summarize in 3 bullets.")
print(result.final_output)Agent 是纯配置 dataclass——官方源码里只有 name、model、instructions、tools、model_settings、tags、metadata 七个字段。Runner 是执行器,接收 Agent 和 task 字符串,内部完成消息构造、模型调用、tool calling 循环、策略检查、状态持久化。
一个容易踩的出入:官方 README 的示例把 Runner.run(agent, ...) 写成同步调用的样子,但源码里 run 是 async def——直接调用拿到的是 coroutine。同步代码请用 Runner.run_sync(...),异步代码用 await Runner.run(...)。
为什么拆成两个?因为 Agent 的定义是可复用的(同一个 Agent 可以跑不同 task),而 Runner 的执行是一次性的(每次运行产生一个独立的 RunResult)。这种分离让 Agent 可以被序列化、共享、测试,而运行时的状态隔离在 Runner 里。
Runner 的入口有三个:run(async)、run_sync(同步包装:无事件循环时走 asyncio.run,在 notebook 这类已有循环的环境里需要 aisuite[mcp] extra 提供的 nest_asyncio,否则抛 RuntimeError)、以及用于续跑的 continue_run / continue_sync(见 State Stores 一节)。
Agents API 还内置了产出与过程的可观测能力:Artifacts(FileArtifactStore / InMemoryArtifactStore)记录 Agent 本次产生了哪些内容,Runner.run 可以直接挂 artifact_store 参数;tracing 给每个 RunResult 附上执行步骤(steps)、原始响应(raw_responses)和 trace_id,还可以传 trace_sinks 接到自己的观测后端。生产环境要回答"这个 Agent 到底做了什么、产出了什么"时,这两块可以直接用,不必另写一套审计日志。
七、Toolkits:预制工具集
aisuite 内建三个 toolkit。注意它们的构造参数并不一致:
ai.toolkits.files(root=".") # 默认只读
ai.toolkits.git(root=".") # 只读
ai.toolkits.shell(cwd=".", allowed_commands=["ls", "cat"]) # 白名单必填files:list_files、read_file、read_file_lines、search_files,以及写侧的write_file、apply_unified_diff、apply_patch、replace_in_file。root限定可访问目录;写工具默认不暴露,要传allow_write=True(或多根模式roots=[{"path": ..., "writable": bool}])才会出现git:只有git_status和git_diff两个只读工具,源码注释写明 “root-scoped read-only git tools”。没有 commit、push、logshell:唯一工具是run_shell(command, timeout_seconds)。构造时必须给allowed_commands白名单或allow_all=True,否则直接抛ValueError;默认超时 30 秒,工具元数据标了risk_level="high"和requires_approval=True。allow_shell=True才走完整 shell(支持管道),默认按shlex分词单命令执行
为什么预制这三个?因为绝大多数开发类 Agent 的工具需求都落在这三类里。自己写 file 读写工具不难,但每次都要处理路径校验、权限边界、错误格式——这些是重复劳动。toolkit 把这些细节封装好,root="." 就把工具的访问范围锁在当前目录。
tools=[*ai.toolkits.files(root="."), *ai.toolkits.git(root=".")] 这种写法说明 toolkit 返回的是 tool 列表,用 * 解包后拼进 Agent 的 tools 数组。你可以混搭 toolkit 和自定义工具。
八、Tool Policies:审批与拦截
shell 这类工具有破坏性。Tool Policies 在"模型决定调用工具"和"工具实际执行"之间加了一道闸门——但首先要纠正一个位置:策略挂在 Runner 调用上,不在 Agent 定义里。Agent 没有 tool_policy 字段:
from aisuite import RequireApprovalPolicy, Runner
def human_approval(context) -> bool:
# context 是 ToolPolicyContext:tool_name、arguments、agent_name、messages 等
print(f"Approve {context.tool_name}? args={context.arguments}")
return input("y/n: ").strip() == "y"
result = Runner.run_sync(
agent, task,
tool_policy=RequireApprovalPolicy(callback=human_approval),
)RequireApprovalPolicy 只接收一个 callback:每次工具调用都会触发它,回调返回 True 放行、False 拒绝(也可以返回 ToolPolicyDecision(allowed=..., reason=...) 带拒绝理由)。它不是"列出需要审批的工具名"的配置项——按工具过滤要写在回调自己的逻辑里。
内置策略类一共四个,覆盖常见档位:AllowAllToolPolicy、AllowToolsPolicy(allowed_tools=[...])、DenyAllToolPolicy(reason=...)、RequireApprovalPolicy。任何接收单个 ToolPolicyContext 的 callable 也能直接当策略用:
def my_policy(context):
if context.tool_name == "run_shell" and "rm" in context.arguments.get("command", ""):
return False
return True
result = Runner.run_sync(agent, task, tool_policy=my_policy)ToolPolicyContext 携带 tool_name、arguments、agent_name、messages、trace_id、tags 等字段,策略逻辑拿得到完整现场。
被拒绝时发生什么:工具不执行,框架把 {"error": "Tool call denied by policy", "reason": ...} 作为工具结果回填给模型,模型可以据此换方案或收尾。每次策略评估都会生成事件,挂在 response.tool_policy_events 上,事后可审计。
还有一点要说透:RequireApprovalPolicy 名字里有 approval,但它没有内建"暂停等人工点头"的机制——回调是同步立即执行的。上面示例里的人工确认,是调用方自己在回调里写 input() 实现的。aisuite 给的是决策钩子,审批交互归你。
为什么把 policy 做成框架级能力?因为策略需要统一作用于所有工具(包括 toolkit 提供的、MCP 引入的、自定义的),如果分散在每个 tool 函数里,很容易漏掉。集中式 policy 是唯一能保证"所有 tool call 都经过检查"的方式。
九、State Stores:断点续跑
Agent 任务可能跑很久(多轮 tool calling、大文件处理、网络等待),中途崩溃或被中断时,如果没有持久化,所有进度丢失。State Stores 解决这个问题:
from aisuite import FileStateStore, PostgresStateStore, Runner
store = PostgresStateStore.from_dsn("postgres://user:pass@localhost/agent")
# 持久化运行:state_store 和 thread_id 必须成对出现
result = Runner.run_sync(agent, task, state_store=store, thread_id="support-42")
# 之后(哪怕换了进程),同一个 thread_id 加载状态继续
result = Runner.continue_sync(agent, "continue with the second part",
state_store=store, thread_id="support-42")三种 backend,都是 StateStore 协议的实现对象:
InMemoryStateStore:不持久化,进程结束即丢失FileStateStore():写到本地文件,默认目录.aisuite/state,适合单机开发和测试PostgresStateStore.from_dsn("postgres://..."):生产级持久化,需要另装pip install 'aisuite[postgres]'
关键语义是 thread_id 而非 run_id:RunResult 上并没有 run_id 字段,持久化的单位是"对话线程"。run 时给 state_store 和 thread_id(只给一个会直接 ValueError),续跑时用同一个 thread_id 找回状态;continue_sync 的第一个参数传 Agent(从存储恢复)或 RunResult(内存内直接续)。线程不存在抛 StateNotFoundError,同名线程已存在再 run 会抛 ThreadAlreadyExistsError 并提示改用 continue_sync,每次保存带 revision 做乐观并发控制。
为什么用 Postgres 做生产主存储?因为 Agent 状态是结构化的(消息历史、tool call 记录、中间结果),需要查询和事务保证,Postgres 的关系模型和 ACID 特性更合适。Redis 适合缓存和会话存储,做持久化状态机的主存储不是它的强项。
十、MCP:作为一等公民
MCP(Model Context Protocol)在 aisuite 里是一等公民——框架原生支持,不需要自己写 adapter,用前装 pip install 'aisuite[mcp]'。
内联配置方式(在 chat completions 的 tools 里直接声明 MCP server,框架负责拉起和关闭):
client = ai.Client()
response = client.chat.completions.create(
model="openai:gpt-4o",
messages=[{"role": "user", "content": "List the files in the current directory"}],
tools=[{
"type": "mcp",
"name": "filesystem",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/directory"]
}],
max_turns=3
)显式 MCPClient 方式(自己控制生命周期,支持复用连接):
from aisuite.mcp import MCPClient
mcp = MCPClient(
command="npx",
args=["-y", "@modelcontextprotocol/server-filesystem", "/path/to/directory"],
)
response = client.chat.completions.create(
model="openai:gpt-4o",
messages=[{"role": "user", "content": "List the files"}],
tools=mcp.get_callable_tools(),
max_turns=3,
)
mcp.close()get_callable_tools() 把 MCP 工具转成 Python callable,支持 allowed_tools 过滤和 use_tool_prefix 加前缀防命名冲突;list_tools() 返回原始工具描述;from_config() / get_tools_from_config() 接受和内联配置相同的 dict。传输层除了 stdio 的 command,还支持 server_url 直连 HTTP 端点——两种传输二选一。
两种方式的分工:内联配置把 server 生命周期交给框架,适合一次性使用;显式 MCPClient 适合跨多次调用复用连接、加安全过滤的场景。
为什么把 MCP 做成一等公民?因为 MCP 生态已经有大量现成的 server(filesystem、github、slack、postgres 等),框架原生支持意味着这些 server 可以直接作为 Agent 的工具来源,省去为每个工具写 wrapper 的工作。
十一、安装与依赖管理
aisuite 的依赖管理走 opt-in extras 路线:
pip install aisuite # 仅基础包(不含任何 provider SDK)
pip install 'aisuite[anthropic]' # 加单个 provider
pip install 'aisuite[all]' # 装齐全部 provider 与可选能力基础包不包含任何 provider SDK,按需安装。只用 OpenAI 的用户不会被强制装 anthropic、google 等一堆 SDK;用 Ollama 的本地用户不需要装云端 provider 的包(Ollama 和 LM Studio、DeepSeek 都复用 openai SDK)。
两个非 provider 的 extras 值得单独记住:
pip install 'aisuite[mcp]' # MCP 支持(mcp + nest-asyncio)
pip install 'aisuite[postgres]' # Postgres state store(psycopg)MCP 依赖较重(涉及 subprocess 管理和异步通信),单独拆出来让不需要 MCP 的用户保持轻量安装。
十二、和同类方案的对比
| 维度 | aisuite | LiteLLM | OpenAI Agents SDK | LangChain |
|---|---|---|---|---|
| 跨 provider | 强 | 强 | 原生绑定 OpenAI 模型,跨 provider 需桥接 | 中 |
| Tool calling 自动循环 | 内建 | 无 | 内建 | 内建 |
| 预制 toolkit | files/git/shell | 无 | 需手写 | 部分 |
| MCP 支持 | 库内一等公民 | Proxy 网关内建,SDK 实验性 | 内建 | 中等 |
| Tool policy | Runner 级内建 | SDK 无;Proxy 有 guardrails | 内建 | 无 |
| State store | 三选(含 Postgres + thread 续跑) | 无内建 | 内建 session/memory | 中等 |
| 参考应用 | OpenWorker | 无 | 无 | LangServe 示例 |
| 安装门槛 | 低(opt-in extras) | 低 | 低 | 高 |
aisuite 的差异化集中在工具相关抽象(Toolkits / MCP / Policies / State Stores)。如果 Agent 工作流主要是"调多个外部工具 + 长期 state 持久化 + 关键操作拦截",aisuite 比 LiteLLM 顺手;如果只想切 provider,LiteLLM 够用。
和 OpenAI Agents SDK 比,aisuite 的优势是跨 provider 和预制 toolkit;劣势是 OpenAI Agents SDK 背靠官方文档和生态,迭代速度可能更快。和 LangChain 比,aisuite 更轻、抽象层级更少,但缺少 graph-based 工作流编排能力。
十三、采用建议
适合选 aisuite 的场景:
- 多 provider 混用(Anthropic / Ollama / 国内云模型),需要统一接口 + 统一工具调用抽象。
- 需要 MCP server 即插即用,不想自己写 adapter。
- 生产 Agent 需要工具策略、状态持久化和审计——policy 挂在 Runner、state store 带 thread 续跑、artifacts 和 tracing 开箱即用。
- 想搭桌面端 AI 应用,OpenWorker 是现成的参考实现,源码在独立仓库
andrewyng/openworker;aisuite 主仓库的openworker-archive/里也保留了历史快照,都可以直接读。
不太适合:
- 只想切 provider、不要 agent 抽象——LiteLLM 更轻,没有额外的认知负担。
- 需要 graph-based 工作流编排(条件分支、循环、子图)——aisuite 目前不主打这块,LangGraph 或 LangChain 更合适。
- 企业级合规要求 RBAC、租户隔离、审计签名——aisuite 是 MIT 开源库,这些能力需要团队自行包装。
- 需要原生"暂停等待人工审批"的交互式 HITL 流程——policy 回调是同步立即决策,异步审批(审批人几小时后批)要自己在 state store + 回调之上搭。
如果"换一个 provider 改一处代码"和"工具调用循环每次自己写"是当前项目的痛点,aisuite 值得花半天评估。库本身写得不重,但每一层抽象都对齐了实际工程痛点:tool calling 循环、工具拦截、状态持久化、MCP 集成——这些是从 demo 走向 production 时绕不开的环节。
十四、常见问题 FAQ
Q1:aisuite 和 LiteLLM 有什么区别?能一起用吗?
LiteLLM 提供一层抽象(统一 provider 路由,Proxy 形态还带 MCP 网关和 guardrails);aisuite 在其上加了一层 Agent harness(Toolkits / MCP / Tool Policies / State Stores)。两者可以共存:如果你已经用 LiteLLM 且只需要切 provider,不必换;如果需要 Agent 运行时抽象,可以迁到 aisuite。
Q2:OpenAI Agents SDK 和 aisuite 该选哪个?
如果只用 OpenAI 且不需要跨 provider,选 OpenAI Agents SDK,官方生态迭代快;如果需要跨 provider 加统一工具调用抽象,选 aisuite。
Q3:可以先学 Chat Completions API,再学 Agents API 吗?
可以,而且推荐这样做。下层更简单,适合快速上手;上层更复杂,用于构建生产级 Agent。先跑通一层,再进二层,能少踩不少坑。
Q4:State Store 用 Postgres 是不是太重了?
生产级 Agent 的状态是结构化的,需要事务保证和并发控制,Postgres 合适;开发测试阶段用 FileStateStore(默认写 .aisuite/state)或 InMemoryStateStore 就够了。Postgres 后端要另装 aisuite[postgres] extra。
Q5:Tool Policy 会影响 Agent 的自主性吗?
会。它本质是在"让 Agent 自主决策"和"防止危险操作"之间做权衡。建议从 AllowToolsPolicy 或自定义回调起步,先拦 run_shell 这类高危工具,再根据 response.tool_policy_events 里的拦截记录调整尺度。注意 git toolkit 只有只读工具,真正危险的是 shell。
Q6:如何给 Agent 添加工具调用能力?
挂载 toolkit 即可,例如 tools=[*ai.toolkits.files(root=".", allow_write=True), *ai.toolkits.shell(cwd=".", allowed_commands=["ls", "grep"])],也可以混搭自定义工具;需要拦截时把策略传给 Runner.run_sync(..., tool_policy=...)。
Q7:aisuite 支持哪些 provider?
README 主列表是 OpenAI、Anthropic、Google、Mistral、Hugging Face、AWS、Cohere、Ollama、OpenRouter、Requesty;aisuite/providers/ 目录里还有 Groq、Cerebras、DeepSeek、xAI、Together、通义、SambaNova、Nebius、Fireworks、Watsonx、LM Studio、Deepgram 等约 30 个适配文件。用不到的 provider SDK 按需安装,不必一次装齐。
Q8:MCP 服务器启动失败怎么排查?
先确认命令和参数能在本机直接跑通(npx -y @modelcontextprotocol/server-filesystem /path),再看框架的 MCPClient 日志。网络隔离环境需提前把 npm 包缓存到本地。stdio 和 HTTP 两种传输只能二选一,command 和 server_url 同时传会直接报错。
十五、故障排查
安装阶段
pip install aisuite后导入失败:确认 Python 版本 ≥ 3.10。import aisuite报错时,先执行python --version确认版本。- 安装
[all]依赖冲突:pip install 'aisuite[all]'可能和现有环境的包版本冲突。建议先创建虚拟环境,或按需安装单个 provider extra。
Provider 接入阶段
- API Key 配置后仍报错:检查环境变量是否设置正确。可以在 Python 里先
import os; print(os.environ.get("OPENAI_API_KEY"))确认键值已读取。 - 本地 Ollama 连接失败:确认 Ollama 已启动并监听默认端口(11434)。用
curl http://localhost:11434/api/tags测试连接。
Agent 运行阶段
max_turns上限触发,Agent 未完成任务:调大max_turns(Runner 层默认 5),或省略max_turns改用手动模式逐步调试。工具调用的中间交互历史在response.choices[0].intermediate_messages里(走 Chat Completions 路径时),可打印出来排查每一轮的输入输出。- 挂
toolkits.shell(cwd=".")直接报 ValueError:shell toolkit 要求显式给allowed_commands白名单或allow_all=True,这是设计出来的安全闸门,不是 bug。 Runner.run(...)返回 coroutine 而不是结果:run是 async 方法。同步代码用Runner.run_sync(...),异步代码await Runner.run(...)。- 续跑时报
StateNotFoundError:thread_id 在 state store 里不存在——检查是不是换了存储目录(FileStateStore默认.aisuite/state)或 DSN 指向了另一个库。反过来,新建 run 报ThreadAlreadyExistsError说明该 thread_id 已经存过状态,改用Runner.continue_sync。 - Tool Policy 拦截了不该拦截的工具:检查回调里对
context.tool_name的判断。注意策略是每次工具调用都触发,被拒的工具会收到Tool call denied by policy,完整拦截记录看response.tool_policy_events。
十六、自测题
用以下 5 题检验理解程度。答案折叠在每题下方。
Q1: aisuite 的两层抽象分别是什么?各解决什么问题?
查看答案
答:下层 Chat Completions API(统一多 provider 接口 + tool calling 循环);上层 Agents API(Agent 定义 + Runner 执行器 + 工具集 + 策略 + 状态持久化)。Q2: model 参数的 provider:model 格式有什么好处?
查看答案
答:一个字符串同时携带 provider 信息和模型名,路由器解析后直接分发,调用方不需要为每个 provider 实例化不同的 client。Q3: max_turns 的作用是什么?为什么需要它?
查看答案
答:控制 tool calling 循环上限,防止模型反复调用工具却不收敛导致死循环或超成本。省略 max_turns 并传 JSON tool spec 则退回手动循环。Q4: Toolkits 预制了哪三类工具?各自的危险边界在哪里?
查看答案
答:files(默认只读,allow_write=True 才暴露写工具)、git(只有 git_status/git_diff 两个只读工具)、shell(必须传 allowed_commands 白名单或 allow_all=True,工具元数据标 requires_approval=True)。Q5: aisuite 和同类方案相比,差异化集中在哪几块?
查看答案
答:工具相关抽象(Toolkits / MCP / Policies / State Stores)。十七、练习
练习一:从零跑通 aisuite 的两层抽象
- 安装 aisuite 和基础 provider:
pip install 'aisuite[openai]' - 写一个最小示例:用 Chat Completions API 调用 GPT-4o,传入一个简单消息
- 改写为例:用 Agents API 定义一个 Agent,挂载 files toolkit,让它读取当前目录的文件列表
- 对比两段代码的差异:哪段更短?哪段更易扩展?
- 记录:安装耗时、首次运行耗时、遇到的错误信息
练习二:对比 aisuite 和 LiteLLM 的切换成本
- 用 LiteLLM 写一个切换 provider 的示例(OpenAI → Anthropic)
- 用 aisuite 写同样的示例
- 对比两段代码:哪段更短?哪段需要更少的 import?
- 如果要加上 tool calling,两段代码分别需要改几行?
- 记录:切换 provider 时需要改的代码行数、文档查阅次数
练习三:为 aisuite Agent 加上 Tool Policy 和 State Store
- 定义一个 Agent,挂载
shell(cwd=".", allowed_commands=["ls", "cat"])toolkit - 写一个自定义策略回调,拦截
run_shell工具 - 通过
Runner.run_sync(..., tool_policy=...)运行 Agent,触发工具调用,观察response.tool_policy_events里的拦截记录 - 改用
FileStateStore加thread_id,跑一个多轮任务,然后中断程序 - 重启程序,用
Runner.continue_sync(agent, ..., state_store=..., thread_id=...)续跑,确认状态已恢复 - 记录:策略回调的判断依据是否够用、State Store 的写入延迟
十八、进阶路径
深入理解 aisuite 架构
- 读源码:从
aisuite/client.py入手,理解 Provider Router 的实现;然后读aisuite/agents/(runner.py是执行主干,policies.py、state_store.py各管一摊),策略评估的执行点其实在aisuite/utils/tools.py的_prepare_tool_call里;aisuite/design-notes/还留有设计笔记 - 对比 OpenAI Agents SDK:读 OpenAI Agents SDK 的源码,对比两者的 Agent 抽象差异——aisuite 更轻,OpenAI Agents SDK 更完整
- MCP 协议:读 MCP 官方规范,理解为什么 aisuite 把 MCP 做成一等公民
搭建生产级 Agent
- 套一层交互界面:参考 OpenWorker 的实现(独立仓库
andrewyng/openworker),为你的 Agent 加桌面或 Web 交互层 - 集成向量数据库:为 Agent 加上 RAG 能力(用 Pinecone 或 Chroma 作为知识库)
- 多 Agent 协作:用 aisuite 搭一个多 Agent 系统(比如一个 Agent 负责代码生成,另一个负责代码审查)
贡献到 aisuite
- 提交 PR:从修复文档 typo 开始,逐步熟悉代码库
- 写一个新的 toolkit:比如一个
databasetoolkit,封装常用的数据库操作 - 改进 MCP 支持:MCP 支持目前是可选 extra,可以帮助改进文档或测试用例
十九、资料口径说明
本文基于 andrewyng/aisuite 仓库的公开代码、README 与 pyproject 整理,关键 API 已于 2026-09-07 对照 main 分支的 README、docs/agents-quickstart.md 与 aisuite/agents/、aisuite/mcp/、aisuite/toolkits/ 源码逐项复核。以下说明关键判断的取径方式:
两层抽象的设计意图:来自 README 的发布形态——OpenWorker 置顶、库分两层、Agent harness 为重心。仓库名包含作者名字,说明这是他的个人工作台,这个判断有公开证据支持。
和 LiteLLM 的对比:来自两个仓库的 README 与官方文档(LiteLLM 的 MCP 能力以其 docs/mcp 页面为准,截至 2026-09 其 Proxy 已原生支持 MCP,SDK 侧为实验性桥接)。对比表是本文基于公开信息整理的,不是官方立场。
OpenWorker 的定位:README 第一屏就是下载链接,说明这是配套产物;且开发已迁移到独立仓库
andrewyng/openworker,主仓库保留openworker-archive/历史快照。Stars 数:来自 GitHub API(2026-09-07 快照 16240),实际数字可能已有变化。
代码示例:基础示例来自仓库 README 与
docs/agents-quickstart.md;策略、State Store、MCPClient、toolkits 的用法按源码签名核对。所有代码都经过人工阅读,但未经实际运行验证——Runner.run同步形式这一 README 与源码的出入,以源码为准在正文标注。
本文持续更新,欢迎通过 GitHub Issues 提交修正建议。
下一步:从
pip install aisuite开始,跑通一个最小示例,再逐步加上 toolkit、MCP、policy、state store。
参与讨论
使用 GitHub 登录。欢迎补充事实、异议与实践。
讨论暂时无法加载。