Skip to content

Commit 19370f9

Browse files
authored
Merge branch 'main' into austin/oss-85-reference-documentation-for-copilotkitcore
2 parents 601e7f1 + 4c6ecbb commit 19370f9

53 files changed

Lines changed: 14463 additions & 85 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/config-allowlist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ examples/integrations/ms-agent-framework-python/next.config.ts
2929
examples/integrations/pydantic-ai/next.config.ts
3030
examples/integrations/strands-python/next.config.ts
3131
examples/showcases/a2a-travel/next.config.ts
32+
examples/showcases/a2ui-pdf-analyst/next.config.ts
3233
examples/showcases/adk-dashboard/next.config.ts
3334
examples/showcases/banking/next.config.mjs
3435
examples/showcases/chatkit-studio/apps/playground/next.config.ts

.github/workflows/static_quality.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ jobs:
4040
with:
4141
persist-credentials: false
4242
ref: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref }}
43+
# Check the head branch out from the head repo, not the base repo.
44+
# For fork PRs the head branch only exists on the fork, so defaulting
45+
# to the base repo makes checkout fail with "a branch or tag with the
46+
# name '<branch>' could not be found". Same-repo PRs resolve to the
47+
# base repo unchanged, so the auto-format push-back below still works.
48+
repository: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name || github.repository }}
4349
token: ${{ secrets.GITHUB_TOKEN }}
4450
# Full history so we can diff HEAD against the current base branch
4551
# tip to scope the formatter to PR-changed files.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# ── Secrets ────────────────────────────────────────────────────────
2+
.env
3+
.env.*
4+
!.env.example
5+
6+
# ── OS / editor ────────────────────────────────────────────────────
7+
.DS_Store
8+
.idea/
9+
.vscode/
10+
*.swp
11+
*~
12+
13+
# ── Node / Next.js ─────────────────────────────────────────────────
14+
node_modules/
15+
.pnp
16+
.pnp.*
17+
.yarn/*
18+
!.yarn/patches
19+
!.yarn/plugins
20+
!.yarn/releases
21+
!.yarn/versions
22+
23+
# Next.js build output + caches
24+
.next/
25+
out/
26+
build/
27+
*.tsbuildinfo
28+
next-env.d.ts
29+
30+
# Test / coverage
31+
coverage/
32+
33+
# Logs
34+
npm-debug.log*
35+
yarn-debug.log*
36+
yarn-error.log*
37+
.pnpm-debug.log*
38+
39+
# Vercel
40+
.vercel
41+
42+
# ── Python / uv (agent/) ──────────────────────────────────────────
43+
.venv/
44+
__pycache__/
45+
*.py[cod]
46+
*$py.class
47+
*.egg-info/
48+
.pytest_cache/
49+
.ruff_cache/
50+
.mypy_cache/
51+
52+
# ── Misc ───────────────────────────────────────────────────────────
53+
*.log
54+
*.pem
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# A2UI PDF Analyst
2+
3+
Chat with your PDF and watch the agent build the UI for each answer. Powered by **A2UI v0.9 (Agent-to-UI)** — the open protocol that lets an agent describe a surface as structured component operations your frontend renders against its own design system. Same chat input, two rendering strategies, one shared 21-component catalog.
4+
5+
https://github.com/user-attachments/assets/c053d2e8-1d40-43cb-8c5a-8e5c121b851f
6+
7+
**Three routes:**
8+
9+
- **`/fixed`** — hand-authored JSON dashboard. The agent only extracts the data (KPIs, trend, segment splits, table rows) and fills the slots. Predictable layout, brand-locked, single LLM call per turn. Best when the shape of the answer is known up front.
10+
- **`/dynamic`** — no pre-written layout. The agent reads the question, picks components from the catalog, and composes the surface on the fly. A net-income query lands as a single StatCard; a segment breakdown becomes a DonutChart; a research-paper summary composes Overline + Heading + Text + Callout + BulletList. Best when the right answer's _form_ varies with the question.
11+
- **`/catalog`** — every component rendered live, filterable by group (Layout, Content, Data viz, Interactive). Doubles as a sanity check on the renderers and a reference for what the agent is allowed to draw from.
12+
13+
All three routes share the same brand tokens (`src/a2ui/theme.css`), the same React renderers (`src/a2ui/catalog/renderers.tsx`), and the same client-side PDF text extraction pipeline (`src/lib/pdf.ts`). Re-skin one stylesheet, every surface updates.
14+
15+
## Prerequisites
16+
17+
- Node.js 20+ and [pnpm](https://pnpm.io/) (npm works too)
18+
- Python 3.12
19+
- [uv](https://docs.astral.sh/uv/) for the Python agent
20+
- An OpenAI API key
21+
22+
## Run locally
23+
24+
```bash
25+
git clone https://github.com/CopilotKit/CopilotKit.git
26+
cd CopilotKit/examples/showcases/a2ui-pdf-analyst
27+
cp agent/.env.example agent/.env # then put your OPENAI_API_KEY in agent/.env
28+
pnpm install # installs Next.js + runs `uv sync` for the agent
29+
pnpm dev # boots web on :3000, agent on :8123
30+
```
31+
32+
Open <http://localhost:3000>. `npm install && npm run dev` works identically.
33+
34+
## Environment variables
35+
36+
`agent/.env`:
37+
38+
| Variable | Required | Notes |
39+
| ---------------- | -------- | ------------------------------------------------------------------------------------- |
40+
| `OPENAI_API_KEY` | yes | used by the main agent and by the secondary LLMs inside `query_pdf` / `generate_a2ui` |
41+
42+
## Architecture
43+
44+
```
45+
a2ui-pdf-analyst/
46+
├── package.json → Next.js manifest + concurrently runs the agent alongside
47+
├── next.config.ts
48+
├── postcss.config.mjs
49+
├── tsconfig.json
50+
├── public/ → static assets (CopilotKit brand SVGs)
51+
├── src/ → Next.js 16 · React 19 · Tailwind v4
52+
│ ├── app/
53+
│ │ ├── api/copilotkit/ → CopilotKit V2 runtime endpoint (HttpAgent → Python)
54+
│ │ ├── fixed/ → fixed-schema route: pre-authored dashboard
55+
│ │ ├── dynamic/ → dynamic-schema route: agent invents the layout
56+
│ │ ├── catalog/ → live showcase of all 21 components
57+
│ │ ├── globals.css → app-wide tokens, fonts
58+
│ │ ├── layout.tsx → root layout + Providers
59+
│ │ └── page.tsx → overview
60+
│ ├── a2ui/
61+
│ │ ├── catalog/
62+
│ │ │ ├── definitions.ts → Zod prop schemas + agent-facing descriptions
63+
│ │ │ ├── renderers.tsx → React renderers (Recharts charts, tables, cards)
64+
│ │ │ └── index.ts → createCatalog() (definitions + renderers, catalogId)
65+
│ │ ├── theme.css → brand tokens, scoped to .a2ui-surface
66+
│ │ ├── surface-bus.ts → per-agent A2UI op stream the canvas subscribes to
67+
│ │ └── MirrorRenderer.tsx → activity renderer that forwards ops to the canvas
68+
│ ├── components/
69+
│ │ ├── SurfaceCanvas.tsx → mounts A2UIProvider + renders surfaces
70+
│ │ ├── FilteredUserMessage.tsx → strips inlined PDF text from chat
71+
│ │ ├── FilteredAssistantMessage.tsx → suppresses JSON-shaped agent replies
72+
│ │ ├── Split.tsx → VS-Code-style resizable chat/canvas split
73+
│ │ ├── Providers.tsx → <CopilotKit> + activity renderers
74+
│ │ └── Brand.tsx → SiteNav + PageHeader
75+
│ └── lib/pdf.ts → client-side PDF text extraction (pdfjs-dist)
76+
└── agent/ → Python · LangChain · LangGraph · FastAPI · AG-UI
77+
├── main.py → /fixed and /dynamic FastAPI endpoints
78+
├── pyproject.toml
79+
├── uv.lock
80+
└── src/
81+
├── catalog.py → CATALOG_ID + system-prompt fragment listing components
82+
├── fixed_agent.py → render_dashboard backend tool
83+
├── dynamic_agent.py → query_pdf + generate_a2ui tools
84+
├── pdf_tools.py → query_pdf: PDF text → structured JSON answer
85+
├── multimodal_middleware.py → ag-ui-langgraph patch so PDF text survives the trip to OpenAI
86+
└── a2ui/schemas/dashboard.json → the fixed dashboard layout (Stack / Grid / charts / table)
87+
```
88+
89+
## How it works
90+
91+
**PDF attachment** — CopilotKit's multimodal attachment support lets the user attach a PDF directly in the chat input. The frontend extracts the full text client-side via `pdfjs-dist` and inlines it into the user message under a `[Document: <filename>]` header. `multimodal_middleware.py` patches `ag-ui-langgraph` so this text block survives serialization and arrives intact at OpenAI. The agent scans every message in the conversation history for the most recent `[Document: ...]` header — attach once, ask many questions.
92+
93+
**Fixed schema (`/fixed`)**`agent/src/a2ui/schemas/dashboard.json` is a static A2UI component tree the agent never touches. The `render_dashboard` tool takes typed arguments (KPIs, trend, share, rows, scope chips), packages them as A2UI `update_data_model` ops, and the existing tree picks them up via `{path}` bindings. One LLM pass, one tool call, surface streams in.
94+
95+
**Dynamic schema (`/dynamic`)** — five steps per turn:
96+
97+
1. User attaches a PDF and asks a question. Frontend inlines the PDF text into the message.
98+
2. Agent calls `query_pdf` → a sub-LLM reads the document and returns structured JSON: `shape_hint`, `title`, `summary`, `data`.
99+
3. Agent calls `generate_a2ui` (no arguments) → spawns a second sub-LLM bound to a no-op `render_a2ui` shim with `tool_choice` forced to that shim.
100+
4. The second LLM's tool-call arguments (surfaceId, catalogId, components, data) become A2UI `create_surface` + `update_components` + `update_data_model` operations.
101+
5. The JS-side A2UI middleware detects `a2ui_operations` in the tool result and emits the snapshot events the canvas listens for. Surface renders. Agent emits an empty chat message.
102+
103+
## Sample PDFs
104+
105+
These work well for the dynamic-schema demo:
106+
107+
- Apple Q4 FY24 Consolidated Financial Statements ([download](https://www.apple.com/newsroom/pdfs/fy2024-q4/FY24_Q4_Consolidated_Financial_Statements.pdf)) — structured tables, multiple categorical breakdowns
108+
- Tesla Q3 2024 Update ([download](https://www.tesla.com/sites/default/files/downloads/TSLA-Q3-2024-Update.pdf)) — multi-quarter time-series + production / delivery pairs
109+
- Anthropic's _Constitutional AI: Harmlessness from AI Feedback_ ([download](https://arxiv.org/pdf/2212.08073)) — research paper, mostly prose, for text-heavy explainer surfaces
110+
111+
## Prompts to try
112+
113+
On `/dynamic` after attaching a PDF:
114+
115+
| Ask the agent | Expected surface |
116+
| --------------------------------------------------------------------------------------------- | ------------------------------------- |
117+
| `What was net income last quarter?` | one StatCard |
118+
| `Break iPhone vs Mac vs iPad vs Wearables vs Services as a donut.` | DonutChart |
119+
| `Show Q4 net sales by category as horizontal bars.` | HorizontalBarChart |
120+
| `Plot quarterly production against deliveries across the last 5 quarters as a scatter chart.` | ScatterChart |
121+
| `Explain the main idea of this paper in plain English.` | Heading + Text + Callout + BulletList |
122+
| `Show me the revenue trend over the last 6 quarters.` | LineChart |
123+
124+
On `/fixed` after attaching a PDF:
125+
126+
| Ask the agent | What happens |
127+
| ------------------------------------------- | ---------------------------------------------------------------------- |
128+
| `Render the dashboard.` | full dashboard with KPIs, trend chart, share donut, table, scope chips |
129+
| `Switch scope to FY24.` (or click the chip) | re-renders the same dashboard with FY24 data |
130+
131+
## Tech stack
132+
133+
| Layer | Stack |
134+
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
135+
| Frontend | Next.js 16 · React 19 · Tailwind v4 · TypeScript · `@copilotkit/react-core/v2` · `@copilotkit/a2ui-renderer` · `pdfjs-dist` · Recharts |
136+
| Runtime bridge | `@copilotkit/runtime/v2` · `@ag-ui/client` (HttpAgent) |
137+
| Backend | Python 3.12 · FastAPI · `ag-ui-langgraph` · `copilotkit` (Python SDK) · `langchain` agents + LangGraph · `langchain-openai` |
138+
| Model | `gpt-5.5` for both the main agent and the secondary LLMs |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
OPENAI_API_KEY=sk-your-openai-key
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"""FastAPI server exposing two AG-UI agents:
2+
3+
POST /fixed/ . multi-step launch wizard using fixed A2UI schemas
4+
POST /dynamic/ . dynamic A2UI surfaces generated at runtime
5+
6+
Run with: uvicorn main:app --port 8123 --reload
7+
"""
8+
from __future__ import annotations
9+
10+
import os
11+
12+
import uvicorn
13+
from ag_ui_langgraph import add_langgraph_fastapi_endpoint
14+
from copilotkit import LangGraphAGUIAgent
15+
from dotenv import load_dotenv
16+
from fastapi import FastAPI
17+
from fastapi.middleware.cors import CORSMiddleware
18+
19+
load_dotenv()
20+
21+
# Patch ag-ui-langgraph's multimodal converter so PDF text shipped via
22+
# CopilotChat attachments survives the trip to OpenAI. Must run BEFORE
23+
# the agent graphs are imported so the agents pick up the patched
24+
# converter at first message conversion.
25+
from src.multimodal_middleware import install as _install_doc_inlining # noqa: E402
26+
27+
_install_doc_inlining()
28+
29+
from src.dynamic_agent import graph as dynamic_graph # noqa: E402
30+
from src.fixed_agent import graph as fixed_graph # noqa: E402
31+
32+
app = FastAPI(title="A2UI Demo Agents")
33+
34+
app.add_middleware(
35+
CORSMiddleware,
36+
allow_origins=["*"],
37+
allow_methods=["*"],
38+
allow_headers=["*"],
39+
)
40+
41+
# LangGraph's default recursion_limit is 25. ag_ui_langgraph builds its own
42+
# RunnableConfig per run, which overrides any `.with_config()` we bind at
43+
# compile time on the graph. Pass recursion_limit here so it's honored.
44+
# 50 is plenty for our two-tool flow; bump higher only if a deeper agent
45+
# loop is genuinely needed.
46+
_AGENT_CONFIG = {"recursion_limit": 50}
47+
48+
add_langgraph_fastapi_endpoint(
49+
app=app,
50+
agent=LangGraphAGUIAgent(
51+
name="fixed_agent",
52+
description="Multi-step launch wizard with fixed A2UI schemas.",
53+
graph=fixed_graph,
54+
config=_AGENT_CONFIG,
55+
),
56+
path="/fixed",
57+
)
58+
59+
add_langgraph_fastapi_endpoint(
60+
app=app,
61+
agent=LangGraphAGUIAgent(
62+
name="dynamic_agent",
63+
description="Dynamic A2UI surfaces generated by a secondary LLM.",
64+
graph=dynamic_graph,
65+
config=_AGENT_CONFIG,
66+
),
67+
path="/dynamic",
68+
)
69+
70+
71+
@app.get("/")
72+
def root():
73+
return {
74+
"ok": True,
75+
"agents": {
76+
"fixed_agent": "/fixed/",
77+
"dynamic_agent": "/dynamic/",
78+
},
79+
}
80+
81+
82+
def main():
83+
uvicorn.run(
84+
"main:app",
85+
host="0.0.0.0",
86+
port=int(os.environ.get("PORT", "8123")),
87+
reload=True,
88+
)
89+
90+
91+
if __name__ == "__main__":
92+
main()
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[project]
2+
name = "agent"
3+
version = "0.1.0"
4+
requires-python = ">=3.12,<3.14"
5+
dependencies = [
6+
"ag-ui-langgraph>=0.0.36",
7+
"copilotkit>=0.1.90",
8+
"fastapi>=0.136.3",
9+
"langchain>=1.0",
10+
"langchain-core>=1.4.0",
11+
"langchain-openai>=1.2.2",
12+
"langgraph>=1.0",
13+
"python-dotenv>=1.2.2",
14+
"uvicorn>=0.48.0",
15+
]

examples/showcases/a2ui-pdf-analyst/agent/src/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)