Update ag-ui example

This commit is contained in:
Yiorgis Gozadinos 2025-12-15 13:43:13 +02:00
parent 6e621ea34d
commit 557a296341
No known key found for this signature in database
7 changed files with 28 additions and 372 deletions

View file

@ -15,6 +15,17 @@
### Changed
- **Chunker Sets Order**: Chunkers now set `chunk.order` directly
- **Unified Research Graph**: Simplified and unified research and deep QA into a single configurable graph
- Removed `analyze_insights` node - graph now flows directly from `collect_answers` to `decide`
- Simplified `EvaluationResult` to: `is_sufficient`, `confidence_score`, `reasoning`, `new_questions`
- Simplified `ResearchContext` - removed insight/gap tracking methods
- `ask --deep` now uses research graph with `max_iterations=2`, `confidence_threshold=0.0`
- `ask --deep` output now shows executive summary, key findings, and sources
- Added `include_plan` parameter to `build_research_graph()` for plan-less execution
- Added `max_iterations` and `confidence_threshold` overrides to `ResearchState.from_config()`
- **Improved Synthesis Prompt**: Updated synthesis agent prompt to produce direct answers
- Executive summary now directly answers the question instead of describing the report
- Added explicit examples of good vs bad output style
- **Evaluations Vacuum Strategy**: `populate_db` now uses periodic vacuum to prevent disk exhaustion with large datasets
- Disables auto_vacuum during population, vacuums every N documents with retention=0
- New `--vacuum-interval` CLI option (default: 100) to control vacuum frequency
@ -23,6 +34,16 @@
- Added dedicated Methodology section explaining MRR, MAP, and QA Accuracy metrics
- Organized results by dataset with retrieval and QA subsections
### Removed
- **Deep QA Graph**: Removed `haiku.rag.graph.deep_qa` module entirely
- Use `build_research_graph()` with appropriate parameters instead
- `ask --deep` CLI command now uses research graph internally
- **Insight/Gap Tracking**: Removed over-engineered insight and gap tracking from research graph
- Removed `InsightRecord`, `GapRecord`, `InsightAnalysis`, `InsightStatus`, `GapSeverity` models
- Removed `format_analysis_for_prompt()` helper
- Removed `INSIGHT_AGENT_PROMPT` from prompts
## [0.20.2] - 2025-12-12
### Fixed

View file

