feat(rag): lite-model document summaries — non-markdown docs summarized at import, summary chunk retrieves and resolves to the full source doc
This commit is contained in:
+49
-5
@@ -1,10 +1,12 @@
|
||||
"""Async OpenAI-compatible client for the self-hosted aipi endpoint (PLAN A5).
|
||||
|
||||
Provides the embeddings surface (importer, retrieval) and chat streaming
|
||||
(PLAN A15) for the RAG pipeline. Chat streaming yields typed
|
||||
:class:`StreamPiece` values (phase 17): aipi's ``turbo`` model streams
|
||||
its reasoning as ``delta.reasoning_content`` chunks (deepseek/litellm
|
||||
wire convention, verified live 2026-08-23) **before** the answer's
|
||||
Provides the embeddings surface (importer, retrieval), one-shot chat
|
||||
completions (phase 30: the ``lite`` model summarizes non-markdown
|
||||
documents at import time), and chat streaming (PLAN A15) for the RAG
|
||||
pipeline. Chat streaming yields typed :class:`StreamPiece` values
|
||||
(phase 17): aipi's ``turbo`` model streams its reasoning as
|
||||
``delta.reasoning_content`` chunks (deepseek/litellm wire convention,
|
||||
verified live 2026-08-23) **before** the answer's
|
||||
``delta.content`` chunks, and reasoning counts against ``max_tokens``
|
||||
(an answer can in principle be empty).
|
||||
|
||||
@@ -187,6 +189,48 @@ class LLMClient:
|
||||
(vec,) = await self.embed([text])
|
||||
return vec
|
||||
|
||||
async def chat(
|
||||
self, messages: list[dict[str, str]], model: str | None = None
|
||||
) -> str:
|
||||
"""One-shot (non-streaming) completion (A5 extended, phase 30).
|
||||
|
||||
Short, low-temperature request (``temperature=0.2``, 2048-token
|
||||
cap — summaries and outlines are small, so a fixed budget is
|
||||
enough) against ``BOR_LLM_SUMMARY_MODEL`` (default ``lite``)
|
||||
unless *model* names another. Used by the document summarizer
|
||||
(phase 30) and the KB overview generator (phase 31).
|
||||
|
||||
Any transport/HTTP/malformed failure, a choiceless reply, or an
|
||||
empty/missing ``content`` field raises :class:`LLMError` — a
|
||||
silent empty summary must never be stored.
|
||||
"""
|
||||
try:
|
||||
resp = await self._client.chat.completions.create(
|
||||
model=model or self.settings.llm_summary_model,
|
||||
messages=cast("list[ChatCompletionMessageParam]", messages),
|
||||
temperature=0.2,
|
||||
max_tokens=2048,
|
||||
stream=False,
|
||||
)
|
||||
except LLMError:
|
||||
raise
|
||||
except Exception as e: # noqa: BLE001 — wrap transport-level failures
|
||||
raise LLMError(
|
||||
f"chat completion from {self.settings.llm_base_url} failed: {e}"
|
||||
) from e
|
||||
if not resp.choices:
|
||||
raise LLMError(
|
||||
f"chat completion from {self.settings.llm_base_url} "
|
||||
"returned no choices"
|
||||
)
|
||||
content = resp.choices[0].message.content
|
||||
if content is None or not content.strip():
|
||||
raise LLMError(
|
||||
f"chat completion from {self.settings.llm_base_url} returned "
|
||||
"empty content — refusing to store a silent summary"
|
||||
)
|
||||
return content.strip()
|
||||
|
||||
async def chat_stream(
|
||||
self, messages: list[dict[str, str]]
|
||||
) -> AsyncIterator[StreamPiece]:
|
||||
|
||||
Reference in New Issue
Block a user