跳到主要内容

第 1 章 · 统一 completion 抽象与 provider 层

本章讲什么: Rig 最底层的那块承重墙——它怎么用一个 CompletionModel 特征,把 20+ 个 API 各不相同的供应商,包成同一个「请求进、回复出」的接口。看懂这一章,你就懂了「换供应商只改一行」背后的机制。


1.1 先建直觉:为什么需要一层抽象

每个 LLM 供应商的 HTTP API 都不一样:字段名不同、消息格式不同、工具调用的表示不同、返回的 token 统计口径也不同。

如果业务代码直接调某家 API,换供应商 = 重写。Rig 的解法是经典的「窄腰(narrow waist)」设计:

各种业务写法 一个规范中间层 各种供应商
agent.prompt ─┐ ┌─ OpenAI HTTP
extractor ─┼─► CompletionRequest ──► ┼─ Anthropic HTTP
completion 构建器─┘ (Rig 规范请求) └─ Cohere HTTP …

上面收敛到一个 CompletionRequest,下面每个 provider 只干一件事:CompletionRequest 翻译成自家请求体,再把自家回复翻译回 Rig 的 Message 中间这个「规范请求」就是窄腰。


1.2 规范请求:CompletionRequest

CompletionRequest 是 Rig 里「一次模型调用」的中立表示,字段覆盖了所有供应商的公共需求(crates/rig-core/src/completion/request.rs:668CompletionRequest):

字段装什么
chat_history完整对话历史,最后一条永远是当前 prompt(类型 OneOrMany<Message>,保证至少一条)
preamble遗留的系统提示字段;新代码建议改用 chat_history 里领头的 Message::System
documents要塞给模型的上下文文档(RAG 检索出来的片段)
tools本次可用的工具定义(名字 + 描述 + JSON schema)
temperature / max_tokens / tool_choice通用采样与工具约束参数
additional_params供应商专属参数的逃生舱(任意 JSON,直接透传)
output_schema结构化输出的 JSON Schema(支持原生结构化输出的 provider 会用它约束模型)

注意 additional_params 这个逃生舱:统一抽象总有覆盖不到的供应商特性,Rig 不强行统一,而是留一个「任意 JSON 透传」的口子,避免抽象把人锁死。

关于文档还有个巧处:大多数供应商的 API 不接受「文档」这种输入类型,所以 normalized_documents 会把 documents 转成一条 Message::User 混进对话历史(crates/rig-core/src/completion/request.rs:713normalized_documents)。抽象层在这里替 provider 抹平了差异。


1.3 核心特征:CompletionModel

所有 completion 模型都实现 CompletionModel 特征(crates/rig-core/src/completion/request.rs:613)。它的核心就两个方法:

// 示意,摘自 crates/rig-core/src/completion/request.rs:613 CompletionModel
pub trait CompletionModel: Clone + WasmCompatSend + WasmCompatSync {
type Response: ...; // 该 provider 的原始回复类型(保留原样)
type StreamingResponse: ...; // 流式场景的原始回复类型
type Client; // 造这个 model 的 provider 客户端类型

// 非流式:喂进规范请求,吐出规范回复
fn completion(&self, request: CompletionRequest)
-> impl Future<Output = Result<CompletionResponse<Self::Response>, CompletionError>>;

// 流式:吐出一个回复流
fn stream(&self, request: CompletionRequest)
-> impl Future<Output = Result<StreamingCompletionResponse<Self::StreamingResponse>, CompletionError>>;
}

三个关键设计:

  • type Response 是关联类型,不是统一结构。 Rig 不强行把所有供应商的回复压成同一个结构体,而是让每个 provider 保留自己的原始回复类型,同时把「高层选择」提取到统一的 CompletionResponse。你既能用统一视图,也能拿到原始回复。
  • 返回的是 impl Future,不是 async fn 这是为了兼容 WASM(WasmCompatSend 在非 WASM 下是 Send,WASM 下是空约束),让核心库能编译到浏览器。
  • composes_native_output_with_tools 默认返回 false 这是个诚实的安全默认:只有确认「原生结构化输出能和工具调用共存」的 provider(如 OpenAI、Anthropic)才覆写成 truecrates/rig-core/src/completion/request.rs:661)。细节见第 5 章 OutputMode

统一回复:CompletionResponse

无论哪个 provider,completion 都返回同一个 CompletionResponse<T>crates/rig-core/src/completion/request.rs:488):

字段装什么
choice模型这次回复的内容,OneOrMany<AssistantContent>(可能是文本、也可能是一个或多个工具调用)
usagetoken 用量统计(见下)
raw_response该 provider 的原始回复(type Response),需要抠细节时用
message_idprovider 分配的消息 ID(如 OpenAI Responses API 的 msg_ ID),多轮里用来配对

token 用量:Usage

Usage 把各家口径不一的 token 统计统一成一个结构(crates/rig-core/src/completion/request.rs:533),字段覆盖输入/输出/总数/缓存读写/推理 token 等。有两个值得学的约定:

  • 零值是「provider 没报用量」的哨兵。 has_values() 判断是否全零,全零表示这次调用没拿到用量数据(crates/rig-core/src/completion/request.rs:570)。
  • 实现了 Add / AddAssign 多轮循环里各轮用量能直接累加(crates/rig-core/src/completion/request.rs:581),这在第 2 章的用量聚合里会用到。

