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