"""DeepSeek API 客户端""" import json from typing import AsyncGenerator from app.utils.logger import logger from .base_client import BaseClient class DeepSeekClient(BaseClient): def __init__(self, api_key: str, api_url: str = "https://api.siliconflow.cn/v1/chat/completions", provider: str = "deepseek"): """初始化 DeepSeek 客户端 Args: api_key: DeepSeek API密钥 api_url: DeepSeek API地址 """ super().__init__(api_key, api_url) self.provider = provider def _process_think_tag_content(self, content: str) -> tuple[bool, str]: """处理包含 think 标签的内容 Args: content: 需要处理的内容字符串 Returns: tuple[bool, str]: bool: 是否检测到完整的 think 标签对 str: 处理后的内容 """ has_start = "" in content has_end = "" in content if has_start and has_end: return True, content elif has_start: return False, content elif not has_start and not has_end: return False, content else: return True, content async def stream_chat(self, messages: list, model: str = "deepseek-ai/DeepSeek-R1", is_origin_reasoning: bool = True) -> AsyncGenerator[tuple[str, str], None]: """流式对话 Args: messages: 消息列表 model: 模型名称 Yields: tuple[str, str]: (内容类型, 内容) 内容类型: "reasoning" 或 "content" 内容: 实际的文本内容 """ headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json", "Accept": "text/event-stream", } data = { "model": model, "messages": messages, "stream": True, } logger.debug(f"开始流式对话:{data}") accumulated_content = "" is_collecting_think = False async for chunk in self._make_request(headers, data): chunk_str = chunk.decode('utf-8') try: lines = chunk_str.splitlines() for line in lines: if line.startswith("data: "): json_str = line[len("data: "):] if json_str == "[DONE]": return data = json.loads(json_str) if data and data.get("choices") and data["choices"][0].get("delta"): delta = data["choices"][0]["delta"] if is_origin_reasoning: # 处理 reasoning_content if delta.get("reasoning_content"): content = delta["reasoning_content"] logger.debug(f"提取推理内容:{content}") yield "reasoning", content if delta.get("reasoning_content") is None and delta.get("content"): content = delta["content"] logger.info(f"提取内容信息,推理阶段结束: {content}") yield "content", content else: # 处理其他模型的输出 if delta.get("content"): content = delta["content"] if content == "": # 只跳过完全空的字符串 continue logger.debug(f"非原生推理内容:{content}") accumulated_content += content # 检查累积的内容是否包含完整的 think 标签对 is_complete, processed_content = self._process_think_tag_content(accumulated_content) if "" in content and not is_collecting_think: # 开始收集推理内容 logger.debug(f"开始收集推理内容:{content}") is_collecting_think = True yield "reasoning", content elif is_collecting_think: if "" in content: # 推理内容结束 logger.debug(f"推理内容结束:{content}") is_collecting_think = False yield "reasoning", content # 输出空的 content 来触发 Claude 处理 yield "content", "" # 重置累积内容 accumulated_content = "" else: # 继续收集推理内容 yield "reasoning", content else: # 普通内容 yield "content", content except json.JSONDecodeError as e: logger.error(f"JSON 解析错误: {e}") except Exception as e: logger.error(f"处理 chunk 时发生错误: {e}")