2026 年被称为「智能体落地元年」,但真正能稳定干活的 Agent,难点往往不在模型本身,而在「怎么把模型、工具、上下文编排到一起」。本文用一个可运行的例子,讲清楚 Agent 的工程骨架,以及为什么在真实项目里我们通常不直接裸连单个模型,而是加一层模型路由。

一、为什么 Agent 需要「多模型路由」

很多初学 Agent 的同学会默认:一个 Agent = 一个模型 + 一堆 prompt。这在 demo 阶段没问题,一上生产就暴露问题:

  • 长任务推理用旗舰模型更稳,但成本高;
  • 海量短问答 / 分类 / 抽取用轻量模型就够了,硬上旗舰是浪费钱;
  • 中文长文档摘要某些模型表现更好,代码生成又是另一批模型领先;
  • 单一供应商有可用率风险,需要故障转移。

所以真实项目里,Agent 内部通常不是「一个模型打到死」,而是有一层 模型路由(model routing):根据任务类型、输入长度、预算,把请求派给最合适的模型。

# agent/router.py
# 根据任务特征选择后端模型,统一通过接入层发出请求
# 接入层负责密钥管理、限流、故障转移,业务代码不关心具体供应商
# 注册一个统一接入层示例:https://www.moyu.info/register?aff=CRB8

from enum import Enum

class TaskKind(Enum):
    REASONING = "reasoning"      # 复杂推理,用旗舰模型
    CODEGEN = "codegen"          # 代码生成,用代码强的模型
    EXTRACT = "extract"          # 短文本抽取,用轻量模型
    SUMMARY_CN = "summary_cn"    # 中文长文摘要

ROUTING_TABLE = {
    TaskKind.REASONING:  "gpt-5",
    TaskKind.CODEGEN:    "claude-4-sonnet",
    TaskKind.EXTRACT:     "gpt-5-mini",
    TaskKind.SUMMARY_CN: "deepseek-v4",
}

def select_model(task: TaskKind) -> str:
    return ROUTING_TABLE[task]

路由表本身很简单,麻烦的是后端怎么接。下面展开。

二、Agent 的核心骨架:循环 + 工具

一个最小可用的 Agent,本质是「模型调用 ↔ 工具执行」的循环。这里用 OpenAI 兼容的接口格式(主流接入层都支持这套协议,换供应商不用改业务代码):

# agent/run.py
import json
from agent.router import select_model, TaskKind

def call_chat(model: str, messages: list, tools: list = None) -> dict:
    # 统一走接入层,base_url 指向你的接入网关
    # 业务侧只传 model 名 + messages,密钥在接入层统一管理
    return gateway_chat(model=model, messages=messages, tools=tools)

def run_agent(user_input: str, tools: list):
    messages = [{"role": "user", "content": user_input}]
    for _ in range(8):  # 最多 8 轮工具调用
        task = classify(user_input)         # 简单场景可直接指定
        model = select_model(task)
        resp = call_chat(model, messages, tools)
        if resp.get("tool_calls"):
            for call in resp["tool_calls"]:
                result = execute_tool(call, tools)
                messages.append({"role": "tool", "content": result})
        else:
            return resp["content"]
    return "超过最大轮次"

关键点:model 字段是动态的。同一个 Agent,推理用 gpt-5,抽字段用 gpt-5-mini,代码用 claude-4-sonnet,中文摘要用 deepseek-v4。如果每次都去各家官网申请密钥、拼不同 SDK,代码会很快腐烂。

这就是为什么工程上会引入一层统一接入

  • 业务代码只认 model 字符串;
  • 密钥、配额、限流、重试、故障转移都在接入层做;
  • 换模型、加模型、切供应商,业务零改动。

三、工具怎么定义才不踩坑

工具(function calling)是 Agent 的「手」。定义工具时三个常见坑:

1. 参数 schema 太松

{
  "name": "query_orders",
  "parameters": {
    "type": "object",
    "properties": {
      "status": {"type": "string", "description": "订单状态"}
    }
  }
}

status 不写枚举,模型可能传「已付款」「已支付」「paid」三种东西,下游解析崩溃。一定要写 enum

2. 工具太多导致模型选错

一次性塞 30 个工具,弱模型会选错。按场景做工具分组,或先让模型「选工具组」再「选具体工具」。

3. 工具执行没有超时与兜底

模型可能生成无限循环的工具调用。上面 run_agent 里的 range(8) 就是兜底,生产环境还要加单次执行超时。

四、上下文怎么管

Agent 跑几轮后,messages 会爆。常用策略:

  • 滑动窗口:只保留最近 N 条;
  • 压缩记忆:把早期对话 summarize 成一条;
  • 外部记忆:把事实写进向量库 / KV,用时再取。

简单实现:

def compress_history(messages: list, threshold: int = 12) -> list:
    if len(messages) <= threshold:
        return messages
    early, recent = messages[:-6], messages[-6:]
    summary = call_chat("gpt-5-mini",
        [{"role": "user", "content": f"用三句话总结对话: {early}"}])
    return [{"role": "system", "content": f"历史摘要: {summary}"}] + recent

注意这里压缩用的也是轻量模型,进一步省钱。

五、接入层选型:直连 vs 聚合

真实项目里「模型接入」有几种做法,各有取舍:

方案

优点

缺点

适合场景

各家官网直连

最直接

密钥散落、无统一限流、供应商绑定

只用一个模型

自建轻量代理

可控、可加审计

要自己维护可用性

有基础设施团队

聚合接入层

一套协议接多模型、统一配额

多一层依赖

多模型 / 成本敏感

聚合接入层把多家供应商的接口收敛成统一协议,业务侧切换模型只改一个字符串,还能集中做限流、计费和故障转移。市面上这类方案不少,从开源自建到托管服务都有,选的时候看清楚:是否支持你需要的模型、是否支持自定义模型服务地址、计费是否透明。

六、小结

一个能稳定干活的 Agent,技术含量有一半在「编排」而不是「模型」:

  1. 模型路由按任务分流,别让旗舰模型干抽字段的活;
  1. 业务代码只认 model 字符串,密钥与故障转移交给接入层
  1. 工具 schema 写死枚举、控制数量、加超时;
  1. 上下文用滑动窗口 + 摘要压缩,别让 messages 无限涨。

把这几件事做对,Agent 从 demo 到生产的距离会短很多。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