100 lines
2.5 KiB
Python
100 lines
2.5 KiB
Python
"""DeepSeek LLM 客户端
|
||
|
||
基于 OpenAI 兼容 API,封装异步调用。
|
||
"""
|
||
|
||
import logging
|
||
from openai import AsyncOpenAI
|
||
from app.config import settings
|
||
from app.db.error_logger import log_error
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
_client: AsyncOpenAI | None = None
|
||
|
||
|
||
def get_client() -> AsyncOpenAI | None:
|
||
"""获取 LLM 客户端(惰性初始化)"""
|
||
global _client
|
||
if not settings.deepseek_api_key:
|
||
return None
|
||
if _client is None:
|
||
_client = AsyncOpenAI(
|
||
api_key=settings.deepseek_api_key,
|
||
base_url=settings.deepseek_base_url,
|
||
)
|
||
return _client
|
||
|
||
|
||
async def chat_completion(
|
||
messages: list[dict],
|
||
tools: list[dict] | None = None,
|
||
) -> dict | None:
|
||
"""非流式调用 LLM
|
||
|
||
Returns:
|
||
OpenAI ChatCompletion message dict,或 None(未配置/出错时)
|
||
"""
|
||
client = get_client()
|
||
if not client:
|
||
logger.debug("LLM 未配置,跳过调用")
|
||
return None
|
||
|
||
kwargs = {
|
||
"model": settings.deepseek_model,
|
||
"messages": messages,
|
||
"max_tokens": settings.llm_max_tokens,
|
||
"temperature": settings.llm_temperature,
|
||
}
|
||
if tools:
|
||
kwargs["tools"] = tools
|
||
|
||
try:
|
||
resp = await client.chat.completions.create(**kwargs)
|
||
return resp.choices[0].message
|
||
except Exception as e:
|
||
logger.error(f"LLM 调用失败: {e}")
|
||
await log_error(
|
||
"llm",
|
||
f"LLM 调用失败: {e}",
|
||
detail=f"model={settings.deepseek_model}, tools={bool(tools)}",
|
||
)
|
||
return None
|
||
|
||
|
||
async def stream_chat_completion(
|
||
messages: list[dict],
|
||
tools: list[dict] | None = None,
|
||
):
|
||
"""流式调用 LLM,返回 async generator
|
||
|
||
Yields:
|
||
OpenAI ChatCompletionChunk delta
|
||
"""
|
||
client = get_client()
|
||
if not client:
|
||
return
|
||
|
||
kwargs = {
|
||
"model": settings.deepseek_model,
|
||
"messages": messages,
|
||
"max_tokens": settings.llm_max_tokens,
|
||
"temperature": settings.llm_temperature,
|
||
"stream": True,
|
||
}
|
||
if tools:
|
||
kwargs["tools"] = tools
|
||
|
||
try:
|
||
stream = await client.chat.completions.create(**kwargs)
|
||
async for chunk in stream:
|
||
if chunk.choices and chunk.choices[0].delta:
|
||
yield chunk.choices[0].delta
|
||
except Exception as e:
|
||
logger.error(f"LLM 流式调用失败: {e}")
|
||
await log_error(
|
||
"llm",
|
||
f"LLM 流式调用失败: {e}",
|
||
detail=f"model={settings.deepseek_model}, tools={bool(tools)}",
|
||
)
|