Remove agents/chat/

This commit is contained in:
Yiorgis Gozadinos 2026-02-19 16:39:50 +02:00
parent ed89ff0fc9
commit 8176103eb0
No known key found for this signature in database
29 changed files with 0 additions and 18458 deletions

View file

@ -1,39 +0,0 @@
from haiku.rag.agents.chat.agent import (
DEFAULT_FEATURES,
FEATURE_ANALYSIS,
FEATURE_DOCUMENTS,
FEATURE_QA,
FEATURE_SEARCH,
ChatDeps,
build_chat_toolkit,
create_chat_agent,
prepare_chat_context,
run_chat_agent,
trigger_background_summarization,
)
from haiku.rag.agents.chat.prompts import build_chat_prompt
from haiku.rag.agents.chat.state import (
AGUI_STATE_KEY,
ChatSessionState,
_rebuild_models,
)
from haiku.rag.tools.qa import QAHistoryEntry
_rebuild_models(QAHistoryEntry)
__all__ = [
"AGUI_STATE_KEY",
"DEFAULT_FEATURES",
"FEATURE_ANALYSIS",
"FEATURE_DOCUMENTS",
"FEATURE_QA",
"FEATURE_SEARCH",
"build_chat_prompt",
"build_chat_toolkit",
"create_chat_agent",
"prepare_chat_context",
"run_chat_agent",
"trigger_background_summarization",
"ChatDeps",
"ChatSessionState",
]

View file

@ -1,217 +0,0 @@
from dataclasses import dataclass, field
from typing import Any
from pydantic_ai import Agent
from haiku.rag.agents.chat.context import (
trigger_background_summarization as _trigger_summarization,
)
from haiku.rag.agents.chat.prompts import build_chat_prompt
from haiku.rag.agents.chat.state import AGUI_STATE_KEY
from haiku.rag.config.models import AppConfig
from haiku.rag.tools.context import ToolContext
from haiku.rag.tools.deps import AgentDeps
from haiku.rag.tools.qa import QA_SESSION_NAMESPACE, QASessionState
from haiku.rag.tools.session import SessionContext
from haiku.rag.tools.toolkit import (
FEATURE_ANALYSIS,
FEATURE_DOCUMENTS,
FEATURE_QA,
FEATURE_SEARCH,
Toolkit,
build_toolkit,
)
from haiku.rag.utils import get_model
DEFAULT_FEATURES = [FEATURE_SEARCH, FEATURE_DOCUMENTS, FEATURE_QA]
def _on_qa_complete(qa_session_state: QASessionState, config: AppConfig) -> None:
_trigger_summarization(qa_session_state=qa_session_state, config=config)
@dataclass
class ChatDeps(AgentDeps):
"""Dependencies for chat agent.
Extends AgentDeps with chat-specific config and state handling.
"""
config: AppConfig = field(default_factory=AppConfig)
@AgentDeps.state.setter
def state(self, value: dict[str, Any] | None) -> None:
"""Set state from AG-UI protocol with chat-specific overrides."""
if value is None:
return
state_data = self._extract_state_data(value)
# Preserve server's session_context before restore overwrites it
qa_session_state = self.tool_context.get(QA_SESSION_NAMESPACE, QASessionState)
server_session_context = (
qa_session_state.session_context if qa_session_state is not None else None
)
self.tool_context.restore_state_snapshot(state_data)
# Chat-specific overrides after generic restore
if qa_session_state is not None:
# Prefer server's session_context (background summarizer may
# have updated it since the client's last snapshot).
if server_session_context is not None:
qa_session_state.session_context = server_session_context
# Handle initial_context -> session_context for first message
if qa_session_state.session_context is None:
if "initial_context" in state_data:
initial = state_data.get("initial_context")
if initial:
qa_session_state.session_context = SessionContext(
summary=initial
)
def build_chat_toolkit(
config: AppConfig,
features: list[str] | None = None,
) -> Toolkit:
"""Build a Toolkit configured for the chat agent.
Includes the on_qa_complete callback that triggers background
session summarization.
Args:
config: Application configuration.
features: List of features to enable. Defaults to DEFAULT_FEATURES.
Returns:
A Toolkit ready for chat agent composition and context creation.
"""
if features is None:
features = DEFAULT_FEATURES
return build_toolkit(config, features=features, on_qa_complete=_on_qa_complete)
def prepare_chat_context(
context: ToolContext,
features: list[str] | None = None,
) -> None:
"""Register required namespaces in a ToolContext for chat agent use.
Idempotent safe to call multiple times on the same context.
Args:
context: ToolContext to prepare.
features: List of enabled features. Defaults to DEFAULT_FEATURES.
"""
from haiku.rag.tools.context import prepare_context
if features is None:
features = DEFAULT_FEATURES
prepare_context(context, features=features, state_key=AGUI_STATE_KEY)
def create_chat_agent(
config: AppConfig,
features: list[str] | None = None,
preamble: str | None = None,
toolkit: Toolkit | None = None,
) -> Agent[ChatDeps, str]:
"""Create the chat agent with composed toolsets.
Args:
config: Application configuration.
features: List of features to enable. Defaults to DEFAULT_FEATURES
(search, documents, qa). Available features: "search",
"documents", "qa", "analysis".
preamble: Optional custom identity/rules section for the system prompt.
When provided, replaces the default identity prompt. Tool guidance,
feature rules, and closing are still appended by the builder.
toolkit: Optional pre-built Toolkit. When provided, its toolsets are
used directly. When omitted, a toolkit is built from config and
features.
Returns:
The configured chat agent.
Example:
async with HaikuRAG(db_path, create=True) as client:
toolkit = build_chat_toolkit(config)
context = toolkit.create_context(state_key=AGUI_STATE_KEY)
agent = create_chat_agent(config, toolkit=toolkit)
deps = ChatDeps(config=config, client=client, tool_context=context)
result = await agent.run("Search for X", deps=deps)
"""
if features is None:
features = DEFAULT_FEATURES
if toolkit is None:
toolkit = build_chat_toolkit(config, features=features)
model = get_model(config.qa.model, config)
return Agent(
model,
deps_type=ChatDeps,
output_type=str,
instructions=build_chat_prompt(features, preamble=preamble),
toolsets=toolkit.toolsets,
retries=3,
)
def trigger_background_summarization(deps: ChatDeps) -> None:
"""Trigger background session summarization if qa_history has entries.
Call this after agent.run() or agent.run_stream() completes to update
the session context summary in the background.
Args:
deps: Chat dependencies with tool_context containing QASessionState.
"""
qa_session_state = deps.tool_context.get(QA_SESSION_NAMESPACE, QASessionState)
if qa_session_state is None or not qa_session_state.qa_history:
return
_trigger_summarization(
qa_session_state=qa_session_state,
config=deps.config,
)
async def run_chat_agent(
agent: Agent[ChatDeps, str],
deps: ChatDeps,
message: str,
) -> str:
"""Run the chat agent.
Args:
agent: The chat agent.
deps: Chat dependencies.
message: User message.
Returns:
Agent response.
"""
result = await agent.run(message, deps=deps)
return result.output
__all__ = [
"build_chat_toolkit",
"create_chat_agent",
"prepare_chat_context",
"run_chat_agent",
"trigger_background_summarization",
"ChatDeps",
"AGUI_STATE_KEY",
"FEATURE_SEARCH",
"FEATURE_DOCUMENTS",
"FEATURE_QA",
"FEATURE_ANALYSIS",
"DEFAULT_FEATURES",
]

