跳到正文

目录

andrewyng/aisuite 架构拆解:Python 端 LLM 统一接口的两层抽象(Chat Completions + Agents)和它背后的工程取舍

andrewyng/aisuite 架构拆解:Python 端 LLM 统一接口的两层抽象(Chat Completions + Agents)和它背后的工程取舍

核心判断:aisuite 在 Chat Completions 统一接口之上加了一层 Agents API(Toolkits / MCP / Tool Policies / State Stores),并把"如何用这些能力搭一个生产级 Agent harness"做成参考实现 OpenWorker 一起发布。仓库的设计重心是让作者本人快速搭 Agent,切换不同 LLM 只是底层能力之一。

目录


学习目标

通过本文,你会了解:

  1. aisuite 的两层抽象(Chat Completions API + Agents API)分别解决什么问题
  2. Toolkits、MCP、Tool Policies、State Stores 四块拼图如何协同
  3. 和 LiteLLM、OpenAI Agents SDK、LangChain 的差异化取舍
  4. 什么场景该选 aisuite,什么场景该用别的方案
  5. 如何快速上手并评估 aisuite 是否适合你的项目

一、项目坐标

字段
仓库andrewyng/aisuite
主语言Python(包名 aisuite,仓库内另有 aisuite-js/ 目录与 cli/ 命令行工具)
Stars约 16.2k(2026-09-07 经 GitHub API 核实的快照,会随时间变化)
LicenseMIT
配套产物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"),但读完整套设计后会发现重心完全不同:

维度LiteLLMaisuite
主抽象Provider routerProvider 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 为例,追踪一个任务从入口到输出的完整路径:

  1. 定义 AgentAgent(name="repo-helper", model="anthropic:claude-sonnet-4-6", tools=[files, git], instructions=...)
  2. 启动 RunnerRunner.run_sync(agent, "What changed in the last commit?")
  3. Runner 内部:把 task 字符串包装成 user message,调用 chat.completions.create(model, messages, tools, max_turns)
  4. 第一轮推理:模型返回 tool_call(比如调用 git toolkit 的 git_diff
  5. 策略检查:如果 Runner 挂了 tool_policy,框架在每次工具执行前评估策略,通过才执行
  6. 工具执行:框架调用 git toolkit 里对应的函数,拿到 diff
  7. 结果回填:把 tool 结果作为 tool result message 喂回模型
  8. 第二轮推理:模型基于 diff 生成三条总结
  9. 返回result.final_output 包含最终回答;如果传了 state_storethread_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_KEYANTHROPIC_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 自动循环。传入 toolsmax_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——官方源码里只有 namemodelinstructionstoolsmodel_settingstagsmetadata 七个字段。Runner 是执行器,接收 Agent 和 task 字符串,内部完成消息构造、模型调用、tool calling 循环、策略检查、状态持久化。

一个容易踩的出入:官方 README 的示例把 Runner.run(agent, ...) 写成同步调用的样子,但源码里 runasync 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 还内置了产出与过程的可观测能力:ArtifactsFileArtifactStore / 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"])   # 白名单必填
  • fileslist_filesread_fileread_file_linessearch_files,以及写侧的 write_fileapply_unified_diffapply_patchreplace_in_fileroot 限定可访问目录;写工具默认不暴露,要传 allow_write=True(或多根模式 roots=[{"path": ..., "writable": bool}])才会出现
  • git:只有 git_statusgit_diff 两个只读工具,源码注释写明 “root-scoped read-only git tools”。没有 commit、push、log
  • shell:唯一工具是 run_shell(command, timeout_seconds)。构造时必须allowed_commands 白名单或 allow_all=True,否则直接抛 ValueError;默认超时 30 秒,工具元数据标了 risk_level="high"requires_approval=Trueallow_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=...) 带拒绝理由)。它不是"列出需要审批的工具名"的配置项——按工具过滤要写在回调自己的逻辑里。

