Embedding向量化客户端
前六篇把 Chat 子系统从路由选择到流式路由全部讲完了。这一篇离开 Chat,进入 Embedding 子系统。
在 RAG 系统中,Embedding 的核心作用是把文本转换为向量。两个关键场景:文档入库时,对每个 Chunk 调用 Embedding API 生成向量,存入 Pgvector 或 Milvus 这类向量数据库;用户提问时,对 Query 调用 Embedding API 生成向量,用于在向量数据库中做相似度检索。没有 Embedding,RAG 的检索阶段就无从谈起。
从架构上看,Embedding 子系统和 Chat 子系统遵循同样的三层设计——业务层接口、路由服务、供应商客户端——复用同一套路由和熔断机制。但实现上简单不少:Embedding 只有同步调用,没有流式,不需要 StreamCallback、首包探测那套复杂机制。路由层直接用第三篇讲的 executeWithFallback 就够了。
另一个简化来自协议层面。Ollama 现在通过 OpenAI 兼容模式暴露 /v1/embeddings 端点,和硅基流动走的是同一套 OpenAI 风格协议。于是 Embedding 子系统可以像 Chat 一样抽出一个模板方法基类 AbstractOpenAIStyleEmbeddingClient,把请求构建、HTTP 调用、响应解析全部封装起来。两个供应商实现各自只剩十几二十行代码,通过钩子方法 微调差异点。这篇就围绕这个基类展开。
Embedding 子系统总览
1. 和 Chat 子系统的对比
先用一张表格建立全局对比:
| 维度 | Chat 子系统 | Embedding 子系统 |
|---|---|---|
| 业务层接口 | LLMService | EmbeddingService |
| 供应商接口 | ChatClient | EmbeddingClient |
| 路由服务 | RoutingLLMService | RoutingEmbeddingService |
| 调用模式 | 同步 + 流式 | 仅同步 |
| 模板方法基类 | AbstractOpenAIStyleChatClient | AbstractOpenAIStyleEmbeddingClient |
| 供应商实现 | 三个(Ollama / 百炼 / 硅基流动) | 两个(Ollama / 硅基流动) |
| 同步路由 | executeWithFallback | executeWithFallback |
| 流式路由 | probe-and-commit 首包探测 | 无(没有流式调用) |
| 批量调用 | 无 | embedBatch(支持多条文本) |
| 特有配置 | supports-thinking、deep-thinking-model | dimension(向量维度) |
和 Chat 一样,Embedding 子系统也用模板方法基类封装 OpenAI 兼容协议的通用逻辑。两个供应商 Ollama 和硅基流动都实现 /v1/embeddings 协议:请求体字段 model / input / dimensions 结构相同,响应体都是 {"data": [{"embedding": [...]}, ...]} 的 OpenAI 风格。差异点集中在三个地方:Ollama 不需要 API Key、硅基流动有 32 条的批量上限、硅基流动要求显式 encoding_format: "float" 字段。这三处用钩子方法覆写就好,没必要让每个子类重写一遍完整的 HTTP 调用流程。
Chat 和 Embedding 的模板方法基类在结构上高度对称:都把请求构建、HTTP 调用、JSON 解析、错误处理封装在基类,子类只负责声明自己是谁、覆写几个钩子方法。读完这一篇,你会发现它和第四篇 AbstractOpenAIStyleChatClient 是同一套思路在不同能力上的复用。
2. 三层接口结构
三层结构和 Chat 完全一致:业务层调 EmbeddingService,RoutingEmbeddingService 通过 ModelSelector + ModelRoutingExecutor 路由到具体的 EmbeddingClient 实现。业务层不知道背后调的是 Ollama 还是硅基流动。
在实际的代码中,应用了模板方法模式抽象了核心逻辑,优化后 Ollama 和硅基流动只有很薄一层代码了。