View file

@ -1,148 +0,0 @@
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: # pragma: no cover
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))

View file

@ -1,91 +0,0 @@
from haiku.rag.tools.prompts import build_tools_prompt
_PROMPT_BASE = """You are a helpful research assistant powered by haiku.rag, a knowledge base system.
You have access to a knowledge base of documents. Use your tools to search and answer questions.
CRITICAL RULES:
1. For greetings or casual chat: respond directly WITHOUT using any tools
2. NEVER call the same tool multiple times for a single user message
3. NEVER make up information - always use tools to get facts from the knowledge base"""
_PROMPT_QA_RULES = """
4. For questions: Use the "ask" tool EXACTLY ONCE - it automatically uses prior conversation context"""
_PROMPT_SEARCH_RULES = """
5. For searches: Use the "search" tool EXACTLY ONCE - it handles multi-query expansion internally"""
_PROMPT_SEARCH_OUTPUT = """
After calling search, copy the ENTIRE tool response to your output INCLUDING the content snippets. Do NOT shorten, summarize, or omit any part of the results."""
_PROMPT_CLOSING = """
Be friendly and conversational."""
_PROMPT_QA_CLOSING = (
""" When you use the "ask" tool, summarize the key findings for the user."""
)
def build_chat_prompt(
features: list[str],
preamble: str | None = None,
) -> str:
"""Build a chat system prompt from the given feature list.
Each feature adds its relevant tool guidance to the prompt.
The base identity, critical rules, and closing are always included.
Args:
features: List of feature names (e.g., ["search", "documents", "qa"]).
preamble: Optional custom identity/rules section. When provided,
replaces the default identity prompt. Tool guidance, feature
rules, and closing are still appended.
Returns:
The composed system prompt string.
"""
parts = [preamble if preamble is not None else _PROMPT_BASE]
# Add feature-specific critical rules
if "qa" in features:
parts.append(_PROMPT_QA_RULES)
if "search" in features:
parts.append(_PROMPT_SEARCH_RULES)
# Tool guidance (reusable across agents)
tools_prompt = build_tools_prompt(features)
if tools_prompt:
parts.append(tools_prompt)
# Chat-specific search output rule
if "search" in features:
parts.append(_PROMPT_SEARCH_OUTPUT)
parts.append(_PROMPT_CLOSING)
if "qa" in features:
parts.append(_PROMPT_QA_CLOSING)
return "".join(parts)
CHAT_SYSTEM_PROMPT = build_chat_prompt(["search", "documents", "qa"])
SESSION_SUMMARY_PROMPT = """You are a session summarizer. Given a conversation history of Q&A pairs (and optionally existing context), produce a structured summary that captures key information for future context.
If a "Current Context" section is provided at the start of the input, incorporate that context into your summary. This might be initial background context from the user or a previous summary - build upon it rather than discard it.
Your summary should be concise (aim for 500-1500 tokens) and include:
1. **Key Facts Established** - Specific facts, data, or conclusions learned during the conversation
2. **Documents Referenced** - Documents or sources that were cited, with brief notes on what they contain
3. **Current Focus** - What topic or question thread the user is currently exploring
Rules:
- Extract only high-signal information that would help answer follow-up questions
- When building on existing context, merge new information with prior context
- Omit small talk, greetings, or low-confidence answers
- Use bullet points for clarity
- Keep technical details but compress verbose explanations
- Preserve document names/titles when mentioned in sources
Output the summary directly in markdown format. Do not include meta-commentary about the summary itself."""

View file

@ -1,33 +0,0 @@
from typing import TYPE_CHECKING
from pydantic import BaseModel
from haiku.rag.agents.research.models import Citation
from haiku.rag.tools.session import SessionContext
if TYPE_CHECKING:
from haiku.rag.tools.qa import QAHistoryEntry
AGUI_STATE_KEY = "haiku.rag.chat"
class ChatSessionState(BaseModel):
"""State shared between frontend and agent via AG-UI."""
initial_context: str | None = None
citations: list[Citation] = []
citations_history: list[list[Citation]] = []
qa_history: list["QAHistoryEntry"] = []
session_context: SessionContext | None = None
document_filter: list[str] = []
citation_registry: dict[str, int] = {}
def _rebuild_models(qa_history_entry_cls: type) -> None:
"""Resolve ChatSessionState forward reference to QAHistoryEntry.
Must be called after QAHistoryEntry is defined, passing the class.
"""
ChatSessionState.model_rebuild(
_types_namespace={"QAHistoryEntry": qa_history_entry_cls}
)

File diff suppressed because it is too large Load diff

View file

