148 lines
4.2 KiB
Python
148 lines
4.2 KiB
Python
import asyncio
|
|
from datetime import datetime
|
|
from typing import TYPE_CHECKING
|
|
|
|
from pydantic_ai import Agent
|
|
|
|
from haiku.rag.agents.chat.prompts import SESSION_SUMMARY_PROMPT
|
|
from haiku.rag.config.models import AppConfig
|
|
from haiku.rag.tools.session import SessionContext
|
|
from haiku.rag.utils import get_model
|
|
|
|
if TYPE_CHECKING:
|
|
from haiku.rag.tools.qa import QAHistoryEntry, QASessionState
|
|
|
|
|
|
# Track summarization tasks to allow cancellation
|
|
_summarization_tasks: dict[int, asyncio.Task[None]] = {}
|
|
|
|
|
|
async def summarize_session(
|
|
qa_history: list["QAHistoryEntry"],
|
|
config: AppConfig,
|
|
current_context: str | None = None,
|
|
) -> str:
|
|
"""Summarize qa_history into compact context.
|
|
|
|
Args:
|
|
qa_history: List of Q&A pairs from the conversation.
|
|
config: AppConfig for model selection.
|
|
current_context: Previous session_context.summary to incorporate.
|
|
The summarizer will build upon this.
|
|
|
|
Returns:
|
|
Markdown summary of the conversation history.
|
|
"""
|
|
if not qa_history:
|
|
return ""
|
|
|
|
model = get_model(config.qa.model, config)
|
|
agent: Agent[None, str] = Agent(
|
|
model,
|
|
output_type=str,
|
|
instructions=SESSION_SUMMARY_PROMPT,
|
|
retries=2,
|
|
)
|
|
|
|
history_text = _format_qa_history(qa_history)
|
|
if current_context:
|
|
history_text = f"## Current Context\n{current_context}\n\n{history_text}"
|
|
result = await agent.run(history_text)
|
|
return result.output
|
|
|
|
|
|
async def update_session_context(
|
|
qa_history: list["QAHistoryEntry"],
|
|
config: AppConfig,
|
|
current_context: str | None = None,
|
|
) -> SessionContext:
|
|
"""Summarize qa_history and return the resulting session context.
|
|
|
|
Args:
|
|
qa_history: List of Q&A pairs from the conversation.
|
|
config: AppConfig for model selection.
|
|
current_context: Previous summary to incorporate.
|
|
|
|
Returns:
|
|
The new SessionContext with summary and timestamp.
|
|
"""
|
|
summary = await summarize_session(
|
|
qa_history, config, current_context=current_context
|
|
)
|
|
return SessionContext(
|
|
summary=summary,
|
|
last_updated=datetime.now(),
|
|
)
|
|
|
|
|
|
def _format_qa_history(qa_history: list["QAHistoryEntry"]) -> str:
|
|
"""Format qa_history for input to summarization."""
|
|
lines: list[str] = []
|
|
for i, qa in enumerate(qa_history, 1):
|
|
lines.append(f"## Q{i}: {qa.question}")
|
|
lines.append(f"**Answer** (confidence: {qa.confidence:.0%}):")
|
|
lines.append(qa.answer)
|
|
|
|
if qa.sources:
|
|
lines.append(f"**Sources:** {', '.join(qa.sources)}")
|
|
lines.append("")
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
async def _update_context_background(
|
|
qa_session_state: "QASessionState",
|
|
config: AppConfig,
|
|
) -> None:
|
|
"""Background task to update session context after an ask."""
|
|
try:
|
|
current_summary = (
|
|
qa_session_state.session_context.summary
|
|
if qa_session_state.session_context is not None
|
|
else None
|
|
)
|
|
result = await update_session_context(
|
|
qa_history=list(qa_session_state.qa_history),
|
|
config=config,
|
|
current_context=current_summary,
|
|
)
|
|
|
|
if result.summary:
|
|
qa_session_state.session_context = result
|
|
|
|
except asyncio.CancelledError:
|
|
pass
|
|
except Exception as e:
|
|
import logging
|
|
|
|
logging.getLogger(__name__).exception(f"Background summarization failed: {e}")
|
|
|
|
|
|
def trigger_background_summarization(
|
|
qa_session_state: "QASessionState",
|
|
config: AppConfig,
|
|
) -> None:
|
|
"""Trigger background session summarization if qa_history has entries.
|
|
|
|
Args:
|
|
qa_session_state: QASessionState with qa_history to summarize.
|
|
config: AppConfig for model selection.
|
|
"""
|
|
if not qa_session_state.qa_history:
|
|
return
|
|
|
|
key = id(qa_session_state)
|
|
|
|
# Cancel any existing summarization task for this state
|
|
if key in _summarization_tasks:
|
|
_summarization_tasks[key].cancel()
|
|
|
|
# Spawn background task
|
|
task = asyncio.create_task(
|
|
_update_context_background(
|
|
qa_session_state=qa_session_state,
|
|
config=config,
|
|
)
|
|
)
|
|
_summarization_tasks[key] = task
|
|
task.add_done_callback(lambda _t, k=key: _summarization_tasks.pop(k, None))
|