
【Bug已解决】Add OpenRouterEmbeddings to langchain-openrouter一、现象长什么样langchain-openrouter这个包提供了ChatOpenRouter对接 OpenRouter 的聊天模型但没有对应的Embeddings类。OpenRouter 其实也提供嵌入端点/api/v1/embeddings背后路由到各种嵌入模型于是用户想用 OpenRouter 做向量化时只能自己手写一个最小Embeddings子类重复造轮子。或者混用别的 provider 的 Embeddings失去统一走 OpenRouter的便利API key、base url、路由都两套。结果就是聊天用 OpenRouter、嵌入却要换 provider配置割裂且社区多次提能否补一个 OpenRouterEmbeddings。本篇讲怎么把OpenRouterEmbeddings正确地加进langchain-openrouter并和ChatOpenRouter共享配置。二、背景OpenRouter 的 embeddings API 与 OpenAI 兼容POST https://openrouter.ai/api/v1/embeddings { model: openai/text-embedding-3-small, input: [text1, text2] } - { data: [{embedding: [...]}, ...], model: ... }LangChain 的Embeddings基类要求实现embed_documents(texts)和embed_query(text)内部把输入 POST 到端点取回向量。既然 OpenRouter 端点与 OpenAI 同构最自然的做法是让OpenRouterEmbeddings复用 OpenAIEmbeddings 的通信逻辑仅换 base_url 与默认模型路由。缺这个类是包的功能缺口而非崩溃但影响易用性与一致性。三、根因根因是包功能不完整只覆盖 Chat 不覆盖 Embeddingslangchain-openrouter早期只做了 chat 模型。配置无法共享用户要 embedding 只能另接 providerkey/base_url 重复。缺统一封装社区各自手写行为不一致。本质把聊天和嵌入当成两个无关能力忽略了它们共享同一 provider 配置、应能统一接入。四、最小可运行复现下面演示缺类导致重复造轮子from langchain_openrouter import ChatOpenRouter # 想要 embeddings但没有 OpenRouterEmbeddings try: from langchain_openrouter import OpenRouterEmbeddings except ImportError: # 只能自己写重复 ChatOpenRouter 的 base_url/key 逻辑 class MyEmbeddings(Embeddings): ...修复补上OpenRouterEmbeddings复用 OpenAI 兼容通信。import requests from langchain_core.embeddings import Embeddings class OpenRouterEmbeddings(Embeddings): def __init__(self, modelopenai/text-embedding-3-small, api_keyNone, base_urlhttps://openrouter.ai/api/v1): self.model model self.api_key api_key self.base_url base_url def _post(self, texts): r requests.post(f{self.base_url}/embeddings, headers{Authorization: fBearer {self.api_key}}, json{model: self.model, input: texts}, timeout30) r.raise_for_status() return [d[embedding] for d in r.json()[data]] def embed_documents(self, texts): return self._post(texts) def embed_query(self, text): return self._post([text])[0]五、解决方案第一层最小直接修复最小修法在包里加OpenRouterEmbeddings复用 OpenAI 兼容端点并与ChatOpenRouter共享api_key/base_url读取逻辑。from langchain_core.embeddings import Embeddings class OpenRouterEmbeddings(Embeddings): def __init__(self, modelopenai/text-embedding-3-small, api_keyNone, base_urlhttps://openrouter.ai/api/v1, default_headersNone): self.model model self.api_key api_key or os.environ[OPENROUTER_API_KEY] self.base_url base_url self.default_headers default_headers or {} def embed(self, texts): resp requests.post( f{self.base_url}/embeddings, headers{Authorization: fBearer {self.api_key}, **self.default_headers}, json{model: self.model, input: texts}, timeout30, ) resp.raise_for_status() return [d[embedding] for d in resp.json()[data]] def embed_documents(self, texts): return self.embed(list(texts)) def embed_query(self, text): return self.embed([text])[0]这一层让 embeddings 和 chat 统一走 OpenRouter。六、解决方案第二层结构化改进把OpenRouterEmbeddings 配置策略固化成策略对象作为单一事实来源明确端点、默认模型、与 Chat 共享配置。from dataclasses import dataclass, field from typing import Dict, Optional dataclass(frozenTrue) class LangChainOpenRouterEmbeddingsPolicy: OpenRouterEmbeddings 配置策略的单一事实来源。 base_url: str https://openrouter.ai/api/v1 default_model: str openai/text-embedding-3-small shares_config_with_chat: bool True extra_headers: Dict[str, str] field(default_factorydict) def endpoint(self) - str: return f{self.base_url.rstrip(/)}/embeddings def resolve_key(self, explicit: Optional[str]) - str: if explicit: return explicit if self.shares_config_with_chat: return os.environ.get(OPENROUTER_API_KEY, ) raise ValueError(api_key required) def validate(self) - None: if self.shares_config_with_chat and not self.extra_headers.get(X-Title): # OpenRouter 推荐带 X-Title 标识应用 passOpenRouterEmbeddings用policy取端点与 key与 Chat 同源。七、解决方案第三层断言 / CI 守护用 pytest 锁死配置与行为import pytest from policy import LangChainOpenRouterEmbeddingsPolicy as P def test_endpoint_correct(): p P() assert p.endpoint().endswith(/embeddings) def test_shares_key_with_chat(monkeypatch): p P(shares_config_with_chatTrue) monkeypatch.setenv(OPENROUTER_API_KEY, k) assert p.resolve_key(None) k def test_explicit_key_wins(): p P() assert p.resolve_key(explicit) explicit def test_policy_valid(): P().validate()CI 加一条用 mock embeddings 端点跑embed_documents/embed_query断言返回向量维度一致、且用 OpenRouter base_url。八、排查清单想用 OpenRouter 做 embedding 但没类→ 包缺 OpenRouterEmbeddings。是否与 Chat 共享 key/base_url→ 应统一避免两套配置。端点是否 OpenAI 兼容→/api/v1/embeddings。默认模型路由对吗→ 如 openai/text-embedding-3-small。是否重复造轮子→ 直接用包内 OpenRouterEmbeddings。是否有embed 返回向量测试→ 必须有。九、小结langchain-openrouter只有 Chat 没有 Embeddings 类用户想用 OpenRouter 做向量化只能重复造轮子或换 provider配置割裂。根因是包功能不完整、未把 chat 与 embedding 统一接入。第一层补OpenRouterEmbeddingsOpenAI 兼容端点并与 Chat 共享配置第二层用LangChainOpenRouterEmbeddingsPolicy把配置固化成单一事实来源第三层用 pytest 守护。Provider 封装的通用原则同一 provider 的 chat 与 embedding 应共用配置、统一接入避免用户重复造轮子。