Update ag-ui example
This commit is contained in:
parent
6e621ea34d
commit
557a296341
7 changed files with 28 additions and 372 deletions
21
CHANGELOG.md
21
CHANGELOG.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
||||
|
|
|
|||
|
|
@ -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: [],
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue