Files
life-echo/api/app/adapters/llm/deepseek.py
Kevin a3f61fcc0f feat(api+app): 对话阶段化、回忆录流水线与客户端会话体验
- DB: segments 用户输入文本(Alembic 0002)
- Chat: 阶段检测/阶段提示/回复限制,编排与访谈/画像 prompts 调整
- Memoir: 忠实度检查 agent,叙事与分类等链路更新
- Core: agent 日志、Alembic 启动、LangChain/日志/配置等
- Story: time_hints;Memory 检索与相关测试
- Expo: 助手头像、会话页与消息拆分、实时会话与文案/i18n
- Docs/scripts/tests: 迁移脚本、LLM JSON/记忆检索文档、新增单测
2026-03-26 12:13:36 +08:00

96 lines
3.0 KiB
Python

"""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.
`langchain_llm` 供 Agent / 任务同步调用;若需 JSON object 模式,请用
`app.core.langchain_llm.bind_json_object_mode` 或 `invoke_json_object` /
`ainvoke_json_object`(见 `api/docs/llm-json-mode.md`),勿使用已废弃的
`bind(model_kwargs={"response_format": ...})`。
"""
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