Remove background_context & simplify
This commit is contained in:
parent
9d71ccb213
commit
5aabae4d06
19 changed files with 31 additions and 554 deletions
|
|
@ -82,7 +82,6 @@ async def stream_chat(request: Request) -> Response:
|
|||
|
||||
# Restore session state from incoming AG-UI state (look under namespaced key)
|
||||
initial_qa_history: list[QAResponse] = []
|
||||
background_context: str | None = None
|
||||
session_id: str | None = None
|
||||
state = getattr(run_input, "state", None)
|
||||
if state:
|
||||
|
|
@ -91,7 +90,6 @@ async def stream_chat(request: Request) -> Response:
|
|||
initial_qa_history = [
|
||||
QAResponse(**qa) for qa in chat_state.get("qa_history", [])
|
||||
]
|
||||
background_context = chat_state.get("background_context")
|
||||
session_id = chat_state.get("session_id")
|
||||
|
||||
# Determine session_id: prefer state, fall back to thread_id, generate UUID if neither
|
||||
|
|
@ -105,8 +103,6 @@ async def stream_chat(request: Request) -> Response:
|
|||
session_state=ChatSessionState(
|
||||
session_id=session_id,
|
||||
qa_history=initial_qa_history,
|
||||
background_context=background_context,
|
||||
# session_context intentionally NOT set - agent will fetch from cache
|
||||
),
|
||||
state_key=AGUI_STATE_KEY,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,12 +7,11 @@ import {
|
|||
useCopilotAction,
|
||||
} from "@copilotkit/react-core";
|
||||
import { CopilotChat } from "@copilotkit/react-ui";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import "@copilotkit/react-ui/styles.css";
|
||||
import CitationBlock from "./CitationBlock";
|
||||
import ContextPanel from "./ContextPanel";
|
||||
import DbInfo from "./DbInfo";
|
||||
import SettingsPanel, { STORAGE_KEY } from "./SettingsPanel";
|
||||
|
||||
// Must match AGUI_STATE_KEY from haiku.rag.agents.chat
|
||||
const AGUI_STATE_KEY = "haiku.rag.chat";
|
||||
|
|
@ -44,7 +43,6 @@ interface ChatSessionState {
|
|||
session_id: string;
|
||||
citations: Citation[];
|
||||
qa_history: QAResponse[];
|
||||
background_context: string | null;
|
||||
session_context: SessionContext | null;
|
||||
}
|
||||
|
||||
|
|
@ -141,24 +139,6 @@ function FileIcon() {
|
|||
);
|
||||
}
|
||||
|
||||
function SettingsIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" />
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function BrainIcon() {
|
||||
return (
|
||||
<svg
|
||||
|
|
@ -389,28 +369,9 @@ function ToolCallIndicator({
|
|||
);
|
||||
}
|
||||
|
||||
function ChatContentInner({
|
||||
backgroundContext,
|
||||
setBackgroundContext,
|
||||
}: {
|
||||
backgroundContext: string;
|
||||
setBackgroundContext: (value: string) => void;
|
||||
}) {
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
function ChatContentInner() {
|
||||
const [contextOpen, setContextOpen] = useState(false);
|
||||
|
||||
const handleSaveContext = useCallback(
|
||||
(value: string) => {
|
||||
setBackgroundContext(value);
|
||||
if (value) {
|
||||
localStorage.setItem(STORAGE_KEY, value);
|
||||
} else {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
}
|
||||
},
|
||||
[setBackgroundContext],
|
||||
);
|
||||
|
||||
const { state: agentState } = useCoAgent<AgentState>({
|
||||
name: "chat_agent",
|
||||
initialState: {
|
||||
|
|
@ -418,7 +379,6 @@ function ChatContentInner({
|
|||
session_id: "",
|
||||
citations: [],
|
||||
qa_history: [],
|
||||
background_context: backgroundContext || null,
|
||||
session_context: null,
|
||||
},
|
||||
},
|
||||
|
|
@ -566,19 +526,6 @@ function ChatContentInner({
|
|||
<BrainIcon />
|
||||
Memory
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`header-btn ${backgroundContext ? "has-content" : ""}`}
|
||||
onClick={() => setSettingsOpen(true)}
|
||||
title={
|
||||
backgroundContext
|
||||
? "Background context is set"
|
||||
: "Set background context"
|
||||
}
|
||||
>
|
||||
<SettingsIcon />
|
||||
Settings
|
||||
</button>
|
||||
</div>
|
||||
<div className="chat-content">
|
||||
<CopilotChat
|
||||
|
|
@ -592,12 +539,6 @@ function ChatContentInner({
|
|||
<DbInfo />
|
||||
</div>
|
||||
</div>
|
||||
<SettingsPanel
|
||||
isOpen={settingsOpen}
|
||||
onClose={() => setSettingsOpen(false)}
|
||||
onSave={handleSaveContext}
|
||||
currentValue={backgroundContext}
|
||||
/>
|
||||
<ContextPanel
|
||||
isOpen={contextOpen}
|
||||
onClose={() => setContextOpen(false)}
|
||||
|
|
@ -608,25 +549,7 @@ function ChatContentInner({
|
|||
}
|
||||
|
||||
function ChatContent() {
|
||||
const [backgroundContext, setBackgroundContext] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
setBackgroundContext(stored || "");
|
||||
}, []);
|
||||
|
||||
if (backgroundContext === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ChatContentInner
|
||||
backgroundContext={backgroundContext}
|
||||
setBackgroundContext={setBackgroundContext}
|
||||
/>
|
||||
);
|
||||
return <ChatContentInner />;
|
||||
}
|
||||
|
||||
export default function Chat() {
|
||||
|
|
|
|||
|
|
@ -1,188 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useId, useState } from "react";
|
||||
|
||||
const STORAGE_KEY = "haiku.rag.settings.background_context";
|
||||
|
||||
interface SettingsPanelProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (backgroundContext: string) => void;
|
||||
currentValue: string;
|
||||
}
|
||||
|
||||
export default function SettingsPanel({
|
||||
isOpen,
|
||||
onClose,
|
||||
onSave,
|
||||
currentValue,
|
||||
}: SettingsPanelProps) {
|
||||
const [value, setValue] = useState(currentValue);
|
||||
const titleId = useId();
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setValue(currentValue);
|
||||
}
|
||||
}, [isOpen, currentValue]);
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
onSave(value);
|
||||
onClose();
|
||||
}, [value, onSave, onClose]);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
onClose();
|
||||
}
|
||||
},
|
||||
[onClose],
|
||||
);
|
||||
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>{`
|
||||
.settings-modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
.settings-modal {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
width: 90%;
|
||||
max-width: 600px;
|
||||
max-height: 80vh;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
.settings-modal-title {
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
}
|
||||
.settings-modal-description {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 0.875rem;
|
||||
color: #64748b;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.settings-textarea {
|
||||
width: 100%;
|
||||
min-height: 150px;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
font-size: 0.875rem;
|
||||
font-family: inherit;
|
||||
resize: vertical;
|
||||
color: #334155;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.settings-textarea:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
.settings-textarea::placeholder {
|
||||
color: #94a3b8;
|
||||
}
|
||||
.settings-modal-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.75rem;
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
.settings-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.settings-btn-cancel {
|
||||
background: white;
|
||||
color: #475569;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
.settings-btn-cancel:hover {
|
||||
background: #f8fafc;
|
||||
border-color: #cbd5e1;
|
||||
}
|
||||
.settings-btn-save {
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
.settings-btn-save:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
`}</style>
|
||||
<div
|
||||
className="settings-modal-overlay"
|
||||
onClick={onClose}
|
||||
onKeyDown={handleKeyDown}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby={titleId}
|
||||
>
|
||||
{/* biome-ignore lint/a11y/noStaticElementInteractions: modal content wrapper */}
|
||||
<div
|
||||
className="settings-modal"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<h2 id={titleId} className="settings-modal-title">
|
||||
Background Context
|
||||
</h2>
|
||||
<p className="settings-modal-description">
|
||||
Provide background information that will be used throughout your
|
||||
conversation. This helps the assistant understand domain-specific
|
||||
context, terminology, or any relevant details about your questions.
|
||||
</p>
|
||||
<textarea
|
||||
className="settings-textarea"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="e.g., Focus on Python programming concepts and best practices..."
|
||||
autoFocus
|
||||
/>
|
||||
<div className="settings-modal-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="settings-btn settings-btn-cancel"
|
||||
onClick={onClose}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="settings-btn settings-btn-save"
|
||||
onClick={handleSave}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export { STORAGE_KEY };
|
||||
|
|
@ -103,8 +103,7 @@ The `ChatSessionState` maintains:
|
|||
|
||||
- `session_id` — Unique identifier for the session
|
||||
- `qa_history` — List of previous Q/A pairs (FIFO, max 50)
|
||||
- `background_context` — Optional background context for the conversation
|
||||
- `embedding_cache` — Cached embeddings for semantic ranking
|
||||
- `session_context` — Automatically maintained session context summary
|
||||
|
||||
Q/A history is used to:
|
||||
|
||||
|
|
@ -112,19 +111,6 @@ Q/A history is used to:
|
|||
2. Avoid repeating previous answers
|
||||
3. Enable semantic ranking of relevant past answers
|
||||
|
||||
### Background Context
|
||||
|
||||
You can provide background context that persists throughout the conversation:
|
||||
|
||||
```python
|
||||
session = ChatSessionState(
|
||||
background_context="Focus on Python programming concepts and best practices."
|
||||
)
|
||||
deps = ChatDeps(client=client, config=config, session_state=session)
|
||||
```
|
||||
|
||||
The context is included in the agent's system prompt and passed to the research graph when answering questions.
|
||||
|
||||
### AG-UI Integration
|
||||
|
||||
When using the chat agent with AG-UI streaming, state is emitted under a namespaced key to avoid conflicts with other agents:
|
||||
|
|
@ -230,18 +216,6 @@ async with HaikuRAG(path_to_db) as client:
|
|||
print(report.executive_summary)
|
||||
```
|
||||
|
||||
**With background context:**
|
||||
|
||||
```python
|
||||
context = ResearchContext(
|
||||
original_question="What are the safety protocols?",
|
||||
background_context="Industrial manufacturing and workplace safety domain."
|
||||
)
|
||||
state = ResearchState.from_config(context=context, config=Config)
|
||||
```
|
||||
|
||||
The `background_context` provides domain background that helps the planning and synthesis agents understand the context of the research question.
|
||||
|
||||
**With custom config:**
|
||||
|
||||
```python
|
||||
|
|
|
|||
|
|
@ -57,13 +57,6 @@ def create_chat_agent(config: AppConfig) -> Agent[ChatDeps, str]:
|
|||
retries=3,
|
||||
)
|
||||
|
||||
@agent.system_prompt
|
||||
async def add_background_context(ctx: RunContext[ChatDeps]) -> str:
|
||||
"""Add background_context to system prompt when available."""
|
||||
if ctx.deps.session_state and ctx.deps.session_state.background_context:
|
||||
return f"\nBACKGROUND CONTEXT:\n{ctx.deps.session_state.background_context}"
|
||||
return ""
|
||||
|
||||
@agent.tool
|
||||
async def search(
|
||||
ctx: RunContext[ChatDeps],
|
||||
|
|
@ -117,11 +110,6 @@ def create_chat_agent(config: AppConfig) -> Agent[ChatDeps, str]:
|
|||
qa_history=(
|
||||
ctx.deps.session_state.qa_history if ctx.deps.session_state else []
|
||||
),
|
||||
background_context=(
|
||||
ctx.deps.session_state.background_context
|
||||
if ctx.deps.session_state
|
||||
else None
|
||||
),
|
||||
session_context=get_cached_session_context(session_id)
|
||||
if session_id
|
||||
else None,
|
||||
|
|
@ -177,27 +165,19 @@ def create_chat_agent(config: AppConfig) -> Agent[ChatDeps, str]:
|
|||
|
||||
# Build and run the conversational research graph
|
||||
graph = build_conversational_graph(config=ctx.deps.config)
|
||||
|
||||
# Determine context strategy:
|
||||
# 1. Read from server cache (ignoring client state)
|
||||
# 2. Fall back to explicit background_context (first request)
|
||||
background_context: str | None = None
|
||||
session_id = ctx.deps.session_state.session_id if ctx.deps.session_state else ""
|
||||
if ctx.deps.session_state:
|
||||
cached_context = (
|
||||
get_cached_session_context(session_id) if session_id else None
|
||||
)
|
||||
|
||||
if cached_context and cached_context.summary:
|
||||
# Use cached SessionContext from previous summarization
|
||||
background_context = cached_context.render_markdown()
|
||||
elif ctx.deps.session_state.background_context:
|
||||
# Fall back to explicit background_context (first request)
|
||||
background_context = ctx.deps.session_state.background_context
|
||||
# Get session context from server cache for planning
|
||||
cached_context = get_cached_session_context(session_id) if session_id else None
|
||||
session_context = (
|
||||
cached_context.render_markdown()
|
||||
if cached_context and cached_context.summary
|
||||
else None
|
||||
)
|
||||
|
||||
context = ResearchContext(
|
||||
original_question=question,
|
||||
background_context=background_context,
|
||||
session_context=session_context,
|
||||
)
|
||||
state = ResearchState(
|
||||
context=context,
|
||||
|
|
@ -264,11 +244,6 @@ def create_chat_agent(config: AppConfig) -> Agent[ChatDeps, str]:
|
|||
qa_history=(
|
||||
ctx.deps.session_state.qa_history if ctx.deps.session_state else []
|
||||
),
|
||||
background_context=(
|
||||
ctx.deps.session_state.background_context
|
||||
if ctx.deps.session_state
|
||||
else None
|
||||
),
|
||||
session_context=get_cached_session_context(session_id)
|
||||
if session_id
|
||||
else None,
|
||||
|
|
|
|||
|
|
@ -53,8 +53,8 @@ async def summarize_session(
|
|||
Args:
|
||||
qa_history: List of Q&A pairs from the conversation.
|
||||
config: AppConfig for model selection.
|
||||
current_context: Previous context to incorporate (background_context or
|
||||
previous session_context.summary). The summarizer will build upon this.
|
||||
current_context: Previous session_context.summary to incorporate.
|
||||
The summarizer will build upon this.
|
||||
|
||||
Returns:
|
||||
Markdown summary of the conversation history.
|
||||
|
|
@ -89,14 +89,10 @@ async def update_session_context(
|
|||
config: AppConfig for model selection.
|
||||
session_state: The session state to update.
|
||||
"""
|
||||
# Determine current context to incorporate:
|
||||
# 1. If session_context already exists, use its summary
|
||||
# 2. Otherwise, use background_context (if available)
|
||||
# Use existing session_context summary if available
|
||||
current_context: str | None = None
|
||||
if session_state.session_context and session_state.session_context.summary:
|
||||
current_context = session_state.session_context.summary
|
||||
elif session_state.background_context:
|
||||
current_context = session_state.background_context
|
||||
|
||||
summary = await summarize_session(
|
||||
qa_history, config, current_context=current_context
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ class ChatSessionState(BaseModel):
|
|||
session_id: str = ""
|
||||
citations: list[Citation] = []
|
||||
qa_history: list[QAResponse] = []
|
||||
background_context: str | None = None
|
||||
session_context: SessionContext | None = None
|
||||
|
||||
|
||||
|
|
@ -97,10 +96,6 @@ class ChatDeps:
|
|||
Citation(**c) if isinstance(c, dict) else c
|
||||
for c in state_data.get("citations", [])
|
||||
]
|
||||
if "background_context" in state_data:
|
||||
self.session_state.background_context = state_data.get(
|
||||
"background_context"
|
||||
)
|
||||
if state_data.get("session_id"):
|
||||
self.session_state.session_id = state_data["session_id"]
|
||||
# NOTE: session_context intentionally NOT updated from client
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ class ResearchContext(BaseModel):
|
|||
qa_responses: list[Any] = Field(
|
||||
default_factory=list, description="Structured QA pairs used during research"
|
||||
)
|
||||
background_context: str | None = Field(
|
||||
session_context: str | None = Field(
|
||||
default=None,
|
||||
description="Optional background context provided at session start",
|
||||
description="Session context from previous Q&A summarization",
|
||||
)
|
||||
|
||||
def add_qa_response(self, qa: "SearchAnswer") -> None:
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ def format_context_for_prompt(
|
|||
"""
|
||||
context_data: dict[str, object] = {}
|
||||
|
||||
if context.background_context:
|
||||
context_data["background"] = context.background_context
|
||||
if context.session_context:
|
||||
context_data["background"] = context.session_context
|
||||
|
||||
context_data["question"] = context.original_question
|
||||
|
||||
|
|
@ -78,9 +78,9 @@ async def _plan_step_logic(
|
|||
"""Shared logic for the plan step."""
|
||||
model_config = config.research.model
|
||||
|
||||
# Use context-aware prompt if we have existing qa_responses
|
||||
# Use context-aware prompt if we have existing qa_responses or session_context
|
||||
has_prior_answers = bool(state.context.qa_responses)
|
||||
has_background = bool(state.context.background_context)
|
||||
has_session_context = bool(state.context.session_context)
|
||||
effective_plan_prompt = (
|
||||
build_prompt(PLAN_PROMPT_WITH_CONTEXT, config)
|
||||
if has_prior_answers
|
||||
|
|
@ -118,7 +118,7 @@ async def _plan_step_logic(
|
|||
f"{context_xml}\n\n"
|
||||
f"Main question: {state.context.original_question}"
|
||||
)
|
||||
elif has_background:
|
||||
elif has_session_context:
|
||||
context_xml = format_context_for_prompt(state.context)
|
||||
prompt = (
|
||||
f"Plan a focused approach for the main question.\n\n"
|
||||
|
|
|
|||
|
|
@ -376,7 +376,6 @@ class HaikuRAGApp:
|
|||
cite: bool = False,
|
||||
deep: bool = False,
|
||||
filter: str | None = None,
|
||||
background_context: str | None = None,
|
||||
):
|
||||
"""Ask a question using the RAG system.
|
||||
|
||||
|
|
@ -385,7 +384,6 @@ class HaikuRAGApp:
|
|||
cite: Include citations in the answer
|
||||
deep: Use deep QA mode (multi-step reasoning)
|
||||
filter: SQL WHERE clause to filter documents
|
||||
background_context: Optional background context for the question
|
||||
"""
|
||||
async with HaikuRAG(
|
||||
db_path=self.db_path,
|
||||
|
|
@ -396,9 +394,7 @@ class HaikuRAGApp:
|
|||
citations = []
|
||||
if deep:
|
||||
graph = build_research_graph(config=self.config)
|
||||
context = ResearchContext(
|
||||
original_question=question, background_context=background_context
|
||||
)
|
||||
context = ResearchContext(original_question=question)
|
||||
state = ResearchState.from_config(
|
||||
context=context,
|
||||
config=self.config,
|
||||
|
|
@ -427,14 +423,7 @@ class HaikuRAGApp:
|
|||
else:
|
||||
self.console.print("[yellow]No answer generated.[/yellow]")
|
||||
else:
|
||||
system_prompt = (
|
||||
f"BACKGROUND CONTEXT:\n{background_context}"
|
||||
if background_context
|
||||
else None
|
||||
)
|
||||
answer, citations = await self.client.ask(
|
||||
question, system_prompt=system_prompt, filter=filter
|
||||
)
|
||||
answer, citations = await self.client.ask(question, filter=filter)
|
||||
|
||||
self.console.print(f"[bold blue]Question:[/bold blue] {question}")
|
||||
self.console.print()
|
||||
|
|
@ -448,14 +437,12 @@ class HaikuRAGApp:
|
|||
self,
|
||||
question: str,
|
||||
filter: str | None = None,
|
||||
background_context: str | None = None,
|
||||
):
|
||||
"""Run research via the pydantic-graph pipeline.
|
||||
|
||||
Args:
|
||||
question: The research question
|
||||
filter: SQL WHERE clause to filter documents
|
||||
background_context: Optional background context for the research
|
||||
"""
|
||||
async with HaikuRAG(
|
||||
db_path=self.db_path,
|
||||
|
|
@ -468,9 +455,7 @@ class HaikuRAGApp:
|
|||
self.console.print()
|
||||
|
||||
graph = build_research_graph(config=self.config)
|
||||
context = ResearchContext(
|
||||
original_question=question, background_context=background_context
|
||||
)
|
||||
context = ResearchContext(original_question=question)
|
||||
state = ResearchState.from_config(context=context, config=self.config)
|
||||
state.search_filter = filter
|
||||
deps = ResearchDeps(client=client)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ def run_chat(
|
|||
db_path: Path | None = None,
|
||||
read_only: bool = False,
|
||||
before: datetime | None = None,
|
||||
background_context: str | None = None,
|
||||
) -> None:
|
||||
"""Run the chat TUI.
|
||||
|
||||
|
|
@ -14,7 +13,6 @@ def run_chat(
|
|||
db_path: Path to the LanceDB database. If None, uses default from config.
|
||||
read_only: Whether to open the database in read-only mode.
|
||||
before: Query database as it existed before this datetime.
|
||||
background_context: Optional background context for the conversation.
|
||||
"""
|
||||
try:
|
||||
from haiku.rag.chat.app import ChatApp
|
||||
|
|
@ -33,6 +31,5 @@ def run_chat(
|
|||
db_path,
|
||||
read_only=read_only,
|
||||
before=before,
|
||||
background_context=background_context,
|
||||
)
|
||||
app.run()
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ from haiku.rag.agents.chat.state import (
|
|||
AGUI_STATE_KEY,
|
||||
ChatDeps,
|
||||
ChatSessionState,
|
||||
SessionContext,
|
||||
)
|
||||
from haiku.rag.agents.research.models import Citation
|
||||
from haiku.rag.client import HaikuRAG
|
||||
|
|
@ -92,13 +91,11 @@ class ChatApp(App):
|
|||
db_path: Path,
|
||||
read_only: bool = False,
|
||||
before: datetime | None = None,
|
||||
background_context: str | None = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.db_path = db_path
|
||||
self.read_only = read_only
|
||||
self.before = before
|
||||
self.background_context = background_context
|
||||
self.client: HaikuRAG | None = None
|
||||
self.config = get_config()
|
||||
self.agent: Agent[ChatDeps, str] | None = None
|
||||
|
|
@ -129,15 +126,8 @@ class ChatApp(App):
|
|||
|
||||
# Create agent and session state
|
||||
self.agent = create_chat_agent(self.config)
|
||||
initial_context = (
|
||||
SessionContext(summary=self.background_context, last_updated=datetime.now())
|
||||
if self.background_context
|
||||
else None
|
||||
)
|
||||
self.session_state = ChatSessionState(
|
||||
session_id=str(uuid.uuid4()),
|
||||
background_context=self.background_context,
|
||||
session_context=initial_context,
|
||||
)
|
||||
|
||||
# Focus the input field
|
||||
|
|
@ -283,16 +273,9 @@ class ChatApp(App):
|
|||
self._last_citations.clear()
|
||||
self._selected_citation_idx = None
|
||||
self._message_history.clear()
|
||||
# Reset session state for fresh conversation (preserve background_context)
|
||||
initial_context = (
|
||||
SessionContext(summary=self.background_context, last_updated=datetime.now())
|
||||
if self.background_context
|
||||
else None
|
||||
)
|
||||
# Reset session state for fresh conversation
|
||||
self.session_state = ChatSessionState(
|
||||
session_id=str(uuid.uuid4()),
|
||||
background_context=self.background_context,
|
||||
session_context=initial_context,
|
||||
)
|
||||
|
||||
def action_focus_input(self) -> None:
|
||||
|
|
|
|||
|
|
@ -352,24 +352,7 @@ def ask(
|
|||
"-f",
|
||||
help="SQL WHERE clause to filter documents (e.g., \"uri LIKE '%arxiv%'\")",
|
||||
),
|
||||
context: str | None = typer.Option(
|
||||
None,
|
||||
"--context",
|
||||
help="Background context for the question",
|
||||
),
|
||||
context_file: Path | None = typer.Option(
|
||||
None,
|
||||
"--context-file",
|
||||
help="Path to a file containing background context",
|
||||
),
|
||||
):
|
||||
# Resolve initial context from flag or file
|
||||
background_context: str | None = None
|
||||
if context_file:
|
||||
background_context = context_file.read_text()
|
||||
elif context:
|
||||
background_context = context
|
||||
|
||||
app = create_app(db)
|
||||
asyncio.run(
|
||||
app.ask(
|
||||
|
|
@ -377,7 +360,6 @@ def ask(
|
|||
cite=cite,
|
||||
deep=deep,
|
||||
filter=filter,
|
||||
background_context=background_context,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -396,30 +378,9 @@ def research(
|
|||
"-f",
|
||||
help="SQL WHERE clause to filter documents (e.g., \"uri LIKE '%arxiv%'\")",
|
||||
),
|
||||
context: str | None = typer.Option(
|
||||
None,
|
||||
"--context",
|
||||
help="Background context for the research",
|
||||
),
|
||||
context_file: Path | None = typer.Option(
|
||||
None,
|
||||
"--context-file",
|
||||
help="Path to a file containing background context",
|
||||
),
|
||||
):
|
||||
# Resolve initial context from flag or file
|
||||
background_context: str | None = None
|
||||
if context_file:
|
||||
background_context = context_file.read_text()
|
||||
elif context:
|
||||
background_context = context
|
||||
|
||||
app = create_app(db)
|
||||
asyncio.run(
|
||||
app.research(
|
||||
question=question, filter=filter, background_context=background_context
|
||||
)
|
||||
)
|
||||
asyncio.run(app.research(question=question, filter=filter))
|
||||
|
||||
|
||||
@_cli.command("settings", help="Display current configuration settings")
|
||||
|
|
@ -632,34 +593,16 @@ def chat(
|
|||
"--db",
|
||||
help="Path to the LanceDB database file",
|
||||
),
|
||||
context: str | None = typer.Option(
|
||||
None,
|
||||
"--context",
|
||||
help="Initial context/background information for the conversation",
|
||||
),
|
||||
context_file: Path | None = typer.Option(
|
||||
None,
|
||||
"--context-file",
|
||||
help="Path to a file containing initial context",
|
||||
),
|
||||
):
|
||||
"""Launch the chat TUI for conversational RAG."""
|
||||
from haiku.rag.chat import run_chat
|
||||
|
||||
db_path = db if db else get_config().storage.data_dir / "haiku.rag.lancedb"
|
||||
|
||||
# Resolve initial context from flag or file
|
||||
background_context: str | None = None
|
||||
if context_file:
|
||||
background_context = context_file.read_text()
|
||||
elif context:
|
||||
background_context = context
|
||||
|
||||
run_chat(
|
||||
db_path,
|
||||
read_only=_read_only,
|
||||
before=_before,
|
||||
background_context=background_context,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -76,18 +76,6 @@ def test_chat_session_state():
|
|||
assert state.qa_history == []
|
||||
|
||||
|
||||
def test_chat_agent_has_dynamic_system_prompt():
|
||||
"""Test that chat agent registers a dynamic system prompt for background_context."""
|
||||
agent = create_chat_agent(Config)
|
||||
# The agent should have at least one system prompt function registered
|
||||
# (the add_background_context function)
|
||||
system_prompt_functions = getattr(agent, "_system_prompt_functions")
|
||||
assert len(system_prompt_functions) >= 1
|
||||
# Verify it's the add_background_context function
|
||||
func_names = [r.function.__name__ for r in system_prompt_functions]
|
||||
assert "add_background_context" in func_names
|
||||
|
||||
|
||||
def test_citation():
|
||||
"""Test Citation model."""
|
||||
citation = Citation(
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ class TestSummarizeSession:
|
|||
)
|
||||
]
|
||||
|
||||
# Provide current_context (e.g., background_context or previous summary)
|
||||
# Provide current_context (e.g., previous summary)
|
||||
current_context = "Focus on Python APIs. User is building a web application."
|
||||
|
||||
result = await summarize_session(
|
||||
|
|
@ -362,6 +362,7 @@ class TestSessionContextCache:
|
|||
cached = get_cached_session_context("cache-test-session")
|
||||
assert cached is not None
|
||||
assert cached.summary == "Mocked summary"
|
||||
assert session_state.session_context is not None
|
||||
assert cached.summary == session_state.session_context.summary
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
|
|
@ -34,25 +34,6 @@ def test_max_qa_history_constant():
|
|||
assert MAX_QA_HISTORY == 50
|
||||
|
||||
|
||||
def test_chat_session_state_background_context():
|
||||
"""Test ChatSessionState accepts background_context."""
|
||||
from haiku.rag.agents.chat.state import ChatSessionState
|
||||
|
||||
state = ChatSessionState(
|
||||
session_id="test-session",
|
||||
background_context="This is background knowledge about the topic.",
|
||||
)
|
||||
assert state.background_context == "This is background knowledge about the topic."
|
||||
|
||||
|
||||
def test_chat_session_state_background_context_defaults_to_none():
|
||||
"""Test ChatSessionState background_context defaults to None."""
|
||||
from haiku.rag.agents.chat.state import ChatSessionState
|
||||
|
||||
state = ChatSessionState(session_id="test-session")
|
||||
assert state.background_context is None
|
||||
|
||||
|
||||
def test_chat_deps_state_getter_returns_namespaced_state():
|
||||
"""Test ChatDeps.state getter returns state under namespaced key."""
|
||||
from unittest.mock import MagicMock
|
||||
|
|
@ -67,7 +48,6 @@ def test_chat_deps_state_getter_returns_namespaced_state():
|
|||
qa_history=[
|
||||
QAResponse(question="Q1", answer="A1", confidence=0.9),
|
||||
],
|
||||
background_context="Background info",
|
||||
)
|
||||
|
||||
deps = ChatDeps(
|
||||
|
|
@ -83,7 +63,6 @@ def test_chat_deps_state_getter_returns_namespaced_state():
|
|||
assert state[AGUI_STATE_KEY]["session_id"] == "test-123"
|
||||
assert len(state[AGUI_STATE_KEY]["qa_history"]) == 1
|
||||
assert state[AGUI_STATE_KEY]["qa_history"][0]["question"] == "Q1"
|
||||
assert state[AGUI_STATE_KEY]["background_context"] == "Background info"
|
||||
|
||||
|
||||
def test_chat_deps_state_getter_without_namespace():
|
||||
|
|
@ -152,7 +131,6 @@ def test_chat_deps_state_setter_updates_from_namespaced_state():
|
|||
{"question": "Q1", "answer": "A1", "confidence": 0.9, "citations": []}
|
||||
],
|
||||
"citations": [],
|
||||
"background_context": "New context",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -162,7 +140,6 @@ def test_chat_deps_state_setter_updates_from_namespaced_state():
|
|||
assert deps.session_state.session_id == "updated-123"
|
||||
assert len(deps.session_state.qa_history) == 1
|
||||
assert deps.session_state.qa_history[0].question == "Q1"
|
||||
assert deps.session_state.background_context == "New context"
|
||||
|
||||
|
||||
def test_chat_deps_state_setter_handles_none():
|
||||
|
|
@ -242,7 +219,6 @@ def test_chat_deps_state_setter_with_citation_dicts():
|
|||
"content": "Test content",
|
||||
}
|
||||
],
|
||||
"background_context": None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,65 +46,6 @@ async def test_graph_end_to_end(allow_model_requests, temp_db_path, qa_corpus):
|
|||
client.close()
|
||||
|
||||
|
||||
def test_research_context_background_context():
|
||||
"""Test ResearchContext accepts background_context."""
|
||||
context = ResearchContext(
|
||||
original_question="What is X?",
|
||||
background_context="Background: X is a concept in domain Y.",
|
||||
)
|
||||
assert context.background_context == "Background: X is a concept in domain Y."
|
||||
|
||||
|
||||
def test_research_context_background_context_defaults_to_none():
|
||||
"""Test ResearchContext background_context defaults to None."""
|
||||
context = ResearchContext(original_question="What is X?")
|
||||
assert context.background_context is None
|
||||
|
||||
|
||||
def test_format_context_for_prompt_includes_background():
|
||||
"""Test format_context_for_prompt includes background in output."""
|
||||
from haiku.rag.agents.research.graph import format_context_for_prompt
|
||||
|
||||
context = ResearchContext(
|
||||
original_question="What is X?",
|
||||
background_context="X is a concept in domain Y.",
|
||||
)
|
||||
result = format_context_for_prompt(context)
|
||||
assert "X is a concept in domain Y." in result
|
||||
assert "<background>" in result
|
||||
|
||||
|
||||
def test_format_context_for_prompt_excludes_background_when_none():
|
||||
"""Test format_context_for_prompt excludes background when None."""
|
||||
from haiku.rag.agents.research.graph import format_context_for_prompt
|
||||
|
||||
context = ResearchContext(original_question="What is X?")
|
||||
result = format_context_for_prompt(context)
|
||||
assert "<background>" not in result
|
||||
|
||||
|
||||
def test_format_context_for_prompt_without_pending_includes_background():
|
||||
"""Test format_context_for_prompt with include_pending_questions=False includes background."""
|
||||
from haiku.rag.agents.research.graph import format_context_for_prompt
|
||||
|
||||
context = ResearchContext(
|
||||
original_question="What is X?",
|
||||
background_context="X is a concept in domain Y.",
|
||||
)
|
||||
result = format_context_for_prompt(context, include_pending_questions=False)
|
||||
assert "X is a concept in domain Y." in result
|
||||
assert "<background>" in result
|
||||
|
||||
|
||||
def test_format_context_for_prompt_without_pending_excludes_background_when_none():
|
||||
"""Test format_context_for_prompt with include_pending_questions=False excludes background when None."""
|
||||
from haiku.rag.agents.research.graph import format_context_for_prompt
|
||||
|
||||
context = ResearchContext(original_question="What is X?")
|
||||
result = format_context_for_prompt(context, include_pending_questions=False)
|
||||
assert "<background>" not in result
|
||||
|
||||
|
||||
def test_research_plan_allows_empty_sub_questions():
|
||||
"""Test ResearchPlan accepts empty sub_questions when context is sufficient."""
|
||||
from haiku.rag.agents.research.models import ResearchPlan
|
||||
|
|
|
|||
|
|
@ -304,9 +304,7 @@ async def test_ask_without_cite(app: HaikuRAGApp, monkeypatch):
|
|||
with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
|
||||
await app.ask("test question")
|
||||
|
||||
mock_client.ask.assert_called_once_with(
|
||||
"test question", system_prompt=None, filter=None
|
||||
)
|
||||
mock_client.ask.assert_called_once_with("test question", filter=None)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -335,9 +333,7 @@ async def test_ask_with_cite(app: HaikuRAGApp, monkeypatch):
|
|||
with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
|
||||
await app.ask("test question", cite=True)
|
||||
|
||||
mock_client.ask.assert_called_once_with(
|
||||
"test question", system_prompt=None, filter=None
|
||||
)
|
||||
mock_client.ask.assert_called_once_with("test question", filter=None)
|
||||
# Verify print was called (once for answer, once for citations)
|
||||
assert mock_print.call_count >= 1
|
||||
|
||||
|
|
|
|||
|
|
@ -285,7 +285,6 @@ def test_ask():
|
|||
cite=False,
|
||||
deep=False,
|
||||
filter=None,
|
||||
background_context=None,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -303,7 +302,6 @@ def test_ask_with_cite():
|
|||
cite=True,
|
||||
deep=False,
|
||||
filter=None,
|
||||
background_context=None,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -321,7 +319,6 @@ def test_ask_with_deep():
|
|||
cite=False,
|
||||
deep=True,
|
||||
filter=None,
|
||||
background_context=None,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -339,7 +336,6 @@ def test_ask_with_deep_and_cite():
|
|||
cite=True,
|
||||
deep=True,
|
||||
filter=None,
|
||||
background_context=None,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue