feat: add DeepSeek official API support and capture reasoning content
- Add DeepSeek provider routing: deepseek-chat/deepseek-reasoner use api.deepseek.com, other models use SiliconFlow - Add stream_with_reasoning() using raw OpenAI SDK to capture reasoning_content (langchain_openai strips this field) - RAG chain and conversation chain both use stream_with_reasoning for proper reasoning display in thinking models - Frontend model selector: grouped by provider (DeepSeek official + SiliconFlow), default changed to deepseek-chat - Regenerate message converted to streaming with reasoning capture - Minor UI: globals.css additions, chat store refactoring Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,44 +1,63 @@
|
||||
"""
|
||||
硅基流动大模型API集成
|
||||
大模型API集成 — 支持 SiliconFlow 和 DeepSeek 官方
|
||||
"""
|
||||
import os
|
||||
from typing import List, Dict, Any, Optional, AsyncGenerator
|
||||
from typing import List, Dict, Any, Optional, AsyncGenerator, Tuple
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, SystemMessage
|
||||
from langchain_core.callbacks.base import BaseCallbackHandler
|
||||
from langchain_core.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
|
||||
import openai
|
||||
|
||||
from ..core.config import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
# DeepSeek 官方模型 ID 前缀(用于自动路由)
|
||||
DEEPSEEK_OFFICIAL_MODELS = {
|
||||
"deepseek-chat",
|
||||
"deepseek-reasoner",
|
||||
}
|
||||
|
||||
# 模型 ID → (api_key, base_url) 的路由映射
|
||||
def _resolve_provider(model: str) -> tuple[str, str]:
|
||||
"""根据模型 ID 选择 API provider,返回 (api_key, base_url)"""
|
||||
# DeepSeek 官方模型(不带 siliconflow 前缀的纯 deepseek-xxx)
|
||||
if model in DEEPSEEK_OFFICIAL_MODELS or model.startswith("deepseek/"):
|
||||
if not settings.deepseek_api_key:
|
||||
raise ValueError(
|
||||
f"模型 {model} 需要 DeepSeek 官方 API Key,"
|
||||
"请在 .env 中配置 DEEPSEEK_API_KEY"
|
||||
)
|
||||
actual_model = model.replace("deepseek/", "")
|
||||
return settings.deepseek_api_key, settings.deepseek_base_url, actual_model
|
||||
|
||||
# 默认走 SiliconFlow
|
||||
return settings.siliconflow_api_key, settings.siliconflow_base_url, model
|
||||
|
||||
|
||||
class SiliconFlowLLM:
|
||||
"""硅基流动大模型客户端"""
|
||||
|
||||
"""大模型客户端 — 自动路由 SiliconFlow / DeepSeek 官方"""
|
||||
|
||||
def __init__(self, model: Optional[str] = None):
|
||||
"""初始化LLM客户端"""
|
||||
# 设置环境变量
|
||||
os.environ["OPENAI_API_KEY"] = settings.siliconflow_api_key
|
||||
os.environ["OPENAI_API_BASE"] = settings.siliconflow_base_url
|
||||
|
||||
# 使用传入的模型或默认模型
|
||||
self.model_name = model or settings.siliconflow_model
|
||||
print(f"[DEBUG-LLM] 初始化LLM客户端,使用模型: {self.model_name} (传入参数: {model}, 默认配置: {settings.siliconflow_model})")
|
||||
|
||||
# 创建LLM实例
|
||||
raw_model = model or settings.siliconflow_model
|
||||
api_key, base_url, resolved_model = _resolve_provider(raw_model)
|
||||
|
||||
self.model_name = resolved_model
|
||||
print(f"[LLM] 模型: {resolved_model}, API: {base_url}")
|
||||
|
||||
self.llm = ChatOpenAI(
|
||||
model=self.model_name,
|
||||
api_key=settings.siliconflow_api_key,
|
||||
base_url=settings.siliconflow_base_url,
|
||||
model=resolved_model,
|
||||
api_key=api_key,
|
||||
base_url=base_url,
|
||||
temperature=0.7,
|
||||
max_tokens=2000,
|
||||
streaming=True
|
||||
)
|
||||
# 检查实际使用的模型名称
|
||||
actual_model = getattr(self.llm, 'model_name', None) or getattr(self.llm, 'model', None) or str(self.llm)
|
||||
print(f"[DEBUG-LLM] ChatOpenAI实例创建完成,实际模型: {actual_model}")
|
||||
|
||||
|
||||
self._api_key = api_key
|
||||
self._base_url = base_url
|
||||
|
||||
def chat(self, messages: List[BaseMessage], **kwargs) -> str:
|
||||
"""同步聊天"""
|
||||
try:
|
||||
@@ -46,7 +65,7 @@ class SiliconFlowLLM:
|
||||
return response.content
|
||||
except Exception as e:
|
||||
raise Exception(f"LLM调用失败: {str(e)}")
|
||||
|
||||
|
||||
async def achat(self, messages: List[BaseMessage], **kwargs) -> str:
|
||||
"""异步聊天"""
|
||||
try:
|
||||
@@ -54,7 +73,7 @@ class SiliconFlowLLM:
|
||||
return response.content
|
||||
except Exception as e:
|
||||
raise Exception(f"LLM异步调用失败: {str(e)}")
|
||||
|
||||
|
||||
async def stream_chat(self, messages: List[BaseMessage], **kwargs) -> AsyncGenerator[str, None]:
|
||||
"""流式聊天"""
|
||||
try:
|
||||
@@ -63,31 +82,71 @@ class SiliconFlowLLM:
|
||||
yield chunk.content
|
||||
except Exception as e:
|
||||
raise Exception(f"LLM流式调用失败: {str(e)}")
|
||||
|
||||
|
||||
async def stream_with_reasoning(
|
||||
self, messages: List[BaseMessage], **kwargs
|
||||
) -> AsyncGenerator[Tuple[str, str], None]:
|
||||
"""流式调用(直接使用 OpenAI SDK,捕获推理内容)
|
||||
|
||||
Yields:
|
||||
(type, content) — type 为 "reasoning" 或 "content"
|
||||
"""
|
||||
client = openai.AsyncOpenAI(
|
||||
api_key=self._api_key,
|
||||
base_url=self._base_url
|
||||
)
|
||||
|
||||
openai_messages = []
|
||||
for msg in messages:
|
||||
if isinstance(msg, SystemMessage):
|
||||
openai_messages.append({"role": "system", "content": msg.content})
|
||||
elif isinstance(msg, HumanMessage):
|
||||
openai_messages.append({"role": "user", "content": msg.content})
|
||||
elif isinstance(msg, AIMessage):
|
||||
openai_messages.append({"role": "assistant", "content": msg.content})
|
||||
else:
|
||||
openai_messages.append({"role": "user", "content": str(msg.content)})
|
||||
|
||||
stream = await client.chat.completions.create(
|
||||
model=self.model_name,
|
||||
messages=openai_messages,
|
||||
stream=True,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
async for chunk in stream:
|
||||
if not chunk.choices:
|
||||
continue
|
||||
delta = chunk.choices[0].delta
|
||||
|
||||
rc = getattr(delta, 'reasoning_content', None)
|
||||
if rc:
|
||||
yield ("reasoning", rc)
|
||||
|
||||
if delta.content:
|
||||
yield ("content", delta.content)
|
||||
|
||||
def create_messages(
|
||||
self,
|
||||
user_message: str,
|
||||
self,
|
||||
user_message: str,
|
||||
system_prompt: Optional[str] = None,
|
||||
chat_history: Optional[List[Dict[str, str]]] = None
|
||||
) -> List[BaseMessage]:
|
||||
"""创建消息列表"""
|
||||
messages = []
|
||||
|
||||
# 添加系统提示
|
||||
|
||||
if system_prompt:
|
||||
messages.append(SystemMessage(content=system_prompt))
|
||||
|
||||
# 添加聊天历史
|
||||
|
||||
if chat_history:
|
||||
for msg in chat_history:
|
||||
if msg["role"] == "user":
|
||||
messages.append(HumanMessage(content=msg["content"]))
|
||||
elif msg["role"] == "assistant":
|
||||
messages.append(AIMessage(content=msg["content"]))
|
||||
|
||||
# 添加当前用户消息
|
||||
|
||||
messages.append(HumanMessage(content=user_message))
|
||||
|
||||
|
||||
return messages
|
||||
|
||||
|
||||
@@ -97,12 +156,11 @@ llm_client = SiliconFlowLLM()
|
||||
|
||||
def get_llm_client(model: Optional[str] = None) -> SiliconFlowLLM:
|
||||
"""获取LLM客户端实例
|
||||
|
||||
|
||||
Args:
|
||||
model: 可选的模型名称,如果提供则创建新的实例,否则返回默认实例
|
||||
"""
|
||||
if model is None:
|
||||
return llm_client
|
||||
else:
|
||||
# 为指定模型创建新实例
|
||||
return SiliconFlowLLM(model=model)
|
||||
|
||||
Reference in New Issue
Block a user