Skip to content
This repository was archived by the owner on Dec 23, 2025. It is now read-only.

Commit 77e3743

Browse files
committed
Use new template repo
1 parent 2582cf9 commit 77e3743

29 files changed

+3613
-3472
lines changed

.github/workflows/publish.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ jobs:
1616
matrix:
1717
include:
1818
# Update this to build your own agent images.
19-
- name: adk-debate-judge
20-
dockerfile: scenarios/debate/Dockerfile.adk-debate-judge
2119
- name: debate-judge
2220
dockerfile: scenarios/debate/Dockerfile.debate-judge
2321
- name: debater

README.md

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
## Quickstart
22
1. Clone the repo
33
```
4-
git clone [email protected]:agentbeats/tutorial.git agentbeats-tutorial
4+
git clone [email protected]:RDI-Foundation/tutorial.git agentbeats-tutorial
55
cd agentbeats-tutorial
66
```
77
2. Install dependencies
@@ -12,7 +12,7 @@ uv sync
1212
```
1313
cp sample.env .env
1414
```
15-
Add your Google API key to the .env file
15+
Fill in the keys you plan to use (e.g. `GOOGLE_API_KEY` for the debate example and `OPENAI_API_KEY` for the tau2 example).
1616

1717
4. Run the [debate example](#example)
1818
```
@@ -25,6 +25,8 @@ This command will:
2525

2626
**Note:** Use `--show-logs` to see agent outputs during the assessment, and `--serve-only` to start agents without running the assessment.
2727

28+
**Note:** If you see `Error: Some agent endpoints are already in use`, change the ports in the scenario TOML (or stop the process using them).
29+
2830
To run this example manually, start the agent servers in separate terminals, and then in another terminal run the A2A client on the scenario.toml file to initiate the assessment.
2931

3032
After running, you should see an output similar to this.
@@ -33,21 +35,22 @@ After running, you should see an output similar to this.
3335

3436
## Project Structure
3537
```
36-
src/
37-
└─ agentbeats/
38-
├─ green_executor.py # base A2A green agent executor
39-
├─ models.py # pydantic models for green agent IO
40-
├─ client.py # A2A messaging helpers
41-
├─ client_cli.py # CLI client to start assessment
42-
└─ run_scenario.py # run agents and start assessment
43-
4438
scenarios/
45-
└─ debate/ # implementation of the debate example
46-
├─ debate_judge.py # green agent impl using the official A2A SDK
47-
├─ adk_debate_judge.py # alternative green agent impl using Google ADK
48-
├─ debate_judge_common.py # models and utils shared by above impls
49-
├─ debater.py # debater agent (Google ADK)
50-
└─ scenario.toml # config for the debate example
39+
├─ debate/
40+
│ ├─ judge/src/ # green agent (green-agent-template structure)
41+
│ ├─ debater/src/ # purple agent (agent-template structure)
42+
│ ├─ Dockerfile.debate-judge
43+
│ ├─ Dockerfile.debater
44+
│ └─ scenario.toml
45+
└─ tau2/
46+
├─ evaluator/src/ # green agent (green-agent-template structure)
47+
├─ agent/src/ # purple agent (agent-template structure)
48+
├─ Dockerfile.tau2-evaluator
49+
├─ Dockerfile.tau2-agent
50+
├─ setup.sh # downloads tau2-bench data for local runs
51+
└─ scenario.toml
52+
53+
src/agentbeats/ # optional local runner + A2A client helpers (`agentbeats-run`)
5154
```
5255

5356
# AgentBeats Tutorial
@@ -118,7 +121,10 @@ To make things concrete, we will use a debate scenario as our toy example:
118121
- Two purple agents (`Debater`) participate by presenting arguments for their side of the topic.
119122

120123
To run this example, we start all three servers and then use an A2A client to send an `assessment_request` to the green agent and observe its outputs.
121-
The full example code is given in the template repository. Follow the quickstart guide to setup the project and run the example.
124+
The debate example is implemented using the same structure as the supported templates:
125+
126+
- Green agent: `scenarios/debate/judge/src/` (green-agent-template style)
127+
- Purple agent: `scenarios/debate/debater/src/` (agent-template style)
122128

123129
### Dockerizing Agent
124130

pyproject.toml

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,22 @@ description = "Agentbeats Tutorial"
99
readme = "README.md"
1010
requires-python = ">=3.11"
1111
dependencies = [
12-
"a2a-sdk>=0.3.5",
13-
"google-adk>=1.14.1",
12+
"a2a-sdk[http-server]>=0.3.20",
13+
"httpx>=0.28.1",
14+
"pydantic>=2.11.9",
15+
"python-dotenv>=1.1.1",
16+
"uvicorn>=0.38.0",
17+
]
18+
19+
[project.optional-dependencies]
20+
debate = [
1421
"google-genai>=1.36.0",
22+
]
23+
tau2-agent = [
1524
"litellm>=1.0.0",
16-
"loguru>=0.7.0",
25+
]
26+
tau2-evaluator = [
1727
"nest-asyncio>=1.6.0",
18-
"pydantic>=2.11.9",
19-
"python-dotenv>=1.1.1",
20-
"uvicorn>=0.35.0",
21-
# tau2 from GitHub
2228
"tau2 @ git+https://github.com/sierra-research/tau2-bench.git",
2329
]
2430

sample.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
GOOGLE_GENAI_USE_VERTEXAI=FALSE
22
GOOGLE_API_KEY=
3+
OPENAI_API_KEY=
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
FROM ghcr.io/astral-sh/uv:python3.12-trixie
1+
FROM ghcr.io/astral-sh/uv:python3.13-bookworm
2+
3+
ENV UV_HTTP_TIMEOUT=300
24

35
RUN adduser agentbeats
46
USER agentbeats
@@ -9,10 +11,10 @@ COPY src src
911

1012
RUN \
1113
--mount=type=cache,target=/home/agentbeats/.cache/uv,uid=1000 \
12-
uv sync --locked
14+
uv sync --locked --no-dev --no-install-project --extra debate
1315

1416
COPY scenarios scenarios
1517

16-
ENTRYPOINT ["uv", "run", "scenarios/debate/debate_judge.py"]
18+
ENTRYPOINT ["uv", "run", "scenarios/debate/judge/src/server.py"]
1719
CMD ["--host", "0.0.0.0"]
1820
EXPOSE 9009
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
FROM ghcr.io/astral-sh/uv:python3.12-trixie
1+
FROM ghcr.io/astral-sh/uv:python3.13-bookworm
2+
3+
ENV UV_HTTP_TIMEOUT=300
24

35
RUN adduser agentbeats
46
USER agentbeats
@@ -9,10 +11,10 @@ COPY src src
911

1012
RUN \
1113
--mount=type=cache,target=/home/agentbeats/.cache/uv,uid=1000 \
12-
uv sync --locked
14+
uv sync --locked --no-dev --no-install-project --extra debate
1315

1416
COPY scenarios scenarios
1517

16-
ENTRYPOINT ["uv", "run", "scenarios/debate/debater.py"]
18+
ENTRYPOINT ["uv", "run", "scenarios/debate/debater/src/server.py"]
1719
CMD ["--host", "0.0.0.0"]
1820
EXPOSE 9019
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from dotenv import load_dotenv
2+
from google import genai
3+
4+
from a2a.server.tasks import TaskUpdater
5+
from a2a.types import Message, Part, TaskState, TextPart
6+
from a2a.utils import get_message_text, new_agent_text_message
7+
8+
9+
load_dotenv()
10+
11+
12+
SYSTEM_PROMPT = """
13+
You are a professional debater.
14+
Follow the role instructions in the prompt (Pro/Affirmative or Con/Negative).
15+
Write a persuasive, well-structured argument. Keep it concise (<= 200 words).
16+
"""
17+
18+
19+
class Agent:
20+
def __init__(self):
21+
self.client = genai.Client()
22+
23+
async def run(self, message: Message, updater: TaskUpdater) -> None:
24+
prompt = get_message_text(message)
25+
26+
await updater.update_status(TaskState.working, new_agent_text_message("Thinking..."))
27+
28+
response = self.client.models.generate_content(
29+
model="gemini-2.5-flash-lite",
30+
config=genai.types.GenerateContentConfig(system_instruction=SYSTEM_PROMPT),
31+
contents=prompt,
32+
)
33+
text = response.text or ""
34+
35+
await updater.add_artifact(
36+
parts=[Part(root=TextPart(text=text))],
37+
name="Response",
38+
)
39+
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from a2a.server.agent_execution import AgentExecutor, RequestContext
2+
from a2a.server.events import EventQueue
3+
from a2a.server.tasks import TaskUpdater
4+
from a2a.types import (
5+
InvalidRequestError,
6+
Task,
7+
TaskState,
8+
UnsupportedOperationError,
9+
)
10+
from a2a.utils import (
11+
new_agent_text_message,
12+
new_task,
13+
)
14+
from a2a.utils.errors import ServerError
15+
16+
from agent import Agent
17+
18+
19+
TERMINAL_STATES = {
20+
TaskState.completed,
21+
TaskState.canceled,
22+
TaskState.failed,
23+
TaskState.rejected,
24+
}
25+
26+
27+
class Executor(AgentExecutor):
28+
def __init__(self):
29+
self.agents: dict[str, Agent] = {}
30+
31+
async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
32+
msg = context.message
33+
if not msg:
34+
raise ServerError(error=InvalidRequestError(message="Missing message in request"))
35+
36+
task = context.current_task
37+
if task and task.status.state in TERMINAL_STATES:
38+
raise ServerError(
39+
error=InvalidRequestError(
40+
message=f"Task {task.id} already processed (state: {task.status.state})"
41+
)
42+
)
43+
44+
if not task:
45+
task = new_task(msg)
46+
await event_queue.enqueue_event(task)
47+
48+
context_id = task.context_id
49+
updater = TaskUpdater(event_queue, task.id, context_id)
50+
await updater.start_work()
51+
52+
try:
53+
agent = self.agents.get(context_id)
54+
if not agent:
55+
agent = Agent()
56+
self.agents[context_id] = agent
57+
58+
await agent.run(msg, updater)
59+
if not updater._terminal_state_reached:
60+
await updater.complete()
61+
except Exception as e:
62+
print(f"Task failed with agent error: {e}")
63+
await updater.failed(
64+
new_agent_text_message(f"Agent error: {e}", context_id=context_id, task_id=task.id)
65+
)
66+
67+
async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
68+
raise ServerError(error=UnsupportedOperationError())
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import argparse
2+
3+
import uvicorn
4+
from a2a.server.apps import A2AStarletteApplication
5+
from a2a.server.request_handlers import DefaultRequestHandler
6+
from a2a.server.tasks import InMemoryTaskStore
7+
from a2a.types import (
8+
AgentCapabilities,
9+
AgentCard,
10+
AgentSkill,
11+
)
12+
13+
from executor import Executor
14+
15+
16+
def main():
17+
parser = argparse.ArgumentParser(description="Run the debate participant (purple agent).")
18+
parser.add_argument("--host", type=str, default="127.0.0.1", help="Host to bind the server")
19+
parser.add_argument("--port", type=int, default=9019, help="Port to bind the server")
20+
parser.add_argument("--card-url", type=str, help="URL to advertise in the agent card")
21+
args = parser.parse_args()
22+
23+
skill = AgentSkill(
24+
id="debate_participant",
25+
name="Debate Participant",
26+
description="Participates in a debate as either Pro or Con given role instructions.",
27+
tags=["debate"],
28+
examples=["Debate topic: Should artificial intelligence be regulated? Present your opening argument."],
29+
)
30+
31+
agent_card = AgentCard(
32+
name="Debater",
33+
description="A debater agent that produces persuasive arguments given role instructions.",
34+
url=args.card_url or f"http://{args.host}:{args.port}/",
35+
version="1.0.0",
36+
default_input_modes=["text"],
37+
default_output_modes=["text"],
38+
capabilities=AgentCapabilities(streaming=True),
39+
skills=[skill],
40+
)
41+
42+
request_handler = DefaultRequestHandler(
43+
agent_executor=Executor(),
44+
task_store=InMemoryTaskStore(),
45+
)
46+
server = A2AStarletteApplication(
47+
agent_card=agent_card,
48+
http_handler=request_handler,
49+
)
50+
uvicorn.run(server.build(), host=args.host, port=args.port)
51+
52+
53+
if __name__ == "__main__":
54+
main()
55+

0 commit comments

Comments
 (0)