1.4 数据模型:Message

CompletionRequest 里流动的是 Message——provider 无关的消息模型(crates/rig-core/src/completion/message.rs:22)。它按角色分三种:

Message ──┬── System { content: String } 系统指令
├── User { content: OneOrMany<UserContent> } 用户消息(可多模态)
└── Assistant{ id, content: OneOrMany<AssistantContent> } 助手回复

用户内容和助手内容是两组不同的枚举(因为「用户能发的」和「模型能回的」不是一回事):

枚举变体说明
UserContentText / ToolResult / Image / Audio / Video / Document用户侧内容,含工具执行结果(crates/rig-core/src/completion/message.rs:42
AssistantContentText / ToolCall / Reasoning / Image模型侧回复:文本、工具调用、推理块、图像(crates/rig-core/src/completion/message.rs:60

注意 ToolResult 属于 UserContent工具执行结果是以「用户消息」的身份塞回对话的——这符合大多数供应商 API 的约定(工具结果作为 user 角色的一种内容)。这个细节在第 2 章多轮循环里是关键。

还有 Reasoning(推理块,crates/rig-core/src/completion/message.rs:60):支持 Gemini thinking、Anthropic extended thinking、OpenAI o 系列这类会输出「思考过程」的模型。Rig 把推理当成一等公民,能在多轮里保留和回传(含加密/脱敏的推理载荷,ReasoningContentcrates/rig-core/src/completion/message.rs:75)。


1.5 高层接口:Prompt / Chat / Completion 三个特征

业务代码一般不直接碰 CompletionModel,而是用三个更友好的特征(都在 crates/rig-core/src/completion/request.rs):

特征方法语义位置
Promptprompt(msg)一句 prompt 进、一个 String 出;若回复是工具调用则自动执行工具再返回:371
Chatchat(msg, &mut history)带历史的 prompt;本轮产生的消息会追加进 history:387
TypedPromptprompt_typed::<T>(msg)结构化输出:自动生成 T 的 JSON schema,把回复反序列化成 T:431
Completioncompletion(msg, history)低层:返回一个已预填 agent 配置的请求构建器,让你在发送前微调:462

Prompt::prompt 的文档说得很清楚:如果模型回复是工具调用,它会自动调用工具并把结果作为字符串返回crates/rig-core/src/completion/request.rs:376)。这句「自动」背后,就是第 2 章的多轮循环。


1.6 provider 怎么实现

每个 provider 是 crates/rig-core/src/providers/ 下的一个模块(约 25 个)。模块内定义一个 Client 类型和各能力的 model 类型,然后按需实现能力特征(crates/rig-core/src/providers/mod.rs 顶部的实现清单说得很细)。

关键原则:能力特征只在 provider 真支持时才实现。 比如某 provider 不支持 embedding,就不实现 EmbeddingsClient——编译期就挡住误用。这些能力特征包括:

能力特征提供什么位置
ProviderClient从环境变量/API key 构造客户端crates/rig-core/src/client/mod.rs:113
CompletionClient.agent(model).completion_model(model) 工厂方法crates/rig-core/src/client/completion.rs:9
EmbeddingsClient.embedding_model(model)crates/rig-core/src/client/embeddings.rs

CompletionClient::agent() 就是你在示例里调的那个(crates/rig-core/src/client/completion.rs:50):

// 示意,摘自 crates/rig-core/src/client/completion.rs:50
fn agent(&self, model: impl Into<String>) -> AgentBuilder<Self::CompletionModel> {
AgentBuilder::new(self.completion_model(model))
}

一行:拿 model 名字造一个 completion model,再包成 AgentBuilder。这就是「换供应商只改 Client」的落点——AgentBuilder 及以上全部代码对 provider 一无所知。


1.7 本章小结与去向

  • Rig 用 CompletionRequest(规范请求)+ Message(中立消息模型)当窄腰,provider 只做双向翻译。
  • CompletionModel 特征用关联类型保留各家原始回复,同时提供统一的 CompletionResponse 视图。
  • 业务用 Prompt/Chat/TypedPrompt 三个高层特征;Prompt::prompt 遇到工具调用会「自动循环」。
  • 那个「自动循环」是怎么实现的、为什么能持久化 → 见第 2 章。
  • 工具本身怎么定义、乱调怎么办 → 见第 3 章。

代码地图

主题文件符号
规范请求crates/rig-core/src/completion/request.rsCompletionRequest
文档归一化crates/rig-core/src/completion/request.rsnormalized_documents
核心模型特征crates/rig-core/src/completion/request.rsCompletionModel
统一回复crates/rig-core/src/completion/request.rsCompletionResponse
token 用量crates/rig-core/src/completion/request.rsUsage
消息模型crates/rig-core/src/completion/message.rsMessage / UserContent / AssistantContent
高层特征crates/rig-core/src/completion/request.rsPrompt / Chat / TypedPrompt / Completion
provider 客户端特征crates/rig-core/src/client/mod.rsProviderClient / CompletionClient
agent 工厂crates/rig-core/src/client/completion.rsCompletionClient::agent