Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions with-agent-spec-a2ui/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.*
!.env.example

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist
.output

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp
.cache

# Sveltekit cache directory
.svelte-kit/

# vitepress build output
**/.vitepress/dist

# vitepress cache directory
**/.vitepress/cache

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# Firebase cache directory
.firebase/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v3
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

# Vite files
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
.vite/

next-env.d.ts
Binary file added with-agent-spec-a2ui/Demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
118 changes: 118 additions & 0 deletions with-agent-spec-a2ui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# CopilotKit <> A2A + A2UI Starter


This is a simplified copy that hardcodes a default A2UI surface. No A2A server or model is required.

![Demo](Demo.gif)

## Prerequisites

- Gemeni API Key (for the ADK/A2A agent)
- Python 3.12+
- uv
- Node.js 20+
- Any of the following package managers:
- pnpm (recommended)
- npm
- yarn
- bun

> **Note:** This repository ignores lock files (package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lockb) to avoid conflicts between different package managers. Each developer should generate their own lock file using their preferred package manager. After that, make sure to delete it from the .gitignore.

## Getting Started

1. Install dependencies using your preferred package manager:
```bash
# Using pnpm (recommended)
pnpm install

# Using npm
npm install

# Using yarn
yarn install

# Using bun
bun install
```

> **Note:** This will automatically setup the Python environment as well.
>
> If you have manual isseus, you can run:
>
> ```sh
> npm run install:agent
> ```


3. Set up your Gemeni API key:

Create a `.env` file inside the `agent` folder with the following content:

```
GEMENI_API_KEY=sk-...your-openai-key-here...
```


4. Start the development server:
```bash
# Using pnpm
pnpm dev

# Using npm
npm run dev

# Using yarn
yarn dev

# Using bun
bun run dev
```

This will start both the UI and agent servers concurrently.

## Available Scripts
The following scripts can also be run using your preferred package manager:
- `dev` - Starts both UI and agent servers in development mode
- `dev:debug` - Starts development servers with debug logging enabled
- `dev:ui` - Starts only the Next.js UI server
- `dev:agent` - Starts only the PydanticAI agent server
- `build` - Builds the Next.js application for production
- `start` - Starts the production server
- `lint` - Runs ESLint for code linting
- `install:agent` - Installs Python dependencies for the agent

## Documentation