@ -1,336 +0,0 @@
from datetime import datetime
from pathlib import Path
import pytest
from haiku.rag.agents.research.models import Citation
from haiku.rag.config import Config
from haiku.rag.tools.qa import QAHistoryEntry
from haiku.rag.tools.session import SessionContext
@pytest.fixture(scope="module")
def vcr_cassette_dir():
return str(Path(__file__).parent.parent.parent / "cassettes" / "test_chat_context")
class TestSessionContext:
"""Tests for SessionContext model."""
def test_session_context_creation_empty(self):
"""Test SessionContext can be created with defaults."""
ctx = SessionContext()
assert ctx.summary == ""
assert ctx.last_updated is None
def test_session_context_creation_with_values(self):
"""Test SessionContext can be created with provided values."""
now = datetime.now()
ctx = SessionContext(
summary="User discussed authentication patterns.",
last_updated=now,
)
assert ctx.summary == "User discussed authentication patterns."
assert ctx.last_updated == now
def test_session_context_serialization_roundtrip(self):
"""Test SessionContext serializes and deserializes correctly."""
now = datetime.now()
original = SessionContext(
summary="Test summary with facts.",
last_updated=now,
)
# Serialize to dict
data = original.model_dump()
# Deserialize back
restored = SessionContext(**data)
assert restored.summary == original.summary
assert restored.last_updated == original.last_updated
class TestSummarizeSession:
"""Tests for summarize_session function."""
@pytest.mark.asyncio
async def test_summarize_session_empty_history(self):
"""Test summarize_session with empty qa_history returns empty string."""
from haiku.rag.agents.chat.context import summarize_session
result = await summarize_session(qa_history=[], config=Config)
assert result == ""
@pytest.mark.asyncio
@pytest.mark.vcr()
async def test_summarize_session_single_entry(
self, allow_model_requests, temp_db_path
):
"""Test summarize_session with a single qa entry."""
from haiku.rag.agents.chat.context import summarize_session
qa_history = [
QAHistoryEntry(
question="What is the authentication method?",
answer="The API uses JWT tokens for authentication.",
confidence=0.95,
citations=[
Citation(
index=1,
document_id="doc-1",
chunk_id="chunk-1",
document_uri="auth-guide.md",
document_title="Auth Guide",
content="JWT token details...",
)
],
)
]
result = await summarize_session(qa_history=qa_history, config=Config)
# Should produce a non-empty summary
assert len(result) > 0
# Summary should mention authentication or JWT
assert "authentication" in result.lower() or "jwt" in result.lower()
@pytest.mark.asyncio
@pytest.mark.vcr()
async def test_summarize_session_multiple_entries(
self, allow_model_requests, temp_db_path
):
"""Test summarize_session with multiple qa entries produces consolidated summary."""
from haiku.rag.agents.chat.context import summarize_session
qa_history = [
QAHistoryEntry(
question="What is the authentication method?",
answer="The API uses JWT tokens for authentication.",
confidence=0.95,
citations=[
Citation(
index=1,
document_id="doc-1",
chunk_id="chunk-1",
document_uri="auth-guide.md",
document_title="Auth Guide",
content="JWT token details...",
)
],
),
QAHistoryEntry(
question="What is the rate limit?",
answer="Rate limiting is set to 100 requests per minute.",
confidence=0.9,
citations=[
Citation(
index=1,
document_id="doc-2",
chunk_id="chunk-2",
document_uri="api-reference.md",
document_title="API Reference",
content="Rate limit config...",
)
],
),
QAHistoryEntry(
question="How do I refresh tokens?",
answer="Use the /refresh endpoint with your refresh token.",
confidence=0.85,
citations=[
Citation(
index=1,
document_id="doc-1",
chunk_id="chunk-3",
document_uri="auth-guide.md",
document_title="Auth Guide",
content="Token refresh...",
)
],
),
]
result = await summarize_session(qa_history=qa_history, config=Config)
# Should produce a non-empty summary
assert len(result) > 0
# Summary should contain structured sections
result_lower = result.lower()
assert "key facts" in result_lower or "established" in result_lower
assert "documents" in result_lower or "sources" in result_lower
@pytest.mark.asyncio
@pytest.mark.vcr()
async def test_summarize_session_with_current_context(self, allow_model_requests):
"""Test summarize_session incorporates current_context into the summary."""
from haiku.rag.agents.chat.context import summarize_session
qa_history = [
QAHistoryEntry(
question="What's the rate limit?",
answer="100 requests per minute.",
confidence=0.9,
)
]
# Provide current_context (e.g., previous summary)
current_context = "Focus on Python APIs. User is building a web application."
result = await summarize_session(
qa_history=qa_history,
config=Config,
current_context=current_context,
)
# Summary should be non-empty and ideally incorporate context about Python/web
assert len(result) > 0
# The context about "Python" or "web application" should influence the summary
result_lower = result.lower()
assert (
"rate" in result_lower or "limit" in result_lower or "100" in result_lower
)
class TestUpdateSessionContext:
"""Tests for update_session_context function."""
@pytest.mark.asyncio
@pytest.mark.vcr()
async def test_update_session_context_returns_context(
self, allow_model_requests, temp_db_path
):
"""Test update_session_context returns a populated SessionContext."""
from haiku.rag.agents.chat.context import update_session_context
qa_history = [
QAHistoryEntry(
question="What is the authentication method?",
answer="The API uses JWT tokens.",
confidence=0.95,
)
]
result = await update_session_context(
qa_history=qa_history,
config=Config,
)
assert result.summary != ""
assert result.last_updated is not None
@pytest.mark.asyncio
async def test_update_session_context_with_empty_history(self):
"""Test update_session_context with empty history returns empty summary."""
from haiku.rag.agents.chat.context import update_session_context
result = await update_session_context(
qa_history=[],
config=Config,
)
assert result.summary == ""
class TestTriggerBackgroundSummarization:
"""Tests for trigger_background_summarization."""
def test_trigger_with_empty_qa_history(self):
"""trigger_background_summarization returns early with empty qa_history."""
from haiku.rag.agents.chat.context import (
_summarization_tasks,
trigger_background_summarization,
)
from haiku.rag.tools.qa import QASessionState
tasks_before = len(_summarization_tasks)
qa_session_state = QASessionState()
assert len(qa_session_state.qa_history) == 0
trigger_background_summarization(qa_session_state, config=Config)
# No new task should have been created
assert len(_summarization_tasks) == tasks_before
@pytest.mark.asyncio
async def test_trigger_cancels_existing_task(self):
"""Second trigger cancels the previous background task."""
import asyncio
from unittest.mock import patch
from haiku.rag.agents.chat.context import (
_summarization_tasks,
trigger_background_summarization,
)
from haiku.rag.tools.qa import QAHistoryEntry, QASessionState
_summarization_tasks.clear()
qa_session_state = QASessionState(
qa_history=[QAHistoryEntry(question="Q1", answer="A1", confidence=0.9)]
)
# Patch _update_context_background to be a slow coroutine
async def slow_background(*args, **kwargs):
await asyncio.sleep(10)
with patch(
"haiku.rag.agents.chat.context._update_context_background",
new=slow_background,
):
# First trigger creates a task
trigger_background_summarization(qa_session_state, config=Config)
key = id(qa_session_state)
assert key in _summarization_tasks
first_task = _summarization_tasks[key]
# Second trigger should cancel the first
trigger_background_summarization(qa_session_state, config=Config)
await asyncio.sleep(0) # Let cancellation propagate
assert first_task.cancelled() or first_task.done()
# Cleanup
if key in _summarization_tasks:
_summarization_tasks[key].cancel()
try:
await _summarization_tasks[key]
except asyncio.CancelledError:
pass
_summarization_tasks.clear()
class TestUpdateSessionContextPassesCurrentContext:
"""Tests for update_session_context current_context forwarding."""
@pytest.mark.asyncio
async def test_update_session_context_passes_current_context(self):
"""Test update_session_context passes current_context to summarizer."""
from unittest.mock import patch
from haiku.rag.agents.chat.context import update_session_context
qa_history = [
QAHistoryEntry(
question="What is JWT?",
answer="JSON Web Token for authentication.",
confidence=0.95,
)
]
captured_current_context = []
async def mock_summarize(qa_history, config, current_context=None):
captured_current_context.append(current_context)
return "Mocked summary"
with patch(
"haiku.rag.agents.chat.context.summarize_session",
new=mock_summarize,
):
await update_session_context(
qa_history=qa_history,
config=Config,
current_context="Previous session summary",
)
assert len(captured_current_context) == 1
assert captured_current_context[0] == "Previous session summary"

