Refactor ag-ui-research. Drop human-in-the-loop, use MemoryObjectSendStream to merge the graph and agent streams together
This commit is contained in:
parent
4a623934b2
commit
2c43f034de
16 changed files with 12671 additions and 14635 deletions
|
|
@ -21,6 +21,12 @@
|
|||
- **CLI AG-UI Flag**: New `--agui` flag for `serve` command to start AG-UI server
|
||||
- **Graph Module**: New unified `haiku.rag.graph` module containing all graph-related functionality
|
||||
- **Common Graph Nodes**: New factory functions (`create_plan_node`, `create_search_node`) in `haiku.rag.graph.common.nodes` for reusable graph components
|
||||
- **AG-UI Research Example**: New full-stack example (`examples/ag-ui-research`) demonstrating agent+graph architecture with CopilotKit frontend
|
||||
- Pydantic AI agent with research tool that invokes the research graph
|
||||
- Custom AG-UI streaming endpoint with anyio memory streams
|
||||
- React/Next.js frontend with split-pane UI showing live research state
|
||||
- Real-time progress tracking of questions, answers, insights, and gaps
|
||||
- Docker Compose setup for easy local development
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,11 @@
|
|||
# Must be an absolute path to an existing database created with haiku-rag
|
||||
DB_PATH=/absolute/path/to/your/haiku.rag.lancedb
|
||||
|
||||
# Ollama API base URL (if using Ollama for local models)
|
||||
# If running Ollama on your host machine, use your machine's IP address
|
||||
# that the Docker container can reach (not localhost)
|
||||
OLLAMA_BASE_URL=http://host.docker.internal:11434
|
||||
|
||||
# API keys (set as needed for your QA provider)
|
||||
# OPENAI_API_KEY=your-key-here
|
||||
# ANTHROPIC_API_KEY=your-key-here
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
# Interactive Research Assistant
|
||||
|
||||
Research assistant powered by [haiku.rag](https://ggozad.github.io/haiku.rag/), [Pydantic AI](https://ai.pydantic.dev/), and [AG-UI](https://docs.ag-ui.com/). Ask complex questions and watch the research process unfold in real-time.
|
||||
Research assistant powered by [haiku.rag](https://ggozad.github.io/haiku.rag/), [Pydantic Graph](https://ai.pydantic.dev/graph/), and [AG-UI](https://docs.ag-ui.com/). Ask complex questions and watch the research process unfold in real-time.
|
||||
|
||||
[Watch demo video](https://vimeo.com/1128874386)
|
||||
|
||||
## Features
|
||||
|
||||
- **Multi-step research workflow**: Question decomposition, search, analysis, and synthesis
|
||||
- **Human-in-the-loop**: Approve or revise research plans before execution
|
||||
- **Live state synchronization**: Real-time updates of research progress between backend and frontend
|
||||
- **Context expansion**: Automatically expands top search results for better context
|
||||
- **Rich reporting**: Generates structured reports with findings, conclusions, and citations
|
||||
- **Multi-iteration research graph**: Automated question decomposition, search, insight extraction, and gap analysis
|
||||
- **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
|
||||
|
||||
|
|
@ -57,32 +57,67 @@ Research assistant powered by [haiku.rag](https://ggozad.github.io/haiku.rag/),
|
|||
## How It Works
|
||||
|
||||
1. **Ask a question**: Type your research question in the chat
|
||||
2. **Review the plan**: The agent decomposes your question into 3 sub-questions
|
||||
3. **Approve or revise**: Choose to approve the plan or request changes
|
||||
4. **Watch it work**: The agent automatically:
|
||||
- Searches the knowledge base for each sub-question
|
||||
- Extracts key insights from search results
|
||||
- Evaluates overall confidence in findings
|
||||
5. **Get your report**: Receive a structured research report with citations
|
||||
2. **Plan phase**: The research graph automatically:
|
||||
- Decomposes your question into targeted sub-questions
|
||||
- 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
|
||||
- Iterates until confidence threshold is met or max iterations reached
|
||||
4. **Synthesis**: Generates a comprehensive research report with:
|
||||
- Executive summary
|
||||
- Main findings with supporting evidence
|
||||
- Conclusions and recommendations
|
||||
- Source citations
|
||||
|
||||
## Architecture
|
||||
|
||||
- **Backend** (Python): Pydantic AI agent with haiku.rag integration
|
||||
- Uses published `ghcr.io/ggozad/haiku.rag:latest` Docker image as base
|
||||
- `agent.py`: Research agent with tool definitions
|
||||
- `main.py`: Starlette app serving AG-UI protocol
|
||||
### Agent + Graph Pattern
|
||||
|
||||
- **Frontend** (Next.js): CopilotKit/AG-UI interface
|
||||
- Real-time state synchronization with backend
|
||||
- Interactive approval workflow
|
||||
- Collapsible research plan and insights display
|
||||
This example demonstrates the **agent+graph** architecture pattern:
|
||||
|
||||
1. **Conversational Agent** (`agent.py`):
|
||||
- Pydantic AI agent handles user conversations
|
||||
- Decides when to invoke the research tool based on user intent
|
||||
- Responds directly to greetings/casual chat without tools
|
||||
- Formats research results for the user
|
||||
|
||||
2. **Research Graph** (haiku.rag):
|
||||
- Multi-step research workflow invoked by the agent's tool
|
||||
- Autonomous execution with plan → search → analyze → decide → synthesize flow
|
||||
- Emits AG-UI events for real-time progress tracking
|
||||
|
||||
3. **Shared Event Stream**:
|
||||
- `AGUIEmitter` is shared between agent and graph
|
||||
- Events from both flow through a single stream to the frontend
|
||||
- Custom streaming endpoint (`main.py`) uses anyio memory streams for proper async handling
|
||||
|
||||
### Components
|
||||
|
||||
- **Backend** (Python):
|
||||
- Uses published `ghcr.io/ggozad/haiku.rag:latest` Docker image as base
|
||||
- `agent.py`: Pydantic AI agent with `run_research` tool
|
||||
- `main.py`: Custom AG-UI streaming endpoint with anyio memory object streams
|
||||
- Real-time event forwarding from emitter to SSE stream
|
||||
- Filters out `ACTIVITY_SNAPSHOT` events (not yet supported by CopilotKit)
|
||||
|
||||
- **Frontend** (Next.js/React):
|
||||
- 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
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration is done through `haiku.rag.yaml` (see `haiku.rag.yaml.example`):
|
||||
|
||||
- `qa.provider`: LLM provider (default: `ollama`)
|
||||
- `qa.model`: Model name (default: `gpt-oss:latest`)
|
||||
- `research.provider`: LLM provider (default: `ollama`)
|
||||
- `research.model`: Model name (default: `gpt-oss:latest`)
|
||||
- `research.max_iterations`: Maximum research iterations (default: `3`)
|
||||
- `research.confidence_threshold`: Confidence threshold for completion (default: `0.8`)
|
||||
- `research.max_concurrency`: Parallel sub-question processing (default: `1`)
|
||||
- `providers.ollama.base_url`: Ollama endpoint (default: `http://host.docker.internal:11434`)
|
||||
|
||||
Environment variables (see `.env.example`):
|
||||
|
|
|
|||
|
|
@ -3,15 +3,13 @@ FROM ghcr.io/ggozad/haiku.rag:latest
|
|||
WORKDIR /app
|
||||
|
||||
# Copy backend application files
|
||||
COPY agent.py main.py ./
|
||||
COPY pyproject.toml ./
|
||||
COPY main.py agent.py ./
|
||||
|
||||
# Install backend dependencies
|
||||
# Note: haiku-rag is already installed in the base image
|
||||
# Install additional dependencies for the example
|
||||
# Note: haiku-rag-slim is already installed in the base image
|
||||
RUN pip install --no-cache-dir \
|
||||
starlette>=0.45.2 \
|
||||
uvicorn[standard]>=0.34.2 \
|
||||
pydantic-ai-slim[ag-ui,openai]>=1.1.0 \
|
||||
python-dotenv>=1.0.1
|
||||
|
||||
EXPOSE 8000
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Haiku.rag Research Assistant Backend
|
||||
|
||||
FastAPI backend for the haiku.rag interactive research assistant, using Pydantic AI with AG-UI protocol support.
|
||||
Starlette backend for the haiku.rag interactive research assistant, using the research graph with AG-UI protocol support.
|
||||
|
||||
## Setup
|
||||
|
||||
|
|
@ -11,7 +11,17 @@ uv run python main.py
|
|||
|
||||
The server starts on `http://localhost:8000` and uses [haiku.rag configuration](https://ggozad.github.io/haiku.rag/configuration/).
|
||||
|
||||
## Architecture
|
||||
|
||||
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
|
||||
- **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
|
||||
- `POST /agent` - AG-UI protocol endpoint
|
||||
- `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)
|
||||
|
|
|
|||
|
|
@ -1,432 +1,111 @@
|
|||
import json
|
||||
from dataclasses import dataclass
|
||||
"""Research assistant agent with graph integration."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ag_ui.core import EventType, StateSnapshotEvent
|
||||
from pydantic import BaseModel
|
||||
from pydantic_ai import Agent, RunContext
|
||||
from pydantic_ai.ag_ui import StateDeps
|
||||
|
||||
from haiku.rag.client import HaikuRAG
|
||||
from haiku.rag.config import Config
|
||||
from haiku.rag.config import load_yaml_config
|
||||
from haiku.rag.config.models import AppConfig
|
||||
from haiku.rag.graph.common import get_model
|
||||
from haiku.rag.graph.research.dependencies import ResearchContext
|
||||
from haiku.rag.graph.research.graph import build_research_graph
|
||||
from haiku.rag.graph.research.state import ResearchDeps, ResearchState
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from haiku.rag.graph.agui.emitter import AGUIEmitter
|
||||
from haiku.rag.graph.research.models import ResearchReport
|
||||
|
||||
class ResearchState(BaseModel):
|
||||
"""Shared state between research agent and frontend."""
|
||||
|
||||
question: str = ""
|
||||
phase: str = "idle"
|
||||
status: str = ""
|
||||
plan: list[dict] = []
|
||||
current_question_index: int = 0
|
||||
insights: list[dict] = []
|
||||
document_registry: dict[str, dict] = {}
|
||||
current_document: dict | None = None
|
||||
confidence: float = 0.0
|
||||
final_report: dict | None = None
|
||||
# Load config
|
||||
config_path = Path("/app/haiku.rag.yaml")
|
||||
Config = (
|
||||
AppConfig.model_validate(load_yaml_config(config_path))
|
||||
if config_path.exists()
|
||||
else AppConfig()
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ResearchDeps(StateDeps[ResearchState]):
|
||||
"""Dependencies for the research agent with HaikuRAG client."""
|
||||
class AgentDeps:
|
||||
"""Dependencies for research agent."""
|
||||
|
||||
client: HaikuRAG
|
||||
agui_emitter: "AGUIEmitter[ResearchState, ResearchReport] | None" = None
|
||||
|
||||
|
||||
def _as_state_snapshot(ctx: RunContext[ResearchDeps]) -> StateSnapshotEvent:
|
||||
return StateSnapshotEvent(type=EventType.STATE_SNAPSHOT, snapshot=ctx.deps.state)
|
||||
model = get_model(Config.research.provider, Config.research.model)
|
||||
|
||||
|
||||
def create_agent(
|
||||
qa_provider: str = Config.qa.provider, qa_model: str = Config.qa.model
|
||||
) -> Agent[ResearchDeps, str]:
|
||||
"""Create and configure the research agent.
|
||||
|
||||
Args:
|
||||
qa_provider: QA provider for the agent (default: from Config.qa.provider)
|
||||
qa_model: Model name to use (default: from Config.qa.model)
|
||||
"""
|
||||
print(f"[AGENT SETUP] Creating agent with provider={qa_provider}, model={qa_model}")
|
||||
agent = Agent(
|
||||
model=get_model(qa_provider, qa_model),
|
||||
deps_type=ResearchDeps,
|
||||
instructions="""You are a research co-pilot powered by haiku.rag.
|
||||
|
||||
Your workflow MUST follow these exact steps in order:
|
||||
1. Call propose_research_plan with the user's question
|
||||
2. After propose_research_plan completes, IMMEDIATELY call approve_research_plan (with no arguments)
|
||||
3. WAIT for approve_research_plan to return:
|
||||
- If it returns "APPROVED", proceed to step 4
|
||||
- If it returns "REVISE", ask the user "How would you like me to revise the research plan?" and wait for their response
|
||||
- Once you receive their revision feedback, revise the plan and go back to step 1
|
||||
4. Once approved, process questions ONE AT A TIME:
|
||||
- Call search_question(question_id=0) and WAIT for it to complete
|
||||
- Then call extract_insights_from_results(question_id=0) and WAIT for it to complete
|
||||
- Then call search_question(question_id=1) and WAIT for it to complete
|
||||
- Then call extract_insights_from_results(question_id=1) and WAIT for it to complete
|
||||
- Then call search_question(question_id=2) and WAIT for it to complete
|
||||
- Then call extract_insights_from_results(question_id=2) and WAIT for it to complete
|
||||
5. After all questions are processed, call evaluate_research_confidence
|
||||
6. Ask user if they want to finalize or continue researching
|
||||
7. When user approves, call synthesize_final_report
|
||||
model,
|
||||
deps_type=AgentDeps,
|
||||
system_prompt="""You are an advanced research assistant powered by haiku.rag.
|
||||
|
||||
CRITICAL RULES:
|
||||
- MANDATORY: Call approve_research_plan immediately after propose_research_plan - NO EXCEPTIONS
|
||||
- If approve_research_plan returns "REVISE", ask the user for revision feedback naturally in chat
|
||||
- Call ONE tool at a time - wait for each tool to return before calling the next
|
||||
- NEVER call extract_insights_from_results until search_question has completed and returned results
|
||||
- DO NOT explain what you're about to do - just call the tool
|
||||
- The state updates will show the user what's happening - you don't need to narrate
|
||||
- Process all 3 questions automatically without asking for approval between them
|
||||
1. For greetings (hi, hello, hey, etc) or casual chat: respond directly WITHOUT using any tools
|
||||
2. For questions about yourself or the system: respond directly WITHOUT using any tools
|
||||
3. For substantive questions requiring information: ALWAYS use the run_research tool
|
||||
4. NEVER answer substantive questions from your own knowledge - always use the tool
|
||||
|
||||
Document Viewing:
|
||||
- When user asks to "show document X", call get_full_document with the document_uri
|
||||
How to decide:
|
||||
- "Hi" / "Hello" / "How are you?" → Respond directly, NO tools
|
||||
- "What can you do?" → Respond directly, NO tools
|
||||
- "How does X work in the codebase?" → Use run_research tool
|
||||
- "Tell me about Y" → Use run_research tool
|
||||
|
||||
Remember: Call tools ONE AT A TIME in sequence. Each tool must complete before calling the next.
|
||||
""",
|
||||
When you use run_research, the graph will decompose questions, search the knowledge base,
|
||||
extract insights, and generate a comprehensive report.
|
||||
|
||||
Be friendly and conversational in all responses.""",
|
||||
)
|
||||
|
||||
|
||||
@agent.tool
|
||||
async def propose_research_plan(
|
||||
ctx: RunContext[ResearchDeps], question: str
|
||||
) -> StateSnapshotEvent:
|
||||
"""Propose a research plan by decomposing the question into sub-questions."""
|
||||
ctx.deps.state.question = question
|
||||
ctx.deps.state.phase = "planning"
|
||||
ctx.deps.state.status = "Decomposing question into sub-questions..."
|
||||
async def run_research(ctx: RunContext[AgentDeps], question: str) -> str:
|
||||
"""Execute research graph on a substantive question.
|
||||
|
||||
decompose_prompt = f"""Break down this research question into exactly 3 specific sub-questions that would help answer it comprehensively.
|
||||
Use for questions requiring knowledge base search.
|
||||
DO NOT use for greetings or casual conversation.
|
||||
"""
|
||||
if ctx.deps.agui_emitter:
|
||||
ctx.deps.agui_emitter.log(f"🔍 Starting research on: {question}")
|
||||
|
||||
Research Question: {question}
|
||||
graph = build_research_graph(Config)
|
||||
context = ResearchContext(original_question=question)
|
||||
state = ResearchState.from_config(context=context, config=Config)
|
||||
|
||||
Return ONLY a JSON array of sub-questions, like: ["Question 1?", "Question 2?", "Question 3?"]"""
|
||||
|
||||
response = await ctx.deps.client.ask(decompose_prompt)
|
||||
graph_deps = ResearchDeps(
|
||||
client=ctx.deps.client,
|
||||
agui_emitter=ctx.deps.agui_emitter,
|
||||
)
|
||||
|
||||
try:
|
||||
sub_questions = json.loads(response)
|
||||
except json.JSONDecodeError:
|
||||
sub_questions = [
|
||||
q.strip().lstrip("0123456789.-) ")
|
||||
for q in response.split("\n")
|
||||
if q.strip()
|
||||
][:3]
|
||||
result = await graph.run(state=state, deps=graph_deps)
|
||||
|
||||
plan = [
|
||||
{"id": i, "question": q, "status": "pending"}
|
||||
for i, q in enumerate(sub_questions)
|
||||
]
|
||||
if ctx.deps.agui_emitter:
|
||||
ctx.deps.agui_emitter.log("✅ Research complete!")
|
||||
|
||||
ctx.deps.state.plan = plan
|
||||
ctx.deps.state.current_question_index = 0
|
||||
ctx.deps.state.status = f"Proposed plan with {len(plan)} sub-questions"
|
||||
return f"""Research completed successfully!
|
||||
|
||||
return _as_state_snapshot(ctx)
|
||||
Question: {question}
|
||||
|
||||
@agent.tool
|
||||
async def search_question(
|
||||
ctx: RunContext[ResearchDeps],
|
||||
question_id: int,
|
||||
search_type: str = "hybrid",
|
||||
) -> StateSnapshotEvent:
|
||||
"""Execute search for a specific sub-question."""
|
||||
plan = ctx.deps.state.plan
|
||||
if question_id >= len(plan):
|
||||
raise ValueError(f"Question ID {question_id} not found in plan")
|
||||
Executive Summary: {result.executive_summary}
|
||||
|
||||
question = plan[question_id]["question"]
|
||||
ctx.deps.state.phase = "searching"
|
||||
ctx.deps.state.current_question_index = question_id
|
||||
ctx.deps.state.status = f"Searching: {question}"
|
||||
plan[question_id]["status"] = "searching"
|
||||
Main Findings:
|
||||
{chr(10).join(f"- {finding}" for finding in result.main_findings[:3])}
|
||||
|
||||
search_results = await ctx.deps.client.search(
|
||||
question, limit=5, search_type=search_type
|
||||
)
|
||||
Conclusions:
|
||||
{chr(10).join(f"- {conclusion}" for conclusion in result.conclusions[:2])}
|
||||
|
||||
expanded_map = {}
|
||||
if search_results:
|
||||
expanded_results = await ctx.deps.client.expand_context(
|
||||
search_results[:3], radius=2
|
||||
)
|
||||
expanded_map = {
|
||||
chunk.id: (chunk, score) for chunk, score in expanded_results
|
||||
}
|
||||
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}
|
||||
|
||||
results = []
|
||||
for chunk, score in search_results:
|
||||
doc_uri = chunk.document_uri or "unknown"
|
||||
doc_title = chunk.document_title or chunk.document_uri or "Unknown"
|
||||
The full research report with all citations has been provided to the user.
|
||||
"""
|
||||
|
||||
if doc_uri not in ctx.deps.state.document_registry:
|
||||
ctx.deps.state.document_registry[doc_uri] = {
|
||||
"title": doc_title,
|
||||
"chunks_referenced": [],
|
||||
}
|
||||
|
||||
if (
|
||||
chunk.id
|
||||
not in ctx.deps.state.document_registry[doc_uri]["chunks_referenced"]
|
||||
):
|
||||
ctx.deps.state.document_registry[doc_uri]["chunks_referenced"].append(
|
||||
chunk.id
|
||||
)
|
||||
|
||||
expanded_chunk, _ = (
|
||||
expanded_map[chunk.id] if chunk.id in expanded_map else (chunk, score)
|
||||
)
|
||||
result_data = {
|
||||
"chunk": expanded_chunk.content[:500],
|
||||
"chunk_id": chunk.id,
|
||||
"document_uri": doc_uri,
|
||||
"document_title": doc_title,
|
||||
"chunk_position": chunk.order,
|
||||
"full_chunk_content": expanded_chunk.content,
|
||||
"score": round(score, 3),
|
||||
"expanded": chunk.id in expanded_map,
|
||||
}
|
||||
results.append(result_data)
|
||||
|
||||
plan[question_id]["search_results"] = {
|
||||
"type": search_type,
|
||||
"results": results,
|
||||
}
|
||||
plan[question_id]["status"] = "searched"
|
||||
ctx.deps.state.status = f"Found {len(results)} results"
|
||||
|
||||
return _as_state_snapshot(ctx)
|
||||
|
||||
@agent.tool
|
||||
async def extract_insights_from_results(
|
||||
ctx: RunContext[ResearchDeps],
|
||||
question_id: int,
|
||||
) -> StateSnapshotEvent:
|
||||
"""Extract key insights from search results for a specific question."""
|
||||
plan = ctx.deps.state.plan
|
||||
if question_id >= len(plan):
|
||||
raise ValueError(f"Question ID {question_id} not found in plan")
|
||||
|
||||
question_item = plan[question_id]
|
||||
if "search_results" not in question_item:
|
||||
raise ValueError(
|
||||
f"No search results found for question ID {question_id}. "
|
||||
f"You must call search_question(question_id={question_id}) first."
|
||||
)
|
||||
|
||||
search_results = question_item["search_results"]
|
||||
ctx.deps.state.phase = "analyzing"
|
||||
ctx.deps.state.status = "Extracting insights from results..."
|
||||
|
||||
context_parts = [
|
||||
f"[Result {idx}] [Source: {r['document_title']}] {r['full_chunk_content']}"
|
||||
for idx, r in enumerate(search_results["results"])
|
||||
]
|
||||
context = "\n\n".join(context_parts)
|
||||
|
||||
class InsightResult(BaseModel):
|
||||
summary: str
|
||||
confidence: float
|
||||
result_indices: list[int]
|
||||
|
||||
class InsightsList(BaseModel):
|
||||
insights: list[InsightResult]
|
||||
|
||||
question_text = question_item["question"]
|
||||
extract_prompt = f"""Analyze these search results and extract 1-3 key insights that help answer the question: "{question_text}"
|
||||
|
||||
Search Results:
|
||||
{context}
|
||||
|
||||
For each insight, reference which result numbers (0, 1, 2, etc.) support it."""
|
||||
|
||||
insight_agent: Agent[None, InsightsList] = Agent(
|
||||
ctx.model,
|
||||
output_type=InsightsList,
|
||||
retries=3,
|
||||
)
|
||||
|
||||
result = await insight_agent.run(extract_prompt)
|
||||
raw_insights = [
|
||||
{
|
||||
"summary": insight.summary,
|
||||
"confidence": insight.confidence,
|
||||
"result_indices": insight.result_indices,
|
||||
}
|
||||
for insight in result.output.insights
|
||||
]
|
||||
|
||||
new_insights = []
|
||||
for insight in raw_insights:
|
||||
source_refs = []
|
||||
for idx in insight.get("result_indices", []):
|
||||
if 0 <= idx < len(search_results["results"]):
|
||||
result = search_results["results"][idx]
|
||||
source_refs.append(
|
||||
{
|
||||
"chunk_id": result["chunk_id"],
|
||||
"document_uri": result["document_uri"],
|
||||
"document_title": result["document_title"],
|
||||
"chunk_position": result["chunk_position"],
|
||||
}
|
||||
)
|
||||
|
||||
new_insights.append(
|
||||
{
|
||||
"summary": insight["summary"],
|
||||
"confidence": insight.get("confidence", 0.7),
|
||||
"source_refs": source_refs,
|
||||
}
|
||||
)
|
||||
|
||||
ctx.deps.state.insights.extend(new_insights)
|
||||
plan[question_id]["status"] = "done"
|
||||
ctx.deps.state.status = f"Extracted {len(new_insights)} insights"
|
||||
|
||||
return _as_state_snapshot(ctx)
|
||||
|
||||
@agent.tool
|
||||
async def evaluate_research_confidence(
|
||||
ctx: RunContext[ResearchDeps],
|
||||
) -> StateSnapshotEvent:
|
||||
"""Evaluate overall confidence in the research findings."""
|
||||
insights = ctx.deps.state.insights
|
||||
if not insights:
|
||||
raise ValueError("No insights collected yet")
|
||||
|
||||
ctx.deps.state.phase = "evaluating"
|
||||
ctx.deps.state.status = "Evaluating research confidence..."
|
||||
|
||||
confidences = [i.get("confidence", 0.5) for i in insights]
|
||||
overall_confidence = sum(confidences) / len(confidences) if confidences else 0
|
||||
|
||||
eval_prompt = f"""Evaluate if these insights provide a confident answer to: "{ctx.deps.state.question}"
|
||||
|
||||
Insights collected:
|
||||
{chr(10).join([f"- {i['summary']}" for i in insights])}
|
||||
|
||||
Assess:
|
||||
1. Do we have enough information to answer the question?
|
||||
2. What gaps remain?
|
||||
3. Overall confidence (0.0-1.0)
|
||||
|
||||
Return JSON: {{"confidence": 0.0-1.0, "gaps": ["gap1", "gap2"], "recommendation": "continue" or "finalize"}}"""
|
||||
|
||||
response = await ctx.deps.client.ask(eval_prompt)
|
||||
|
||||
try:
|
||||
evaluation = json.loads(response)
|
||||
overall_confidence = evaluation.get("confidence", overall_confidence)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
ctx.deps.state.confidence = overall_confidence
|
||||
ctx.deps.state.status = f"Confidence: {overall_confidence:.0%}"
|
||||
|
||||
return _as_state_snapshot(ctx)
|
||||
|
||||
@agent.tool
|
||||
async def synthesize_final_report(
|
||||
ctx: RunContext[ResearchDeps],
|
||||
) -> StateSnapshotEvent:
|
||||
"""Generate final research report with citations."""
|
||||
insights = ctx.deps.state.insights
|
||||
if not insights:
|
||||
raise ValueError("No insights to synthesize")
|
||||
|
||||
ctx.deps.state.phase = "synthesizing"
|
||||
ctx.deps.state.status = "Generating final report..."
|
||||
|
||||
insights_summary = []
|
||||
for i in insights:
|
||||
source_titles = [ref["document_title"] for ref in i.get("source_refs", [])]
|
||||
unique_sources = list(dict.fromkeys(source_titles))
|
||||
insights_summary.append(
|
||||
f"- {i['summary']} (sources: {', '.join(unique_sources[:2])})"
|
||||
)
|
||||
|
||||
report_prompt = f"""Generate a comprehensive research report answering: "{ctx.deps.state.question}"
|
||||
|
||||
Based on these insights:
|
||||
{chr(10).join(insights_summary)}
|
||||
|
||||
Create a structured report with:
|
||||
- Executive Summary (2-3 sentences)
|
||||
- Main Findings (bullet points)
|
||||
- Conclusions
|
||||
- Sources (list the document titles mentioned above)
|
||||
|
||||
Return JSON with format:
|
||||
{{
|
||||
"title": "...",
|
||||
"summary": "...",
|
||||
"findings": ["finding1", "finding2", ...],
|
||||
"conclusions": ["conclusion1", ...],
|
||||
"sources": ["source1", "source2", ...]
|
||||
}}"""
|
||||
|
||||
response = await ctx.deps.client.ask(report_prompt)
|
||||
|
||||
try:
|
||||
report = json.loads(response)
|
||||
except json.JSONDecodeError:
|
||||
report = {
|
||||
"title": ctx.deps.state.question,
|
||||
"summary": response[:300],
|
||||
"findings": [i["summary"] for i in insights],
|
||||
"conclusions": ["See findings above"],
|
||||
"sources": [],
|
||||
}
|
||||
|
||||
citations = [
|
||||
{
|
||||
"document_uri": doc_uri,
|
||||
"document_title": doc_info["title"],
|
||||
"chunk_ids": doc_info["chunks_referenced"],
|
||||
}
|
||||
for doc_uri, doc_info in ctx.deps.state.document_registry.items()
|
||||
]
|
||||
report["citations"] = citations
|
||||
|
||||
ctx.deps.state.final_report = report
|
||||
ctx.deps.state.phase = "done"
|
||||
ctx.deps.state.status = "Research complete"
|
||||
|
||||
return _as_state_snapshot(ctx)
|
||||
|
||||
@agent.tool
|
||||
async def get_full_document(
|
||||
ctx: RunContext[ResearchDeps],
|
||||
document_uri: str,
|
||||
) -> StateSnapshotEvent:
|
||||
"""Retrieve and display the full content of a document by its URI."""
|
||||
ctx.deps.state.status = f"Retrieving document: {document_uri}"
|
||||
document = await ctx.deps.client.get_document_by_uri(document_uri)
|
||||
|
||||
if document is None:
|
||||
ctx.deps.state.status = f"Document not found: {document_uri}"
|
||||
ctx.deps.state.current_document = {
|
||||
"uri": document_uri,
|
||||
"title": "Not Found",
|
||||
"content": f"Document with URI '{document_uri}' was not found.",
|
||||
"total_chunks": 0,
|
||||
}
|
||||
else:
|
||||
all_chunks = await ctx.deps.client.search(
|
||||
query="", limit=1000, search_type="fts"
|
||||
)
|
||||
chunks_for_doc = [
|
||||
c for c, _ in all_chunks if c.document_uri == document_uri
|
||||
]
|
||||
|
||||
ctx.deps.state.current_document = {
|
||||
"uri": document.uri or document_uri,
|
||||
"title": document.title or "Untitled",
|
||||
"content": document.content,
|
||||
"total_chunks": len(chunks_for_doc),
|
||||
"metadata": document.metadata,
|
||||
}
|
||||
ctx.deps.state.status = f"Retrieved: {document.title or document_uri}"
|
||||
|
||||
return _as_state_snapshot(ctx)
|
||||
|
||||
return agent
|
||||
except Exception as e:
|
||||
if ctx.deps.agui_emitter:
|
||||
ctx.deps.agui_emitter.log(f"❌ Research error: {str(e)}")
|
||||
return f"I encountered an error while researching: {str(e)}"
|
||||
|
|
|
|||
|
|
@ -1,27 +1,42 @@
|
|||
import logging
|
||||
import os
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
|
||||
from agent import ResearchDeps, ResearchState, create_agent
|
||||
from agent import AgentDeps, agent
|
||||
from anyio import create_memory_object_stream, create_task_group
|
||||
from anyio.streams.memory import MemoryObjectSendStream
|
||||
from starlette.applications import Starlette
|
||||
from starlette.middleware import Middleware
|
||||
from starlette.middleware.cors import CORSMiddleware
|
||||
from starlette.responses import JSONResponse
|
||||
from starlette.routing import Mount, Route
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import JSONResponse, StreamingResponse
|
||||
from starlette.routing import Route
|
||||
|
||||
from haiku.rag.client import HaikuRAG
|
||||
from haiku.rag.config import Config
|
||||
from haiku.rag.config import load_yaml_config
|
||||
from haiku.rag.config.models import AppConfig
|
||||
from haiku.rag.graph.agui.emitter import AGUIEmitter
|
||||
from haiku.rag.graph.agui.server import RunAgentInput, format_sse_event
|
||||
from haiku.rag.graph.research.dependencies import ResearchContext
|
||||
from haiku.rag.graph.research.models import ResearchReport
|
||||
from haiku.rag.graph.research.state import ResearchState
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
client: HaikuRAG | None = None
|
||||
ag_ui_app = None
|
||||
# Load config from mounted haiku.rag.yaml
|
||||
config_path = Path("/app/haiku.rag.yaml")
|
||||
if config_path.exists():
|
||||
yaml_data = load_yaml_config(config_path)
|
||||
Config = AppConfig.model_validate(yaml_data)
|
||||
else:
|
||||
# Fallback to default config
|
||||
Config = AppConfig()
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app):
|
||||
global client
|
||||
# Get DB path from environment
|
||||
db_path_str = os.getenv("DB_PATH", "haiku_rag.lancedb")
|
||||
db_path = Path(db_path_str)
|
||||
|
||||
|
|
@ -30,58 +45,127 @@ async def lifespan(app):
|
|||
logger.error("Run: haiku-rag add <path-to-documents>")
|
||||
raise RuntimeError(f"Database not found: {db_path}")
|
||||
|
||||
logger.info(f"Initializing HaikuRAG client with database: {db_path}")
|
||||
client = HaikuRAG(db_path)
|
||||
logger.info("Research assistant backend ready")
|
||||
logger.info(f"QA Provider: {Config.qa.provider}, Model: {Config.qa.model}")
|
||||
logger.info(f"Initializing research assistant with database: {db_path}")
|
||||
logger.info(
|
||||
f"Research Provider: {Config.research.provider}, Model: {Config.research.model}"
|
||||
)
|
||||
|
||||
yield
|
||||
|
||||
if client:
|
||||
logger.info("Closing HaikuRAG client")
|
||||
client.close()
|
||||
# Store client reference for proper lifecycle management
|
||||
_client_cache: dict[str, HaikuRAG] = {}
|
||||
|
||||
|
||||
agent = create_agent()
|
||||
def get_client(effective_db_path: Path) -> HaikuRAG:
|
||||
"""Get or create cached client."""
|
||||
path_key = str(effective_db_path)
|
||||
if path_key not in _client_cache:
|
||||
_client_cache[path_key] = HaikuRAG(db_path=effective_db_path, config=Config)
|
||||
return _client_cache[path_key]
|
||||
|
||||
|
||||
async def health(request):
|
||||
db_path_str = os.getenv("DB_PATH", "haiku_rag.lancedb")
|
||||
async def stream_research_agent(request: Request) -> StreamingResponse:
|
||||
"""Agent streaming endpoint with research graph integration."""
|
||||
body = await request.json()
|
||||
input_data = RunAgentInput(**body)
|
||||
|
||||
user_message = ""
|
||||
if input_data.messages:
|
||||
user_message = input_data.messages[-1].get("content", "")
|
||||
|
||||
send_stream, receive_stream = create_memory_object_stream[str]()
|
||||
|
||||
async def run_agent_with_streaming(
|
||||
send_stream: MemoryObjectSendStream[str],
|
||||
) -> None:
|
||||
"""Execute agent and forward emitter events to memory stream."""
|
||||
async with send_stream:
|
||||
try:
|
||||
# Create shared emitter
|
||||
emitter: AGUIEmitter[ResearchState, ResearchReport] = AGUIEmitter(
|
||||
thread_id=input_data.thread_id,
|
||||
run_id=input_data.run_id,
|
||||
use_deltas=False,
|
||||
)
|
||||
|
||||
# Get client
|
||||
effective_db_path = input_data.config.get("db_path") or db_path
|
||||
if isinstance(effective_db_path, str):
|
||||
effective_db_path = Path(effective_db_path)
|
||||
client = get_client(effective_db_path)
|
||||
|
||||
# Create agent dependencies with shared emitter
|
||||
agent_deps = AgentDeps(client=client, agui_emitter=emitter)
|
||||
|
||||
# Start run with empty initial state
|
||||
emitter.start_run(
|
||||
initial_state=ResearchState.from_config(
|
||||
context=ResearchContext(original_question=""),
|
||||
config=Config,
|
||||
)
|
||||
)
|
||||
|
||||
# Forward emitter events to stream
|
||||
async def forward_events():
|
||||
async for event in emitter:
|
||||
# Filter out ACTIVITY_SNAPSHOT - not supported by CopilotKit
|
||||
if event.get("type") == "ACTIVITY_SNAPSHOT":
|
||||
continue
|
||||
await send_stream.send(format_sse_event(event))
|
||||
|
||||
# Run agent and event forwarding concurrently
|
||||
async with create_task_group() as tg:
|
||||
tg.start_soon(forward_events)
|
||||
|
||||
result = await agent.run(user_message, deps=agent_deps)
|
||||
emitter.log(result.output)
|
||||
await emitter.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("Error executing agent")
|
||||
try:
|
||||
await send_stream.send(
|
||||
format_sse_event({"type": "error", "error": str(e)})
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def event_generator():
|
||||
"""Generate SSE events from memory stream."""
|
||||
async with create_task_group() as tg:
|
||||
tg.start_soon(run_agent_with_streaming, send_stream)
|
||||
async with receive_stream:
|
||||
async for event_str in receive_stream:
|
||||
yield event_str
|
||||
|
||||
return StreamingResponse(
|
||||
event_generator(),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"X-Accel-Buffering": "no",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
async def health_check(_: Request) -> JSONResponse:
|
||||
"""Health check endpoint with configuration info."""
|
||||
return JSONResponse(
|
||||
{
|
||||
"status": "healthy",
|
||||
"agent_model": str(agent.model),
|
||||
"qa_provider": Config.qa.provider,
|
||||
"qa_model": Config.qa.model,
|
||||
"ollama_base_url": Config.providers.ollama.base_url,
|
||||
"db_path": db_path_str,
|
||||
"db_exists": Path(db_path_str).exists(),
|
||||
"research_provider": Config.research.provider,
|
||||
"research_model": Config.research.model,
|
||||
"db_path": str(db_path),
|
||||
"db_exists": db_path.exists(),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def get_ag_ui_app():
|
||||
global ag_ui_app
|
||||
if ag_ui_app is None and client is not None:
|
||||
research_deps = ResearchDeps(client=client, state=ResearchState())
|
||||
logger.info("Creating AG-UI app")
|
||||
ag_ui_app = agent.to_ag_ui(deps=research_deps)
|
||||
return ag_ui_app
|
||||
|
||||
|
||||
async def agent_endpoint(scope, receive, send):
|
||||
app = get_ag_ui_app()
|
||||
if app is None:
|
||||
response = JSONResponse({"error": "Client not initialized"}, status_code=503)
|
||||
await response(scope, receive, send)
|
||||
return
|
||||
await app(scope, receive, send)
|
||||
|
||||
|
||||
# Create Starlette app
|
||||
app = Starlette(
|
||||
routes=[
|
||||
Route("/health", health),
|
||||
Mount("/agent", agent_endpoint),
|
||||
Route("/v1/research/stream", stream_research_agent, methods=["POST"]),
|
||||
Route("/health", health_check, methods=["GET"]),
|
||||
],
|
||||
middleware=[
|
||||
Middleware(
|
||||
|
|
@ -92,17 +176,11 @@ app = Starlette(
|
|||
allow_headers=["*"],
|
||||
)
|
||||
],
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
print("Starting haiku.rag research assistant backend...")
|
||||
print(f"Agent model: {agent.model}")
|
||||
print(f"QA provider: {Config.qa.provider}")
|
||||
print(f"QA model: {Config.qa.model}")
|
||||
|
||||
uvicorn.run(
|
||||
"main:app",
|
||||
host="0.0.0.0",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ dependencies = [
|
|||
"uvicorn[standard]>=0.34.2",
|
||||
"pydantic-ai-slim[ag-ui,openai]>=1.1.0",
|
||||
"python-dotenv>=1.0.1",
|
||||
"haiku-rag>=0.12.1",
|
||||
"haiku-rag-slim @ file:///Users/ggozad/dev/open-source/haiku.rag-agui/haiku_rag_slim",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
|
|
@ -18,6 +18,9 @@ dev = [
|
|||
"ruff>=0.13.0",
|
||||
]
|
||||
|
||||
[tool.hatch.metadata]
|
||||
allow-direct-references = true
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["."]
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -10,11 +10,16 @@ services:
|
|||
# API keys (set these in your shell or .env file)
|
||||
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
||||
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
||||
# Ollama connection (use value from .env)
|
||||
- OLLAMA_BASE_URL=${OLLAMA_BASE_URL}
|
||||
# Prevent Python bytecode caching for development
|
||||
- PYTHONDONTWRITEBYTECODE=1
|
||||
volumes:
|
||||
- ./backend:/app
|
||||
- /app/.venv
|
||||
- ${DB_PATH}:/app/data/haiku.rag.lancedb
|
||||
- ./haiku.rag.yaml:/app/haiku.rag.yaml:ro
|
||||
- ./backend/main.py:/app/main.py
|
||||
- ./backend/agent.py:/app/agent.py
|
||||
- ../../haiku_rag_slim/haiku:/app/.venv/lib/python3.13/site-packages/haiku
|
||||
networks:
|
||||
- ag-ui-network
|
||||
extra_hosts:
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const runtime = new CopilotRuntime({
|
|||
agents: {
|
||||
// "research_agent" maps to the agent name used in useCoAgent() on the frontend
|
||||
research_agent: new HttpAgent({
|
||||
url: `${process.env.BACKEND_URL || "http://backend:8000"}/agent`,
|
||||
url: `${process.env.BACKEND_URL || "http://backend:8000"}/v1/research/stream`,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,208 +1,94 @@
|
|||
"use client";
|
||||
|
||||
import {
|
||||
CopilotKit,
|
||||
useCoAgent,
|
||||
useCoAgentStateRender,
|
||||
useCopilotAction,
|
||||
} from "@copilotkit/react-core";
|
||||
import { CopilotKit, useCoAgent } from "@copilotkit/react-core";
|
||||
import { CopilotChat } from "@copilotkit/react-ui";
|
||||
import "@copilotkit/react-ui/styles.css";
|
||||
import StateDisplay from "./StateDisplay";
|
||||
|
||||
interface SourceRef {
|
||||
chunk_id: string;
|
||||
document_uri: string;
|
||||
document_title: string;
|
||||
chunk_position: number;
|
||||
interface InsightRecord {
|
||||
id: string;
|
||||
summary: string;
|
||||
status: string;
|
||||
notes?: string;
|
||||
supporting_sources: string[];
|
||||
originating_questions: string[];
|
||||
}
|
||||
|
||||
interface ResearchState {
|
||||
question: string;
|
||||
phase: string;
|
||||
status: string;
|
||||
plan: Array<{
|
||||
id: number;
|
||||
question: string;
|
||||
status: string;
|
||||
search_results?: {
|
||||
type: string;
|
||||
results: Array<{
|
||||
chunk: string;
|
||||
chunk_id: string;
|
||||
document_uri: string;
|
||||
document_title: string;
|
||||
chunk_position: number;
|
||||
full_chunk_content: string;
|
||||
score: number;
|
||||
expanded: boolean;
|
||||
}>;
|
||||
};
|
||||
}>;
|
||||
current_question_index: number;
|
||||
insights: Array<{
|
||||
summary: string;
|
||||
confidence: number;
|
||||
source_refs: SourceRef[];
|
||||
}>;
|
||||
document_registry: Record<
|
||||
string,
|
||||
{
|
||||
title: string;
|
||||
chunks_referenced: string[];
|
||||
interface GapRecord {
|
||||
id: string;
|
||||
description: string;
|
||||
severity: string;
|
||||
blocking: boolean;
|
||||
resolved: boolean;
|
||||
notes?: string;
|
||||
supporting_sources: string[];
|
||||
resolved_by: string[];
|
||||
}
|
||||
>;
|
||||
current_document: {
|
||||
uri: string;
|
||||
title: string;
|
||||
content: string;
|
||||
total_chunks: number;
|
||||
metadata?: Record<string, unknown>;
|
||||
} | null;
|
||||
|
||||
interface SearchAnswer {
|
||||
query: string;
|
||||
answer: string;
|
||||
confidence: number;
|
||||
final_report: {
|
||||
title: string;
|
||||
context: string[];
|
||||
sources: string[];
|
||||
}
|
||||
|
||||
interface ResearchContext {
|
||||
original_question: string;
|
||||
sub_questions: string[];
|
||||
qa_responses: SearchAnswer[];
|
||||
insights: InsightRecord[];
|
||||
gaps: GapRecord[];
|
||||
}
|
||||
|
||||
interface EvaluationResult {
|
||||
confidence: number;
|
||||
reasoning: string;
|
||||
should_continue: boolean;
|
||||
gaps_identified: string[];
|
||||
follow_up_questions: string[];
|
||||
}
|
||||
|
||||
interface ResearchReport {
|
||||
question: string;
|
||||
summary: string;
|
||||
findings: string[];
|
||||
conclusions: string[];
|
||||
sources: string[];
|
||||
citations: Array<{
|
||||
document_uri: string;
|
||||
document_title: string;
|
||||
chunk_ids: string[];
|
||||
}>;
|
||||
insights_used: string[];
|
||||
methodology: string;
|
||||
}
|
||||
|
||||
interface ResearchState {
|
||||
context: ResearchContext;
|
||||
iterations: number;
|
||||
max_iterations: number;
|
||||
confidence_threshold: number;
|
||||
max_concurrency: number;
|
||||
last_eval: EvaluationResult | null;
|
||||
last_analysis: {
|
||||
insights_extracted: InsightRecord[];
|
||||
gaps_identified: GapRecord[];
|
||||
} | null;
|
||||
result?: ResearchReport;
|
||||
}
|
||||
|
||||
function AgentContent() {
|
||||
const { state } = useCoAgent<ResearchState>({
|
||||
name: "research_agent",
|
||||
initialState: {
|
||||
question: "",
|
||||
phase: "idle",
|
||||
status: "",
|
||||
plan: [],
|
||||
current_question_index: 0,
|
||||
context: {
|
||||
original_question: "",
|
||||
sub_questions: [],
|
||||
qa_responses: [],
|
||||
insights: [],
|
||||
document_registry: {},
|
||||
current_document: null,
|
||||
confidence: 0.0,
|
||||
final_report: null,
|
||||
gaps: [],
|
||||
},
|
||||
});
|
||||
|
||||
useCopilotAction({
|
||||
name: "approve_research_plan",
|
||||
description:
|
||||
"Request user approval for the research plan. Returns 'APPROVED' if approved or 'REVISE' if user wants to revise.",
|
||||
parameters: [],
|
||||
renderAndWaitForResponse: ({ respond, status }) => (
|
||||
<div
|
||||
style={{
|
||||
padding: "1.5rem",
|
||||
background: "white",
|
||||
borderRadius: "8px",
|
||||
border: "2px solid #4299e1",
|
||||
marginBottom: "1rem",
|
||||
boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
|
||||
}}
|
||||
>
|
||||
<h3
|
||||
style={{
|
||||
fontSize: "1.25rem",
|
||||
fontWeight: "bold",
|
||||
marginBottom: "1rem",
|
||||
color: "#2d3748",
|
||||
}}
|
||||
>
|
||||
Research Plan Approval
|
||||
</h3>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "0.875rem",
|
||||
color: "#4a5568",
|
||||
marginBottom: "1rem",
|
||||
}}
|
||||
>
|
||||
Please review the research plan in the right pane.
|
||||
</p>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "1rem",
|
||||
}}
|
||||
className={status !== "executing" ? "hidden" : ""}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => respond?.("REVISE")}
|
||||
disabled={status !== "executing"}
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: "0.75rem",
|
||||
background: "white",
|
||||
border: "2px solid #e2e8f0",
|
||||
borderRadius: "6px",
|
||||
fontSize: "0.875rem",
|
||||
fontWeight: "600",
|
||||
cursor: status === "executing" ? "pointer" : "not-allowed",
|
||||
opacity: status === "executing" ? 1 : 0.5,
|
||||
}}
|
||||
>
|
||||
Revise Plan
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => respond?.("APPROVED")}
|
||||
disabled={status !== "executing"}
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: "0.75rem",
|
||||
background: "#4299e1",
|
||||
color: "white",
|
||||
border: "none",
|
||||
borderRadius: "6px",
|
||||
fontSize: "0.875rem",
|
||||
fontWeight: "600",
|
||||
cursor: status === "executing" ? "pointer" : "not-allowed",
|
||||
opacity: status === "executing" ? 1 : 0.5,
|
||||
}}
|
||||
>
|
||||
Approve & Start Research
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
|
||||
useCoAgentStateRender<ResearchState>({
|
||||
name: "research_agent",
|
||||
render: ({ state: newState }) => {
|
||||
const phaseMessages: Record<string, string> = {
|
||||
planning: "Planning research...",
|
||||
searching: "Searching...",
|
||||
analyzing: "Extracting insights...",
|
||||
evaluating: `Evaluating confidence: ${(newState.confidence * 100).toFixed(0)}%`,
|
||||
synthesizing: "Generating final report...",
|
||||
done: "Research complete!",
|
||||
};
|
||||
const phaseMessage =
|
||||
phaseMessages[newState.phase] || newState.status || "Ready";
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: "1rem",
|
||||
background: "#e6f7ff",
|
||||
borderRadius: "4px",
|
||||
marginBottom: "0.5rem",
|
||||
border: "1px solid #91d5ff",
|
||||
}}
|
||||
>
|
||||
<strong>Research Update:</strong> {phaseMessage}
|
||||
</div>
|
||||
);
|
||||
iterations: 0,
|
||||
max_iterations: 3,
|
||||
confidence_threshold: 0.8,
|
||||
max_concurrency: 1,
|
||||
last_eval: null,
|
||||
last_analysis: null,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -3,68 +3,70 @@
|
|||
import { Markdown } from "@copilotkit/react-ui";
|
||||
import { useState } from "react";
|
||||
|
||||
interface SourceRef {
|
||||
chunk_id: string;
|
||||
document_uri: string;
|
||||
document_title: string;
|
||||
chunk_position: number;
|
||||
interface InsightRecord {
|
||||
id: string;
|
||||
summary: string;
|
||||
status: string;
|
||||
notes?: string;
|
||||
supporting_sources: string[];
|
||||
originating_questions: string[];
|
||||
}
|
||||
|
||||
interface ResearchState {
|
||||
question: string;
|
||||
phase: string;
|
||||
status: string;
|
||||
plan: Array<{
|
||||
id: number;
|
||||
question: string;
|
||||
status: string;
|
||||
search_results?: {
|
||||
type: string;
|
||||
results: Array<{
|
||||
chunk: string;
|
||||
chunk_id: string;
|
||||
document_uri: string;
|
||||
document_title: string;
|
||||
chunk_position: number;
|
||||
full_chunk_content: string;
|
||||
score: number;
|
||||
expanded: boolean;
|
||||
}>;
|
||||
};
|
||||
}>;
|
||||
current_question_index: number;
|
||||
insights: Array<{
|
||||
summary: string;
|
||||
confidence: number;
|
||||
source_refs: SourceRef[];
|
||||
}>;
|
||||
document_registry: Record<
|
||||
string,
|
||||
{
|
||||
title: string;
|
||||
chunks_referenced: string[];
|
||||
interface GapRecord {
|
||||
id: string;
|
||||
description: string;
|
||||
severity: string;
|
||||
blocking: boolean;
|
||||
resolved: boolean;
|
||||
notes?: string;
|
||||
supporting_sources: string[];
|
||||
resolved_by: string[];
|
||||
}
|
||||
>;
|
||||
current_document: {
|
||||
uri: string;
|
||||
title: string;
|
||||
content: string;
|
||||
total_chunks: number;
|
||||
metadata?: Record<string, unknown>;
|
||||
} | null;
|
||||
|
||||
interface SearchAnswer {
|
||||
sub_question: string;
|
||||
answer: string;
|
||||
confidence: number;
|
||||
final_report: {
|
||||
title: string;
|
||||
chunks_used: number;
|
||||
}
|
||||
|
||||
interface ResearchContext {
|
||||
original_question: string;
|
||||
sub_questions: string[];
|
||||
qa_responses: SearchAnswer[];
|
||||
insights: InsightRecord[];
|
||||
gaps: GapRecord[];
|
||||
}
|
||||
|
||||
interface EvaluationResult {
|
||||
confidence: number;
|
||||
reasoning: string;
|
||||
should_continue: boolean;
|
||||
gaps_identified: string[];
|
||||
follow_up_questions: string[];
|
||||
}
|
||||
|
||||
interface ResearchReport {
|
||||
question: string;
|
||||
summary: string;
|
||||
findings: string[];
|
||||
conclusions: string[];
|
||||
sources: string[];
|
||||
citations: Array<{
|
||||
document_uri: string;
|
||||
document_title: string;
|
||||
chunk_ids: string[];
|
||||
}>;
|
||||
insights_used: string[];
|
||||
methodology: string;
|
||||
}
|
||||
|
||||
interface ResearchState {
|
||||
context: ResearchContext;
|
||||
iterations: number;
|
||||
max_iterations: number;
|
||||
confidence_threshold: number;
|
||||
max_concurrency: number;
|
||||
last_eval: EvaluationResult | null;
|
||||
last_analysis: {
|
||||
insights_extracted: InsightRecord[];
|
||||
gaps_identified: GapRecord[];
|
||||
} | null;
|
||||
result?: ResearchReport;
|
||||
}
|
||||
|
||||
interface StateDisplayProps {
|
||||
|
|
@ -75,14 +77,14 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
const [expandedSections, setExpandedSections] = useState<
|
||||
Record<string, boolean>
|
||||
>({
|
||||
plan: true,
|
||||
questions: true,
|
||||
insights: true,
|
||||
gaps: true,
|
||||
report: true,
|
||||
document: true,
|
||||
});
|
||||
|
||||
const [expandedQuestions, setExpandedQuestions] = useState<
|
||||
Record<number, boolean>
|
||||
Record<string, boolean>
|
||||
>({});
|
||||
|
||||
const toggleSection = (section: string) => {
|
||||
|
|
@ -92,20 +94,19 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
}));
|
||||
};
|
||||
|
||||
const toggleQuestion = (questionId: number) => {
|
||||
const toggleQuestion = (questionId: string) => {
|
||||
setExpandedQuestions((prev) => ({
|
||||
...prev,
|
||||
[questionId]: !prev[questionId],
|
||||
}));
|
||||
};
|
||||
|
||||
// Calculate research progress
|
||||
const completedQuestions = state.plan.filter(
|
||||
(q) => q.status === "done",
|
||||
).length;
|
||||
const totalQuestions = state.plan.length;
|
||||
// Calculate research progress based on iterations
|
||||
const researchProgress =
|
||||
totalQuestions > 0 ? (completedQuestions / totalQuestions) * 100 : 0;
|
||||
state.max_iterations > 0
|
||||
? (state.iterations / state.max_iterations) * 100
|
||||
: 0;
|
||||
const confidence = state.last_eval?.confidence || 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -115,7 +116,7 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
gap: "1rem",
|
||||
}}
|
||||
>
|
||||
{/* Current Phase & Status */}
|
||||
{/* Current Status */}
|
||||
<div
|
||||
style={{
|
||||
background: "white",
|
||||
|
|
@ -131,57 +132,48 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
marginBottom: "0.5rem",
|
||||
}}
|
||||
>
|
||||
Current Phase
|
||||
Research Progress
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: "0.75rem", alignItems: "center" }}>
|
||||
<div
|
||||
style={{
|
||||
padding: "0.5rem 1rem",
|
||||
background:
|
||||
state.phase === "idle"
|
||||
state.iterations === 0
|
||||
? "#e2e8f0"
|
||||
: state.phase === "planning"
|
||||
? "#fef3c7"
|
||||
: state.phase === "searching"
|
||||
? "#dbeafe"
|
||||
: state.phase === "analyzing"
|
||||
? "#e0e7ff"
|
||||
: state.phase === "evaluating"
|
||||
? "#fce7f3"
|
||||
: "#d1fae5",
|
||||
: state.result
|
||||
? "#d1fae5"
|
||||
: "#dbeafe",
|
||||
color:
|
||||
state.phase === "idle"
|
||||
state.iterations === 0
|
||||
? "#718096"
|
||||
: state.phase === "planning"
|
||||
? "#92400e"
|
||||
: state.phase === "searching"
|
||||
? "#1e40af"
|
||||
: state.phase === "analyzing"
|
||||
? "#3730a3"
|
||||
: state.phase === "evaluating"
|
||||
? "#9f1239"
|
||||
: "#065f46",
|
||||
: state.result
|
||||
? "#065f46"
|
||||
: "#1e40af",
|
||||
borderRadius: "6px",
|
||||
fontSize: "1rem",
|
||||
fontWeight: "700",
|
||||
textTransform: "capitalize",
|
||||
}}
|
||||
>
|
||||
{state.phase}
|
||||
{state.iterations === 0
|
||||
? "Ready"
|
||||
: state.result
|
||||
? "Complete"
|
||||
: "Researching"}
|
||||
</div>
|
||||
{state.status && (
|
||||
{state.iterations > 0 && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: "0.875rem",
|
||||
color: "#4a5568",
|
||||
}}
|
||||
>
|
||||
{state.status}
|
||||
Iteration {state.iterations} of {state.max_iterations}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Research Progress Bar */}
|
||||
{totalQuestions > 0 && state.phase !== "idle" && (
|
||||
{state.iterations > 0 && (
|
||||
<div style={{ marginTop: "1rem" }}>
|
||||
<div
|
||||
style={{
|
||||
|
|
@ -197,7 +189,7 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
color: "#718096",
|
||||
}}
|
||||
>
|
||||
Research Progress
|
||||
Iterations
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
|
|
@ -206,7 +198,7 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
color: "#2d3748",
|
||||
}}
|
||||
>
|
||||
{completedQuestions}/{totalQuestions} questions
|
||||
{state.iterations}/{state.max_iterations}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
|
|
@ -231,7 +223,7 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
</div>
|
||||
|
||||
{/* Question */}
|
||||
{state.question && (
|
||||
{state.context.original_question && (
|
||||
<div
|
||||
style={{
|
||||
background: "white",
|
||||
|
|
@ -256,13 +248,13 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
color: "#2d3748",
|
||||
}}
|
||||
>
|
||||
{state.question}
|
||||
{state.context.original_question}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Confidence Meter */}
|
||||
{state.confidence > 0 && (
|
||||
{confidence > 0 && (
|
||||
<div
|
||||
style={{
|
||||
background: "white",
|
||||
|
|
@ -292,12 +284,12 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
>
|
||||
<div
|
||||
style={{
|
||||
width: `${state.confidence * 100}%`,
|
||||
width: `${confidence * 100}%`,
|
||||
height: "100%",
|
||||
background:
|
||||
state.confidence > 0.8
|
||||
confidence > 0.8
|
||||
? "#48bb78"
|
||||
: state.confidence > 0.5
|
||||
: confidence > 0.5
|
||||
? "#ed8936"
|
||||
: "#f56565",
|
||||
transition: "width 0.3s ease",
|
||||
|
|
@ -309,21 +301,36 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
fontSize: "1.5rem",
|
||||
fontWeight: "bold",
|
||||
color:
|
||||
state.confidence > 0.8
|
||||
confidence > 0.8
|
||||
? "#48bb78"
|
||||
: state.confidence > 0.5
|
||||
: confidence > 0.5
|
||||
? "#ed8936"
|
||||
: "#f56565",
|
||||
}}
|
||||
>
|
||||
{(state.confidence * 100).toFixed(0)}%
|
||||
{(confidence * 100).toFixed(0)}%
|
||||
</div>
|
||||
</div>
|
||||
{state.last_eval?.reasoning && (
|
||||
<div
|
||||
style={{
|
||||
marginTop: "0.75rem",
|
||||
fontSize: "0.875rem",
|
||||
color: "#4a5568",
|
||||
padding: "0.75rem",
|
||||
background: "#f7fafc",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
>
|
||||
<Markdown content={state.last_eval.reasoning} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Research Plan */}
|
||||
{state.plan.length > 0 && (
|
||||
{/* Sub-Questions and QA Responses */}
|
||||
{(state.context.sub_questions.length > 0 ||
|
||||
state.context.qa_responses.length > 0) && (
|
||||
<div
|
||||
style={{
|
||||
background: "white",
|
||||
|
|
@ -334,7 +341,7 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleSection("plan")}
|
||||
onClick={() => toggleSection("questions")}
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
|
|
@ -350,10 +357,13 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
color: "#2d3748",
|
||||
}}
|
||||
>
|
||||
<span>Research Plan ({state.plan.length} questions)</span>
|
||||
<span>{expandedSections.plan ? "▼" : "▶"}</span>
|
||||
<span>
|
||||
Sub-Questions ({state.context.sub_questions.length}) • Answers (
|
||||
{state.context.qa_responses.length})
|
||||
</span>
|
||||
<span>{expandedSections.questions ? "▼" : "▶"}</span>
|
||||
</button>
|
||||
{expandedSections.plan && (
|
||||
{expandedSections.questions && (
|
||||
<div
|
||||
style={{
|
||||
padding: "1rem",
|
||||
|
|
@ -363,9 +373,43 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
borderRadius: "0 0 4px 4px",
|
||||
}}
|
||||
>
|
||||
{state.plan.map((item) => (
|
||||
{/* Show pending sub_questions */}
|
||||
{state.context.sub_questions.map((question, idx) => (
|
||||
<div
|
||||
key={item.id}
|
||||
key={`pending-${idx}`}
|
||||
style={{
|
||||
marginBottom: "0.5rem",
|
||||
background: "white",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid #e2e8f0",
|
||||
padding: "0.75rem",
|
||||
display: "flex",
|
||||
gap: "0.75rem",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "1.25rem",
|
||||
color: "#a0aec0",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
⏳
|
||||
</div>
|
||||
<div style={{ flex: 1, fontSize: "0.875rem", color: "#4a5568" }}>
|
||||
<Markdown content={question} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Show all qa_responses (each has query + answer) */}
|
||||
{state.context.qa_responses.map((qaResponse, idx) => {
|
||||
const questionId = `q-${idx}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={questionId}
|
||||
style={{
|
||||
marginBottom: "0.5rem",
|
||||
background: "white",
|
||||
|
|
@ -376,7 +420,7 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleQuestion(item.id)}
|
||||
onClick={() => toggleQuestion(questionId)}
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
|
|
@ -392,23 +436,11 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
<div
|
||||
style={{
|
||||
fontSize: "1.25rem",
|
||||
color:
|
||||
item.status === "done"
|
||||
? "#48bb78"
|
||||
: item.status === "searching" ||
|
||||
item.status === "searched"
|
||||
? "#4299e1"
|
||||
: "#a0aec0",
|
||||
color: "#48bb78",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{item.status === "done"
|
||||
? "✓"
|
||||
: item.status === "searching"
|
||||
? "🔍"
|
||||
: item.status === "searched"
|
||||
? "📊"
|
||||
: "⏳"}
|
||||
✓
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div
|
||||
|
|
@ -417,9 +449,8 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
color: "#4a5568",
|
||||
}}
|
||||
>
|
||||
<Markdown content={item.question} />
|
||||
<Markdown content={qaResponse.query} />
|
||||
</div>
|
||||
{item.search_results && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: "0.75rem",
|
||||
|
|
@ -427,24 +458,21 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
marginTop: "0.25rem",
|
||||
}}
|
||||
>
|
||||
{item.search_results.results.length} results
|
||||
Confidence: {(qaResponse.confidence * 100).toFixed(0)}%
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{item.search_results && (
|
||||
<span
|
||||
style={{
|
||||
fontSize: "0.875rem",
|
||||
color: "#718096",
|
||||
}}
|
||||
>
|
||||
{expandedQuestions[item.id] ? "▼" : "▶"}
|
||||
{expandedQuestions[questionId] ? "▼" : "▶"}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Search Results nested inside question */}
|
||||
{expandedQuestions[item.id] && item.search_results && (
|
||||
{/* QA Response nested inside question */}
|
||||
{expandedQuestions[questionId] && (
|
||||
<div
|
||||
style={{
|
||||
padding: "1rem",
|
||||
|
|
@ -460,87 +488,38 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
fontWeight: "600",
|
||||
}}
|
||||
>
|
||||
Search Type: {item.search_results.type}
|
||||
Answer
|
||||
</div>
|
||||
{item.search_results.results.map((result, idx) => (
|
||||
<div
|
||||
key={`${result.chunk_id}-${idx}`}
|
||||
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.875rem",
|
||||
fontWeight: "600",
|
||||
color: "#2d3748",
|
||||
lineHeight: "1.5",
|
||||
}}
|
||||
>
|
||||
{result.document_title}
|
||||
</span>
|
||||
<div style={{ display: "flex", gap: "0.5rem" }}>
|
||||
{result.expanded && (
|
||||
<span
|
||||
style={{
|
||||
fontSize: "0.75rem",
|
||||
padding: "0.125rem 0.5rem",
|
||||
background: "#bee3f8",
|
||||
color: "#2c5282",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
>
|
||||
Expanded
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
style={{
|
||||
fontSize: "0.875rem",
|
||||
fontWeight: "bold",
|
||||
color:
|
||||
result.score > 0.8
|
||||
? "#48bb78"
|
||||
: result.score > 0.6
|
||||
? "#ed8936"
|
||||
: "#a0aec0",
|
||||
}}
|
||||
>
|
||||
{result.score.toFixed(2)}
|
||||
</span>
|
||||
<Markdown content={qaResponse.answer} />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "0.875rem",
|
||||
color: "#718096",
|
||||
lineHeight: "1.4",
|
||||
}}
|
||||
>
|
||||
<Markdown content={`${result.chunk}...`} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Insights */}
|
||||
{state.insights.length > 0 && (
|
||||
{state.context.insights.length > 0 && (
|
||||
<div
|
||||
style={{
|
||||
background: "white",
|
||||
|
|
@ -567,7 +546,7 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
color: "#2d3748",
|
||||
}}
|
||||
>
|
||||
<span>Key Insights ({state.insights.length})</span>
|
||||
<span>Key Insights ({state.context.insights.length})</span>
|
||||
<span>{expandedSections.insights ? "▼" : "▶"}</span>
|
||||
</button>
|
||||
{expandedSections.insights && (
|
||||
|
|
@ -580,9 +559,9 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
borderRadius: "0 0 4px 4px",
|
||||
}}
|
||||
>
|
||||
{state.insights.map((insight, idx) => (
|
||||
{state.context.insights.map((insight) => (
|
||||
<div
|
||||
key={`${insight.summary.substring(0, 30)}-${idx}`}
|
||||
key={insight.id}
|
||||
style={{
|
||||
padding: "0.75rem",
|
||||
background: "white",
|
||||
|
|
@ -602,12 +581,22 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
style={{
|
||||
fontSize: "0.75rem",
|
||||
padding: "0.125rem 0.5rem",
|
||||
background: "#c6f6d5",
|
||||
color: "#22543d",
|
||||
background:
|
||||
insight.status === "validated"
|
||||
? "#c6f6d5"
|
||||
: insight.status === "active"
|
||||
? "#bee3f8"
|
||||
: "#fed7d7",
|
||||
color:
|
||||
insight.status === "validated"
|
||||
? "#22543d"
|
||||
: insight.status === "active"
|
||||
? "#2c5282"
|
||||
: "#742a2a",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
>
|
||||
{(insight.confidence * 100).toFixed(0)}% confidence
|
||||
{insight.status}
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
|
|
@ -615,7 +604,7 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
color: "#718096",
|
||||
}}
|
||||
>
|
||||
{insight.source_refs?.length || 0} sources
|
||||
{insight.supporting_sources.length} sources
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
|
|
@ -628,7 +617,19 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
>
|
||||
<Markdown content={insight.summary} />
|
||||
</div>
|
||||
{insight.source_refs && insight.source_refs.length > 0 && (
|
||||
{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",
|
||||
|
|
@ -637,12 +638,174 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
}}
|
||||
>
|
||||
<span style={{ fontWeight: "600" }}>Sources: </span>
|
||||
{insight.source_refs.map((ref, refIdx) => (
|
||||
<span key={ref.chunk_id}>
|
||||
{refIdx > 0 && ", "}
|
||||
<span style={{ fontSize: "0.75rem" }}>
|
||||
{ref.document_title}
|
||||
{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>
|
||||
|
|
@ -655,7 +818,7 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
)}
|
||||
|
||||
{/* Final Report */}
|
||||
{state.final_report && (
|
||||
{state.result && (
|
||||
<div
|
||||
style={{
|
||||
background: "white",
|
||||
|
|
@ -703,7 +866,7 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
color: "#2d3748",
|
||||
}}
|
||||
>
|
||||
{state.final_report.title}
|
||||
{state.result.question}
|
||||
</h3>
|
||||
<div style={{ marginBottom: "1.5rem" }}>
|
||||
<h4
|
||||
|
|
@ -714,7 +877,7 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
marginBottom: "0.5rem",
|
||||
}}
|
||||
>
|
||||
Executive Summary
|
||||
Summary
|
||||
</h4>
|
||||
<div
|
||||
style={{
|
||||
|
|
@ -723,7 +886,7 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
lineHeight: "1.6",
|
||||
}}
|
||||
>
|
||||
<Markdown content={state.final_report.summary} />
|
||||
<Markdown content={state.result.summary} />
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginBottom: "1.5rem" }}>
|
||||
|
|
@ -735,7 +898,7 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
marginBottom: "0.5rem",
|
||||
}}
|
||||
>
|
||||
Main Findings
|
||||
Key Findings
|
||||
</h4>
|
||||
<ul
|
||||
style={{
|
||||
|
|
@ -745,7 +908,7 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
lineHeight: "1.6",
|
||||
}}
|
||||
>
|
||||
{state.final_report.findings.map((finding, idx) => (
|
||||
{state.result.findings.map((finding, idx) => (
|
||||
<li
|
||||
key={`finding-${idx}-${finding.substring(0, 30)}`}
|
||||
style={{ marginBottom: "0.5rem" }}
|
||||
|
|
@ -774,7 +937,7 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
lineHeight: "1.6",
|
||||
}}
|
||||
>
|
||||
{state.final_report.conclusions.map((conclusion, idx) => (
|
||||
{state.result.conclusions.map((conclusion, idx) => (
|
||||
<li
|
||||
key={`conclusion-${idx}-${conclusion.substring(0, 30)}`}
|
||||
style={{ marginBottom: "0.5rem" }}
|
||||
|
|
@ -784,6 +947,27 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div style={{ marginBottom: "1.5rem" }}>
|
||||
<h4
|
||||
style={{
|
||||
fontSize: "0.875rem",
|
||||
fontWeight: "600",
|
||||
color: "#718096",
|
||||
marginBottom: "0.5rem",
|
||||
}}
|
||||
>
|
||||
Methodology
|
||||
</h4>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "0.875rem",
|
||||
color: "#4a5568",
|
||||
lineHeight: "1.6",
|
||||
}}
|
||||
>
|
||||
<Markdown content={state.result.methodology} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4
|
||||
style={{
|
||||
|
|
@ -793,10 +977,8 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
marginBottom: "0.5rem",
|
||||
}}
|
||||
>
|
||||
Citations
|
||||
Insights Used ({state.result.insights_used.length})
|
||||
</h4>
|
||||
{state.final_report.citations &&
|
||||
state.final_report.citations.length > 0 ? (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
|
|
@ -804,9 +986,13 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
gap: "0.5rem",
|
||||
}}
|
||||
>
|
||||
{state.final_report.citations.map((citation) => (
|
||||
{state.result.insights_used.map((insightId, idx) => {
|
||||
const insight = state.context.insights.find(
|
||||
(i) => i.id === insightId,
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={citation.document_uri}
|
||||
key={`insight-${idx}-${insightId}`}
|
||||
style={{
|
||||
padding: "0.5rem",
|
||||
background: "#f7fafc",
|
||||
|
|
@ -814,45 +1000,31 @@ export default function StateDisplay({ state }: StateDisplayProps) {
|
|||
border: "1px solid #e2e8f0",
|
||||
}}
|
||||
>
|
||||
{insight ? (
|
||||
<div
|
||||
style={{
|
||||
fontSize: "0.875rem",
|
||||
fontWeight: "600",
|
||||
color: "#2d3748",
|
||||
marginBottom: "0.25rem",
|
||||
lineHeight: "1.4",
|
||||
}}
|
||||
>
|
||||
{citation.document_title}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "0.75rem",
|
||||
color: "#718096",
|
||||
}}
|
||||
>
|
||||
{citation.chunk_ids.length} chunk
|
||||
{citation.chunk_ids.length !== 1 ? "s" : ""}{" "}
|
||||
referenced
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<Markdown content={insight.summary} />
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: "0.75rem",
|
||||
fontSize: "0.875rem",
|
||||
color: "#718096",
|
||||
lineHeight: "1.4",
|
||||
}}
|
||||
>
|
||||
{state.final_report.sources?.map((source) => (
|
||||
<div key={source} style={{ marginBottom: "0.25rem" }}>
|
||||
{source}
|
||||
</div>
|
||||
))}
|
||||
Insight ID: {insightId}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
116
examples/ag-ui-research/frontend/package-lock.json
generated
116
examples/ag-ui-research/frontend/package-lock.json
generated
|
|
@ -42,6 +42,7 @@
|
|||
"version": "0.0.40",
|
||||
"resolved": "https://registry.npmjs.org/@ag-ui/client/-/client-0.0.40.tgz",
|
||||
"integrity": "sha512-4ftyZgMN7DIAX64k7Mdex/KGq7lfz8yxEKzniqosD6TE/xk65k4Z0v3bxTzPk2iS2+Cj2uVBgFkb5lC7k5Loqg==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ag-ui/core": "0.0.39",
|
||||
"@ag-ui/encoder": "0.0.39",
|
||||
|
|
@ -58,6 +59,7 @@
|
|||
"version": "0.0.39",
|
||||
"resolved": "https://registry.npmjs.org/@ag-ui/core/-/core-0.0.39.tgz",
|
||||
"integrity": "sha512-T5Hp4oFkQ+H5MynWAvSwrX/rNYJOD+PJ4qPQ0o771oSZQAxoIvDDft47Cx5wRyBNNLXAe1RWqJjfWUUwJFNKqA==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"rxjs": "7.8.1",
|
||||
"zod": "^3.22.4"
|
||||
|
|
@ -67,6 +69,7 @@
|
|||
"version": "0.0.39",
|
||||
"resolved": "https://registry.npmjs.org/@ag-ui/encoder/-/encoder-0.0.39.tgz",
|
||||
"integrity": "sha512-6fsoFwPWkStK7Uyj3pwBn7+aQjUWf7pbDTSI43cD53sBLvTr5oEFNnoKOzRfC5UqvHc4JjUIuLKPQyjHRwWg4g==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ag-ui/core": "0.0.39",
|
||||
"@ag-ui/proto": "0.0.39"
|
||||
|
|
@ -92,6 +95,7 @@
|
|||
"version": "0.0.39",
|
||||
"resolved": "https://registry.npmjs.org/@ag-ui/proto/-/proto-0.0.39.tgz",
|
||||
"integrity": "sha512-xlj/PzZHkJ3CgoQC5QP9g7DEl/78wUK1+A2rdkoLKoNAMOkM2g6jKw0N88iFIh5GZhtiCNN2wb8XwRWPYx9XQQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ag-ui/core": "0.0.39",
|
||||
"@bufbuild/protobuf": "^2.2.5",
|
||||
|
|
@ -127,6 +131,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz",
|
||||
"integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@aws-crypto/util": "^5.2.0",
|
||||
"@aws-sdk/types": "^3.222.0",
|
||||
|
|
@ -161,6 +166,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-agent-runtime/-/client-bedrock-agent-runtime-3.911.0.tgz",
|
||||
"integrity": "sha512-gXxE6CecfTVM9Uuuja2W69WBT0XoEi0AyTHuN4E7RC7s0SbVZvkCmCgtJ60LTsg7akAa8+AEbIn8aNqxkxV7Kw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-browser": "5.2.0",
|
||||
"@aws-crypto/sha256-js": "5.2.0",
|
||||
|
|
@ -240,6 +246,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.911.0.tgz",
|
||||
"integrity": "sha512-DScoogLAX1WaDF7N3sDvA4l7PKUXRqZWTP1sTjUfUK3hwpAm624RfoQFoxgz5wQPv1zs5Slvntmg8WnPo0T9LQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-browser": "5.2.0",
|
||||
"@aws-crypto/sha256-js": "5.2.0",
|
||||
|
|
@ -325,6 +332,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-kendra/-/client-kendra-3.911.0.tgz",
|
||||
"integrity": "sha512-insxvWLbh5mRsymBvsq+O/yPgpF1RnYUi2cNYzJDLzHnpsvpdtvrKO1Ymg0Trk32Xh5dW/9W7IMrqI1qDMzBrw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-browser": "5.2.0",
|
||||
"@aws-crypto/sha256-js": "5.2.0",
|
||||
|
|
@ -632,6 +640,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.911.0.tgz",
|
||||
"integrity": "sha512-4oGpLwgQCKNtVoJROztJ4v7lZLhCqcUMX6pe/DQ2aU0TktZX7EczMCIEGjVo5b7yHwSNWt2zW0tDdgVUTsMHPw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@aws-sdk/credential-provider-env": "3.911.0",
|
||||
"@aws-sdk/credential-provider-http": "3.911.0",
|
||||
|
|
@ -1387,7 +1396,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@browserbasehq/sdk/-/sdk-2.6.0.tgz",
|
||||
"integrity": "sha512-83iXP5D7xMm8Wyn66TUaUrgoByCmAJuoMoZQI3sGg3JAiMlTfnCIMqyVBoNSaItaPIkaCnrsj6LiusmXV2X9YA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/node-fetch": "^2.6.4",
|
||||
|
|
@ -1403,7 +1411,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
|
||||
"integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
|
|
@ -1412,15 +1419,13 @@
|
|||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@browserbasehq/stagehand": {
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@browserbasehq/stagehand/-/stagehand-1.14.0.tgz",
|
||||
"integrity": "sha512-Hi/EzgMFWz+FKyepxHTrqfTPjpsuBS4zRy3e9sbMpBgLPv+9c0R+YZEvS7Bw4mTS66QtvvURRT6zgDGFotthVQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.27.3",
|
||||
"@browserbasehq/sdk": "^2.0.0",
|
||||
|
|
@ -1440,7 +1445,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.27.3.tgz",
|
||||
"integrity": "sha512-IjLt0gd3L4jlOfilxVXTifn42FnVffMgDC04RJK1KDZpmkBWLv0XC92MVVmkxrFZNS/7l3xWgP/I3nqtX1sQHw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/node-fetch": "^2.6.4",
|
||||
|
|
@ -1456,7 +1460,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
|
||||
"integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
|
|
@ -1465,8 +1468,7 @@
|
|||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@bufbuild/protobuf": {
|
||||
"version": "2.9.0",
|
||||
|
|
@ -2087,7 +2089,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@ibm-cloud/watsonx-ai/-/watsonx-ai-1.7.0.tgz",
|
||||
"integrity": "sha512-TmLaoFXmLc7yVFJIQS25mzZcuWfju4JmRXcO62KthDKNENyPpXXJukrHN6gXfv1BotzFt0M2kyRnO1Vt8ZLlxQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^18.0.0",
|
||||
"extend": "3.0.2",
|
||||
|
|
@ -2103,7 +2104,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
|
||||
"integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
|
|
@ -2112,8 +2112,7 @@
|
|||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@img/colour": {
|
||||
"version": "1.0.0",
|
||||
|
|
@ -2558,6 +2557,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@langchain/aws/-/aws-0.1.15.tgz",
|
||||
"integrity": "sha512-oyOMhTHP0rxdSCVI/g5KXYCOs9Kq/FpXMZbOk1JSIUoaIzUg4p6d98lsHu7erW//8NSaT+SX09QRbVDAgt7pNA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-bedrock-agent-runtime": "^3.755.0",
|
||||
"@aws-sdk/client-bedrock-runtime": "^3.840.0",
|
||||
|
|
@ -3110,6 +3110,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.78.tgz",
|
||||
"integrity": "sha512-Nn0x9erQlK3zgtRU1Z8NUjLuyW0gzdclMsvLQ6wwLeDqV91pE+YKl6uQb+L2NUDs4F0N7c2Zncgz46HxrvPzuA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@cfworker/json-schema": "^4.0.2",
|
||||
"ansi-styles": "^5.0.0",
|
||||
|
|
@ -3203,7 +3204,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-0.1.10.tgz",
|
||||
"integrity": "sha512-9srSCb2bSvcvehMgjA2sMMwX0o1VUgPN6ghwm5Fwc9JGAKsQa6n1S4eCwy1h4abuYxwajH5n3spBw+4I2WYbgw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"p-queue": "^6.6.2",
|
||||
|
|
@ -3236,7 +3236,6 @@
|
|||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
|
|
@ -4462,6 +4461,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz",
|
||||
"integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@smithy/util-buffer-from": "^2.2.0",
|
||||
"tslib": "^2.6.2"
|
||||
|
|
@ -4535,8 +4535,7 @@
|
|||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
|
||||
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/debug": {
|
||||
"version": "4.1.12",
|
||||
|
|
@ -4628,6 +4627,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
|
||||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
|
|
@ -4658,8 +4658,7 @@
|
|||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
|
||||
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/unist": {
|
||||
"version": "2.0.11",
|
||||
|
|
@ -5176,6 +5175,7 @@
|
|||
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz",
|
||||
"integrity": "sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/validator": "^13.11.8",
|
||||
"libphonenumber-js": "^1.11.1",
|
||||
|
|
@ -5785,6 +5785,7 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"strnum": "^2.1.0"
|
||||
},
|
||||
|
|
@ -5810,7 +5811,6 @@
|
|||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz",
|
||||
"integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"readable-web-to-node-stream": "^3.0.0",
|
||||
"strtok3": "^6.2.4",
|
||||
|
|
@ -5876,7 +5876,6 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
|
|
@ -5947,6 +5946,20 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
|
|
@ -6035,6 +6048,7 @@
|
|||
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.9.0.tgz",
|
||||
"integrity": "sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"arrify": "^2.0.0",
|
||||
"base64-js": "^1.3.0",
|
||||
|
|
@ -6083,6 +6097,7 @@
|
|||
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz",
|
||||
"integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
|
||||
}
|
||||
|
|
@ -6117,6 +6132,7 @@
|
|||
"resolved": "https://registry.npmjs.org/graphql-scalars/-/graphql-scalars-1.25.0.tgz",
|
||||
"integrity": "sha512-b0xyXZeRFkne4Eq7NAnL400gStGqG/Sx9VqX0A05nHyEbv57UJnWKsjNnrpVqv5e/8N1MUxkt0wwcRXbiyKcFg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.5.0"
|
||||
},
|
||||
|
|
@ -6132,6 +6148,7 @@
|
|||
"resolved": "https://registry.npmjs.org/graphql-yoga/-/graphql-yoga-5.16.0.tgz",
|
||||
"integrity": "sha512-/R2dJea7WgvNlXRU4F8iFwWd95Qn1mN+R+yC8XBs1wKjUzr0Pvv8cGYtt6UUcVHw5CiDEtu7iQY5oOe3sDAWCQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@envelop/core": "^5.3.0",
|
||||
"@envelop/instrumentation": "^1.0.0",
|
||||
|
|
@ -6709,7 +6726,6 @@
|
|||
"resolved": "https://registry.npmjs.org/ibm-cloud-sdk-core/-/ibm-cloud-sdk-core-5.4.3.tgz",
|
||||
"integrity": "sha512-D0lvClcoCp/HXyaFlCbOT4aTYgGyeIb4ncxZpxRuiuw7Eo79C6c49W53+8WJRD9nxzT5vrIdaky3NBcTdBtaEg==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/node": "^18.19.80",
|
||||
|
|
@ -6736,7 +6752,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
|
||||
"integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
|
|
@ -6745,8 +6760,7 @@
|
|||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
|
|
@ -6905,8 +6919,7 @@
|
|||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||
"integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jose": {
|
||||
"version": "5.10.0",
|
||||
|
|
@ -6976,7 +6989,6 @@
|
|||
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
|
||||
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"jws": "^3.2.2",
|
||||
"lodash.includes": "^4.3.0",
|
||||
|
|
@ -6999,7 +7011,6 @@
|
|||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
|
||||
"integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"buffer-equal-constant-time": "^1.0.1",
|
||||
"ecdsa-sig-formatter": "1.0.11",
|
||||
|
|
@ -7011,7 +7022,6 @@
|
|||
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
|
||||
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"jwa": "^1.4.1",
|
||||
"safe-buffer": "^5.0.1"
|
||||
|
|
@ -7242,50 +7252,43 @@
|
|||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isboolean": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isinteger": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isnumber": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isstring": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.once": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/long": {
|
||||
"version": "5.3.2",
|
||||
|
|
@ -8801,6 +8804,7 @@
|
|||
"resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz",
|
||||
"integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/node-fetch": "^2.6.4",
|
||||
|
|
@ -8953,7 +8957,6 @@
|
|||
"resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz",
|
||||
"integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
|
|
@ -9054,7 +9057,6 @@
|
|||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.0.tgz",
|
||||
"integrity": "sha512-1SXl7pMfemAMSDn5rkPeZljxOCYAmQnYLBTExuh6E8USHXGSX3dx6lYZN/xPpTz1vimXmPA9CDnILvmJaB8aSQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
|
|
@ -9192,15 +9194,13 @@
|
|||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/psl": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
|
||||
"integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"punycode": "^2.3.1"
|
||||
},
|
||||
|
|
@ -9223,7 +9223,6 @@
|
|||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
|
|
@ -9247,8 +9246,7 @@
|
|||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
||||
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/quick-format-unescaped": {
|
||||
"version": "4.0.4",
|
||||
|
|
@ -9285,6 +9283,7 @@
|
|||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
||||
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
|
@ -9294,6 +9293,7 @@
|
|||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
|
||||
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
|
|
@ -9376,7 +9376,6 @@
|
|||
"resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz",
|
||||
"integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"readable-stream": "^4.7.0"
|
||||
},
|
||||
|
|
@ -10385,8 +10384,7 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/retry": {
|
||||
"version": "0.13.1",
|
||||
|
|
@ -10402,7 +10400,6 @@
|
|||
"resolved": "https://registry.npmjs.org/retry-axios/-/retry-axios-2.6.0.tgz",
|
||||
"integrity": "sha512-pOLi+Gdll3JekwuFjXO3fTq+L9lzMQGcSq7M5gIjExcl3Gu1hd4XXuf5o3+LuSBsaULQH7DiNbsqPd1chVpQGQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.7.0"
|
||||
},
|
||||
|
|
@ -10820,7 +10817,6 @@
|
|||
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz",
|
||||
"integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@tokenizer/token": "^0.3.0",
|
||||
"peek-readable": "^4.1.0"
|
||||
|
|
@ -10930,7 +10926,6 @@
|
|||
"resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz",
|
||||
"integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@tokenizer/token": "^0.3.0",
|
||||
"ieee754": "^1.2.1"
|
||||
|
|
@ -10948,7 +10943,6 @@
|
|||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
|
||||
"integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"psl": "^1.1.33",
|
||||
"punycode": "^2.1.1",
|
||||
|
|
@ -11255,7 +11249,6 @@
|
|||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
|
||||
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
|
|
@ -11280,7 +11273,6 @@
|
|||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"querystringify": "^2.1.1",
|
||||
"requires-port": "^1.0.0"
|
||||
|
|
@ -11578,7 +11570,6 @@
|
|||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
|
|
@ -11663,6 +11654,7 @@
|
|||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,23 @@
|
|||
# haiku.rag configuration for ag-ui-research example
|
||||
# Copy to haiku.rag.yaml and customize
|
||||
|
||||
qa:
|
||||
research:
|
||||
provider: ollama
|
||||
model: gpt-oss:latest
|
||||
max_iterations: 3
|
||||
confidence_threshold: 0.8
|
||||
max_concurrency: 1
|
||||
|
||||
providers:
|
||||
ollama:
|
||||
base_url: http://host.docker.internal:11434
|
||||
|
||||
# For OpenAI:
|
||||
# qa:
|
||||
# research:
|
||||
# provider: openai
|
||||
# model: gpt-4o-mini
|
||||
|
||||
# For Anthropic:
|
||||
# qa:
|
||||
# research:
|
||||
# provider: anthropic
|
||||
# model: claude-3-5-haiku-20241022
|
||||
|
|
|
|||
|
|
@ -156,8 +156,11 @@ def create_search_node[AgentDepsT: GraphAgentDeps](
|
|||
deps: GraphDeps = ctx.deps # type: ignore[assignment]
|
||||
sub_q = ctx.inputs
|
||||
|
||||
# Create unique step name from question text
|
||||
step_name = f"search: {sub_q}"
|
||||
|
||||
if deps.agui_emitter and with_step_wrapper:
|
||||
deps.agui_emitter.start_step("search_one")
|
||||
deps.agui_emitter.start_step(step_name)
|
||||
|
||||
try:
|
||||
# Create semaphore if not already provided
|
||||
|
|
|
|||
Loading…
Reference in a new issue