内置策略类一共四个,覆盖常见档位:AllowAllToolPolicyAllowToolsPolicy(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_nameargumentsagent_namemessagestrace_idtags 等字段,策略逻辑拿得到完整现场。

被拒绝时发生什么:工具不执行,框架把 {"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_idRunResult 上并没有 run_id 字段,持久化的单位是"对话线程"。run 时给 state_storethread_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 的用户保持轻量安装。

十二、和同类方案的对比

维度aisuiteLiteLLMOpenAI Agents SDKLangChain
跨 provider原生绑定 OpenAI 模型,跨 provider 需桥接
Tool calling 自动循环内建内建内建
预制 toolkitfiles/git/shell需手写部分
MCP 支持库内一等公民Proxy 网关内建,SDK 实验性内建中等
Tool policyRunner 级内建SDK 无;Proxy 有 guardrails内建
State store三选(含 Postgres + thread 续跑)无内建内建 session/memory中等
参考应用OpenWorkerLangServe 示例
安装门槛低(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 两种传输只能二选一,commandserver_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 的两层抽象

  1. 安装 aisuite 和基础 provider:pip install 'aisuite[openai]'
  2. 写一个最小示例:用 Chat Completions API 调用 GPT-4o,传入一个简单消息
  3. 改写为例:用 Agents API 定义一个 Agent,挂载 files toolkit,让它读取当前目录的文件列表
  4. 对比两段代码的差异:哪段更短?哪段更易扩展?
  5. 记录:安装耗时、首次运行耗时、遇到的错误信息

练习二:对比 aisuite 和 LiteLLM 的切换成本

  1. 用 LiteLLM 写一个切换 provider 的示例(OpenAI → Anthropic)
  2. 用 aisuite 写同样的示例
  3. 对比两段代码:哪段更短?哪段需要更少的 import?
  4. 如果要加上 tool calling,两段代码分别需要改几行?
  5. 记录:切换 provider 时需要改的代码行数、文档查阅次数

练习三:为 aisuite Agent 加上 Tool Policy 和 State Store

  1. 定义一个 Agent,挂载 shell(cwd=".", allowed_commands=["ls", "cat"]) toolkit
  2. 写一个自定义策略回调,拦截 run_shell 工具
  3. 通过 Runner.run_sync(..., tool_policy=...) 运行 Agent,触发工具调用,观察 response.tool_policy_events 里的拦截记录
  4. 改用 FileStateStorethread_id,跑一个多轮任务,然后中断程序
  5. 重启程序,用 Runner.continue_sync(agent, ..., state_store=..., thread_id=...) 续跑,确认状态已恢复
  6. 记录:策略回调的判断依据是否够用、State Store 的写入延迟

十八、进阶路径

深入理解 aisuite 架构

  • 读源码:从 aisuite/client.py 入手,理解 Provider Router 的实现;然后读 aisuite/agents/runner.py 是执行主干,policies.pystate_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:比如一个 database toolkit,封装常用的数据库操作
  • 改进 MCP 支持:MCP 支持目前是可选 extra,可以帮助改进文档或测试用例

十九、资料口径说明

本文基于 andrewyng/aisuite 仓库的公开代码、README 与 pyproject 整理,关键 API 已于 2026-09-07 对照 main 分支的 README、docs/agents-quickstart.mdaisuite/agents/aisuite/mcp/aisuite/toolkits/ 源码逐项复核。以下说明关键判断的取径方式:

  1. 两层抽象的设计意图:来自 README 的发布形态——OpenWorker 置顶、库分两层、Agent harness 为重心。仓库名包含作者名字,说明这是他的个人工作台,这个判断有公开证据支持。

  2. 和 LiteLLM 的对比:来自两个仓库的 README 与官方文档(LiteLLM 的 MCP 能力以其 docs/mcp 页面为准,截至 2026-09 其 Proxy 已原生支持 MCP,SDK 侧为实验性桥接)。对比表是本文基于公开信息整理的,不是官方立场。

  3. OpenWorker 的定位:README 第一屏就是下载链接,说明这是配套产物;且开发已迁移到独立仓库 andrewyng/openworker,主仓库保留 openworker-archive/ 历史快照。

  4. Stars 数:来自 GitHub API(2026-09-07 快照 16240),实际数字可能已有变化。

  5. 代码示例:基础示例来自仓库 README 与 docs/agents-quickstart.md;策略、State Store、MCPClient、toolkits 的用法按源码签名核对。所有代码都经过人工阅读,但未经实际运行验证——Runner.run 同步形式这一 README 与源码的出入,以源码为准在正文标注。

本文持续更新,欢迎通过 GitHub Issues 提交修正建议。


下一步:从 pip install aisuite 开始,跑通一个最小示例,再逐步加上 toolkit、MCP、policy、state store。

参与讨论

使用 GitHub 登录。欢迎补充事实、异议与实践。