Skip to content

2.5 运行时切换 LLM

本节你会学到

  • 会话中切换模型 / provider 的三个公开 API
  • 路由模式:便宜-vs-贵、主-备、A/B
  • 为什么没有 OpenAIProvider / AnthropicProvider 抽象

一个 Agentao 实例构造时绑定一个 LLM。但你常常想动态决定下一轮用哪个模型:小问题走便宜模型、规划类问题走贵模型、主 provider 超时就退到备用。本节讲三个公开 API,教你不重建 agent 就做到这件事。

2.5.1 三个方法

python
# 查询
current = agent.get_current_model()   # -> "gpt-5.4"

# 只换模型——provider / API key 不变
agent.set_model("gpt-5.4")

# 整套切换——key + endpoint + model
agent.set_provider(
    api_key="sk-deepseek-xxx",
    base_url="https://api.deepseek.com",
    model="deepseek-chat",
)

# 列出当前 endpoint 声明的模型
models = agent.list_available_models()  # -> ["gpt-5.4", "gpt-5.4", ...]

四个都是同步便宜set_* 本身不发网络请求),只改进程内状态;下一次 chat() 就会用新设置。

2.5.2 切换会变什么,不会变什么

切换会改切换不改
LLM 客户端的 model / api_key / base_urlagent.messages(整段历史)
tiktoken 编码(用于 token 估算)激活中的技能
token 计数缓存(_last_api_prompt_tokens 置空)记忆(两种作用域)
agentao.log 里一行 "Model changed from X to Y"工作目录 / transport / MCP

重要:对话历史不会清空。新模型会看到旧模型看过的所有历史,包括工具调用轨迹。如果不希望这种上下文渗透,先调用 clear_history() 再 swap。

2.5.3 常见路由模式

快便宜 / 慢聪明 的路由

python
CHEAP_MODEL     = "gpt-5.4"
SMART_MODEL     = "gpt-5.4"
SMART_KEYWORDS  = ("规划", "设计", "重构", "架构", "plan", "design")

def route(agent: Agentao, user_message: str) -> str:
    wants_smart = any(kw in user_message.lower() for kw in SMART_KEYWORDS)
    target = SMART_MODEL if wants_smart else CHEAP_MODEL
    if agent.get_current_model() != target:
        agent.set_model(target)
    return agent.chat(user_message)

多 provider 级联退路

python
PROVIDERS = [
    {"api_key": os.environ["OPENAI_API_KEY"], "base_url": None,                         "model": "gpt-5.4"},
    {"api_key": os.environ["DEEPSEEK_API_KEY"], "base_url": "https://api.deepseek.com", "model": "deepseek-chat"},
    {"api_key": os.environ["MOONSHOT_API_KEY"], "base_url": "https://api.moonshot.cn/v1","model": "moonshot-v1-8k"},
]

def chat_with_fallback(agent: Agentao, msg: str) -> str:
    last_err = None
    for p in PROVIDERS:
        try:
            agent.set_provider(**p)
            return agent.chat(msg)
        except Exception as e:      # 限流 / 超时 / refusal …
            last_err = e
            continue
    raise RuntimeError(f"所有 provider 都失败:{last_err}") from last_err

注意:这是在同一轮上让下一个 provider 重试。历史保留,所以第二个 provider 会看到同一条用户消息 + 可能的半截 assistant 输出——这通常是你要的。但如果第一个 provider 已经提交了 tool call,就不是你要的——这种情况下先 clear_history() 再重放最小上下文。

多租户模型选择(SaaS)

python
def build_agent_for(tenant: Tenant) -> Agentao:
    agent = Agentao(
        working_directory=tenant.workdir,
        model=tenant.plan.default_model,     # "gpt-5.4" 或 "gpt-5.4"
    )
    return agent

# 租户中途升级套餐时
agent.set_model(tenant.plan.default_model)

在线 A/B 测试

python
shadow_reply = None
if random.random() < 0.05:                 # 5% 流量打 challenger
    agent.set_model("gpt-5.4")
    shadow_reply = agent.chat(msg)
    agent.set_model("gpt-5.4")         # 恢复默认
real_reply = agent.chat(msg)
log_ab(real=real_reply, shadow=shadow_reply)

⚠️ 单轮调两次 chat() 会把费用翻倍,并且给历史里塞两条 assistant 回复。认真做 A/B 请用两个独立的 Agentao 实例,把同一 prompt 分别跑。

2.5.4 list_available_models() — 何时调,何时别调

python
def list_available_models(self) -> List[str]

会打 provider 的 /models 端点。适合:

  • 填充 UI 模型下拉框
  • 构造后立刻验证凭据是否可用(401 在这里就暴露,不必等 3 轮后才炸)
  • set_model(...) 前校验目标模型确实存在

不要每轮都调——这是网络 IO,结果请缓存。

失败(网络错误、Key 错)时会 RuntimeError,自己兜住:

python
try:
    models = agent.list_available_models()
except RuntimeError:
    models = [agent.get_current_model()]  # 降级用当前模型

2.5.5 坑

  1. 跨 provider 的 tokenizer 不一致set_provider() 会按新模型名换 tiktoken 编码;但 tiktoken 本身是 OpenAI 的,对 DeepSeek/Moonshot/Qwen 等非 OpenAI 家只能近似估算。预算留点余量

  2. 工具调用 schema 依赖 provider 所有 Agentao 支持的 provider 必须讲 OpenAI 兼容的 tool-calls schema。换到不兼容的 provider,chat() 要么报 "function not supported",要么悄无声息地丢 tool call。级联链里每个 provider 都要测

  3. 流式行为可能不同 不同 provider 的 chunk 切分、首 token 延迟不同。切换后 UI 感受可能明显变化。Transport.emit(...) 仍会照样触发——只是 chunk 边界不一样

  4. 上下文窗口会变gpt-5.4 128k、gpt-5.4 128k、deepseek-chat 64k。set_model("deepseek-chat") 后,原本装得下的长历史可能触发压缩。上下文管理器会自动处理——但会出现摘要块,心里有数

  5. chat() 进行中切换是未定义行为 不要在 chat() 里面调 set_model()——chat() 持有 LLM 客户端的引用,切换要下一轮才生效。要做请用 clear_history() + set_model() + 新的 chat() 这个组合

2.5.6 为什么 provider 不是构造参数

你会注意到 Agentao(...) 直接收 api_key / base_url / model,没有抽象的 provider=。这是故意的:

  • Agentao 只说 OpenAI 兼容协议
  • 任何暴露 OpenAI schema 的 provider(OpenAI、Azure、DeepSeek、Moonshot、Together、本地 Ollama / vLLM……)都能直连
  • "切换 provider" 就等于"切换 key + base_url + model"

所以没有 OpenAIProvider / AnthropicProvider / GoogleProvider 抽象——路由代码保持简单。需要原生 Anthropic / Gemini 请套一个 OpenAI 兼容网关。

TL;DR

  • 三个 API:set_provider(api_key, base_url, model)(整套切换)·set_model(name)(只换模型)·get_current_model()(读取)。
  • 切换不会清历史——下一轮 chat() 会用新模型继续同一上下文。
  • 所有 provider 都走 OpenAI schema;不存在按厂商分的 Provider 抽象。要原生 Anthropic / Gemini,请套 OpenAI 兼容网关。
  • 常见路由:便宜→贵(小问题/规划)、主+备(超时切换)、A/B(同会话评测不同模型)。

下一节:2.6 取消与超时 →