@ -6,10 +6,9 @@ Research assistant powered by [haiku.rag](https://ggozad.github.io/haiku.rag/),
## Features
- **Multi-iteration research graph**: Automated question decomposition, search, insight extraction, and gap analysis
- **Multi-iteration research graph**: Automated question decomposition and search
- **Intelligent evaluation**: Confidence-based decision making with automatic iteration until sufficient information is gathered
- **Live state synchronization**: Real-time delta updates of research progress via AG-UI protocol
- **Insight & gap tracking**: Structured insights with provenance and automatic gap identification
- **Rich reporting**: Generates comprehensive research reports with findings, conclusions, and sources
## Quick Start
@ -81,9 +80,8 @@ Research assistant powered by [haiku.rag](https://ggozad.github.io/haiku.rag/),
- Gathers initial context about the topic
3. **Research iterations**: The graph autonomously:
- Searches the knowledge base for each sub-question in parallel
- Extracts structured insights with source provenance
- Identifies information gaps and assesses confidence
- Generates new follow-up questions for gaps
- Assesses confidence in gathered information
- Generates new follow-up questions if needed
- Iterates until confidence threshold is met or max iterations reached
4. **Synthesis**: Generates a comprehensive research report with:
- Executive summary
@ -126,7 +124,7 @@ This example demonstrates the **agent+graph** architecture pattern:
- CopilotKit for AG-UI protocol integration
- Split-pane UI: chat on left, live research state on right
- Real-time state synchronization via Server-Sent Events (SSE)
- `StateDisplay` component with collapsible sections for questions, insights, and gaps
- `StateDisplay` component with collapsible sections for questions and report
## Configuration

View file

@ -15,13 +15,11 @@ The server starts on `http://localhost:8000` and uses [haiku.rag configuration](
The backend uses `create_agui_server()` from `haiku.rag.graph.agui.server` which provides:
- **Research graph execution**: Multi-iteration research workflow with insight/gap tracking
- **Research graph execution**: Multi-iteration research workflow
- **AG-UI protocol**: Server-Sent Events (SSE) streaming for real-time state updates
- **Delta state updates**: Efficient incremental state synchronization using JSON Patch operations
- **Both research and deep_qa endpoints**: `/agent/research` and `/agent/deep_qa`
## Endpoints
- `GET /health` - Health check with configuration info
- `POST /agent/research/stream` - Research graph streaming endpoint (AG-UI protocol)
- `POST /agent/deep_qa/stream` - Deep QA graph streaming endpoint (AG-UI protocol)

View file

@ -56,7 +56,7 @@ How to decide:
- "Tell me about Y" Use run_research tool
When you use run_research, the graph will decompose questions, search the knowledge base,
extract insights, and generate a comprehensive report.
and generate a comprehensive report.
Be friendly and conversational in all responses.""",
)
@ -100,7 +100,6 @@ Main Findings:
Conclusions:
{chr(10).join(f"- {conclusion}" for conclusion in result.conclusions[:2])}
Total insights gathered: {len(state.context.insights)}
Confidence: {f"{state.last_eval.confidence_score:.0%}" if state.last_eval else "N/A"}
Iterations completed: {state.iterations}

View file

@ -6,26 +6,6 @@ import "@copilotkit/react-ui/styles.css";
import DocumentSelector from "./DocumentSelector";
import StateDisplay from "./StateDisplay";
interface InsightRecord {
id: string;
summary: string;
status: string;
notes?: string;
supporting_sources: string[];
originating_questions: string[];
}
interface GapRecord {
id: string;
description: string;
severity: string;
blocking: boolean;
resolved: boolean;
notes?: string;
supporting_sources: string[];
resolved_by: string[];
}
interface Citation {
document_id: string;
chunk_id: string;
@ -48,14 +28,10 @@ interface ResearchContext {
original_question: string;
sub_questions: string[];
qa_responses: SearchAnswer[];
insights: InsightRecord[];
gaps: GapRecord[];
}
interface EvaluationResult {
key_insights: string[];
new_questions: string[];
gaps: string[];
confidence_score: number;
is_sufficient: boolean;
reasoning: string;
@ -78,10 +54,6 @@ interface ResearchState {
confidence_threshold: number;
max_concurrency: number;
last_eval: EvaluationResult | null;
last_analysis: {
insights_extracted: InsightRecord[];
gaps_identified: GapRecord[];
} | null;
result?: ResearchReport;
current_activity?: string;
current_activity_message?: string;
@ -96,15 +68,12 @@ function AgentContent() {
original_question: "",
sub_questions: [],
qa_responses: [],
insights: [],
gaps: [],
},
iterations: 0,
max_iterations: 3,
confidence_threshold: 0.8,
max_concurrency: 1,
last_eval: null,
last_analysis: null,
documentFilter: [],
},
});

View file

@ -11,26 +11,6 @@ interface VisualGroundingState {
error: string | null;
}
interface InsightRecord {
id: string;
summary: string;
status: string;
notes?: string;
supporting_sources: string[];
originating_questions: string[];
}
interface GapRecord {
id: string;
description: string;
severity: string;
blocking: boolean;
resolved: boolean;
notes?: string;
supporting_sources: string[];
resolved_by: string[];
}
interface Citation {
document_id: string;
chunk_id: string;
@ -53,14 +33,10 @@ interface ResearchContext {
original_question: string;
sub_questions: string[];
qa_responses: SearchAnswer[];
insights: InsightRecord[];
gaps: GapRecord[];
}
interface EvaluationResult {
key_insights: string[];
new_questions: string[];
gaps: string[];
confidence_score: number;
is_sufficient: boolean;
reasoning: string;
@ -83,10 +59,6 @@ interface ResearchState {
confidence_threshold: number;
max_concurrency: number;
last_eval: EvaluationResult | null;
last_analysis: {
insights_extracted: InsightRecord[];
gaps_identified: GapRecord[];
} | null;
result?: ResearchReport;
current_activity?: string;
current_activity_message?: string;
@ -101,8 +73,6 @@ export default function StateDisplay({ state }: StateDisplayProps) {
Record<string, boolean>
>({
questions: true,
insights: true,
gaps: true,
report: true,
});
@ -694,305 +664,6 @@ export default function StateDisplay({ state }: StateDisplayProps) {
</div>
)}
{/* Insights */}
{state.context.insights.length > 0 && (
<div
style={{
background: "white",
borderRadius: "8px",
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
overflow: "hidden",
}}
>
<button
type="button"
onClick={() => toggleSection("insights")}
style={{
width: "100%",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "0.75rem",
background: "#edf2f7",
border: "1px solid #e2e8f0",
borderRadius: "4px",
cursor: "pointer",
fontSize: "1rem",
fontWeight: "600",
color: "#2d3748",
}}
>
<span>Key Insights ({state.context.insights.length})</span>
<span>{expandedSections.insights ? "▼" : "▶"}</span>
</button>
{expandedSections.insights && (
<div
style={{
padding: "1rem",
background: "#f7fafc",
border: "1px solid #e2e8f0",
borderTop: "none",
borderRadius: "0 0 4px 4px",
}}
>
{state.context.insights.map((insight) => (
<div
key={insight.id}
style={{
padding: "0.75rem",
background: "white",
borderRadius: "4px",
marginBottom: "0.5rem",
border: "1px solid #e2e8f0",
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: "0.5rem",
}}
>
<span
style={{
fontSize: "0.75rem",
padding: "0.125rem 0.5rem",
background:
insight.status === "validated"
? "#c6f6d5"
: insight.status === "active"
? "#bee3f8"
: "#fed7d7",
color:
insight.status === "validated"
? "#22543d"
: insight.status === "active"
? "#2c5282"
: "#742a2a",
borderRadius: "4px",
}}
>
{insight.status}
</span>
<span
style={{
fontSize: "0.75rem",
color: "#718096",
}}
>
{insight.supporting_sources.length} sources
</span>
</div>
<div
style={{
fontSize: "0.875rem",
color: "#2d3748",
lineHeight: "1.5",
marginBottom: "0.5rem",
}}
>
<Markdown content={insight.summary} />
</div>
{insight.notes && (
<div
style={{
fontSize: "0.75rem",
color: "#718096",
marginTop: "0.5rem",
fontStyle: "italic",
}}
>
<Markdown content={insight.notes} />
</div>
)}
{insight.supporting_sources.length > 0 && (
<div
style={{
fontSize: "0.75rem",
color: "#718096",
marginTop: "0.5rem",
}}
>
<span style={{ fontWeight: "600" }}>Sources: </span>
{insight.supporting_sources.map((source, srcIdx) => (
<span key={`${insight.id}-src-${srcIdx}`}>
{srcIdx > 0 && ", "}
{source}
</span>
))}
</div>
)}
</div>
))}
</div>
)}
</div>
)}
{/* Knowledge Gaps */}
{state.context.gaps.length > 0 && (
<div
style={{
background: "white",
borderRadius: "8px",
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
overflow: "hidden",
}}
>
<button
type="button"
onClick={() => toggleSection("gaps")}
style={{
width: "100%",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "0.75rem",
background: "#edf2f7",
border: "1px solid #e2e8f0",
borderRadius: "4px",
cursor: "pointer",
fontSize: "1rem",
fontWeight: "600",
color: "#2d3748",
}}
>
<span>Knowledge Gaps ({state.context.gaps.length})</span>
<span>{expandedSections.gaps ? "▼" : "▶"}</span>
</button>
{expandedSections.gaps && (
<div
style={{
padding: "1rem",
background: "#f7fafc",
border: "1px solid #e2e8f0",
borderTop: "none",
borderRadius: "0 0 4px 4px",
}}
>
{state.context.gaps.map((gap) => (
<div
key={gap.id}
style={{
padding: "0.75rem",
background: "white",
borderRadius: "4px",
marginBottom: "0.5rem",
border: "1px solid #e2e8f0",
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: "0.5rem",
gap: "0.5rem",
flexWrap: "wrap",
}}
>
<div style={{ display: "flex", gap: "0.5rem" }}>
<span
style={{
fontSize: "0.75rem",
padding: "0.125rem 0.5rem",
background:
gap.severity === "critical"
? "#fed7d7"
: gap.severity === "high"
? "#feebc8"
: gap.severity === "medium"
? "#fef5e7"
: "#e6fffa",
color:
gap.severity === "critical"
? "#742a2a"
: gap.severity === "high"
? "#7c2d12"
: gap.severity === "medium"
? "#744210"
: "#234e52",
borderRadius: "4px",
fontWeight: "600",
}}
>
{gap.severity}
</span>
{gap.blocking && (
<span
style={{
fontSize: "0.75rem",
padding: "0.125rem 0.5rem",
background: "#fed7d7",
color: "#742a2a",
borderRadius: "4px",
fontWeight: "600",
}}
>
Blocking
</span>
)}
{gap.resolved && (
<span
style={{
fontSize: "0.75rem",
padding: "0.125rem 0.5rem",
background: "#c6f6d5",
color: "#22543d",
borderRadius: "4px",
fontWeight: "600",
}}
>
Resolved
</span>
)}
</div>
</div>
<div
style={{
fontSize: "0.875rem",
color: "#2d3748",
lineHeight: "1.5",
marginBottom: "0.5rem",
}}
>
<Markdown content={gap.description} />
</div>
{gap.notes && (
<div
style={{
fontSize: "0.75rem",
color: "#718096",
marginTop: "0.5rem",
fontStyle: "italic",
}}
>
<Markdown content={gap.notes} />
</div>
)}
{gap.resolved && gap.resolved_by.length > 0 && (
<div
style={{
fontSize: "0.75rem",
color: "#718096",
marginTop: "0.5rem",
}}
>
<span style={{ fontWeight: "600" }}>Resolved by: </span>
{gap.resolved_by.map((source, srcIdx) => (
<span key={`${gap.id}-resolved-${srcIdx}`}>
{srcIdx > 0 && ", "}
{source}
</span>
))}
</div>
)}
</div>
))}
</div>
)}
</div>
)}
{/* Final Report */}
{state.result && (
<div

View file

@ -64,7 +64,7 @@ def create_plan_node[AgentDepsT: GraphAgentDeps](
Args:
model_config: ModelConfig with provider, model, and settings
deps_type: Type of dependencies for the agent (e.g., ResearchDependencies, DeepQADependencies)
deps_type: Type of dependencies for the agent (e.g., ResearchDependencies)
activity_message: Message to show during planning activity
output_retries: Number of output retries for the agent (optional)
config: AppConfig object (defaults to global Config)