astock-agent/backend/app/llm/client.py
2026-04-23 17:24:55 +08:00

100 lines
2.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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