mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-17 19:21:26 +08:00
- Auto-rebuild knowledge/index.md from the real directory tree on create/import so it never drifts or loses documents (no longer relies on the agent hand-writing it). - Auto-open the created/imported document in the tree after success. - Add create_document status message, shorten EN action buttons, and localize the "insert template" content. - Show filename for protected system files (index.md/log.md) in the tree instead of their H1 heading. - Reuse a shared embedding-provider factory so knowledge index sync also gets vectors instead of degrading to keyword-only search.
210 lines
7.5 KiB
Python
210 lines
7.5 KiB
Python
"""
|
|
Shared embedding provider factory.
|
|
|
|
Resolves the embedding provider purely from config.json, so every caller
|
|
(agent initialization, knowledge base sync, index rebuild, ...) selects the
|
|
same provider instead of silently degrading to keyword-only search.
|
|
|
|
Two paths:
|
|
A. Default (no `embedding_provider` in config.json):
|
|
Auto-init OpenAI -> LinkAI fallback.
|
|
B. Explicit (`embedding_provider` is set):
|
|
Initialize the requested vendor with unified dim (default per vendor).
|
|
"""
|
|
|
|
import os
|
|
from typing import Optional
|
|
|
|
from common.log import logger
|
|
|
|
# Track whether the embedding model log has been printed in this process,
|
|
# so we avoid spamming it once per session/caller.
|
|
_embedding_logged: bool = False
|
|
|
|
|
|
def create_default_embedding_provider():
|
|
"""Build the embedding provider from config, or None for keyword-only mode."""
|
|
from config import conf
|
|
|
|
explicit_provider = (conf().get("embedding_provider") or "").strip().lower()
|
|
if not explicit_provider:
|
|
return _init_legacy_provider()
|
|
return _init_explicit_provider(explicit_provider)
|
|
|
|
|
|
def _init_legacy_provider():
|
|
"""Legacy auto-init path: OpenAI -> LinkAI."""
|
|
from agent.memory.embedding.provider import create_embedding_provider
|
|
from config import conf
|
|
|
|
embedding_provider = None
|
|
embedding_model = None
|
|
|
|
openai_api_key = conf().get("open_ai_api_key", "")
|
|
openai_api_base = conf().get("open_ai_api_base", "")
|
|
if openai_api_key and openai_api_key not in ["", "YOUR API KEY", "YOUR_API_KEY"]:
|
|
try:
|
|
model = "text-embedding-3-small"
|
|
embedding_provider = create_embedding_provider(
|
|
provider="openai",
|
|
model=model,
|
|
api_key=openai_api_key,
|
|
api_base=openai_api_base or "https://api.openai.com/v1",
|
|
)
|
|
embedding_model = f"openai/{model}"
|
|
except Exception as e:
|
|
logger.warning(f"[EmbeddingFactory] OpenAI embedding failed: {e}")
|
|
|
|
if embedding_provider is None:
|
|
linkai_api_key = conf().get("linkai_api_key", "") or os.environ.get("LINKAI_API_KEY", "")
|
|
linkai_api_base = conf().get("linkai_api_base", "https://api.link-ai.tech")
|
|
if linkai_api_key and linkai_api_key not in ["", "YOUR API KEY", "YOUR_API_KEY"]:
|
|
try:
|
|
model = "text-embedding-3-small"
|
|
embedding_provider = create_embedding_provider(
|
|
provider="linkai",
|
|
model=model,
|
|
api_key=linkai_api_key,
|
|
api_base=f"{linkai_api_base}/v1",
|
|
)
|
|
embedding_model = f"linkai/{model}"
|
|
except Exception as e:
|
|
logger.warning(f"[EmbeddingFactory] LinkAI embedding failed: {e}")
|
|
|
|
if embedding_provider is not None and embedding_model:
|
|
_log_provider_once(f"{embedding_model} (dim={embedding_provider.dimensions})")
|
|
|
|
return embedding_provider
|
|
|
|
|
|
def _init_explicit_provider(provider_key: str):
|
|
"""Explicit-provider path: build the configured vendor."""
|
|
from agent.memory.embedding.provider import EMBEDDING_VENDORS, create_embedding_provider
|
|
from config import conf
|
|
|
|
# Custom providers ("custom:<id>") resolve credentials from custom_providers.
|
|
resolved_provider_key = provider_key
|
|
if provider_key.startswith("custom:"):
|
|
resolved_provider_key = "custom"
|
|
|
|
meta = EMBEDDING_VENDORS.get(resolved_provider_key)
|
|
if meta is None:
|
|
logger.error(
|
|
f"[EmbeddingFactory] Unknown embedding_provider '{provider_key}'. "
|
|
f"Supported: {sorted(EMBEDDING_VENDORS.keys())}. "
|
|
f"Memory will run in keyword-only mode."
|
|
)
|
|
return None
|
|
|
|
api_key = _resolve_api_key(provider_key)
|
|
api_base = _resolve_api_base(provider_key, meta["default_base_url"])
|
|
|
|
if not api_key:
|
|
logger.error(
|
|
f"[EmbeddingFactory] embedding_provider='{provider_key}' is set but its "
|
|
f"API key is missing. Memory will run in keyword-only mode."
|
|
)
|
|
return None
|
|
|
|
model = (conf().get("embedding_model") or "").strip()
|
|
# Custom providers without a model fall back to the provider's default.
|
|
if not model and resolved_provider_key == "custom":
|
|
from models.custom_provider import parse_custom_bot_type, get_custom_providers, _find_provider_by_id
|
|
_, custom_id = parse_custom_bot_type(provider_key)
|
|
if custom_id:
|
|
entry = _find_provider_by_id(get_custom_providers(), custom_id)
|
|
if entry and entry.get("model"):
|
|
model = entry["model"]
|
|
if not model and resolved_provider_key != "custom":
|
|
model = meta["default_model"]
|
|
|
|
try:
|
|
cfg_dim = int(conf().get("embedding_dimensions") or 0)
|
|
except (TypeError, ValueError):
|
|
cfg_dim = 0
|
|
dim = cfg_dim if cfg_dim > 0 else meta["default_dimensions"]
|
|
|
|
try:
|
|
provider = create_embedding_provider(
|
|
provider=resolved_provider_key,
|
|
model=model,
|
|
api_key=api_key,
|
|
api_base=api_base,
|
|
dimensions=dim,
|
|
)
|
|
except Exception as e:
|
|
logger.error(
|
|
f"[EmbeddingFactory] Failed to init embedding provider "
|
|
f"'{provider_key}/{model}': {e}"
|
|
)
|
|
return None
|
|
|
|
_log_provider_once(f"{provider_key}/{model} (dim={provider.dimensions})")
|
|
return provider
|
|
|
|
|
|
def _resolve_api_key(provider_key: str) -> str:
|
|
"""Pick the API key for an explicit embedding provider from config."""
|
|
from config import conf
|
|
|
|
if provider_key.startswith("custom:"):
|
|
from models.custom_provider import parse_custom_bot_type, get_custom_providers, _find_provider_by_id
|
|
_, custom_id = parse_custom_bot_type(provider_key)
|
|
if custom_id:
|
|
entry = _find_provider_by_id(get_custom_providers(), custom_id)
|
|
if entry:
|
|
return entry.get("api_key", "")
|
|
return ""
|
|
|
|
key_map = {
|
|
"openai": "open_ai_api_key",
|
|
"linkai": "linkai_api_key",
|
|
"dashscope": "dashscope_api_key",
|
|
"doubao": "ark_api_key",
|
|
"zhipu": "zhipu_ai_api_key",
|
|
}
|
|
field = key_map.get(provider_key)
|
|
if not field:
|
|
return ""
|
|
value = conf().get(field, "") or ""
|
|
if value in ["", "YOUR API KEY", "YOUR_API_KEY"]:
|
|
return ""
|
|
return value
|
|
|
|
|
|
def _resolve_api_base(provider_key: str, default_base: str) -> str:
|
|
"""Pick the API base for an explicit embedding provider from config."""
|
|
from config import conf
|
|
|
|
if provider_key.startswith("custom:"):
|
|
from models.custom_provider import parse_custom_bot_type, get_custom_providers, _find_provider_by_id
|
|
_, custom_id = parse_custom_bot_type(provider_key)
|
|
if custom_id:
|
|
entry = _find_provider_by_id(get_custom_providers(), custom_id)
|
|
if entry and entry.get("api_base"):
|
|
return entry["api_base"]
|
|
return default_base
|
|
|
|
base_map = {
|
|
"openai": "open_ai_api_base",
|
|
"linkai": "linkai_api_base",
|
|
"doubao": "ark_base_url",
|
|
"zhipu": "zhipu_ai_api_base",
|
|
}
|
|
field = base_map.get(provider_key)
|
|
if not field:
|
|
return default_base
|
|
value = (conf().get(field) or "").strip()
|
|
if not value:
|
|
return default_base
|
|
if provider_key == "linkai" and not value.rstrip("/").endswith("/v1"):
|
|
return f"{value.rstrip('/')}/v1"
|
|
return value
|
|
|
|
|
|
def _log_provider_once(detail: str):
|
|
global _embedding_logged
|
|
if not _embedding_logged:
|
|
logger.info(f"[EmbeddingFactory] Embedding model in use: {detail}")
|
|
_embedding_logged = True
|