- MemoryService 异步路径委托 MemoryIngestService / MemoryRetrievalService;富化派发经 MemoryEnrichmentScheduler - WebSocket pipeline 经 ChatTurnService 与显式 DTO 编排单轮对话;回忆录片段入队由 MemoirIngestScheduler 封装 - 新增 LlmGateway(LlmUseCase),各 agent、任务与适配器对齐 ports - 补充 memory 提示适配、runtime 类型、memory-retrieval 文档、ai-touchpoints 说明与扫描脚本及配套测试 Made-with: Cursor
109 lines
3.4 KiB
Python
109 lines
3.4 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-v4-flash",
|
|
temperature: float = 0.7,
|
|
*,
|
|
extra_body: dict | None = None,
|
|
):
|
|
self._default_model = model
|
|
self._default_temperature = temperature
|
|
kwargs: dict = {
|
|
"temperature": temperature,
|
|
"model": model,
|
|
"api_key": api_key,
|
|
}
|
|
if extra_body:
|
|
kwargs["extra_body"] = extra_body
|
|
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,
|
|
max_tokens: int | None = None,
|
|
) -> str:
|
|
llm = self._get_llm(temperature, model, max_tokens)
|
|
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,
|
|
max_tokens: int | None = None,
|
|
) -> AsyncIterator[str]:
|
|
llm = self._get_llm(temperature, model, max_tokens)
|
|
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,
|
|
max_tokens: int | None = None,
|
|
):
|
|
if temperature is None and model is None and max_tokens is None:
|
|
return self._llm
|
|
kwargs: dict = {}
|
|
if temperature is not None:
|
|
kwargs["temperature"] = temperature
|
|
if model is not None:
|
|
kwargs["model"] = model
|
|
if max_tokens is not None:
|
|
kwargs["max_tokens"] = max_tokens
|
|
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
|