- utils/api_client.py -> utils/api_client/ (mixin-based) - utils/tool_result_formatter.py -> utils/tool_result_formatter/ (by tool category) - core/main_terminal_parts/tools_definition.py -> core/main_terminal_parts/tools_definition/ (by tool group) - Update AGENTS.md, CLAUDE.md, and split memory index - Keep original files as compatibility re-exports - Includes TerminalPanel display adjustment and ToolAction.vue diff line fix
147 lines
5.7 KiB
Python
147 lines
5.7 KiB
Python
# ========== 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 DeepSeekClientProfileMixin:
|
||
def apply_profile(self, profile: Dict):
|
||
"""
|
||
动态应用模型配置
|
||
profile 示例:
|
||
{
|
||
"fast": {"base_url": "...", "api_key": "...", "model_id": "...", "max_tokens": 8192},
|
||
"thinking": {...} 或 None,
|
||
"supports_thinking": True/False,
|
||
"fast_only": True/False
|
||
}
|
||
"""
|
||
if not profile or "fast" not in profile:
|
||
raise ValueError("无效的模型配置")
|
||
fast = profile["fast"] or {}
|
||
thinking = profile.get("thinking") or fast
|
||
self.fast_api_config = {
|
||
"base_url": fast.get("base_url") or self.fast_api_config.get("base_url"),
|
||
"api_key": fast.get("api_key") or self.fast_api_config.get("api_key"),
|
||
"model_id": fast.get("model_id") or self.fast_api_config.get("model_id")
|
||
}
|
||
self.thinking_api_config = {
|
||
"base_url": thinking.get("base_url") or self.thinking_api_config.get("base_url"),
|
||
"api_key": thinking.get("api_key") or self.thinking_api_config.get("api_key"),
|
||
"model_id": thinking.get("model_id") or self.thinking_api_config.get("model_id")
|
||
}
|
||
self.fast_max_tokens = fast.get("max_tokens")
|
||
self.thinking_max_tokens = thinking.get("max_tokens")
|
||
self.fast_extra_params = fast.get("extra_params") or {}
|
||
self.thinking_extra_params = thinking.get("extra_params") or {}
|
||
self.model_multimodal = self._normalize_multimodal_capability(profile.get("multimodal"))
|
||
self.default_context_window = profile.get("context_window") or fast.get("context_window")
|
||
# 同步旧字段
|
||
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"]
|
||
try:
|
||
self._debug_log({
|
||
"event": "apply_profile",
|
||
"model_key": self.model_key,
|
||
"fast_model_id": self.fast_api_config.get("model_id"),
|
||
"thinking_model_id": self.thinking_api_config.get("model_id"),
|
||
"fast_max_tokens": self.fast_max_tokens,
|
||
"thinking_max_tokens": self.thinking_max_tokens,
|
||
"fast_extra_params": self.fast_extra_params,
|
||
"thinking_extra_params": self.thinking_extra_params,
|
||
"default_context_window": self.default_context_window,
|
||
})
|
||
except Exception:
|
||
pass
|
||
|
||
def update_context_budget(self, current_tokens: int, max_tokens: Optional[int]):
|
||
"""
|
||
由上层在每次调用前告知当前对话占用的token数和模型最大上下文。
|
||
"""
|
||
try:
|
||
self.current_context_tokens = max(0, int(current_tokens))
|
||
except (TypeError, ValueError):
|
||
self.current_context_tokens = 0
|
||
try:
|
||
self.max_context_tokens = int(max_tokens) if max_tokens is not None else None
|
||
except (TypeError, ValueError):
|
||
self.max_context_tokens = None
|
||
|
||
def get_current_thinking_mode(self) -> bool:
|
||
"""获取当前应该使用的思考模式"""
|
||
if self.deep_thinking_session:
|
||
return True
|
||
if not self.thinking_mode:
|
||
return False
|
||
if self.force_thinking_next_call:
|
||
return True
|
||
if self.skip_thinking_next_call:
|
||
return False
|
||
return self.current_task_first_call
|
||
|
||
def set_deep_thinking_mode(self, enabled: bool):
|
||
"""配置深度思考模式(持续使用思考模型)。"""
|
||
self.deep_thinking_mode = bool(enabled)
|
||
if not enabled:
|
||
self.deep_thinking_session = False
|
||
|
||
def start_new_task(self, force_deep: bool = False):
|
||
"""开始新任务(重置任务级别的状态)"""
|
||
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
|
||
self.deep_thinking_session = bool(force_deep) or bool(self.deep_thinking_mode)
|
||
|
||
def _build_headers(self, api_key: str) -> Dict[str, str]:
|
||
return {
|
||
"Authorization": f"Bearer {api_key}",
|
||
"Content-Type": "application/json"
|
||
}
|
||
|
||
def _select_api_config(self, use_thinking: bool) -> Dict[str, str]:
|
||
"""
|
||
根据当前模式选择API配置,确保缺失字段回退到默认模型。
|
||
"""
|
||
config = self.thinking_api_config if use_thinking else self.fast_api_config
|
||
fallback = self.fast_api_config
|
||
return {
|
||
"base_url": config.get("base_url") or fallback["base_url"],
|
||
"api_key": config.get("api_key") or fallback["api_key"],
|
||
"model_id": config.get("model_id") or fallback["model_id"]
|
||
}
|