"""Vector backend configuration. Default is ``disabled`` for lightweight local startup. Install optional local RAG dependencies and set RAG_VECTOR_BACKEND=chroma to restore local vectors. """ from __future__ import annotations import importlib.util import json import os from pathlib import Path from ..config import DATA_DIR def settings(): path = DATA_DIR / 'rag-backend.json' try: config = json.loads(path.read_text(encoding='utf-8')) if not isinstance(config, dict): raise ValueError('知识索引配置必须是对象') except FileNotFoundError: config = {} value = os.environ.get('RAG_VECTOR_BACKEND', config.get('backend', 'disabled')) backend_name = str(value or 'disabled').strip().lower() if backend_name not in {'disabled', 'chroma', 'qdrant'}: raise ValueError('知识索引后端配置无效') return {**config, 'backend': backend_name} def configured_backend() -> str: return settings()['backend'] def local_chroma_available() -> bool: return importlib.util.find_spec('chromadb') is not None def effective_backend() -> str: """Return the backend that can actually run in this installation.""" value = configured_backend() if value == 'chroma' and not local_chroma_available(): return 'disabled' return value def is_disabled() -> bool: return effective_backend() == 'disabled' def is_qdrant(): return effective_backend() == 'qdrant' def qdrant_settings(): config = settings() url = str(os.environ.get('QDRANT_URL', config.get('url', ''))).rstrip('/') if not url.startswith(('http://', 'https://')): raise ValueError('请配置 Qdrant 服务地址') key = os.environ.get('QDRANT_API_KEY', '') if not key and config.get('api_key_file'): key = Path(config['api_key_file']).read_text(encoding='utf-8').strip() if not key: raise ValueError('请配置 Qdrant API 密钥') prefix = str(config.get('collection_prefix', 'yawan_')) if not prefix or not all(c.isalnum() or c in '_-' for c in prefix): raise ValueError('Qdrant 知识库命名空间无效') return url, key, prefix def namespace(): value = effective_backend() if value == 'qdrant': config = settings() return json.dumps(['qdrant', str(os.environ.get('QDRANT_URL', config.get('url', ''))).rstrip('/'), str(config.get('collection_prefix', 'yawan_'))], separators=(',', ':')) if value == 'disabled': return 'disabled' return 'chroma' if local_chroma_available() else 'chroma-missing'