View file

@ -1,189 +0,0 @@
from pydantic_ai import FunctionToolset
from haiku.rag.agents.chat.agent import (
DEFAULT_FEATURES,
FEATURE_ANALYSIS,
FEATURE_DOCUMENTS,
FEATURE_QA,
FEATURE_SEARCH,
ChatDeps,
create_chat_agent,
prepare_chat_context,
)
from haiku.rag.agents.chat.prompts import build_chat_prompt
from haiku.rag.client import HaikuRAG
from haiku.rag.config import Config
from haiku.rag.tools.context import ToolContext
from haiku.rag.tools.qa import QA_SESSION_NAMESPACE, QASessionState
from haiku.rag.tools.session import SESSION_NAMESPACE, SessionState
def _count_function_toolsets(agent) -> int:
"""Count FunctionToolset instances in an agent (excludes internal toolsets)."""
return sum(1 for t in agent.toolsets if type(t) is FunctionToolset)
# =============================================================================
# Feature Selection Tests
# =============================================================================
def test_default_features(temp_db_path):
"""Default features create search + document + qa toolsets and register both states."""
context = ToolContext()
prepare_chat_context(context)
agent = create_chat_agent(Config)
# Should have 3 toolsets (search, document, qa)
assert _count_function_toolsets(agent) == 3
# Both SessionState and QASessionState should be registered
assert context.get(SESSION_NAMESPACE, SessionState) is not None
assert context.get(QA_SESSION_NAMESPACE, QASessionState) is not None
def test_search_only(temp_db_path):
"""features=["search"] creates only search toolset, no QASessionState."""
context = ToolContext()
prepare_chat_context(context, features=[FEATURE_SEARCH])
agent = create_chat_agent(Config, features=[FEATURE_SEARCH])
assert _count_function_toolsets(agent) == 1
# SessionState always registered, but QASessionState should NOT be
assert context.get(SESSION_NAMESPACE, SessionState) is not None
assert context.get(QA_SESSION_NAMESPACE, QASessionState) is None
def test_search_and_documents(temp_db_path):
"""features=["search", "documents"] creates both toolsets, no QASessionState."""
context = ToolContext()
prepare_chat_context(context, features=[FEATURE_SEARCH, FEATURE_DOCUMENTS])
agent = create_chat_agent(Config, features=[FEATURE_SEARCH, FEATURE_DOCUMENTS])
assert _count_function_toolsets(agent) == 2
assert context.get(SESSION_NAMESPACE, SessionState) is not None
assert context.get(QA_SESSION_NAMESPACE, QASessionState) is None
def test_all_features(temp_db_path):
"""All four features create four toolsets."""
context = ToolContext()
prepare_chat_context(
context,
features=[FEATURE_SEARCH, FEATURE_DOCUMENTS, FEATURE_QA, FEATURE_ANALYSIS],
)
agent = create_chat_agent(
Config,
features=[FEATURE_SEARCH, FEATURE_DOCUMENTS, FEATURE_QA, FEATURE_ANALYSIS],
)
assert _count_function_toolsets(agent) == 4
assert context.get(SESSION_NAMESPACE, SessionState) is not None
assert context.get(QA_SESSION_NAMESPACE, QASessionState) is not None
def test_no_qa_skips_qa_session_state(temp_db_path):
"""Without QA feature, QASessionState is not registered."""
context = ToolContext()
prepare_chat_context(context, features=[FEATURE_SEARCH, FEATURE_DOCUMENTS])
assert context.get(QA_SESSION_NAMESPACE, QASessionState) is None
def test_chat_deps_state_without_qa(temp_db_path):
"""ChatDeps.state getter omits qa_history/session_context when QASessionState absent."""
from haiku.rag.agents.chat.state import AGUI_STATE_KEY
client = HaikuRAG(temp_db_path, create=True)
context = ToolContext()
prepare_chat_context(context, features=[FEATURE_SEARCH])
deps = ChatDeps(config=Config, client=client, tool_context=context)
state = deps.state
# State is wrapped under the AGUI state key
assert AGUI_STATE_KEY in state
inner = state[AGUI_STATE_KEY]
# SessionState fields should be present
assert "document_filter" in inner
assert "citation_registry" in inner
assert "citations" in inner
# QA fields should NOT be present
assert "qa_history" not in inner
assert "session_context" not in inner
client.close()
# =============================================================================
# Prompt Composition Tests
# =============================================================================
def test_build_chat_prompt_default():
"""Default features produce prompt mentioning all standard tools."""
prompt = build_chat_prompt(DEFAULT_FEATURES)
assert "list_documents" in prompt
assert "get_document" in prompt
assert "summarize_document" in prompt
assert "ask" in prompt
assert "search" in prompt
assert "analyze" not in prompt
def test_build_chat_prompt_search_only():
"""Search-only prompt doesn't mention ask or document tools."""
prompt = build_chat_prompt([FEATURE_SEARCH])
assert "search" in prompt
assert '"ask"' not in prompt
assert '"list_documents"' not in prompt
assert '"get_document"' not in prompt
assert '"summarize_document"' not in prompt
def test_build_chat_prompt_includes_analysis():
"""Analysis feature adds analyze guidance to prompt."""
prompt = build_chat_prompt(
[FEATURE_SEARCH, FEATURE_QA, FEATURE_DOCUMENTS, FEATURE_ANALYSIS]
)
assert "analyze" in prompt
assert "search" in prompt
assert "ask" in prompt
def test_build_chat_prompt_with_preamble():
"""Custom preamble replaces the default identity section."""
custom = "You are a custom assistant."
prompt = build_chat_prompt(DEFAULT_FEATURES, preamble=custom)
assert prompt.startswith(custom)
# Tool guidance should still be appended
assert "search" in prompt
assert "ask" in prompt
# Default identity should NOT be present
assert "haiku.rag" not in prompt
def test_build_chat_prompt_without_preamble_uses_default():
"""Without preamble, the default identity section is used."""
prompt = build_chat_prompt(DEFAULT_FEATURES)
assert "haiku.rag" in prompt
def test_create_chat_agent_with_preamble():
"""create_chat_agent passes preamble through to build_chat_prompt."""
custom = "You are a domain expert."
agent = create_chat_agent(Config, preamble=custom)
assert agent is not None
# _instructions is the internal list of instruction strings/callables
assert any(
custom in instr for instr in agent._instructions if isinstance(instr, str)
)

