Embedding 管理优化方案¶
Date: 2024-09-20\ Author: SAGE Team\ Summary: Embedding 优化计划
📋 当前状态分析¶
现有架构¶
packages/sage-middleware/src/sage/middleware/utils/embedding/
├── embedding_api.py # 工厂函数 apply_embedding_model()
├── embedding_model.py # EmbeddingModel 主类
├── hf.py # HuggingFace wrapper
├── openai.py # OpenAI wrapper
├── mockembedder.py # Mock wrapper
├── jina.py, zhipu.py, ... # 其他 provider wrappers
└── README.md
现有问题¶
1. 重复实现 - sage chat 自己实现了 HashingEmbedder¶
# packages/sage-tools/src/sage/tools/cli/commands/chat.py:104
class HashingEmbedder:
"""Lightweight embedding that hashes tokens into a fixed-length vector."""
def __init__(self, dim: int = DEFAULT_FIXED_DIM) -> None:
self._dim = max(64, int(dim))
def embed(self, text: str) -> List[float]:
# 实现哈希embedding...
问题: 这个应该统一到 middleware 的 embedding 模块中。
2. 不一致的接口 - build_embedder 逻辑分散¶
# packages/sage-tools/src/sage/tools/cli/commands/chat.py:222
def build_embedder(config: Dict[str, object]) -> Any:
method = str(config.get("method", DEFAULT_EMBEDDING_METHOD))
params = dict(config.get("params", {}))
if method == "hash":
return HashingEmbedder(dim) # ← 特殊处理
if method == "mockembedder" and "fixed_dim" not in params:
params["fixed_dim"] = DEFAULT_FIXED_DIM
embedder = EmbeddingModel(method=method, **params) # ← 其他走标准接口
return embedder
问题:
hash方法需要特殊处理- 默认参数逻辑分散
- 类型不统一 (
HashingEmbeddervsEmbeddingModel)
3. 缺少模型发现能力 - 用户不知道有哪些模型可用¶
# 当前没有这些功能:
# - list_embedding_models()
# - get_model_info(model_name)
# - check_model_availability(model_name)
用户痛点:
- 不知道支持哪些 embedding 方法
- 不知道某个模型是否需要 API Key
- 不知道模型是否已缓存到本地
- 不知道模型的维度信息
4. 硬编码的模型信息 - dimension_mapping 不灵活¶
# packages/sage-middleware/src/sage/middleware/utils/embedding/embedding_model.py:87
dimension_mapping = {
"mistral_embed": 1024,
"embed-multilingual-v3.0": 1024,
"BAAI/bge-m3": 1024,
"sentence-transformers/all-MiniLM-L6-v2": 384,
"mockembedder": 128,
}
问题:
- 新模型需要手动添加
- 无法动态查询 HuggingFace 模型的维度
- 错误提示不友好:
<UNK> embedding <UNK>{model_name}
5. 缺少统一的抽象层 - 每个 wrapper 接口不一致¶
# hf.py
def hf_embed_sync(text: str, tokenizer, embed_model) -> list[float]:
...
# openai.py
def openai_embed_sync(text: str, model: str, api_key: str, base_url: str) -> list[float]:
...
# mockembedder.py
class MockTextEmbedder:
def encode(self, text: str) -> list[float]:
...
问题: 参数传递方式不一致,难以扩展。
🎯 优化目标¶
目标 1: 统一接口抽象¶
所有 embedding 方法提供一致的接口:
embed(text: str) -> List[float]- 单文本 embeddingembed_batch(texts: List[str]) -> List[List[float]]- 批量 embeddingget_dim() -> int- 获取维度method_name- 获取方法名
目标 2: 模型注册与发现¶
提供统一的模型管理:
list_embedding_models()- 列出所有可用模型get_embedding_model(name, **kwargs)- 获取模型实例check_model_status(name)- 检查模型状态(需要 API Key / 已缓存 / 可用)
目标 3: 清晰的分层架构¶
┌─────────────────────────────────────────────────────┐
│ Application Layer (sage chat, pipeline builder) │
│ - 使用 get_embedding_model() 获取模型 │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ Embedding Manager (NEW) │
│ - EmbeddingRegistry: 模型注册表 │
│ - EmbeddingFactory: 创建模型实例 │
│ - list_models(), get_model(), check_status() │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ Base Wrapper (NEW) │
│ - BaseEmbedding: 抽象基类 │
│ - 统一接口: embed(), embed_batch(), get_dim() │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ Provider Wrappers (REFACTORED) │
│ - HFEmbedding, OpenAIEmbedding, ... │
│ - 继承 BaseEmbedding │
└─────────────────────────────────────────────────────┘
🚀 优化方案¶
Phase 1: 创建抽象基类和统一接口 ✅¶
文件: packages/sage-middleware/src/sage/middleware/utils/embedding/base.py
from abc import ABC, abstractmethod
from typing import List, Optional, Dict, Any
class BaseEmbedding(ABC):
"""所有 Embedding 模型的抽象基类"""
def __init__(self, **kwargs):
self.config = kwargs
@abstractmethod
def embed(self, text: str) -> List[float]:
"""单文本 embedding"""
pass
def embed_batch(self, texts: List[str]) -> List[List[float]]:
"""批量 embedding(默认实现:逐个调用)"""
return [self.embed(text) for text in texts]
@abstractmethod
def get_dim(self) -> int:
"""获取 embedding 维度"""
pass
@property
@abstractmethod
def method_name(self) -> str:
"""返回方法名(如 'hf', 'openai')"""
pass
@classmethod
def get_model_info(cls) -> Dict[str, Any]:
"""返回模型元信息(子类可选实现)"""
return {
"method": cls.__name__,
"requires_api_key": False,
"requires_model_download": False,
"default_dimension": None,
}
Phase 2: 模型注册表 ✅¶
文件: packages/sage-middleware/src/sage/middleware/utils/embedding/registry.py
from typing import Dict, List, Type, Optional, Any
from dataclasses import dataclass
from enum import Enum
class ModelStatus(Enum):
"""模型可用状态"""
AVAILABLE = "available" # 直接可用
NEEDS_API_KEY = "needs_api_key" # 需要 API Key
NEEDS_DOWNLOAD = "needs_download" # 需要下载模型
CACHED = "cached" # 已缓存到本地
UNAVAILABLE = "unavailable" # 不可用
@dataclass
class ModelInfo:
"""模型元信息"""
method: str # 方法名:hf, openai, mockembedder, hash
display_name: str # 显示名称
description: str # 描述
requires_api_key: bool # 是否需要 API Key
requires_model_download: bool # 是否需要下载模型
default_dimension: Optional[int] # 默认维度
example_models: List[str] # 示例模型名称
wrapper_class: Type # Wrapper 类
class EmbeddingRegistry:
"""Embedding 模型注册表"""
_registry: Dict[str, ModelInfo] = {}
@classmethod
def register(
cls,
method: str,
display_name: str,
description: str,
wrapper_class: Type,
requires_api_key: bool = False,
requires_model_download: bool = False,
default_dimension: Optional[int] = None,
example_models: List[str] = None,
) -> None:
"""注册 embedding 方法"""
cls._registry[method] = ModelInfo(
method=method,
display_name=display_name,
description=description,
requires_api_key=requires_api_key,
requires_model_download=requires_model_download,
default_dimension=default_dimension,
example_models=example_models or [],
wrapper_class=wrapper_class,
)
@classmethod
def list_methods(cls) -> List[str]:
"""列出所有已注册的方法"""
return list(cls._registry.keys())
@classmethod
def get_model_info(cls, method: str) -> Optional[ModelInfo]:
"""获取模型信息"""
return cls._registry.get(method)
@classmethod
def check_status(cls, method: str, **kwargs) -> ModelStatus:
"""检查模型状态"""
info = cls.get_model_info(method)
if not info:
return ModelStatus.UNAVAILABLE
# API Key 检查
if info.requires_api_key:
api_key = kwargs.get("api_key") or os.getenv(f"{method.upper()}_API_KEY")
if not api_key:
return ModelStatus.NEEDS_API_KEY
# 本地模型缓存检查
if info.requires_model_download:
model_name = kwargs.get("model")
if model_name and is_model_cached(model_name):
return ModelStatus.CACHED
return ModelStatus.NEEDS_DOWNLOAD
return ModelStatus.AVAILABLE
@classmethod
def get_wrapper_class(cls, method: str) -> Optional[Type]:
"""获取 Wrapper 类"""
info = cls.get_model_info(method)
return info.wrapper_class if info else None
def is_model_cached(model_name: str) -> bool:
"""检查 HuggingFace 模型是否已缓存"""
from pathlib import Path
cache_dir = Path.home() / ".cache" / "huggingface" / "hub"
# 简化检查:存在 models-- 开头的目录
model_slug = model_name.replace("/", "--")
cached = any(cache_dir.glob(f"models--{model_slug}*"))
return cached
Phase 3: Embedding 工厂 ✅¶
文件: packages/sage-middleware/src/sage/middleware/utils/embedding/factory.py
import os
from typing import Optional, Dict, Any
from .base import BaseEmbedding
from .registry import EmbeddingRegistry, ModelStatus
class EmbeddingFactory:
"""Embedding 模型工厂"""
@staticmethod
def create(method: str, **kwargs) -> BaseEmbedding:
"""
创建 Embedding 实例
Args:
method: embedding 方法名 (hf, openai, hash, mockembedder, ...)
**kwargs: 方法特定参数
- model: 模型名称 (hf, openai 等需要)
- api_key: API 密钥 (openai, jina 等需要)
- base_url: API 端点 (openai 可选)
- fixed_dim: 固定维度 (mockembedder, hash 需要)
Returns:
BaseEmbedding 实例
Raises:
ValueError: 不支持的方法或缺少必要参数
RuntimeError: 模型不可用
Examples:
>>> # HuggingFace 模型
>>> emb = EmbeddingFactory.create(
... method="hf",
... model="BAAI/bge-small-zh-v1.5"
... )
>>> # OpenAI API
>>> emb = EmbeddingFactory.create(
... method="openai",
... model="text-embedding-3-small",
... api_key=os.getenv("OPENAI_API_KEY")
... )
>>> # Mock embedder (测试)
>>> emb = EmbeddingFactory.create(
... method="mockembedder",
... fixed_dim=384
... )
"""
# 获取注册信息
wrapper_class = EmbeddingRegistry.get_wrapper_class(method)
if not wrapper_class:
available = ", ".join(EmbeddingRegistry.list_methods())
raise ValueError(
f"不支持的 embedding 方法: {method}\n"
f"可用方法: {available}"
)
# 检查状态
status = EmbeddingRegistry.check_status(method, **kwargs)
if status == ModelStatus.NEEDS_API_KEY:
raise RuntimeError(
f"{method} 方法需要 API Key。\n"
f"请设置环境变量 {method.upper()}_API_KEY 或传递 api_key 参数。"
)
# 创建实例
try:
return wrapper_class(**kwargs)
except Exception as e:
raise RuntimeError(
f"创建 {method} embedding 实例失败: {e}"
) from e
@staticmethod
def list_models() -> Dict[str, Dict[str, Any]]:
"""
列出所有可用的 embedding 方法
Returns:
Dict[method_name, model_info]
"""
result = {}
for method in EmbeddingRegistry.list_methods():
info = EmbeddingRegistry.get_model_info(method)
if info:
result[method] = {
"display_name": info.display_name,
"description": info.description,
"requires_api_key": info.requires_api_key,
"requires_download": info.requires_model_download,
"default_dimension": info.default_dimension,
"examples": info.example_models,
}
return result
@staticmethod
def check_availability(method: str, **kwargs) -> Dict[str, Any]:
"""
检查特定方法的可用性
Returns:
{
"status": "available|needs_api_key|needs_download|unavailable",
"message": "详细说明",
"action": "建议操作"
}
"""
status = EmbeddingRegistry.check_status(method, **kwargs)
messages = {
ModelStatus.AVAILABLE: ("✅ 可用", "可以直接使用"),
ModelStatus.CACHED: ("✅ 已缓存", "模型已下载到本地"),
ModelStatus.NEEDS_API_KEY: (
"⚠️ 需要 API Key",
f"设置环境变量 {method.upper()}_API_KEY"
),
ModelStatus.NEEDS_DOWNLOAD: (
"⚠️ 需要下载模型",
f"首次使用将从 HuggingFace 下载模型: {kwargs.get('model', '?')}"
),
ModelStatus.UNAVAILABLE: ("❌ 不可用", "方法未注册"),
}
message, action = messages.get(status, ("❓ 未知", ""))
return {
"status": status.value,
"message": message,
"action": action,
}
Phase 4: 重构 Wrapper 类 ✅¶
示例: packages/sage-middleware/src/sage/middleware/utils/embedding/wrappers/hash_wrapper.py
import hashlib
import re
from typing import List
from ..base import BaseEmbedding
class HashEmbedding(BaseEmbedding):
"""基于哈希的轻量级 Embedding(用于快速测试)"""
def __init__(self, dim: int = 384, **kwargs):
super().__init__(dim=dim, **kwargs)
self._dim = max(64, int(dim))
def embed(self, text: str) -> List[float]:
if not text:
return [0.0] * self._dim
vector = [0.0] * self._dim
tokens = re.findall(r"[\w\u4e00-\u9fa5]+", text.lower())
if not tokens:
tokens = [text.lower()]
for token in tokens:
digest = hashlib.sha256(token.encode("utf-8")).digest()
for offset in range(0, len(digest), 4):
chunk = digest[offset : offset + 4]
if len(chunk) < 4:
chunk = chunk.ljust(4, b"\0")
idx = int.from_bytes(chunk, "little") % self._dim
vector[idx] += 1.0
# 归一化
norm = sum(v * v for v in vector) ** 0.5 or 1.0
return [v / norm for v in vector]
def get_dim(self) -> int:
return self._dim
@property
def method_name(self) -> str:
return "hash"
@classmethod
def get_model_info(cls):
return {
"method": "hash",
"requires_api_key": False,
"requires_model_download": False,
"default_dimension": 384,
}
示例: packages/sage-middleware/src/sage/middleware/utils/embedding/wrappers/hf_wrapper.py
from typing import List
from ..base import BaseEmbedding
from ..hf import hf_embed_sync # 复用现有实现
class HFEmbedding(BaseEmbedding):
"""HuggingFace Embedding Wrapper"""
def __init__(self, model: str, **kwargs):
super().__init__(model=model, **kwargs)
from transformers import AutoModel, AutoTokenizer
try:
self.tokenizer = AutoTokenizer.from_pretrained(model)
self.embed_model = AutoModel.from_pretrained(
model, trust_remote_code=True
)
except Exception as e:
raise RuntimeError(
f"Failed to load HuggingFace model '{model}': {e}"
) from e
# 推断维度
self._dim = self._infer_dimension()
def embed(self, text: str) -> List[float]:
return hf_embed_sync(text, self.tokenizer, self.embed_model)
def embed_batch(self, texts: List[str]) -> List[List[float]]:
"""优化的批量 embedding"""
# TODO: 实现真正的批量处理
return [self.embed(text) for text in texts]
def get_dim(self) -> int:
return self._dim
@property
def method_name(self) -> str:
return "hf"
def _infer_dimension(self) -> int:
"""通过 embedding 一个示例文本推断维度"""
try:
sample = self.embed("test")
return len(sample)
except:
return 768 # 默认维度
@classmethod
def get_model_info(cls):
return {
"method": "hf",
"requires_api_key": False,
"requires_model_download": True,
"default_dimension": None, # 动态推断
}
Phase 5: 注册所有 Wrapper ✅¶
文件: packages/sage-middleware/src/sage/middleware/utils/embedding/__init__.py
from .base import BaseEmbedding
from .registry import EmbeddingRegistry, ModelStatus, ModelInfo
from .factory import EmbeddingFactory
# 导入所有 wrapper
from .wrappers.hash_wrapper import HashEmbedding
from .wrappers.mock_wrapper import MockEmbedding
from .wrappers.hf_wrapper import HFEmbedding
from .wrappers.openai_wrapper import OpenAIEmbedding
# ... 其他 wrappers
# 注册所有方法
def _register_all():
"""注册所有 embedding 方法"""
EmbeddingRegistry.register(
method="hash",
display_name="Hash Embedding",
description="轻量级哈希 embedding(测试用)",
wrapper_class=HashEmbedding,
default_dimension=384,
example_models=["hash-384", "hash-768"],
)
EmbeddingRegistry.register(
method="mockembedder",
display_name="Mock Embedder",
description="随机 embedding(测试用)",
wrapper_class=MockEmbedding,
default_dimension=128,
example_models=["mock-128", "mock-384"],
)
EmbeddingRegistry.register(
method="hf",
display_name="HuggingFace Models",
description="本地 Transformer 模型",
wrapper_class=HFEmbedding,
requires_model_download=True,
example_models=[
"BAAI/bge-small-zh-v1.5",
"BAAI/bge-base-zh-v1.5",
"sentence-transformers/all-MiniLM-L6-v2",
],
)
EmbeddingRegistry.register(
method="openai",
display_name="OpenAI Embedding API",
description="OpenAI 官方或兼容 API",
wrapper_class=OpenAIEmbedding,
requires_api_key=True,
example_models=[
"text-embedding-3-small",
"text-embedding-3-large",
"text-embedding-ada-002",
],
)
# ... 注册其他方法
_register_all()
# 统一导出接口
def get_embedding_model(method: str, **kwargs) -> BaseEmbedding:
"""
获取 Embedding 模型实例(推荐使用)
Examples:
>>> emb = get_embedding_model("hf", model="BAAI/bge-small-zh-v1.5")
>>> vec = emb.embed("hello world")
>>> dim = emb.get_dim()
"""
return EmbeddingFactory.create(method, **kwargs)
def list_embedding_models():
"""列出所有可用的 embedding 方法"""
return EmbeddingFactory.list_models()
def check_model_availability(method: str, **kwargs):
"""检查模型可用性"""
return EmbeddingFactory.check_availability(method, **kwargs)
__all__ = [
"BaseEmbedding",
"EmbeddingRegistry",
"EmbeddingFactory",
"ModelStatus",
"get_embedding_model",
"list_embedding_models",
"check_model_availability",
]
📝 使用示例¶
示例 1: Sage Chat 简化后的代码¶
# packages/sage-tools/src/sage/tools/cli/commands/chat.py
from sage.common.components.sage_embedding import get_embedding_model
def build_embedder(config: Dict[str, object]) -> BaseEmbedding:
"""构建 embedder(简化版)"""
method = str(config.get("method", "hash"))
params = dict(config.get("params", {}))
# 一行搞定,不需要特殊处理!
return get_embedding_model(method, **params)
示例 2: 用户查询可用模型¶
from sage.common.components.sage_embedding import list_embedding_models
models = list_embedding_models()
for method, info in models.items():
print(f"{method}: {info['description']}")
if info['requires_api_key']:
print(" ⚠️ 需要 API Key")
if info['examples']:
print(f" 示例: {', '.join(info['examples'][:2])}")
输出:
hash: 轻量级哈希 embedding(测试用)
示例: hash-384, hash-768
hf: 本地 Transformer 模型
⚠️ 需要下载模型
示例: BAAI/bge-small-zh-v1.5, BAAI/bge-base-zh-v1.5
openai: OpenAI 官方或兼容 API
⚠️ 需要 API Key
示例: text-embedding-3-small, text-embedding-3-large
示例 3: 检查模型状态¶
from sage.common.components.sage_embedding import check_model_availability
# 检查 HuggingFace 模型
status = check_model_availability("hf", model="BAAI/bge-small-zh-v1.5")
print(status)
# {"status": "cached", "message": "✅ 已缓存", "action": "模型已下载到本地"}
# 检查 OpenAI(无 API Key)
status = check_model_availability("openai")
print(status)
# {"status": "needs_api_key", "message": "⚠️ 需要 API Key", "action": "设置环境变量 OPENAI_API_KEY"}
示例 4: CLI 命令¶
# 列出所有可用方法
sage embedding list
# 检查特定方法状态
sage embedding check hf --model BAAI/bge-small-zh-v1.5
# 测试 embedding
sage embedding test hf --model BAAI/bge-small-zh-v1.5 --text "测试文本"
🎯 迁移计划¶
Step 1: 创建新架构(不破坏现有代码)✅¶
packages/sage-middleware/src/sage/middleware/utils/embedding/
├── base.py # NEW
├── registry.py # NEW
├── factory.py # NEW
├── wrappers/ # NEW
│ ├── __init__.py
│ ├── hash_wrapper.py # 迁移 sage chat 的 HashingEmbedder
│ ├── mock_wrapper.py # 迁移 mockembedder.py
│ ├── hf_wrapper.py # 包装 hf.py
│ ├── openai_wrapper.py # 包装 openai.py
│ └── ...
├── embedding_model.py # KEEP (向后兼容)
├── embedding_api.py # KEEP (向后兼容)
└── 现有 provider 文件 # KEEP (被 wrapper 调用)
Step 2: 向后兼容层 ✅¶
# embedding_model.py - 添加兼容层
class EmbeddingModel(BaseEmbedding):
"""向后兼容的 EmbeddingModel 类"""
def __init__(self, method: str = "openai", **kwargs):
# 内部使用新架构
self._impl = get_embedding_model(method, **kwargs)
def embed(self, text: str) -> list[float]:
return self._impl.embed(text)
def get_dim(self):
return self._impl.get_dim()
# ... 其他方法委托给 _impl
Step 3: 逐步迁移 sage chat ✅¶
# 阶段 1: 使用新接口,保持功能不变
from sage.common.components.sage_embedding import get_embedding_model
def build_embedder(config):
return get_embedding_model(**config)
# 阶段 2: 删除 HashingEmbedder 类定义
# 阶段 3: 简化 ingest 命令参数处理
✅ 验收标准¶
功能完整性¶
- [ ] 所有现有 embedding 方法正常工作
- [ ] sage chat 功能不受影响
- [ ] 新增
list_embedding_models()API - [ ] 新增
check_model_availability()API - [ ] 统一的
get_embedding_model()接口
代码质量¶
- [ ] 所有 wrapper 继承
BaseEmbedding - [ ] 接口一致性:
embed(),embed_batch(),get_dim(),method_name - [ ] 完整的类型注解
- [ ] 详细的文档字符串
用户体验¶
- [ ] 清晰的错误提示(缺少 API Key、模型不存在等)
- [ ] CLI 命令支持模型发现
- [ ] README 更新使用示例
测试覆盖¶
- [ ] 单元测试覆盖所有 wrapper
- [ ] 集成测试覆盖 sage chat
- [ ] 向后兼容性测试
🤔 需要决策的问题¶
Q1: 是否保留 EmbeddingModel 类?¶
选项 A: 完全废弃,只使用新架构
- ✅ 代码更清晰
- ❌ 破坏现有代码
选项 B: 作为向后兼容层保留(推荐)
- ✅ 平滑迁移
- ❌ 多一层抽象
建议: 选项 B,保留 EmbeddingModel 作为兼容层。
Q2: dimension_mapping 如何处理?¶
选项 A: 硬编码在 Registry
选项 B: 动态推断(推荐)
建议: 选项 B + fallback 到硬编码表。
Q3: 是否需要 EmbeddingManager?¶
当前设计: Factory + Registry(静态方法)
Manager 设计: 实例化的管理器
建议: 当前的 Factory + Registry 已足够,暂不需要 Manager。
📊 工作量估算¶
| 任务 | 工作量 | 优先级 |
|---|---|---|
| 创建 base.py | 1h | P0 |
| 创建 registry.py | 2h | P0 |
| 创建 factory.py | 2h | P0 |
| 创建 hash_wrapper.py | 1h | P0 |
| 创建 mock_wrapper.py | 1h | P0 |
| 创建 hf_wrapper.py | 2h | P0 |
| 创建 openai_wrapper.py | 1h | P1 |
| 创建其他 wrappers (8个) | 8h | P1 |
| 更新 init.py | 1h | P0 |
| 迁移 sage chat | 2h | P0 |
| 添加 CLI 命令 | 3h | P2 |
| 编写单元测试 | 4h | P1 |
| 更新文档 | 2h | P1 |
总计: ~30 小时
P0 (核心): ~12 小时 P1 (重要): ~16 小时\ P2 (增强): ~3 小时
🎓 总结¶
当前问题¶
- ❌ HashingEmbedder 重复实现
- ❌ 接口不统一
- ❌ 缺少模型发现能力
- ❌ 硬编码的模型信息
- ❌ Wrapper 接口不一致
优化后¶
- ✅ 统一的
BaseEmbedding抽象 - ✅
EmbeddingRegistry模型注册表 - ✅
EmbeddingFactory统一创建接口 - ✅ 所有 wrapper 继承
BaseEmbedding - ✅
list_embedding_models()发现能力 - ✅
check_model_availability()状态检查 - ✅ 向后兼容现有代码
核心 API¶
# 简单直接的接口
from sage.common.components.sage_embedding import get_embedding_model
emb = get_embedding_model("hf", model="BAAI/bge-small-zh-v1.5")
vec = emb.embed("hello world")
建议: 先实现 P0 核心功能(~12h),验证可行性后再推进 P1/P2。