Basic skeleton for running an ag-ui demo using starlette and nextjs
This commit is contained in:
parent
b9c22e634c
commit
e3b07db8b9
26 changed files with 16682 additions and 0 deletions
23
examples/ag-ui-research/.env.example
Normal file
23
examples/ag-ui-research/.env.example
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# QA Provider for the research agent (ollama, openai, anthropic, etc.)
|
||||||
|
QA_PROVIDER=ollama
|
||||||
|
|
||||||
|
# QA Model name
|
||||||
|
QA_MODEL=gpt-oss:latest
|
||||||
|
|
||||||
|
# Ollama base URL (only needed if using ollama provider)
|
||||||
|
# For Docker: http://host.docker.internal:11434
|
||||||
|
# For local development: http://localhost:11434
|
||||||
|
OLLAMA_BASE_URL=http://host.docker.internal:11434
|
||||||
|
|
||||||
|
# Path to the LanceDB database
|
||||||
|
# For Docker: /app/data/haiku_rag.lancedb
|
||||||
|
# For local development: ./haiku_rag.lancedb
|
||||||
|
DB_PATH=haiku_rag.lancedb
|
||||||
|
|
||||||
|
# API keys (set as needed for your QA provider)
|
||||||
|
# OPENAI_API_KEY=your-key-here
|
||||||
|
# ANTHROPIC_API_KEY=your-key-here
|
||||||
|
|
||||||
|
# Embedding provider configuration (optional, defaults will be used)
|
||||||
|
# EMBEDDING_PROVIDER=openai
|
||||||
|
# EMBEDDING_MODEL=text-embedding-3-small
|
||||||
47
examples/ag-ui-research/README.md
Normal file
47
examples/ag-ui-research/README.md
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
# Haiku.rag Interactive Research Assistant
|
||||||
|
|
||||||
|
Interactive research assistant powered by **Haiku.rag**, **Pydantic AI**, and **AG-UI** protocol. Ask complex questions and watch the multi-agent research process unfold in real-time with synchronized state between backend and frontend.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Docker and Docker Compose
|
||||||
|
- Ollama running on host (or configure another QA provider)
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
1. **Clone the repository**
|
||||||
|
```bash
|
||||||
|
git clone <repository-url>
|
||||||
|
cd haiku.rag/examples/ag-ui-research
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Configure environment** (optional, defaults to Ollama with gpt-oss:latest)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# Edit .env to customize provider/model or add API keys
|
||||||
|
```
|
||||||
|
|
||||||
|
See [haiku.rag configuration docs](https://ggozad.github.io/haiku.rag/configuration/) for provider setup.
|
||||||
|
|
||||||
|
3. **Prepare your knowledge base**
|
||||||
|
|
||||||
|
Create and populate a haiku.rag database:
|
||||||
|
```bash
|
||||||
|
# Create a data directory
|
||||||
|
mkdir -p data
|
||||||
|
# Add documents (requires haiku-rag installed locally)
|
||||||
|
haiku-rag add "Your documents here" --db data/haiku_rag.lancedb
|
||||||
|
# Or add from files
|
||||||
|
haiku-rag add-src document.pdf --db data/haiku_rag.lancedb
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Start the application**
|
||||||
|
```bash
|
||||||
|
docker compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Open the application**
|
||||||
|
- Frontend: http://localhost:3000
|
||||||
12
examples/ag-ui-research/backend/.gitignore
vendored
Normal file
12
examples/ag-ui-research/backend/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
*.egg-info/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
27
examples/ag-ui-research/backend/Dockerfile
Normal file
27
examples/ag-ui-research/backend/Dockerfile
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Enable bytecode compilation
|
||||||
|
ENV UV_COMPILE_BYTECODE=1
|
||||||
|
|
||||||
|
# Copy from the cache instead of linking since it's a mounted volume
|
||||||
|
ENV UV_LINK_MODE=copy
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||||
|
--mount=type=bind,source=uv.lock,target=uv.lock \
|
||||||
|
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
||||||
|
uv sync --frozen --no-install-project --no-dev
|
||||||
|
|
||||||
|
# Copy the project into the image
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Sync the project
|
||||||
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||||
|
uv sync --frozen --no-dev
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# Run with uv
|
||||||
|
CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
|
||||||
17
examples/ag-ui-research/backend/README.md
Normal file
17
examples/ag-ui-research/backend/README.md
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Haiku.rag Research Assistant Backend
|
||||||
|
|
||||||
|
FastAPI backend for the haiku.rag interactive research assistant, using Pydantic AI with AG-UI protocol support.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv sync
|
||||||
|
uv run python main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
The server starts on `http://localhost:8000` and uses [haiku.rag configuration](https://ggozad.github.io/haiku.rag/configuration/).
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
- `GET /health` - Health check
|
||||||
|
- `POST /agent` - AG-UI protocol endpoint
|
||||||
70
examples/ag-ui-research/backend/agent.py
Normal file
70
examples/ag-ui-research/backend/agent.py
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
"""Pydantic AI research agent for haiku.rag with AG-UI protocol."""
|
||||||
|
|
||||||
|
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.config import Config
|
||||||
|
from haiku.rag.graph.common import get_model
|
||||||
|
|
||||||
|
|
||||||
|
class ResearchState(BaseModel):
|
||||||
|
"""Shared state between research agent and frontend."""
|
||||||
|
|
||||||
|
question: str = ""
|
||||||
|
status: str = "idle"
|
||||||
|
current_iteration: int = 0
|
||||||
|
max_iterations: int = 2
|
||||||
|
confidence: float = 0.0
|
||||||
|
plan: list[dict] = []
|
||||||
|
findings: list[dict] = []
|
||||||
|
final_report: dict | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def _as_state_snapshot(ctx: RunContext[StateDeps[ResearchState]]) -> StateSnapshotEvent:
|
||||||
|
"""Helper to create a state snapshot event for AG-UI."""
|
||||||
|
return StateSnapshotEvent(type=EventType.STATE_SNAPSHOT, snapshot=ctx.deps.state)
|
||||||
|
|
||||||
|
|
||||||
|
def create_agent(
|
||||||
|
qa_provider: str = Config.QA_PROVIDER, qa_model: str = Config.QA_MODEL
|
||||||
|
) -> Agent[StateDeps[ResearchState], 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)
|
||||||
|
"""
|
||||||
|
agent = Agent(
|
||||||
|
model=get_model(qa_provider, qa_model),
|
||||||
|
deps_type=StateDeps[ResearchState],
|
||||||
|
instructions="""You are a research assistant powered by haiku.rag.
|
||||||
|
|
||||||
|
You help users conduct deep research on complex questions by:
|
||||||
|
- Breaking down questions into sub-questions
|
||||||
|
- Searching through a knowledge base
|
||||||
|
- Evaluating findings for completeness and confidence
|
||||||
|
- Synthesizing comprehensive reports with citations
|
||||||
|
|
||||||
|
The state is shared with the frontend application, showing research progress in real-time.
|
||||||
|
|
||||||
|
Currently, tools are placeholder stubs. Full integration with haiku.rag research pipeline
|
||||||
|
will be implemented in the next phase.""",
|
||||||
|
)
|
||||||
|
|
||||||
|
@agent.tool
|
||||||
|
async def get_research_status(ctx: RunContext[StateDeps[ResearchState]]) -> dict:
|
||||||
|
"""Get the current research state and progress."""
|
||||||
|
return {
|
||||||
|
"question": ctx.deps.state.question,
|
||||||
|
"status": ctx.deps.state.status,
|
||||||
|
"iteration": ctx.deps.state.current_iteration,
|
||||||
|
"max_iterations": ctx.deps.state.max_iterations,
|
||||||
|
"confidence": ctx.deps.state.confidence,
|
||||||
|
"has_plan": len(ctx.deps.state.plan) > 0,
|
||||||
|
"findings_count": len(ctx.deps.state.findings),
|
||||||
|
"has_report": ctx.deps.state.final_report is not None,
|
||||||
|
}
|
||||||
|
|
||||||
|
return agent
|
||||||
63
examples/ag-ui-research/backend/main.py
Normal file
63
examples/ag-ui-research/backend/main.py
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
"""Main entry point for the haiku.rag AG-UI research assistant backend."""
|
||||||
|
|
||||||
|
from agent import ResearchState, create_agent
|
||||||
|
from pydantic_ai.ag_ui import StateDeps
|
||||||
|
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 haiku.rag.config import Config
|
||||||
|
|
||||||
|
# Create research agent instance using haiku.rag config
|
||||||
|
agent = create_agent()
|
||||||
|
|
||||||
|
|
||||||
|
async def health(request):
|
||||||
|
"""Health check endpoint."""
|
||||||
|
return JSONResponse(
|
||||||
|
{
|
||||||
|
"status": "healthy",
|
||||||
|
"agent_model": str(agent.model),
|
||||||
|
"qa_provider": Config.QA_PROVIDER,
|
||||||
|
"qa_model": Config.QA_MODEL,
|
||||||
|
"ollama_base_url": Config.OLLAMA_BASE_URL,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Convert PydanticAI agent to AG-UI compatible ASGI app
|
||||||
|
ag_ui_app = agent.to_ag_ui(deps=StateDeps(ResearchState())) # type: ignore[arg-type]
|
||||||
|
|
||||||
|
# Mount the AG-UI app at /agent and add health endpoint
|
||||||
|
app = Starlette(
|
||||||
|
routes=[
|
||||||
|
Route("/health", health),
|
||||||
|
Mount("/agent", ag_ui_app),
|
||||||
|
],
|
||||||
|
middleware=[
|
||||||
|
Middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["http://localhost:3000", "http://frontend:3000"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
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",
|
||||||
|
port=8000,
|
||||||
|
reload=True,
|
||||||
|
)
|
||||||
26
examples/ag-ui-research/backend/pyproject.toml
Normal file
26
examples/ag-ui-research/backend/pyproject.toml
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
[project]
|
||||||
|
name = "haiku-rag-research-assistant"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Haiku.rag research assistant with AG-UI protocol support"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
dependencies = [
|
||||||
|
"starlette>=0.45.2",
|
||||||
|
"uvicorn[standard]>=0.34.2",
|
||||||
|
"pydantic-ai-slim[ag-ui,openai]>=1.1.0",
|
||||||
|
"python-dotenv>=1.0.1",
|
||||||
|
"haiku-rag>=0.12.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"pyright>=1.1.406",
|
||||||
|
"ruff>=0.13.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.wheel]
|
||||||
|
packages = ["."]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
4011
examples/ag-ui-research/backend/uv.lock
Normal file
4011
examples/ag-ui-research/backend/uv.lock
Normal file
File diff suppressed because it is too large
Load diff
50
examples/ag-ui-research/docker-compose.yml
Normal file
50
examples/ag-ui-research/docker-compose.yml
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
services:
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
environment:
|
||||||
|
- QA_PROVIDER=${QA_PROVIDER:-ollama}
|
||||||
|
- QA_MODEL=${QA_MODEL:-gpt-oss:latest}
|
||||||
|
- DB_PATH=${DB_PATH:-/app/data/haiku_rag.lancedb}
|
||||||
|
- OLLAMA_BASE_URL=${OLLAMA_BASE_URL:-http://host.docker.internal:11434}
|
||||||
|
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
||||||
|
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
||||||
|
volumes:
|
||||||
|
- ./backend:/app
|
||||||
|
- /app/.venv
|
||||||
|
- ./data:/app/data
|
||||||
|
networks:
|
||||||
|
- ag-ui-network
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: ./frontend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
- BACKEND_URL=http://backend:8000
|
||||||
|
volumes:
|
||||||
|
- ./frontend:/app
|
||||||
|
- /app/node_modules
|
||||||
|
- /app/.next
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
networks:
|
||||||
|
- ag-ui-network
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
networks:
|
||||||
|
ag-ui-network:
|
||||||
|
driver: bridge
|
||||||
7
examples/ag-ui-research/frontend/.dockerignore
Normal file
7
examples/ag-ui-research/frontend/.dockerignore
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
node_modules
|
||||||
|
.next
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
README.md
|
||||||
|
npm-debug.log
|
||||||
|
.env*.local
|
||||||
39
examples/ag-ui-research/frontend/.gitignore
vendored
Normal file
39
examples/ag-ui-research/frontend/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# env files
|
||||||
|
.env*.local
|
||||||
|
.env
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
12
examples/ag-ui-research/frontend/Dockerfile
Normal file
12
examples/ag-ui-research/frontend/Dockerfile
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Development Dockerfile for Next.js frontend
|
||||||
|
FROM node:22-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package.json package-lock.json* ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY . .
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Run in development mode with hot reload
|
||||||
|
CMD ["npm", "run", "dev"]
|
||||||
33
examples/ag-ui-research/frontend/app/api/copilotkit/route.ts
Normal file
33
examples/ag-ui-research/frontend/app/api/copilotkit/route.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { HttpAgent } from "@ag-ui/client";
|
||||||
|
import {
|
||||||
|
CopilotRuntime,
|
||||||
|
copilotRuntimeNextJSAppRouterEndpoint,
|
||||||
|
ExperimentalEmptyAdapter,
|
||||||
|
} from "@copilotkit/runtime";
|
||||||
|
import type { NextRequest } from "next/server";
|
||||||
|
|
||||||
|
// Service adapter for multi-agent support (empty since we only have one agent)
|
||||||
|
const serviceAdapter = new ExperimentalEmptyAdapter();
|
||||||
|
|
||||||
|
// Connect CopilotKit to PydanticAI via HttpAgent
|
||||||
|
// The HttpAgent creates a bridge between the Next.js frontend and the Python backend
|
||||||
|
// It communicates with the server created by agent.to_ag_ui()
|
||||||
|
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`,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Next.js API route handler that proxies requests between frontend and backend
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
|
||||||
|
runtime,
|
||||||
|
serviceAdapter,
|
||||||
|
endpoint: "/api/copilotkit",
|
||||||
|
});
|
||||||
|
|
||||||
|
return handleRequest(request);
|
||||||
|
}
|
||||||
24
examples/ag-ui-research/frontend/app/globals.css
Normal file
24
examples/ag-ui-research/frontend/app/globals.css
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
max-width: 100vw;
|
||||||
|
overflow-x: hidden;
|
||||||
|
font-family:
|
||||||
|
system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||||
|
"Helvetica Neue", Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: linear-gradient(to bottom, #f8f9fa, #e9ecef);
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
20
examples/ag-ui-research/frontend/app/layout.tsx
Normal file
20
examples/ag-ui-research/frontend/app/layout.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Haiku.rag Research Assistant",
|
||||||
|
description:
|
||||||
|
"Interactive research powered by Haiku.rag, Pydantic AI, and AG-UI",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body>{children}</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
9
examples/ag-ui-research/frontend/app/page.tsx
Normal file
9
examples/ag-ui-research/frontend/app/page.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import Agent from "@/components/Agent";
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<main>
|
||||||
|
<Agent />
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
34
examples/ag-ui-research/frontend/biome.json
Normal file
34
examples/ag-ui-research/frontend/biome.json
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/2.2.6/schema.json",
|
||||||
|
"vcs": {
|
||||||
|
"enabled": false,
|
||||||
|
"clientKind": "git",
|
||||||
|
"useIgnoreFile": false
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"ignoreUnknown": false
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"indentStyle": "tab"
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"recommended": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"javascript": {
|
||||||
|
"formatter": {
|
||||||
|
"quoteStyle": "double"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"assist": {
|
||||||
|
"enabled": true,
|
||||||
|
"actions": {
|
||||||
|
"source": {
|
||||||
|
"organizeImports": "on"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
122
examples/ag-ui-research/frontend/components/Agent.tsx
Normal file
122
examples/ag-ui-research/frontend/components/Agent.tsx
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CopilotKit,
|
||||||
|
useCoAgent,
|
||||||
|
useCoAgentStateRender,
|
||||||
|
} from "@copilotkit/react-core";
|
||||||
|
import { CopilotSidebar } from "@copilotkit/react-ui";
|
||||||
|
import "@copilotkit/react-ui/styles.css";
|
||||||
|
import StateDisplay from "./StateDisplay";
|
||||||
|
|
||||||
|
interface ResearchState {
|
||||||
|
question: string;
|
||||||
|
status: string;
|
||||||
|
current_iteration: number;
|
||||||
|
max_iterations: number;
|
||||||
|
confidence: number;
|
||||||
|
plan: Array<Record<string, unknown>>;
|
||||||
|
findings: Array<Record<string, unknown>>;
|
||||||
|
final_report: Record<string, unknown> | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function AgentContent() {
|
||||||
|
// Use useCoAgent to sync state with the backend research agent
|
||||||
|
const { state } = useCoAgent<ResearchState>({
|
||||||
|
name: "research_agent",
|
||||||
|
initialState: {
|
||||||
|
question: "",
|
||||||
|
status: "idle",
|
||||||
|
current_iteration: 0,
|
||||||
|
max_iterations: 2,
|
||||||
|
confidence: 0.0,
|
||||||
|
plan: [],
|
||||||
|
findings: [],
|
||||||
|
final_report: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render state updates from the research agent
|
||||||
|
useCoAgentStateRender<ResearchState>({
|
||||||
|
name: "research_agent",
|
||||||
|
render: ({ state: newState }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: "1rem",
|
||||||
|
background: "#e6f7ff",
|
||||||
|
borderRadius: "4px",
|
||||||
|
marginBottom: "0.5rem",
|
||||||
|
border: "1px solid #91d5ff",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<strong>Research Update:</strong> Status: {newState.status},
|
||||||
|
Iteration: {newState.current_iteration}/{newState.max_iterations},
|
||||||
|
Confidence: {(newState.confidence * 100).toFixed(0)}%
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ display: "flex", height: "100vh" }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
padding: "2rem",
|
||||||
|
overflow: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
maxWidth: "800px",
|
||||||
|
margin: "0 auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<header style={{ marginBottom: "2rem" }}>
|
||||||
|
<h1
|
||||||
|
style={{
|
||||||
|
fontSize: "2.5rem",
|
||||||
|
fontWeight: "bold",
|
||||||
|
marginBottom: "0.5rem",
|
||||||
|
color: "#1a202c",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Haiku.rag Research Assistant
|
||||||
|
</h1>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: "1.125rem",
|
||||||
|
color: "#4a5568",
|
||||||
|
lineHeight: "1.6",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Interactive research powered by <strong>Haiku.rag</strong>,{" "}
|
||||||
|
<strong>Pydantic AI</strong>, and <strong>AG-UI</strong>
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<StateDisplay state={state} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CopilotSidebar
|
||||||
|
defaultOpen={true}
|
||||||
|
clickOutsideToClose={false}
|
||||||
|
labels={{
|
||||||
|
title: "Research Assistant",
|
||||||
|
initial:
|
||||||
|
"Hello! I can help you conduct deep research on complex questions using the haiku.rag knowledge base. Ask me anything!",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Agent() {
|
||||||
|
return (
|
||||||
|
<CopilotKit runtimeUrl="/api/copilotkit" agent="research_agent">
|
||||||
|
<AgentContent />
|
||||||
|
</CopilotKit>
|
||||||
|
);
|
||||||
|
}
|
||||||
277
examples/ag-ui-research/frontend/components/StateDisplay.tsx
Normal file
277
examples/ag-ui-research/frontend/components/StateDisplay.tsx
Normal file
|
|
@ -0,0 +1,277 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
interface ResearchState {
|
||||||
|
question: string;
|
||||||
|
status: string;
|
||||||
|
current_iteration: number;
|
||||||
|
max_iterations: number;
|
||||||
|
confidence: number;
|
||||||
|
plan: Array<Record<string, unknown>>;
|
||||||
|
findings: Array<Record<string, unknown>>;
|
||||||
|
final_report: Record<string, unknown> | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StateDisplayProps {
|
||||||
|
state: ResearchState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function StateDisplay({ state }: StateDisplayProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: "white",
|
||||||
|
borderRadius: "8px",
|
||||||
|
padding: "1.5rem",
|
||||||
|
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
||||||
|
marginTop: "2rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
style={{
|
||||||
|
fontSize: "1.5rem",
|
||||||
|
fontWeight: "600",
|
||||||
|
marginBottom: "1rem",
|
||||||
|
color: "#2d3748",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Research State
|
||||||
|
</h2>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
color: "#4a5568",
|
||||||
|
lineHeight: "1.6",
|
||||||
|
marginBottom: "1rem",
|
||||||
|
fontSize: "0.875rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
This state is shared between the research agent and the frontend via the
|
||||||
|
AG-UI protocol.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "grid",
|
||||||
|
gap: "1rem",
|
||||||
|
marginTop: "1rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: "1rem",
|
||||||
|
background: "#f7fafc",
|
||||||
|
borderRadius: "4px",
|
||||||
|
border: "1px solid #e2e8f0",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: "0.875rem",
|
||||||
|
color: "#718096",
|
||||||
|
marginBottom: "0.25rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Question
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: "1.125rem",
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: "#2d3748",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{state.question || "No question yet"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "repeat(2, 1fr)",
|
||||||
|
gap: "1rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: "1rem",
|
||||||
|
background: "#f7fafc",
|
||||||
|
borderRadius: "4px",
|
||||||
|
border: "1px solid #e2e8f0",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: "0.875rem",
|
||||||
|
color: "#718096",
|
||||||
|
marginBottom: "0.25rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Status
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: "1.125rem",
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: state.status === "idle" ? "#718096" : "#38a169",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{state.status}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: "1rem",
|
||||||
|
background: "#f7fafc",
|
||||||
|
borderRadius: "4px",
|
||||||
|
border: "1px solid #e2e8f0",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: "0.875rem",
|
||||||
|
color: "#718096",
|
||||||
|
marginBottom: "0.25rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Confidence
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: "1.5rem",
|
||||||
|
fontWeight: "bold",
|
||||||
|
color:
|
||||||
|
state.confidence > 0.8
|
||||||
|
? "#38a169"
|
||||||
|
: state.confidence > 0.5
|
||||||
|
? "#d69e2e"
|
||||||
|
: "#e53e3e",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(state.confidence * 100).toFixed(0)}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "repeat(3, 1fr)",
|
||||||
|
gap: "1rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: "1rem",
|
||||||
|
background: "#f7fafc",
|
||||||
|
borderRadius: "4px",
|
||||||
|
border: "1px solid #e2e8f0",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: "0.875rem",
|
||||||
|
color: "#718096",
|
||||||
|
marginBottom: "0.25rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Progress
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: "1.125rem",
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: "#2d3748",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{state.current_iteration} / {state.max_iterations}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: "1rem",
|
||||||
|
background: "#f7fafc",
|
||||||
|
borderRadius: "4px",
|
||||||
|
border: "1px solid #e2e8f0",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: "0.875rem",
|
||||||
|
color: "#718096",
|
||||||
|
marginBottom: "0.25rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Plan Items
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: "1.125rem",
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: "#2d3748",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{state.plan.length}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: "1rem",
|
||||||
|
background: "#f7fafc",
|
||||||
|
borderRadius: "4px",
|
||||||
|
border: "1px solid #e2e8f0",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: "0.875rem",
|
||||||
|
color: "#718096",
|
||||||
|
marginBottom: "0.25rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Findings
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: "1.125rem",
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: "#2d3748",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{state.findings.length}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: "1rem",
|
||||||
|
background: "#f7fafc",
|
||||||
|
borderRadius: "4px",
|
||||||
|
border: "1px solid #e2e8f0",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: "0.875rem",
|
||||||
|
color: "#718096",
|
||||||
|
marginBottom: "0.25rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Final Report
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: "1.125rem",
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: state.final_report ? "#38a169" : "#a0aec0",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{state.final_report ? "Ready" : "Not ready"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
7
examples/ag-ui-research/frontend/next.config.ts
Normal file
7
examples/ag-ui-research/frontend/next.config.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
output: "standalone",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
11690
examples/ag-ui-research/frontend/package-lock.json
generated
Normal file
11690
examples/ag-ui-research/frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
29
examples/ag-ui-research/frontend/package.json
Normal file
29
examples/ag-ui-research/frontend/package.json
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"name": "ag-ui-frontend",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint",
|
||||||
|
"check": "biome check app components",
|
||||||
|
"format": "biome check --write app components"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@ag-ui/client": "^0.0.40",
|
||||||
|
"@copilotkit/react-core": "^1.10.6",
|
||||||
|
"@copilotkit/react-ui": "^1.10.6",
|
||||||
|
"@copilotkit/runtime": "^1.10.6",
|
||||||
|
"next": "15.5.5",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "2.2.6",
|
||||||
|
"@types/node": "^22",
|
||||||
|
"@types/react": "^19",
|
||||||
|
"@types/react-dom": "^19",
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
||||||
0
examples/ag-ui-research/frontend/public/.gitkeep
Normal file
0
examples/ag-ui-research/frontend/public/.gitkeep
Normal file
27
examples/ag-ui-research/frontend/tsconfig.json
Normal file
27
examples/ag-ui-research/frontend/tsconfig.json
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
6
examples/ag-ui-research/package-lock.json
generated
Normal file
6
examples/ag-ui-research/package-lock.json
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"name": "haiku-ag-ui",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue