"""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}")