Spaces:
Sleeping
Sleeping
File size: 6,114 Bytes
5693654 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
"""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 = "<think>" in content
has_end = "</think>" 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 "<think>" in content and not is_collecting_think:
# 开始收集推理内容
logger.debug(f"开始收集推理内容:{content}")
is_collecting_think = True
yield "reasoning", content
elif is_collecting_think:
if "</think>" 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}")
|