""" VOC 流水线 Chat 模型配置(DeepSeek OpenAI 兼容 API)。 密钥(任选其一):: export DEEPSEEK_API_KEY="sk-..." export DEEPSEEK_API_KEY_FILE="/path/to/key.txt" 项目根单行文件 .deepseek_key 模型:: 默认 deepseek-v4-pro;可用 DEEPSEEK_MODEL 覆盖。 流水线默认关闭思考(extra_body thinking disabled);报告等单独开启处见 voc_report。 """ from __future__ import annotations import os from pathlib import Path from typing import Any, Dict from openai import OpenAI PROJECT_ROOT = Path(__file__).resolve().parent CHAT_BASE_URL = os.environ.get("DEEPSEEK_BASE_URL", "https://api.deepseek.com").strip() CHAT_MODEL = os.environ.get("DEEPSEEK_MODEL", "deepseek-v4-pro").strip() def resolve_chat_api_key() -> str: """环境变量 > DEEPSEEK_API_KEY_FILE > 项目根 .deepseek_key。""" v = os.environ.get("DEEPSEEK_API_KEY", "").strip() if v: return v fp = os.environ.get("DEEPSEEK_API_KEY_FILE", "").strip() if fp: p = Path(fp).expanduser() if p.is_file(): return p.read_text(encoding="utf-8").strip().strip('"').strip("'") local = PROJECT_ROOT / ".deepseek_key" if local.is_file(): return local.read_text(encoding="utf-8").strip().strip('"').strip("'") # 兼容:仅有 .dashscope_key 时提示(DashScope 密钥不能用于 api.deepseek.com) legacy = PROJECT_ROOT / ".dashscope_key" if legacy.is_file(): raise RuntimeError( "检测到 .dashscope_key,但 Chat 已切换为 DeepSeek。" "请在项目根创建 .deepseek_key(单行 DEEPSEEK 密钥)," "或执行 export DEEPSEEK_API_KEY='sk-...'" ) return "" def require_chat_api_key() -> str: key = resolve_chat_api_key() if not key: raise RuntimeError( "缺少 DeepSeek API Key:设置 DEEPSEEK_API_KEY," "或 DEEPSEEK_API_KEY_FILE,或在项目根创建 .deepseek_key(单行)" ) return key def create_chat_client(*, api_key: str | None = None, timeout: float | None = None) -> OpenAI: kw: Dict[str, Any] = { "api_key": api_key or require_chat_api_key(), "base_url": CHAT_BASE_URL, } if timeout is not None: kw["timeout"] = timeout return OpenAI(**kw) def chat_extra_body(model: str | None = None) -> Dict[str, Any]: """流水线 Chat 默认关闭思考(Pro 模型 API 默认 otherwise 为 enabled)。""" _ = (model or CHAT_MODEL).lower() return {"thinking": {"type": "disabled"}}