Files
life-echo/api/app/adapters/llm/deepseek.py

84 lines
2.7 KiB
Python
Raw Normal View History

"""DeepSeek / OpenAI-compatible LLM adapter — implements LLMProvider port."""
from collections.abc import AsyncIterator
from langchain_openai import ChatOpenAI
class DeepSeekLLMProvider:
"""LangChain-based LLM adapter for DeepSeek and OpenAI-compatible APIs."""
def __init__(
self,
api_key: str,
base_url: str = "https://api.deepseek.com",
model: str = "deepseek-chat",
temperature: float = 0.7,
):
self._default_model = model
self._default_temperature = temperature
kwargs: dict = {
"temperature": temperature,
"model": model,
"api_key": api_key,
}
if base_url:
cleaned = base_url.rstrip("/")
for suffix in ("/v1/chat/completions", "/v1"):
if cleaned.endswith(suffix):
cleaned = cleaned[: -len(suffix)]
kwargs["base_url"] = cleaned
self._llm = ChatOpenAI(**kwargs)
@property
def langchain_llm(self) -> ChatOpenAI:
"""Expose underlying ChatOpenAI for LangChain agent interop (Phase 2 will remove)."""
return self._llm
async def complete(
self,
messages: list[dict],
*,
temperature: float | None = None,
model: str | None = None,
) -> str:
llm = self._get_llm(temperature, model)
lc_messages = _to_langchain_messages(messages)
result = await llm.ainvoke(lc_messages)
return str(result.content)
async def stream(
self,
messages: list[dict],
*,
temperature: float | None = None,
model: str | None = None,
) -> AsyncIterator[str]:
llm = self._get_llm(temperature, model)
lc_messages = _to_langchain_messages(messages)
async for chunk in llm.astream(lc_messages):
if chunk.content:
yield str(chunk.content)
def _get_llm(self, temperature: float | None, model: str | None):
if temperature is None and model is None:
return self._llm
kwargs: dict = {}
if temperature is not None:
kwargs["temperature"] = temperature
if model is not None:
kwargs["model"] = model
return self._llm.bind(**kwargs) if kwargs else self._llm
def _to_langchain_messages(messages: list[dict]) -> list:
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
mapping = {"system": SystemMessage, "human": HumanMessage, "user": HumanMessage, "ai": AIMessage, "assistant": AIMessage}
result = []
for msg in messages:
cls = mapping.get(msg.get("role", ""), HumanMessage)
result.append(cls(content=msg.get("content", "")))
return result