Skip to content
Merged
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
10 changes: 10 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ updates:
dependencies:
patterns: ['*']
exclude-patterns: ['@aparte/*']
- package-ecosystem: pip
directories: ['/langgraph-ag-ui', '/pydantic-ai-ag-ui', '/google-adk-ag-ui']
schedule:
interval: weekly
ignore:
- dependency-name: '*'
update-types: ['version-update:semver-major']
groups:
dependencies:
patterns: ['*']
- package-ecosystem: github-actions
directory: /
schedule:
Expand Down
31 changes: 28 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ jobs:
runs-on: ubuntu-latest
outputs:
examples: ${{ steps.find.outputs.examples }}
agents: ${{ steps.find.outputs.agents }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- id: find
run: echo "examples=$(ls -d */package.json | xargs -n1 dirname | jq -R . | jq -sc .)" >> "$GITHUB_OUTPUT"
run: |
echo "examples=$(ls -d */package.json | xargs -n1 dirname | jq -R . | jq -sc .)" >> "$GITHUB_OUTPUT"
echo "agents=$(ls -d */requirements.txt | xargs -n1 dirname | jq -R . | jq -sc .)" >> "$GITHUB_OUTPUT"

example:
needs: list
Expand All @@ -42,11 +45,33 @@ jobs:
- run: npm run check
- run: npm run build

# Every folder with a requirements.txt holds a Python agent: it is installed and run, with its scripted model.
agent:
needs: list
name: ${{ matrix.example }} (agent)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
example: ${{ fromJson(needs.list.outputs.agents) }}
defaults:
run:
working-directory: ${{ matrix.example }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: '3.13'
cache: pip
cache-dependency-path: ${{ matrix.example }}/requirements.txt
- run: pip install -r requirements.txt
- run: python smoke_test.py

# The one check main requires: it passes when every example does, however many there are.
all:
name: all examples
if: always()
needs: [list, example]
needs: [list, example, agent]
runs-on: ubuntu-latest
steps:
- run: test "${{ needs.list.result }}" = success && test "${{ needs.example.result }}" = success
- run: test "${{ needs.list.result }}" = success && test "${{ needs.example.result }}" = success && test "${{ needs.agent.result }}" = success
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
node_modules
dist
.venv
__pycache__
.svelte-kit
build
.output
Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
# aparté examples

Apps built on [aparté](https://github.com/apartejs/aparte), an MIT chat UI for AI made of Web Components. Each folder is a whole app: open it in StackBlitz, or copy it and run it.
Apps built on [aparté](https://github.com/apartejs/aparte), an MIT chat UI for AI made of Web Components. Each folder is a whole app: copy it and run it, or open it in StackBlitz when it runs there.

| Example | Stack | What it shows | Try it |
|---|---|---|---|
| [sveltekit-ai-sdk](./sveltekit-ai-sdk) | SvelteKit, Vercel AI SDK | A tool the model can only run once the user approves it, drawn by a Svelte component; Markdown formatted as it streams | [StackBlitz](https://stackblitz.com/github/apartejs/examples/tree/main/sveltekit-ai-sdk?file=src/lib/chat.ts) |
| [langgraph-ag-ui](./langgraph-ag-ui) | LangGraph, AG-UI | A Python agent streaming into the chat, and a tool that waits for a person through `interrupt()` | On your machine |
| [pydantic-ai-ag-ui](./pydantic-ai-ag-ui) | Pydantic AI, AG-UI | A Python agent streaming into the chat, and a tool that waits for a person through `requires_approval` | On your machine |
| [google-adk-ag-ui](./google-adk-ag-ui) | Google ADK, AG-UI | A Python agent streaming into the chat, a tool that waits for a person through `require_confirmation`, and ADK's confirmation tool kept off the transcript | On your machine |

## Run one on your machine

Expand All @@ -15,13 +18,13 @@ npm install
npm run dev
```

With no API key, an example answers with a scripted model, which is how it runs on StackBlitz. Each folder's `.env.example` names the key that switches it to a real model.
With no API key, an example answers with a scripted model, which is how it runs on StackBlitz. Each folder's `.env.example` names the key that switches it to a real model. An example with a Python agent starts that agent first: its README has the commands.

## Adding an example

- **One folder, one app.** Nothing is installed at the root, and no folder imports from another: a folder copied alone must run.
- **Name it after its stack**, framework first: `sveltekit-ai-sdk`, `nextjs-ai-sdk`. Add the topic only when two examples share a stack.
- **aparté comes from npm**, at a published version, with the lockfile committed: the example installs what a reader's app would.
- **It runs without a key.** A scripted model answers when the key is missing, and its answer says it is scripted.
- **It has `check` and `build` scripts.** CI runs `npm ci`, `npm run check` and `npm run build` in every folder that has a `package.json`, and Dependabot updates each folder's lockfile.
- **Its README opens with a StackBlitz link**, and it gets a row in the table above.
- **It has `check` and `build` scripts.** CI runs `npm ci`, `npm run check` and `npm run build` in every folder that has a `package.json`, and Dependabot updates each folder's lockfile. A folder with a `requirements.txt` holds a Python agent: CI installs it and runs its `smoke_test.py`.
- **Its README says how to try it**, with a StackBlitz link when it runs there (a Python agent does not), and it gets a row in the table above.
3 changes: 3 additions & 0 deletions google-adk-ag-ui/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Optional. Without MODEL, a scripted model answers (scripted_model.py).
MODEL=
GOOGLE_API_KEY=
34 changes: 34 additions & 0 deletions google-adk-ag-ui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Google ADK + aparté

A [Google ADK](https://google.github.io/adk-docs/) agent served over [AG-UI](https://docs.ag-ui.com), with [aparté](https://apartejs.dev) as its chat. Replies stream, tool calls show as they run, and one tool waits for a person before it runs.

## Run it

The agent runs in Python, so this one runs on your machine and not on StackBlitz. Tested with Python 3.13 and Node 24.

```bash
npx degit apartejs/examples/google-adk-ag-ui my-agent
cd my-agent
python -m venv .venv
source .venv/bin/activate # on Windows: .venv\Scripts\activate
pip install -r requirements.txt
uvicorn agent:app --port 8000
```

In a second terminal:

```bash
npm install
npm run dev
```

Open http://localhost:5173 and ask it to send an email.

With nothing configured, a scripted model answers, and says it is scripted. For a real model, copy `.env.example` to `.env` and set `MODEL` to a Gemini model and `GOOGLE_API_KEY` to your key.

## What to look at

- [`agent.py`](./agent.py): the agent and its FastAPI route. `require_confirmation=True` on the tool pauses the run for a person, and `emit_interrupt_outcome=True` ends that run the way AG-UI 1.0 describes it.
- [`src/main.ts`](./src/main.ts): the whole page. ADK asks for a confirmation through a tool of its own, `adk_request_confirmation`, so `registerToolDisplay` hides that row and has the question name the real tool. The rest knows nothing about ADK: `@aparte/runner-ag-ui` reads the agent's AG-UI stream.
- [`scripted_model.py`](./scripted_model.py): the stand-in model.
- [`smoke_test.py`](./smoke_test.py): what CI runs against the route: a reply, a pause, and a resume.
41 changes: 41 additions & 0 deletions google-adk-ag-ui/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""A Google ADK agent served over AG-UI, with a tool that waits for a person."""

import os

from ag_ui_adk import ADKAgent, add_adk_fastapi_endpoint
from dotenv import load_dotenv
from fastapi import FastAPI
from google.adk.agents import LlmAgent
from google.adk.tools import FunctionTool

load_dotenv()


def send_email(to: str, subject: str) -> str:
"""Send an email. A person has to approve it first."""
return f"Email sent to {to}: {subject}"


def make_model():
if os.environ.get("MODEL"):
return os.environ["MODEL"]

from scripted_model import ScriptedModel

return ScriptedModel()


agent = LlmAgent(
name="assistant",
model=make_model(),
instruction="You help the user. Send an email only when asked to.",
tools=[FunctionTool(send_email, require_confirmation=True)],
)

app = FastAPI()
add_adk_fastapi_endpoint(
app,
# emit_interrupt_outcome: a pause ends the run the way AG-UI 1.0 describes it, which is what the chat reads.
ADKAgent(adk_agent=agent, app_name="example", user_id="reader", emit_interrupt_outcome=True),
path="/agent",
)
15 changes: 15 additions & 0 deletions google-adk-ag-ui/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Google ADK + aparté</title>
<style>
body { margin: 0; }
</style>
</head>
<body>
<aparte-chat style="height: 100dvh"></aparte-chat>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
Loading
Loading