数据截至 (上游 commit 460c729002dc)
第 3 章 · 统一 LLM 层(Backend)
本章讲什么: 第 2 章的通行证要求「这一轮必须调
think工具」。可是 Ollama 的 OpenAI 兼容端点根本不认tool_choice这个参数。BeeAI 怎么让同一份约束在两种模型上都生效?这一章就答这个问题。
3.1 ChatModel 承担了什么
ChatModel(backend/chat.py:252)是个抽象类,子类只需实现两个方法:
# python/beeai_framework/backend/chat.py:407-451 签名
async def _create(self, input: ChatModelInput, run: RunContext) -> ChatModelOutput # 非流式
def _create_stream(self, input: ChatModelInput, run: RunContext) -> AsyncGenerator[...] # 流式
剩下的全在基类里做完了:
| 关切 | 谁负责 |
|---|---|
| 入参归一 | _prepare_model_input(chat.py:453-502) |
| 缓存 | CacheEntry + self.cache |
| 重试 | Retryable(chat.py:542-549) |
| 事件 | start / new_token / success / error / finish |
| 空响应处理 | EmptyChatModelResponseError + 自动重试 |
| 工具调用合法性校验 | _assert_tool_response(chat.py:864-924) |
| 坏 JSON 修复 | _fix_tool_calls(chat.py:683-704) |
| 能力降级 | _force_tool_call_via_response_format(chat.py:797-820) |
最后一项是本章主角。
3.2 问题:各家的 tool_choice 支持度不一样
tool_choice 有四种模式,框架用一个集合声明每个 provider 支持哪些:
# python/beeai_framework/backend/chat.py:298
tool_choice_support: ClassVar[set[ToolChoiceType]] = {"required", "none", "single", "auto"}
| 模式 | 含义 |
|---|---|
auto | 模型自己决定调不调 |
required | 必须调某个工具,调哪个随意 |
single | 必须调指定的那一个工具 |
none | 不准调工具 |
真实的 provider 声明差异很大:
| Provider | tool_choice_support | 出处 |
|---|---|---|
| 默认(OpenAI 等) | 四种全支持 | backend/chat.py:298 |
| Ollama | 空集合 set() | adapters/ollama/backend/chat.py:19 |
| watsonx | {"none", "single", "auto"}(无 required) | adapters/watsonx/backend/chat.py:19 |
| AgentStack 里的 Cerebras / Together / RITS | {"none", "single", "auto"} | adapters/agentstack/backend/chat.py:51-69 |
OpenAI 但配了自定义 base_url | 自动摘掉 required | adapters/openai/backend/chat.py:55-57 |
最后一条特别务实:你把 base_url 指向某个 OpenAI 兼容的第三方网关时,框架默认那个网关未必实现了 required,主动降级。