View file

@ -1,141 +0,0 @@
from haiku.rag.agents.chat.state import ChatSessionState
from haiku.rag.agents.research.models import Citation
from haiku.rag.tools.session import SessionContext, SessionState
def test_max_qa_history_constant():
"""Test MAX_QA_HISTORY constant value."""
from haiku.rag.tools.qa import MAX_QA_HISTORY
assert MAX_QA_HISTORY == 50
def test_citation_registry_index_assignment():
"""Test get_or_assign_index basic index assignment behavior.
Verifies:
- First chunk gets index 1
- Second unique chunk gets index 2
- Same chunk_id always returns same index
"""
session_state = SessionState()
# First chunk gets index 1
index1 = session_state.get_or_assign_index("chunk-abc")
assert index1 == 1
# Second unique chunk gets index 2
index2 = session_state.get_or_assign_index("chunk-def")
assert index2 == 2
# Same chunk_id returns same index (not incremented)
index1_again = session_state.get_or_assign_index("chunk-abc")
assert index1_again == 1
def test_citation_registry_stability():
"""Test citation indices are stable across multiple calls in any order."""
session_state = SessionState()
# First round assigns indices 1, 2, 3
idx_a = session_state.get_or_assign_index("chunk-a")
idx_b = session_state.get_or_assign_index("chunk-b")
idx_c = session_state.get_or_assign_index("chunk-c")
# Second round - existing chunks keep their indices regardless of order
assert session_state.get_or_assign_index("chunk-b") == idx_b
assert session_state.get_or_assign_index("chunk-a") == idx_a
assert session_state.get_or_assign_index("chunk-c") == idx_c
# New chunk gets next index
idx_d = session_state.get_or_assign_index("chunk-d")
assert idx_d == 4
def test_citation_registry_serialization_roundtrip():
"""Test citation_registry serializes and deserializes correctly for AG-UI state."""
# Create state and assign indices
original = ChatSessionState()
original.citation_registry = {"chunk-a": 1, "chunk-b": 2}
# Serialize
state_dict = original.model_dump()
assert "citation_registry" in state_dict
assert state_dict["citation_registry"] == {"chunk-a": 1, "chunk-b": 2}
# Deserialize (simulating AG-UI state restoration)
restored = ChatSessionState.model_validate(state_dict)
assert restored.citation_registry == {"chunk-a": 1, "chunk-b": 2}
def test_chat_session_state_initial_context_default_none():
"""Initial context should default to None."""
state = ChatSessionState()
assert state.initial_context is None
def test_chat_session_state_initial_context_preserved():
"""Explicit initial_context should be preserved."""
state = ChatSessionState(initial_context="Background info about the project")
assert state.initial_context == "Background info about the project"
def test_chat_session_state_initial_context_serialization():
"""initial_context should serialize and deserialize correctly."""
state = ChatSessionState(
initial_context="User is working on authentication",
)
state_dict = state.model_dump()
assert state_dict["initial_context"] == "User is working on authentication"
restored = ChatSessionState.model_validate(state_dict)
assert restored.initial_context == "User is working on authentication"
def test_chat_session_state_model_dump_json_serializes_datetime():
"""model_dump(mode='json') should serialize datetime to ISO string.
Agent tools use model_dump(mode='json') when creating StateSnapshotEvent
to ensure datetime fields are JSON-serializable for external clients
persisting AG-UI state to database JSON columns.
"""
from datetime import datetime
session_state = ChatSessionState(
session_context=SessionContext(
summary="Test summary",
last_updated=datetime(2025, 1, 27, 12, 0, 0),
),
)
# This is how agent.py creates snapshots for StateSnapshotEvent
snapshot = session_state.model_dump(mode="json")
# datetime should be serialized as ISO string, not datetime object
assert isinstance(snapshot["session_context"]["last_updated"], str)
assert snapshot["session_context"]["last_updated"] == "2025-01-27T12:00:00"
def test_chat_session_state_citations_history_default():
"""citations_history defaults to empty list."""
state = ChatSessionState()
assert state.citations_history == []
def test_chat_session_state_citations_history_roundtrip():
"""citations_history serializes and deserializes correctly."""
citation = Citation(
index=1,
document_id="d1",
chunk_id="c1",
document_uri="test://doc",
document_title="Doc",
page_numbers=[],
headings=None,
content="content",
)
state = ChatSessionState(citations_history=[[citation]])
data = state.model_dump(mode="json")
restored = ChatSessionState.model_validate(data)
assert len(restored.citations_history) == 1
assert restored.citations_history[0][0].chunk_id == "c1"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,187 +0,0 @@
interactions:
- request:
headers:
accept:
- application/json
accept-encoding:
- gzip, deflate, zstd
connection:
- keep-alive
content-length:
- '5211'
content-type:
- application/json
host:
- localhost:11434
method: POST
parsed_body:
messages:
- content: |-
You are a helpful research assistant powered by haiku.rag, a knowledge base system.
You have access to a knowledge base of documents. Use your tools to search and answer questions.
CRITICAL RULES:
1. For greetings or casual chat: respond directly WITHOUT using any tools
2. For questions: Use the "ask" tool EXACTLY ONCE - it automatically uses prior conversation context
3. For searches: Use the "search" tool EXACTLY ONCE - it handles multi-query expansion internally
4. NEVER call the same tool multiple times for a single user message
5. NEVER make up information - always use tools to get facts from the knowledge base
How to decide which tool to use:
- "list_documents" - Use when the user wants to browse or see what documents are available (e.g., "what documents are available?", "show me the documents", "list available docs").
- "summarize_document" - Use when the user wants an overview or summary of a specific document (e.g., "summarize document X", "what does Y cover?", "give me an overview of Z").
- "get_document" - Use when the user wants the FULL content of a specific document (e.g., "get the paper about Y", "fetch 2412.00566", "show me the full document").
- "ask" - Use for questions about topics in the knowledge base. It automatically finds relevant prior answers from conversation history and searches across documents to return answers with citations.
- "search" - Use when the user explicitly asks to search/find/explore documents. Call it ONCE. After calling search, copy the ENTIRE tool response to your output INCLUDING the content snippets. Do NOT shorten, summarize, or omit any part of the results.
IMPORTANT - When user mentions a document in search/ask:
- If user says "search in <doc>", "find in <doc>", "answer from <doc>", or "<topic> in <doc>":
- Extract the TOPIC as `query`/`question`
- Extract the DOCUMENT NAME as `document_name`
- Examples for search:
- "search for embeddings in the ML paper" → query="embeddings", document_name="ML paper"
- "find transformer architecture in 2412.00566" → query="transformer architecture", document_name="2412.00566"
- Examples for ask:
- "what does the ML paper say about embeddings?" → question="what are the embedding methods?", document_name="ML paper"
- "answer from 2412.00566 about model training" → question="how is the model trained?", document_name="2412.00566"
Be friendly and conversational. When you use the "ask" tool, summarize the key findings for the user.
role: system
- content: Get me the nonexistent document
role: user
model: gpt-oss
reasoning_effort: low
stream: false
tool_choice: auto
tools:
- function:
description: |-
Search the knowledge base for relevant documents.
Use this when you need to find documents or explore the knowledge base.
Results are displayed to the user - just list the titles found.
name: search
parameters:
additionalProperties: false
properties:
document_name:
anyOf:
- type: string
- type: 'null'
default: null
description: Optional document name/title to search within
limit:
anyOf:
- type: integer
- type: 'null'
default: null
description: 'Number of results to return (default: 5)'
query:
description: The search query (what to search for)
type: string
required:
- query
type: object
type: function
- function:
description: |-
Answer a specific question using the knowledge base.
Use this for direct questions that need a focused answer with citations.
Uses a research graph for planning, searching, and synthesis.
name: ask
parameters:
additionalProperties: false
properties:
document_name:
anyOf:
- type: string
- type: 'null'
default: null
description: Optional document name/title to search within (e.g., "tbmed593", "army manual")
question:
description: The question to answer
type: string
required:
- question
type: object
type: function
- function:
description: |-
List available documents in the knowledge base.
Use this when the user wants to browse or see what documents are available.
name: list_documents
parameters:
additionalProperties: false
properties:
page:
default: 1
description: 'Page number (default: 1, 50 documents per page)'
type: integer
type: object
type: function
- function:
description: |-
Retrieve a specific document by title or URI.
Use this when the user wants to fetch/get/retrieve a specific document.
name: get_document
parameters:
additionalProperties: false
properties:
query:
description: The document title or URI to look up
type: string
required:
- query
type: object
strict: true
type: function
- function:
description: |-
Generate a summary of a specific document.
Use this when the user wants an overview or summary of a document's content.
name: summarize_document
parameters:
additionalProperties: false
properties:
query:
description: The document title or URI to summarize
type: string
required:
- query
type: object
strict: true
type: function
uri: http://localhost:11434/v1/chat/completions
response:
headers:
content-length:
- '539'
content-type:
- application/json
parsed_body:
choices:
- finish_reason: stop
index: 0
message:
content: Im sorry, but that document isnt available in the knowledge base. If theres another topic or document
youd like help with, just let me know!
reasoning: User asking for nonexistent document. Need to respond that none exists. No tool usage.
role: assistant
created: 1769793913
id: chatcmpl-124
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
completion_tokens: 60
prompt_tokens: 1025
total_tokens: 1085
status:
code: 200
message: OK
version: 1

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,196 +0,0 @@
interactions:
- request:
headers:
accept:
- application/json
accept-encoding:
- gzip, deflate, zstd
connection:
- keep-alive
content-length:
- '5368'
content-type:
- application/json
host:
- localhost:11434
method: POST
parsed_body:
messages:
- content: |-
You are a helpful research assistant powered by haiku.rag, a knowledge base system.
You have access to a knowledge base of documents. Use your tools to search and answer questions.
CRITICAL RULES:
1. For greetings or casual chat: respond directly WITHOUT using any tools
2. For questions: Use the "ask" tool EXACTLY ONCE - it automatically uses prior conversation context
3. For searches: Use the "search" tool EXACTLY ONCE - it handles multi-query expansion internally
4. NEVER call the same tool multiple times for a single user message
5. NEVER make up information - always use tools to get facts from the knowledge base
How to decide which tool to use:
- "list_documents" - Use when the user wants to browse or see what documents are available (e.g., "what documents are available?", "show me the documents", "list available docs").
- "summarize_document" - Use when the user wants an overview or summary of a specific document (e.g., "summarize document X", "what does Y cover?", "give me an overview of Z").
- "get_document" - Use when the user wants the FULL content of a specific document (e.g., "get the paper about Y", "fetch 2412.00566", "show me the full document").
- "ask" - Use for questions about topics in the knowledge base. It automatically finds relevant prior answers from conversation history and searches across documents to return answers with citations.
- "search" - Use when the user explicitly asks to search/find/explore documents. Call it ONCE. After calling search, copy the ENTIRE tool response to your output INCLUDING the content snippets. Do NOT shorten, summarize, or omit any part of the results.
IMPORTANT - When user mentions a document in search/ask:
- If user says "search in <doc>", "find in <doc>", "answer from <doc>", or "<topic> in <doc>":
- Extract the TOPIC as `query`/`question`
- Extract the DOCUMENT NAME as `document_name`
- Examples for search:
- "search for embeddings in the ML paper" → query="embeddings", document_name="ML paper"
- "find transformer architecture in 2412.00566" → query="transformer architecture", document_name="2412.00566"
- Examples for ask:
- "what does the ML paper say about embeddings?" → question="what are the embedding methods?", document_name="ML paper"
- "answer from 2412.00566 about model training" → question="how is the model trained?", document_name="2412.00566"
Be friendly and conversational. When you use the "ask" tool, summarize the key findings for the user.
role: system
- content: Summarize the nonexistent document
role: user
model: gpt-oss
reasoning_effort: low
stream: false
tool_choice: auto
tools:
- function:
description: |-
Search the knowledge base for relevant documents.
Use this when you need to find documents or explore the knowledge base.
Results are displayed to the user - just list the titles found.
name: search
parameters:
additionalProperties: false
properties:
document_name:
anyOf:
- type: string
- type: 'null'
default: null
description: Optional document name/title to search within
limit:
anyOf:
- type: integer
- type: 'null'
default: null
description: 'Number of results to return (default: 5)'
query:
description: The search query (what to search for)
type: string
required:
- query
type: object
type: function
- function:
description: |-
Answer a specific question using the knowledge base.
Use this for direct questions that need a focused answer with citations.
Uses a research graph for planning, searching, and synthesis.
name: ask
parameters:
additionalProperties: false
properties:
document_name:
anyOf:
- type: string
- type: 'null'
default: null
description: Optional document name/title to search within (e.g., "tbmed593", "army manual")
question:
description: The question to answer
type: string
required:
- question
type: object
type: function
- function:
description: |-
List available documents in the knowledge base.
Use this when the user wants to browse or see what documents are available.
name: list_documents
parameters:
additionalProperties: false
properties:
limit:
anyOf:
- type: integer
- type: 'null'
default: null
description: Maximum number of documents to return
offset:
anyOf:
- type: integer
- type: 'null'
default: null
description: Number of documents to skip (for pagination)
type: object
type: function
- function:
description: |-
Retrieve a specific document by title or URI.
Use this when the user wants to fetch/get/retrieve a specific document.
name: get_document
parameters:
additionalProperties: false
properties:
query:
description: The document title or URI to look up
type: string
required:
- query
type: object
strict: true
type: function
- function:
description: |-
Generate a summary of a specific document.
Use this when the user wants an overview or summary of a document's content.
name: summarize_document
parameters:
additionalProperties: false
properties:
query:
description: The document title or URI to summarize
type: string
required:
- query
type: object
strict: true
type: function
uri: http://localhost:11434/v1/chat/completions
response:
headers:
content-length:
- '655'
content-type:
- application/json
parsed_body:
choices:
- finish_reason: stop
index: 0
message:
content: Im sorry, but I couldnt find a document with that name. If you have the exact title or a related keyword,
let me know and Ill try again.
reasoning: User asks to summarize nonexistent document. According to rule, for summary use summarize_document tool,
but if document doesn't exist? We must search? Likely we respond that document not found. No tool needed.
role: assistant
created: 1769523898
id: chatcmpl-69
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
completion_tokens: 86
prompt_tokens: 1039
total_tokens: 1125
status:
code: 200
message: OK
version: 1

