上一篇我们看到流水线的
MessageProcessor把一条Query交给「模型」处理。但「模型」这个词背后藏着真正的复杂度:要同时接入 OpenAI、Anthropic、DeepSeek、月之暗面等上百家服务,要支持 LLM/Embedding/Rerank 三类任务,还要让大模型能调用来自插件、MCP、技能、原生四种来源的工具。这一篇就拆开pkg/provider——LangBot 把「模型」和「工具」这两件最易发散的事,抽象成了什么样子。
把它的职责拆成两条独立的线:
这两条线在 pkg/provider 下分别由 modelmgr/ 和 tools/ 承载。下面分别拆解。
classDiagram
class ModelManager {
+provider_dict: dict~uuid, RuntimeProvider~
+llm_models: list~RuntimeLLMModel~
+embedding_models: list~RuntimeEmbeddingModel~
+rerank_models: list~RuntimeRerankModel~
+load_models_from_db()
+get_model_by_uuid(uuid)
}
class RuntimeProvider {
+provider_entity: ModelProvider
+token_mgr: TokenManager
+requester: ProviderAPIRequester
+invoke_llm()
+invoke_llm_stream()
+invoke_embedding()
+invoke_rerank()
}
class RuntimeLLMModel {
+model_entity: LLMModel
+provider: RuntimeProvider
}
class ProviderAPIRequester {
<<abstract>>
+invoke_llm()
+invoke_embedding()
+invoke_rerank()
}
ModelManager o-- RuntimeProvider
ModelManager o-- RuntimeLLMModel
RuntimeLLMModel --> RuntimeProvider
RuntimeProvider --> ProviderAPIRequester
三层职责泾渭分明:
ModelManager(src/langbot/pkg/provider/modelmgr/modelmgr.py:15)——注册表。它从数据库加载所有 provider 和三类模型,维护 provider_dict(uuid → RuntimeProvider)以及 llm_models / embedding_models / rerank_models 三个列表,对外提供 get_model_by_uuid() 之类的查找。RuntimeProvider(modelmgr/requester.py:28)——一个「提供商」的运行时实例。它是三样东西的组合:provider 配置数据、API Key 管理器、底层请求器。所有对外的 invoke_* 方法都在这一层。RuntimeLLMModel / RuntimeEmbeddingModel / RuntimeRerankModel(requester.py:308 起)——最薄的一层,就是把「模型元数据(名字、能力、上下文长度)」绑定到一个 RuntimeProvider 上。一个 provider 可以挂多个模型。注意一个关键的组合关系:模型不持有调用逻辑,调用逻辑全在它指向的 RuntimeProvider 上。所以「换 provider」时(reload_provider,modelmgr.py:388)只需把所有指向旧 provider 的模型的 .provider 引用换掉即可,模型本身不动。
ModelManager.initialize()(modelmgr.py:63)做了三件事:
# src/langbot/pkg/provider/modelmgr/modelmgr.py:63
async def initialize(self):
self.requester_components = self.ap.discover.get_components_by_kind('LLMAPIRequester')
# ... 把非 litellm 的 requester 类登记进 requester_dict ...
await self.load_models_from_db()
# ... 若未禁用,再从 LangBot Space 同步最新模型清单 ...
kind='LLMAPIRequester' 找出所有请求器。这里有个值得注意的分流(modelmgr.py:67):凡是 manifest 里声明了 litellm_provider 的组件,都跳过加载它的 Python 类——因为它们会统一走 LiteLLMRequester(见第三节)。只有少数需要特殊处理的请求器才保留自己的 Python 类。load_models_from_db() 先建 provider,再分别加载三类模型,每个模型用 provider_dict 里对应的 RuntimeProvider 绑定。sync_new_models_from_space,modelmgr.py:170)。注释里有个很有意思的细节:Space 会复用 uuid 跨重命名(比如曾经是 claude-opus-4-6 的那个 uuid 后来变成 claude-opus-4-7),所以是按 uuid upsert 而非按名字。