📘 mcpgw 图解教程 · 目录 第二部分 · 检索深入 · 向量检索 08 / 15
第二部分 · 检索深入 · 向量检索

装配与配置

本课把前几课的零件接成一台机器:当配置选 strategy = "vector" 时, 启动期如何构造 embedder、把它注入 gateway,并在缺凭证时立刻失败(fail-fast)—— 而不是等到第一次查询才崩。配置层负责声明意图,装配层负责一次性兑现可靠性约束

build_strategy:按名字造策略

🔬 细节 / 代码对应

build_strategy 只认一个字符串名字和一个可选 embedder, 返回一个装箱的 RetrievalStrategy

它故意只收 &str(不依赖 config 类型),所以 retrieval 这个 crate 不必反向依赖配置层。 另外提醒:默认策略仍是 bm25,向量是显式选择项。

crates/retrieval/src/lib.rs · build_strategy
pub fn build_strategy(
    name: &str,
    embedder: Option<&Arc<dyn Embedder>>,
) -> Result<Box<dyn RetrievalStrategy>, StrategyError> {
    match name {
        "bm25" => Ok(Box::new(Bm25Strategy::new())),
        "vector" => match embedder {
            Some(e) => Ok(Box::new(VectorStrategy::new(e.clone()))),
            None => Err(StrategyError::EmbedderRequired(name.to_string())),
        },
        "hybrid" => match embedder {     // M2-B:RRF 融合,同样需 embedder
            Some(e) => Ok(Box::new(HybridStrategy::new(e.clone()))),
            None => Err(StrategyError::EmbedderRequired(name.to_string())),
        },
        other => Err(StrategyError::NotImplemented(other.to_string())),
    }
}

build_embedder:vector / hybrid 才造,且只造一次

🔬 细节 / 代码对应

build_embedder 在启动期跑一次。当 strategy"vector""hybrid" 时(两者都需 embedder):

因为这个 Arc 只构造一次并被 gateway 一直持有,所以缓存能跨 rebuild 持久存活—— 目录重建不会丢掉已经算好的嵌入。

crates/mcpgw/src/main.rs · build_embedder
fn build_embedder(cfg: &config::Config)
  -> Result<Option<Arc<dyn retrieval::Embedder>>, String> {
    match cfg.retrieval.strategy.as_str() {
        "vector" | "hybrid" => {
            let v = cfg.retrieval.vector.as_ref()
                .ok_or_else(|| format!("strategy={:?} requires [retrieval.vector]", cfg.retrieval.strategy))?;
            // fail-fast:错误只提变量名,不含密钥值
            let api_key = std::env::var(&v.api_key_env)
                .map_err(|_| format!("[retrieval.vector]: env {:?} is not set", v.api_key_env))?;
            let openai = embedder::OpenAiEmbedder::new(
                v.base_url.clone(), v.model.clone(), api_key,
                v.dim, v.timeout_ms.map(Duration::from_millis),
            );
            // 只造一次 → 缓存跨 rebuild 持久
            Ok(Some(Arc::new(retrieval::CachingEmbedder::new(Arc::new(openai)))))
        }
        _ => Ok(None),
    }
}

prepare_state 据此分支:拿到 Some(embedder)GatewayState::with_embedder(&cfg.retrieval.strategy, embedder); 拿到 NoneGatewayState::new(&cfg.retrieval.strategy)。 注意名字仍是原样传进去,由 build_strategy 再做一次校验。

VectorConfig:把意图写进配置

🔬 细节 / 代码对应

[retrieval.vector] 对应 VectorConfig(带 deny_unknown_fields,写错键名会被拒)。字段:

字段类型说明
base_urlString嵌入服务地址,默认 OpenAI https://api.openai.com/v1
modelString嵌入模型名(必填)
api_key_envString存放密钥的环境变量名(必填)——配置里只放变量名,不放密钥本身
dimOption<usize>期望维度(可选,用于校验返回向量长度)
timeout_msOption<u64>单次请求超时(可选)
batch_sizeOption<usize>预留 / 未启用(见下方易错点)

一份最小可用配置:

[retrieval]
strategy = "vector"

[retrieval.vector]
base_url = "https://api.openai.com/v1"
model = "text-embedding-3-small"
api_key_env = "OPENAI_API_KEY"
⚠️ 三个易错点
✅ 关键要点
💡 设计亮点
配置层把「要不要向量、向量打到哪、用哪个 key」声明化成一段 TOML; 装配层(build_embedderprepare_statebuild_strategy)则在启动期一次性 把这些声明兑现成可靠性约束——缺 key 立刻失败、缓存只建一次、名字与 embedder 的匹配性被强校验。意图与装配彻底分离。

向量检索专章到此结束。下一步——Hybrid 检索与 RRF 融合——把字面与语义两路用 RRF 合并,见第四部分「Hybrid 检索(RRF)」一课。