View file

@ -1,94 +0,0 @@
interactions:
- request:
headers:
accept:
- application/json
accept-encoding:
- gzip, deflate, zstd
connection:
- keep-alive
content-length:
- '1493'
content-type:
- application/json
host:
- localhost:11434
method: POST
parsed_body:
messages:
- content: |-
You are a session summarizer. Given a conversation history of Q&A pairs, produce a structured summary that captures key information for future context.
Your summary should be concise (aim for 500-1500 tokens) and include:
1. **Key Facts Established** - Specific facts, data, or conclusions learned during the conversation
2. **Documents Referenced** - Documents or sources that were cited, with brief notes on what they contain
3. **Current Focus** - What topic or question thread the user is currently exploring
Rules:
- Extract only high-signal information that would help answer follow-up questions
- Omit small talk, greetings, or low-confidence answers
- Use bullet points for clarity
- Keep technical details but compress verbose explanations
- Preserve document names/titles when mentioned in sources
Output the summary directly in markdown format. Do not include meta-commentary about the summary itself.
role: system
- content: |
## Q1: What is the authentication method?
**Answer** (confidence: 95%):
The API uses JWT tokens for authentication.
**Sources:** Auth Guide
## Q2: What is the rate limit?
**Answer** (confidence: 90%):
Rate limiting is set to 100 requests per minute.
**Sources:** API Reference
## Q3: How do I refresh tokens?
**Answer** (confidence: 85%):
Use the /refresh endpoint with your refresh token.
**Sources:** Auth Guide
role: user
model: gpt-oss
reasoning_effort: low
stream: false
uri: http://localhost:11434/v1/chat/completions
response:
headers:
content-length:
- '865'
content-type:
- application/json
parsed_body:
choices:
- finish_reason: stop
index: 0
message:
content: |-
## Key Facts Established
- **Authentication method**: JWT tokens.
- **Rate limit**: 100 requests per minute.
- **Token refresh**: use `/refresh` endpoint with a refresh token.
## Documents Referenced
- **Auth Guide** contains details on JWT usage, token issuance, and refresh mechanism.
- **API Reference** includes rate limiting policy and endpoint descriptions.
## Current Focus
The user is currently gathering foundational API usage details, specifically authentication methods, rate limits, and token refresh procedures.
reasoning: We need to summarize.
role: assistant
created: 1769007514
id: chatcmpl-760
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
completion_tokens: 121
prompt_tokens: 365
total_tokens: 486
status:
code: 200
message: OK
version: 1