The main UI component is in `src/app/page.tsx`, but most of the UI comes from from the agent in the form of A2UI declarative components. To see and edit the components it can generate, look in `agent/prompt_builder.py`.
To generate new components, try the [A2UI Composer](https://a2ui-editor.ag-ui.com)

## 📚 Documentation
- [A2UI + CopilotKit Documentation](https://docs.copilotkit.ai/a2a) - Learn more about how to use A2UI with CopilotKit
- [A2UI Documentation](https://a2ui.org) - Learn more about A2UI and its capabilities
- [CopilotKit Documentation](https://docs.copilotkit.ai) - Explore CopilotKit's capabilities
- [Next.js Documentation](https://nextjs.org/docs) - Learn about Next.js features and API

## Contributing

Feel free to submit issues and enhancement requests! This starter is designed to be easily extensible.

## License

This project is licensed under the MIT License - see the LICENSE file for details.

## Troubleshooting

### Agent Connection Issues
If you see "I'm having trouble connecting to my tools", make sure:
1. The ADK agent is running on port 10002
2. Your Gemini API key is set correctly
3. Both servers started successfully

### Python Dependencies
If you encounter Python import errors:
```bash
cd agent
uv sync
uv run .
```
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added with-agent-spec-a2ui/agent/images/italian1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added with-agent-spec-a2ui/agent/images/italian2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added with-agent-spec-a2ui/agent/images/italian3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added with-agent-spec-a2ui/agent/images/kungpao.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added with-agent-spec-a2ui/agent/images/lasagna.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added with-agent-spec-a2ui/agent/images/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added with-agent-spec-a2ui/agent/images/mapotofu.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added with-agent-spec-a2ui/agent/images/margharita.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions with-agent-spec-a2ui/agent/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[project]
name = "agent"
version = "0.1.0"
description = "Agent Spec A2UI LLM agent"
requires-python = ">=3.10"
dependencies = [
"uvicorn",
"python-dotenv",
"ag-ui-agent-spec[langgraph, wayflow]",
]

[tool.uv]
dev-dependencies = []

[tool.uv.sources]
ag-ui-agent-spec = { path = "../../ag-ui/integrations/agent-spec/python", editable = true }

72 changes: 72 additions & 0 deletions with-agent-spec-a2ui/agent/src/a2ui_from_llm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from __future__ import annotations

import os

from pyagentspec.agent import Agent
from pyagentspec.llms import OpenAiCompatibleConfig
from pyagentspec.serialization import AgentSpecSerializer
from pyagentspec.tools import ServerTool
from pyagentspec.property import Property

from prompt_builder import get_ui_prompt, RESTAURANT_UI_EXAMPLES


agent_llm = OpenAiCompatibleConfig(
name="a2ui_llm",
model_id=os.environ.get("OPENAI_MODEL", "gpt-4o"),
url=os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1"),
)


AGENT_INSTRUCTION = """
You are a helpful restaurant finding assistant. Your goal is to help users find and book restaurants using a rich UI.

To achieve this, you MUST follow this logic:

1. **For finding restaurants:**
a. You MUST call the `get_restaurants` tool. Extract the cuisine, location, and a specific number (`count`) of restaurants from the user's query (e.g., for "top 5 chinese places", count is 5).
b. After receiving the data, you MUST follow the instructions precisely to generate the final a2ui UI JSON, using the appropriate UI example from the `prompt_builder.py` based on the number of restaurants.

2. **For booking a table (when you receive a query like 'USER_WANTS_TO_BOOK...'):**
a. You MUST use the appropriate UI example from `prompt_builder.py` to generate the UI, populating the `dataModelUpdate.contents` with the details from the user's query.

3. **For confirming a booking (when you receive a query like 'User submitted a booking...'):**
a. You MUST use the appropriate UI example from `prompt_builder.py` to generate the confirmation UI, populating the `dataModelUpdate.contents` with the final booking details.
"""


agent = Agent(
name="a2ui_llm_agent",
description="LLM that emits A2UI operations in a two-part response.",
system_prompt= AGENT_INSTRUCTION + get_ui_prompt("http://localhost:8000", RESTAURANT_UI_EXAMPLES),
llm_config=agent_llm,
tools=[
ServerTool(
name="get_restaurants",
description="Get a list of restaurants by cuisine and location.",
inputs=[
Property(
title="cuisine",
json_schema={"title": "cuisine", "type": "string"},
),
Property(
title="location",
json_schema={"title": "location", "type": "string"},
),
Property(
title="count",
json_schema={"title": "count", "type": "number"},
),
],
outputs=[
Property(
title="restaurants",
json_schema={"title": "restaurants", "type": "string"},
)
],
)
],
)


a2ui_llm_agent_json = AgentSpecSerializer().to_json(agent)
10 changes: 10 additions & 0 deletions with-agent-spec-a2ui/agent/src/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from __future__ import annotations

from ag_ui_agentspec.agent import AgentSpecAgent
from a2ui_from_llm import a2ui_llm_agent_json


def build_agentspec_agent(runtime: str = "langgraph") -> AgentSpecAgent:
from restaurant_tool import get_restaurants
return AgentSpecAgent(a2ui_llm_agent_json, runtime=runtime, tool_registry={"get_restaurants": get_restaurants})

26 changes: 26 additions & 0 deletions with-agent-spec-a2ui/agent/src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from ag_ui_agentspec.endpoint import add_agentspec_fastapi_endpoint
from fastapi import FastAPI
from starlette.staticfiles import StaticFiles
from pathlib import Path
import uvicorn

from agent import build_agentspec_agent


def build_server() -> FastAPI:
app = FastAPI(title="Agent Spec A2UI Agent")
agent = build_agentspec_agent(runtime="langgraph")
add_agentspec_fastapi_endpoint(app, agent, path="/")
# Serve demo images at /static from the Next.js public/images directory
project_root = Path(__file__).resolve().parents[2] # .../with-agent-spec-a2ui
images_dir = project_root / "agent" / "images"
if images_dir.exists():
app.mount("/static", StaticFiles(directory=str(images_dir)), name="static")
return app


app = build_server()


if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
Loading