# ========== api_client.py ========== # utils/api_client.py - OpenAI-compatible API 客户端(支持Web模式) import httpx import json import asyncio import base64 import mimetypes import os from typing import List, Dict, Optional, AsyncGenerator, Any from pathlib import Path from datetime import datetime from pathlib import Path from typing import Tuple try: from config import ( OUTPUT_FORMATS, DEFAULT_RESPONSE_MAX_TOKENS, LOGS_DIR, ) except ImportError: import sys from pathlib import Path project_root = Path(__file__).resolve().parents[1] if str(project_root) not in sys.path: sys.path.insert(0, str(project_root)) from config import ( OUTPUT_FORMATS, DEFAULT_RESPONSE_MAX_TOKENS, LOGS_DIR, ) from utils.log_rotation import append_line, prune_dir from utils.api_client.utils import _api_dump_enabled class DeepSeekClientBaseMixin: def __init__(self, thinking_mode: bool = True, web_mode: bool = False): # 模型 API 配置(端点 / 密钥 / 模型 ID)由 apply_profile 从 custom_models 注入; # 这里仅初始化为空,未应用任何模型档案前不可直接发请求。 self.fast_api_config = { "base_url": "", "api_key": "", "model_id": "" } self.thinking_api_config = { "base_url": "", "api_key": "", "model_id": "" } self.fast_max_tokens = None self.thinking_max_tokens = None self.fast_extra_params: Dict = {} self.thinking_extra_params: Dict = {} # 上下文预算(由上层在每次请求前设置) self.current_context_tokens: int = 0 self.max_context_tokens: Optional[int] = None self.default_context_window: Optional[int] = None self.thinking_mode = thinking_mode # True=智能思考模式, False=快速模式 self.deep_thinking_mode = False # 深度思考模式:整轮都使用思考模型 self.deep_thinking_session = False # 当前任务是否处于深度思考会话 self.web_mode = web_mode # Web模式标志,用于禁用print输出 # 兼容旧代码路径 self.api_base_url = self.fast_api_config["base_url"] self.api_key = self.fast_api_config["api_key"] self.model_id = self.fast_api_config["model_id"] self.model_key = None # 由宿主终端注入,便于做模型兼容处理 self.model_multimodal = "none" # 由模型 profile 注入,model_key 不可用时用于能力兜底 self.project_path: Optional[str] = None # 每个任务的独立状态 self.current_task_first_call = True # 当前任务是否是第一次调用 self.current_task_thinking = "" # 当前任务的思考内容 self.force_thinking_next_call = False # 单次强制思考 self.skip_thinking_next_call = False # 单次强制快速 self.last_call_used_thinking = False # 最近一次调用是否使用思考模型 # 最近一次API错误详情 self.last_error_info: Optional[Dict[str, Any]] = None # 请求体落盘目录(跟随 LOGS_DIR,默认 ~/.astrion//logs/api_requests) self.request_dump_dir = Path(LOGS_DIR) / "api_requests" self.debug_log_path = Path(LOGS_DIR) / "api_debug.log" def _resolve_env_value(self, name: str) -> Optional[str]: """从环境变量获取配置(已由 config/__init__.py 统一注入)。""" value = os.environ.get(name) if value is None: return None value = value.strip() return value or None def _print(self, message: str, end: str = "\n", flush: bool = False): """安全的打印函数,在Web模式下不输出""" if not self.web_mode: print(message, end=end, flush=flush)