View file

@ -1,81 +0,0 @@
interactions:
- request:
headers:
accept:
- application/json
accept-encoding:
- gzip, deflate, zstd
connection:
- keep-alive
content-length:
- '1207'
content-type:
- application/json
host:
- localhost:11434
method: POST
parsed_body:
messages:
- content: |-
You are a session summarizer. Given a conversation history of Q&A pairs, produce a structured summary that captures key information for future context.
Your summary should be concise (aim for 500-1500 tokens) and include:
1. **Key Facts Established** - Specific facts, data, or conclusions learned during the conversation
2. **Documents Referenced** - Documents or sources that were cited, with brief notes on what they contain
3. **Current Focus** - What topic or question thread the user is currently exploring
Rules:
- Extract only high-signal information that would help answer follow-up questions
- Omit small talk, greetings, or low-confidence answers
- Use bullet points for clarity
- Keep technical details but compress verbose explanations
- Preserve document names/titles when mentioned in sources
Output the summary directly in markdown format. Do not include meta-commentary about the summary itself.
role: system
- content: |
## Q1: What is the authentication method?
**Answer** (confidence: 95%):
The API uses JWT tokens for authentication.
**Sources:** Auth Guide
role: user
model: gpt-oss
reasoning_effort: low
stream: false
uri: http://localhost:11434/v1/chat/completions
response:
headers:
content-length:
- '575'
content-type:
- application/json
parsed_body:
choices:
- finish_reason: stop
index: 0
message:
content: |-
**Key Facts Established**
- The API uses **JWT tokens** for authentication.
**Documents Referenced**
- **Auth Guide** Provides details on JWT usage for this API.
**Current Focus**
- Understanding the authentication method employed by the API.
reasoning: We need to summarize.
role: assistant
created: 1769007512
id: chatcmpl-739
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
completion_tokens: 65
prompt_tokens: 292
total_tokens: 357
status:
code: 200
message: OK
version: 1

