Some checks failed
Sync to Gitee / sync (push) Has been cancelled
Submit the formed RAG documentation set produced across Sprint-1/2/3 (WS-12 through WS-26) under docs/rag/. Includes: - README.md / INDEX.md: landing + total index (responsibility matrix, review verdicts, dual-link to source issues) - overview/: full-pipeline architecture (4 .mmd diagrams), 11-stage boundary contracts, doc map, source-code inventory - pipeline/: 5 deep-dives (Loader/Parser/Chunking, Embedding, VDB & retrieval, GraphRAG, Rerank/Prompt/LLM) - graphrag/, end-to-end/: v1.0 formal versions with full source retained as reference - evolution/: 11 architecture-refactor proposals, 6-direction roadmap, capability map - review/: S3-T1 / S3-T2 final reviews, S2-T7 final summary - _indexes/: glossary (81 terms), source->doc reverse index, chart index - _release/: v1.0-RC1 release manifest, versioning convention, ops & freshness plan - _meta/README.md: placeholder noting WS-12 governance assets gap Aggregate review score 92.6/100 (8/8 PASS, 31/31 source-code spot checks hit). The legacy docs/ ignore in .gitignore is narrowed to docs/* with an explicit allowlist for docs/rag/. Refs: WS-26 Co-authored-by: multica-agent <github@multica.ai>
26 KiB
26 KiB
[S2-T2] Embedding 模型选择与向量生成实现详解
一句话定位
MemoryBear 的 Embedding 层负责将文本 Chunk 转化为稠密向量,是连接"非结构化文本"与"向量数据库"的核心桥梁。当前系统同时存在两条 Embedding 调用路径:基于 LangChain 的统一封装层(RedBearEmbeddings,面向 ES 向量库) 与 遗留的原始实现层(embedding_model.py,面向 GraphRAG 与 Dealer 检索)。
设计目标与适用场景
- 多提供商兼容:覆盖 OpenAI、Azure、DashScope(通义千问)、Volcano(火山引擎)、Xinference、GPUStack、Ollama、Bedrock 等主流 Embedding 服务
- 多模态扩展:火山引擎支持文本/图片/视频多模态 Embedding
- 知识库隔离:每个知识库独立配置 Embedding 模型,通过
knowledge.embedding_id关联 - GraphRAG 支撑:为实体/关系节点生成向量,用于图检索中的语义匹配
关键概念与术语表
| 术语 | 含义 |
|---|---|
RedBearEmbeddings |
LangChain 统一封装类,面向 ES 向量库的主入口 |
OpenAIEmbed |
遗留原始实现,面向 GraphRAG 与 Dealer 检索 |
ModelApiKey |
数据库表,存储模型的 API Key、base_url、provider |
ModelConfig |
数据库表,存储模型的配置参数(capability、timeout、max_retries 等) |
EMBEDDING_BATCH_SIZE |
环境变量,控制向量化批处理大小 |
chat_limiter |
Trio 并发限流器,控制 GraphRAG 中 Embedding 并发数 |
get_embed_cache |
Redis 缓存函数,缓存 GraphRAG 中的实体/关系向量 |
实现概览
架构分层
┌─────────────────────────────────────────────────────────────┐
│ 调用方(检索 / 入库) │
│ ElasticSearchVector Dealer.search GraphRAG │
├─────────────────────────────────────────────────────────────┤
│ Embedding 封装层 │
│ RedBearEmbeddings(新) │ embedding_model.py(遗留) │
├─────────────────────────────────────────────────────────────┤
│ 底层 SDK / API │
│ langchain_openai dashscope volcenginesdkarkruntime ... │
└─────────────────────────────────────────────────────────────┘
数据流:Chunk → Vector
DocumentChunk(page_content="...", metadata={...})
│
▼
ElasticSearchVector.add_chunks(chunks) [elasticsearch_vector.py:55]
│
├─► 火山引擎多模态: self.embeddings.embed_batch(texts)
└─► 其他 provider: self.embeddings.embed_documents(list(texts))
│
▼
RedBearEmbeddings.embed_documents(texts) [models/embedding.py:65]
│
▼
OpenAIEmbeddings.embed_documents(texts) [LangChain 内部]
│
▼
HTTP API Call (OpenAI-compatible / provider-specific)
│
▼
List[List[float]] → ES dense_vector field
1. 模型选择策略
1.1 遗留层支持的模型(embedding_model.py)
| 类名 | _FACTORY_NAME | 默认模型 | 上下文长度 | 截断策略 | batch_size | 备注 |
|---|---|---|---|---|---|---|
OpenAIEmbed |
OpenAI | text-embedding-ada-002 | 8000 tokens | truncate(t, 8000) |
16 | OpenAI 官方 API |
AzureEmbed |
Azure-OpenAI | 继承 OpenAI | 8000 tokens | 同上 | 16 | Azure OpenAI Service |
BaiChuanEmbed |
BaiChuan | Baichuan-Text-Embedding | 8000 tokens | 同上 | 16 | 百川智能 |
QWenEmbed |
Tongyi-Qianwen | text_embedding_v2 | 2048 tokens | truncate(t, 2048) |
4 | 阿里 DashScope,自带 5 次重试 |
XinferenceEmbed |
Xinference | 用户指定 | 8000 tokens | 同上 | 16 | Xinference 本地部署 |
NvidiaEmbed |
NVIDIA | 用户指定 | 不截断(API 端截断) | 无 | 16 | NVIDIA API,含特殊模型路由 |
HuggingFaceEmbed |
HuggingFace | 用户指定 | 不截断 | 无 | 无(全量发送) | 本地 TEI 服务 |
VolcEngineEmbed |
VolcEngine | 用户指定 | 8000 tokens | 同上 | 16 | 火山引擎 Ark |
GPUStackEmbed |
GPUStack | 用户指定 | 8000 tokens | 同上 | 16 | GPUStack 本地部署 |
LocalAIEmbed |
LocalAI | 用户指定 | 8000 tokens | 同上 | 16 | LocalAI / LMStudio |
1.2 统一封装层支持的模型(RedBearEmbeddings)
| Provider | 对应的 LangChain 类 | 默认超时 | 默认重试 | 多模态支持 |
|---|---|---|---|---|
openai |
langchain_openai.OpenAIEmbeddings |
120s | 2 次 | 否 |
xinference |
langchain_openai.OpenAIEmbeddings |
120s | 2 次 | 否 |
gpustack |
langchain_openai.OpenAIEmbeddings |
120s | 2 次 | 否 |
dashscope |
langchain_community.DashScopeEmbeddings |
120s | 2 次 | 否 |
ollama |
langchain_ollama.OllamaEmbeddings |
120s | 2 次 | 否 |
bedrock |
langchain_aws.BedrockEmbeddings |
120s | 2 次 | 否 |
volcano |
volcenginesdkarkruntime.Ark (原生 SDK) |
120s | 2 次 | 是(文本/图片/视频) |
1.3 默认模型
- 知识库默认 Embedding:通过
workspace.embedding继承,或管理员在创建知识库时手动指定embedding_id - 数据库关联:
knowledge.embedding_id→model_configs.id(ModelConfig 表)→model_api_keys(API Key 表) - 无默认模型硬编码:系统不内置默认模型名称,完全依赖数据库配置
1.4 切换方式
- 管理后台配置:在模型管理页面添加新的 Embedding 模型配置(provider + model_name + api_key + base_url)
- 知识库绑定:创建/编辑知识库时选择新的
embedding_id - 即时生效:新写入的 Chunk 使用新模型;历史 Chunk 向量保持不变(见"维度变更兼容"章节)
2. 调用链路详解
2.1 入库链路(Chunk → ES Vector)
memory_konwledges_server.py:430
vector_service.add_chunks([chunk])
│
▼
elasticsearch_vector.py:55-63
def add_chunks(self, chunks: list[DocumentChunk], **kwargs):
texts = [chunk.page_content for chunk in chunks]
if self.is_multimodal_embedding:
embeddings = self.embeddings.embed_batch(texts) # 火山引擎
else:
embeddings = self.embeddings.embed_documents(list(texts)) # 其他
self.create(chunks, embeddings, **kwargs)
│
▼
models/embedding.py:65-78
def embed_documents(self, texts: list[str]) -> list[list[float]]:
if self._is_volcano:
# 多模态 Embedding
contents = [{"type": "text", "text": text} for text in texts]
response = self._client.multimodal_embeddings.create(...)
return [response.data.embedding]
else:
return self._model.embed_documents(texts) # LangChain 标准接口
2.2 检索链路(Query → Vector → ES Search)
elasticsearch_vector.py:374-380
def search_by_vector(self, query: str, **kwargs: Any) -> list[DocumentChunk]:
if self.is_multimodal_embedding:
query_vector = self.embeddings.embed_text(query) # 火山引擎
else:
query_vector = self.embeddings.embed_query(query) # 其他
# ES script_score: cosineSimilarity(params.query_vector, 'vector') + 1.0
2.3 GraphRAG 链路(Entity/Relation → Vector)
graphrag/utils.py:301-327
async def graph_node_to_chunk(kb_id, embd_mdl, ent_name, meta, chunks):
ebd = get_embed_cache(embd_mdl.model_name, ent_name)
if ebd is None:
async with chat_limiter: # 并发限流
with trio.fail_after(...):
ebd, _ = await trio.to_thread.run_sync(
lambda: embd_mdl.encode([ent_name])) # 遗留 OpenAIEmbed
ebd = ebd[0]
set_embed_cache(embd_mdl.model_name, ent_name, ebd) # Redis 缓存
chunk["q_%d_vec" % len(ebd)] = ebd
2.4 Dealer 检索链路(加权融合检索)
nlp/search.py:365-373
def get_vector(self, txt, emb_mdl, topk=10, similarity=0.1):
qv, _ = emb_mdl.encode_queries(txt) # 遗留 OpenAIEmbed
embedding_data = [get_float(v) for v in qv]
vector_column_name = f"q_{len(embedding_data)}_vec"
return MatchDenseExpr(vector_column_name, embedding_data, ...)
2.5 同步/异步说明
| 场景 | 模式 | 说明 |
|---|---|---|
| ES 向量入库 | 同步 | embed_documents() 为同步调用,在请求线程中执行 |
| ES 向量检索 | 同步 | embed_query() 为同步调用 |
| GraphRAG 实体嵌入 | 异步 | trio.to_thread.run_sync() 将同步 Embedding 调用放入线程池 |
| 模型验证 | 异步 | asyncio.to_thread() 包装同步调用 |
2.6 批量大小与并发控制
| 控制点 | 数值 | 位置 |
|---|---|---|
| OpenAI 兼容类 batch_size | 16 | embedding_model.py:52, :83, :178 |
| QWen batch_size | 4 | embedding_model.py:133 |
| HuggingFace | 无批量(全量发送) | embedding_model.py:258 |
| GraphRAG 并发限流 | MAX_CONCURRENT_CHATS(默认 10) |
graphrag/utils.py:41 |
| RedBearModelConfig 并发 | 5(配置项,当前未在 Embedding 中使用) | models/base.py:37 |
3. 生产级关注点
3.1 限流与配额管理
现状分析:
- 无显式 API 速率限制:代码中未发现针对 Embedding API 的 RPM/TPM 限流逻辑
- LangChain 内部限流:
OpenAIEmbeddings内部有基础请求间隔控制,但不可配置 - 并发控制仅存在于 GraphRAG:
chat_limiter = trio.CapacityLimiter(10)限制 GraphRAG 中实体/关系嵌入的并发数
源码引用:
# graphrag/utils.py:41
chat_limiter = trio.CapacityLimiter(int(os.environ.get("MAX_CONCURRENT_CHATS", 10)))
# graphrag/utils.py:320-322
async with chat_limiter:
with trio.fail_after(3 if enable_timeout_assertion else 30000000):
ebd, _ = await trio.to_thread.run_sync(lambda: embd_mdl.encode([ent_name]))
3.2 失败重试与降级
现状分析:
| 路径 | 重试机制 | 降级策略 |
|---|---|---|
| QWenEmbed(遗留) | 显式 5 次重试,间隔 10s | 抛出异常,无降级 |
| RedBearEmbeddings(统一层) | max_retries(默认 2,由 LangChain SDK 内部实现) |
抛出异常,无降级 |
| ES 连接 | retry_on_timeout=True, max_retries=3 |
抛出 ConnectionError |
| 知识检索 | 单库失败不影响其他库 | continue 跳过 |
源码引用:
# embedding_model.py:138-143(QWen 显式重试)
retry_max = 5
resp = dashscope.TextEmbedding.call(...)
while (resp["output"] is None ...) and retry_max > 0:
time.sleep(10)
resp = dashscope.TextEmbedding.call(...)
retry_max -= 1
# models/base.py:34-36(统一层重试配置)
timeout: float = Field(default_factory=lambda: float(os.getenv("LLM_TIMEOUT", "120.0")))
max_retries: int = Field(default_factory=lambda: int(os.getenv("LLM_MAX_RETRIES", "2")))
⚠️ 关键缺口:无备用模型降级机制。 当主 Embedding 模型服务不可用时,系统会直接失败,不会自动切换备用模型。
3.3 缓存策略
现状分析:
- GraphRAG 实体/关系缓存:Redis 缓存,TTL 24 小时,key 为
xxhash(model_name + text) - ES 向量入库/检索:无缓存,每次调用都实时请求 Embedding API
- 无全局 Embedding 缓存层
源码引用:
# graphrag/utils.py:115-134
redis_client = redis.StrictRedis(**redis_conn_params)
def get_embed_cache(llmnm, txt):
hasher = xxhash.xxh64()
hasher.update(str(llmnm).encode("utf-8"))
hasher.update(str(txt).encode("utf-8"))
k = hasher.hexdigest()
bin = redis_client.get(k)
if not bin:
return
return np.array(json.loads(bin))
def set_embed_cache(llmnm, txt, arr):
# ... 设置 Redis,TTL = 24 * 3600
影响评估:
- 重复文本(如相同实体名)在 GraphRAG 中可命中缓存,节省 API 调用
- 常规知识库检索/入库中,相同 Chunk 或 Query 重复向量化,造成冗余 API 开销
3.4 维度变更对历史向量的兼容
现状分析:
- 无自动兼容机制:更换 Embedding 模型后,历史 Chunk 的向量维度不变,新 Chunk 使用新维度
- ES Mapping 冲突:
create_collection()在创建索引时根据第一条向量的长度设置dense_vector.dims,若后续向量维度不同会写入失败 - 混合维度风险:同一索引中既有 1536 维又有 768 维的向量,ES
dense_vector字段要求固定维度
源码引用:
# elasticsearch_vector.py:653-658
Field.VECTOR.value: {
"type": "dense_vector",
"dims": len(embeddings[0]), # 根据第一条向量动态决定
"index": True,
"similarity": "cosine"
}
推荐操作(如何安全替换 Embedding 模型):
- 创建新知识库:为新知识库配置新的 Embedding 模型,避免影响已有数据
- 重建索引(谨慎):如需迁移历史数据,需:
- 删除旧 ES 索引(
Vector_index_{knowledge_id}_Node) - 重新解析所有文档(触发新的 Embedding 调用)
- 确认所有 Chunk 使用同一模型生成向量
- 删除旧 ES 索引(
- 版本标记:建议在知识库 metadata 中记录当前使用的 Embedding 模型版本,便于追踪
影响面分析:
| 操作 | 影响范围 | 风险等级 |
|---|---|---|
| 修改知识库 embedding_id | 仅新入库 Chunk | 低 |
| 修改已有知识库 embedding_id + 不重建索引 | 检索时 Query 向量与 Chunk 向量维度不匹配 | 高 |
| 重建索引 | 全量重新 Embedding,API 费用 + 时间成本 | 中 |
4. 配置项汇总
4.1 环境变量
| 变量名 | 默认值 | 说明 | 影响范围 |
|---|---|---|---|
LLM_TIMEOUT |
120.0 | Embedding HTTP 请求超时(秒) | RedBearEmbeddings 统一层 |
LLM_MAX_RETRIES |
2 | Embedding 请求最大重试次数 | RedBearEmbeddings 统一层 |
MAX_CONCURRENT_CHATS |
10 | GraphRAG Embedding 并发限流 | graphrag/utils.py |
ELASTICSEARCH_HOST |
127.0.0.1 | ES 主机地址 | ES 向量存储 |
ELASTICSEARCH_PORT |
9200 | ES 端口 | ES 向量存储 |
ELASTICSEARCH_REQUEST_TIMEOUT |
100000 | ES 请求超时 | ES 连接 |
ELASTICSEARCH_MAX_RETRIES |
10 | ES 连接重试 | ES 连接 |
EMBEDDING_BATCH_SIZE |
(注释掉,未使用) | 预留环境变量 | — |
4.2 数据库配置(model_configs / model_api_keys 表)
| 字段 | 类型 | 说明 | 推荐值 |
|---|---|---|---|
provider |
String | 提供商标识 | openai / dashscope / volcano / xinference |
model_name |
String | 模型实际名称 | text-embedding-3-small / text-embedding-v3 |
api_key |
String | API 密钥 | — |
api_base |
String | 基础 URL | https://api.openai.com/v1 |
timeout |
Float | 请求超时 | 120.0(复杂文档可适当延长) |
max_retries |
Int | 最大重试 | 2(生产环境建议 3-5) |
capability |
Array | 模型能力列表 | [](Embedding 模型通常无需特殊能力) |
4.3 调用入参(运行时)
| 参数 | 位置 | 默认值 | 说明 |
|---|---|---|---|
top_k |
search_by_vector() |
1024 | 向量检索返回数量 |
score_threshold |
search_by_vector() |
0.3 | 相似度阈值(归一化后 [0,1]) |
similarity_threshold |
knowledge_retrieval() |
0.2 | 全文检索阈值 |
vector_similarity_weight |
knowledge_retrieval() |
0.3 | 混合检索中向量权重 |
5. 关键源码片段
5.1 Embedding 模型基类与统一接口
# api/app/core/rag/llm/embedding_model.py:14-38
class Base(ABC):
def __init__(self, key, model_name, **kwargs):
pass
def encode(self, texts: list):
raise NotImplementedError("Please implement encode method!")
def encode_queries(self, text: str):
raise NotImplementedError("Please implement encode method!")
5.2 OpenAI 兼容 Embedding 实现(批量处理)
# api/app/core/rag/llm/embedding_model.py:50-65
class OpenAIEmbed(Base):
def encode(self, texts: list):
batch_size = 16
texts = [truncate(t, 8000) for t in texts] # 安全截断
ress = []
total_tokens = 0
for i in range(0, len(texts), batch_size):
res = self.client.embeddings.create(
input=texts[i : i + batch_size],
model=self.model_name,
encoding_format="float",
extra_body={"drop_params": True}
)
ress.extend([d.embedding for d in res.data])
total_tokens += self.total_token_count(res)
return np.array(ress), total_tokens
5.3 统一封装层(RedBearEmbeddings)
# api/app/core/models/embedding.py:9-23
class RedBearEmbeddings(Embeddings):
def __init__(self, config: RedBearModelConfig):
self._config = config
self._is_volcano = config.provider.lower() == ModelProvider.VOLCANO
if self._is_volcano:
self._client = self._create_volcano_client(config)
self._model = None
else:
self._model = self._create_model(config)
self._client = None
# api/app/core/models/embedding.py:65-78
def embed_documents(self, texts: list[str]) -> list[list[float]]:
if self._is_volcano:
contents = [{"type": "text", "text": text} for text in texts]
response = self._client.multimodal_embeddings.create(
model=self._config.model_name,
input=contents,
encoding_format="float"
)
return [response.data.embedding]
else:
return self._model.embed_documents(texts)
5.4 ES 向量写入与 Mapping 创建
# api/app/core/rag/vdb/elasticsearch/elasticsearch_vector.py:55-63
def add_chunks(self, chunks: list[DocumentChunk], **kwargs):
texts = [chunk.page_content for chunk in chunks]
if self.is_multimodal_embedding:
embeddings = self.embeddings.embed_batch(texts)
else:
embeddings = self.embeddings.embed_documents(list(texts))
self.create(chunks, embeddings, **kwargs)
# api/app/core/rag/vdb/elasticsearch/elasticsearch_vector.py:653-658
Field.VECTOR.value: {
"type": "dense_vector",
"dims": len(embeddings[0]),
"index": True,
"similarity": "cosine"
}
5.5 检索端向量生成
# api/app/core/rag/vdb/elasticsearch/elasticsearch_vector.py:374-380
def search_by_vector(self, query: str, **kwargs: Any) -> list[DocumentChunk]:
if self.is_multimodal_embedding:
query_vector = self.embeddings.embed_text(query)
else:
query_vector = self.embeddings.embed_query(query)
# cosineSimilarity(params.query_vector, 'vector') + 1.0
5.6 GraphRAG 中的 Embedding 缓存
# api/app/core/rag/graphrag/utils.py:115-134
redis_client = redis.StrictRedis(**redis_conn_params)
def get_embed_cache(llmnm, txt):
hasher = xxhash.xxh64()
hasher.update(str(llmnm).encode("utf-8"))
hasher.update(str(txt).encode("utf-8"))
k = hasher.hexdigest()
bin = redis_client.get(k)
if not bin:
return
return np.array(json.loads(bin))
def set_embed_cache(llmnm, txt, arr):
# ... TTL = 24 * 3600
5.7 模型配置基类
# api/app/core/models/base.py:22-38
class RedBearModelConfig(BaseModel):
model_name: str
provider: str
api_key: str
base_url: Optional[str] = None
timeout: float = Field(default_factory=lambda: float(os.getenv("LLM_TIMEOUT", "120.0")))
max_retries: int = Field(default_factory=lambda: int(os.getenv("LLM_MAX_RETRIES", "2")))
concurrency: int = 5
extra_params: Dict[str, Any] = {}
6. 如何替换 Embedding 模型(操作步骤 + 影响面分析)
6.1 操作步骤
场景 A:为新知识库配置新模型(推荐,零风险)
- 进入管理后台 → 模型管理 → 添加新 Embedding 模型配置
- 填写 provider、model_name、api_key、base_url
- 验证模型可用性(model_service.py 会调用
embed_documents测试) - 创建新知识库时选择该模型作为
embedding_id - 新入库文档自动使用新模型生成向量
场景 B:替换已有知识库的 Embedding 模型(高风险,需重建索引)
- 备份数据:导出知识库下所有文档元数据
- 删除旧 ES 索引:
# 索引名格式: Vector_index_{knowledge_id}_Node vector_service.delete() # elasticsearch_vector.py:176 - 更新知识库配置:修改
knowledge.embedding_id为新模型 ID - 重新解析所有文档:触发完整的 Chunk → Embedding → ES 写入流程
- 验证维度一致性:确认所有 Chunk 向量维度相同
- 检索验证:执行测试查询,确认向量检索正常返回
6.2 影响面分析
| 组件 | 影响 | 说明 |
|---|---|---|
| ES 索引 | 必须重建 | dense_vector.dims 在创建时固定,不支持动态变更 |
| 历史 Chunk | 需重新嵌入 | 旧向量与新向量维度/语义空间不同,不能混用 |
| 检索质量 | 可能变化 | 不同模型的语义表示能力不同,需重新调参阈值 |
| API 成本 | 短期增加 | 重建索引期间产生全量 Embedding API 调用费用 |
| GraphRAG | 需同步更新 | 实体/关系向量也需使用同一模型,否则语义空间不一致 |
| 混合检索 | 需重新校准 | 向量相似度权重 vector_similarity_weight 可能需要调整 |
7. 边界条件与已知限制
- 维度上限:ES
dense_vector字段index: True时维度上限 1024;index: False时上限 2048。当前代码index: True,若使用 1536 维模型(如 OpenAI text-embedding-ada-002)会触发此限制 - batch_size 硬编码:各模型的 batch_size(16 或 4)在源码中写死,不可配置
- 无 Embedding 调用计费统计:系统未记录 Embedding API 的调用次数和 Token 消耗(仅 LLM 有统计)
- 无 Embedding 降级:主模型失败时无自动切换到备用模型的机制
- QWen 截断差异:QWen 截断到 2048 tokens,而其他 OpenAI 兼容类截断到 8000,混合使用时需特别注意
- 文本截断使用 cl100k_base:
token_utils.py使用cl100k_base编码器,可能与实际模型使用的 tokenizer 不一致(如 QWen 使用自己的 tokenizer),导致截断长度不准
8. 监控指标与排错指引
8.1 建议监控指标
| 指标 | 采集方式 | 告警阈值建议 |
|---|---|---|
| Embedding API 响应时间 | LangChain callback 或中间件拦截 | P99 > 5s |
| Embedding API 错误率 | 异常捕获统计 | > 1% |
| Embedding Token 消耗 | API 响应中的 usage.total_tokens | 按预算设置 |
| ES 向量写入延迟 | ES bulk API 响应时间 | > 2s |
| Redis 缓存命中率 | get_embed_cache 命中统计 |
< 50% 时排查 |
8.2 常见故障排查
| 现象 | 根因 | 排查路径 |
|---|---|---|
| 向量检索返回空 | 维度不匹配 / 相似度阈值过高 | 检查 dense_vector.dims 与 Embedding 输出维度是否一致;降低 score_threshold |
| Embedding 调用超时 | API 服务商响应慢 / 文本过长 | 检查 LLM_TIMEOUT;检查文本是否被正确截断 |
| 批量 Embedding 失败 | batch_size 过大 | 减小 batch_size(需改源码) |
| GraphRAG 实体向量不一致 | 缓存命中但模型已更换 | 清除 Redis 中 get_embed_cache 相关 key |
| ES 写入报错 "illegal_argument_exception" | dense_vector 维度超限 | 确认 index: True 时 dims <= 1024 |
9. 优化建议与未来扩展点
9.1 短期优化(代码级)
- 全局 Embedding 缓存层:将
get_embed_cache/set_embed_cache机制扩展到 ES 向量入库/检索链路,减少重复 API 调用 - 可配置 batch_size:将硬编码的 16/4 提取为环境变量或数据库配置项
- 备用模型降级:实现 Embedding 模型的主备切换逻辑(类似 LLM 的 fallback 机制)
- 维度一致性校验:在
add_chunks()和search_by_vector()中增加维度校验,提前发现不匹配问题
9.2 中期优化(架构级)
- Embedding 服务化:将 Embedding 调用抽离为独立微服务,支持:
- 统一缓存(Redis + 本地 LRU)
- 请求队列 + 速率限制
- 多模型负载均衡
- 异步 Embedding 流水线:文档入库时先写入队列,后台异步完成 Embedding 和 ES 写入
- Embedding 质量监控:定期抽样检测向量空间的分布质量(如余弦相似度分布、异常值检测)
9.3 长期扩展(功能级)
- 多模态 Embedding 全链路支持:当前仅火山引擎支持多模态,未来可扩展到更多 provider
- 自适应维度选择:根据知识库数据量和精度需求,自动推荐最优 Embedding 维度
- Embedding 微调:支持基于领域数据的 Embedding 模型微调(如 fine-tune BGE)
- 跨模型向量映射:研究不同 Embedding 模型之间的向量映射技术,实现平滑迁移而不重建索引
文档基于 MemoryBear 仓库 commit 最新状态梳理。关键源码路径均已标注行号,可在 ±3 行范围内验证。