View file

@ -1,89 +0,0 @@
interactions:
- request:
headers:
accept:
- application/json
accept-encoding:
- gzip, deflate, zstd
connection:
- keep-alive
content-length:
- '1581'
content-type:
- application/json
host:
- localhost:11434
method: POST
parsed_body:
messages:
- content: |-
You are a session summarizer. Given a conversation history of Q&A pairs (and optionally existing context), produce a structured summary that captures key information for future context.
If a "Current Context" section is provided at the start of the input, incorporate that context into your summary. This might be initial background context from the user or a previous summary - build upon it rather than discard it.
Your summary should be concise (aim for 500-1500 tokens) and include:
1. **Key Facts Established** - Specific facts, data, or conclusions learned during the conversation
2. **Documents Referenced** - Documents or sources that were cited, with brief notes on what they contain
3. **Current Focus** - What topic or question thread the user is currently exploring
Rules:
- Extract only high-signal information that would help answer follow-up questions
- When building on existing context, merge new information with prior context
- Omit small talk, greetings, or low-confidence answers
- Use bullet points for clarity
- Keep technical details but compress verbose explanations
- Preserve document names/titles when mentioned in sources
Output the summary directly in markdown format. Do not include meta-commentary about the summary itself.
role: system
- content: |
## Current Context
Focus on Python APIs. User is building a web application.
## Q1: What's the rate limit?
**Answer** (confidence: 90%):
100 requests per minute.
role: user
model: gpt-oss
reasoning_effort: low
stream: false
uri: http://localhost:11434/v1/chat/completions
response:
headers:
content-length:
- '682'
content-type:
- application/json
parsed_body:
choices:
- finish_reason: stop
index: 0
message:
content: |-
## Summary
- **Key Facts Established**
- The user is building a web application and is focused on Python APIs.
- The relevant rate limit is **100 requests per minute** (confidence 90%).
- **Documents Referenced**
- None cited in this exchange.
- **Current Focus**
- Understanding and managing API rate limits for the Python-based web application.
reasoning: We need summary.
role: assistant
created: 1769164539
id: chatcmpl-369
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
completion_tokens: 91
prompt_tokens: 362
total_tokens: 453
status:
code: 200
message: OK
version: 1

View file

@ -1,80 +0,0 @@
interactions:
- request:
headers:
accept:
- application/json
accept-encoding:
- gzip, deflate, zstd
connection:
- keep-alive
content-length:
- '1163'
content-type:
- application/json
host:
- localhost:11434
method: POST
parsed_body:
messages:
- content: |-
You are a session summarizer. Given a conversation history of Q&A pairs, produce a structured summary that captures key information for future context.
Your summary should be concise (aim for 500-1500 tokens) and include:
1. **Key Facts Established** - Specific facts, data, or conclusions learned during the conversation
2. **Documents Referenced** - Documents or sources that were cited, with brief notes on what they contain
3. **Current Focus** - What topic or question thread the user is currently exploring
Rules:
- Extract only high-signal information that would help answer follow-up questions
- Omit small talk, greetings, or low-confidence answers
- Use bullet points for clarity
- Keep technical details but compress verbose explanations
- Preserve document names/titles when mentioned in sources
Output the summary directly in markdown format. Do not include meta-commentary about the summary itself.
role: system
- content: |
## Q1: What is the authentication method?
**Answer** (confidence: 95%):
The API uses JWT tokens.
role: user
model: gpt-oss
reasoning_effort: low
stream: false
uri: http://localhost:11434/v1/chat/completions
response:
headers:
content-length:
- '559'
content-type:
- application/json
parsed_body:
choices:
- finish_reason: stop
index: 0
message:
content: |-
## Key Facts Established
- The API authentication method is **JWT tokens** (high confidence 95%).
## Documents Referenced
- *None provided*.
## Current Focus
- The user is exploring details related to **API authentication mechanisms**.
reasoning: We need summary.
role: assistant
created: 1769007530
id: chatcmpl-975
model: gpt-oss
object: chat.completion
system_fingerprint: fp_ollama
usage:
completion_tokens: 64
prompt_tokens: 284
total_tokens: 348
status:
code: 200
message: OK
version: 1