Skip to content

Commit e96b528

Browse files
committed
updating the repository with gemeini to have a running green agent and a docker image
1 parent 12c677c commit e96b528

9 files changed

Lines changed: 2376 additions & 514 deletions

File tree

README.md

Lines changed: 58 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,81 @@
1-
# A2A Agent Template
1+
# Code Translator Green Agent (Evaluator)
22

3-
A minimal template for building [A2A (Agent-to-Agent)](https://a2a-protocol.org/latest/) green agents compatible with the [AgentBeats](https://agentbeats.dev) platform.
3+
This repository contains the **Green Agent** for the Code Translator system. Built with the [Google Agent Development Kit (ADK)](https://google.github.io/adk-docs/), this agent acts as the evaluator and orchestrator for code translation scenarios.
44

5-
## Project Structure
5+
## Overview
66

7-
```
8-
src/
9-
├─ server.py # Server setup and agent card configuration
10-
├─ executor.py # A2A request handling
11-
├─ agent.py # Your agent implementation goes here
12-
└─ messenger.py # A2A messaging utilities
13-
tests/
14-
└─ test_agent.py # Agent tests
15-
Dockerfile # Docker configuration
16-
pyproject.toml # Python dependencies
17-
.github/
18-
└─ workflows/
19-
└─ test-and-publish.yml # CI workflow
20-
```
7+
The Green Agent is responsible for:
8+
1. **Orchestrating** the interaction between participant agents (Purple Agents).
9+
2. **Evaluating** the quality of code translations provided by participants.
10+
3. **Scoring** the submissions based on specific criteria.
2111

22-
## Getting Started
12+
### Evaluation Criteria
13+
The agent uses `gemini-2.5-flash` to judge translations based on:
14+
* **Execution Correctness**: The code must run without errors.
15+
* **Style & Documentation**: Adherence to the target language's style guides and proper commenting.
16+
* **Conciseness**: Efficient code without unnecessary boilerplate.
17+
* **Relevance**: Logical and structural equivalence to the original code.
2318

24-
1. **Create your repository** - Click "Use this template" to create your own repository from this template
19+
## Architecture
2520

26-
2. **Implement your agent** - Add your agent logic to [`src/agent.py`](src/agent.py)
21+
* **Framework**: Google ADK (`google-adk[a2a]`)
22+
* **Model**: Gemini 2.5 Flash
23+
* **Communication**: Agent-to-Agent (A2A) Protocol
24+
* **Server**: Uvicorn + FastAPI (exposed via ADK)
2725

28-
3. **Configure your agent card** - Fill in your agent's metadata (name, skills, description) in [`src/server.py`](src/server.py)
26+
## Prerequisites
2927

30-
4. **Write your tests** - Add custom tests for your agent in [`tests/test_agent.py`](tests/test_agent.py)
28+
* Python 3.11+
29+
* [uv](https://github.com/astral-sh/uv) (recommended) or pip
30+
* Google GenAI API Key
3131

32-
For a concrete example of implementing a green agent using this template, see this [draft PR](https://github.com/RDI-Foundation/green-agent-template/pull/3).
32+
## Setup & Installation
3333

34-
## Running Locally
35-
36-
```bash
37-
# Install dependencies
38-
uv sync
34+
1. **Clone the repository:**
35+
```bash
36+
git clone <repository-url>
37+
cd code_translator_green_agent
38+
```
3939

40-
# Run the server
41-
uv run src/server.py
42-
```
43-
44-
## Running with Docker
45-
46-
```bash
47-
# Build the image
48-
docker build -t my-agent .
40+
2. **Configure Environment:**
41+
Create a `.env` file in the root directory:
42+
```bash
43+
GOOGLE_API_KEY=your_api_key_here
44+
```
4945

50-
# Run the container
51-
docker run -p 9009:9009 my-agent
52-
```
46+
3. **Install Dependencies:**
47+
Using `uv`:
48+
```bash
49+
uv sync
50+
```
5351

54-
## Testing
52+
## Running the Agent
5553

56-
Run A2A conformance tests against your agent.
54+
### Local Execution
55+
To run the agent server locally:
5756

5857
```bash
59-
# Install test dependencies
60-
uv sync --extra test
61-
62-
# Start your agent (uv or docker; see above)
63-
64-
# Run tests against your running agent URL
65-
uv run pytest --agent-url http://localhost:9009
58+
uv run src/server.py --host 0.0.0.0 --port 9009
6659
```
6760

68-
## Publishing
61+
The agent will be available at `http://localhost:9009`.
6962

70-
The repository includes a GitHub Actions workflow that automatically builds, tests, and publishes a Docker image of your agent to GitHub Container Registry.
63+
### Docker Execution
64+
To build and run using Docker:
7165

72-
If your agent needs API keys or other secrets, add them in Settings → Secrets and variables → Actions → Repository secrets. They'll be available as environment variables during CI tests.
66+
1. **Build the image:**
67+
```bash
68+
docker build -t code-translator-green .
69+
```
7370

74-
- **Push to `main`** → publishes `latest` tag:
75-
```
76-
ghcr.io/<your-username>/<your-repo-name>:latest
77-
```
71+
2. **Run the container:**
72+
```bash
73+
docker run -p 9009:9009 --env-file .env code-translator-green
74+
```
7875

79-
- **Create a git tag** (e.g. `git tag v1.0.0 && git push origin v1.0.0`) → publishes version tags:
80-
```
81-
ghcr.io/<your-username>/<your-repo-name>:1.0.0
82-
ghcr.io/<your-username>/<your-repo-name>:1
83-
```
84-
85-
Once the workflow completes, find your Docker image in the Packages section (right sidebar of your repository). Configure the package visibility in package settings.
76+
## Project Structure
8677

87-
> **Note:** Organization repositories may need package write permissions enabled manually (Settings → Actions → General). Version tags must follow [semantic versioning](https://semver.org/) (e.g., `v1.0.0`).
78+
* `src/agent.py`: Defines the ADK Agent, system prompt, and evaluation logic.
79+
* `src/server.py`: Entry point for the HTTP server.
80+
* `src/tool_provider.py`: Tools for the agent (e.g., A2A communication).
81+
* `src/common.py`: Shared data models (e.g., `TranslatorEval` schema).

pyproject.toml

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
[project]
2-
name = "green-agent-template"
2+
name = "code-translator-green-agent"
33
version = "0.1.0"
4-
description = "A template for A2A green agents"
5-
readme = "README.md"
6-
requires-python = ">=3.13"
4+
description = "Green Agent for Code Translator"
5+
requires-python = ">=3.11"
76
dependencies = [
8-
"a2a-sdk[http-server]>=0.3.20",
9-
"pydantic>=2.12.5",
10-
"uvicorn>=0.38.0",
7+
"python-dotenv",
8+
"uvicorn",
9+
"httpx",
10+
"google-genai",
11+
"pydantic",
12+
"google-adk[a2a]"
1113
]
1214

13-
[project.optional-dependencies]
14-
test = [
15-
"pytest>=8.0.0",
16-
"pytest-asyncio>=0.24.0",
17-
"httpx>=0.28.1",
18-
]
15+
[build-system]
16+
requires = ["hatchling"]
17+
build-backend = "hatchling.build"
18+
19+
[tool.hatch.build.targets.wheel]
20+
packages = ["src"]

src/agent.py

Lines changed: 38 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,38 @@
1-
from typing import Any
2-
from pydantic import BaseModel, HttpUrl, ValidationError
3-
from a2a.server.tasks import TaskUpdater
4-
from a2a.types import Message, TaskState, Part, TextPart, DataPart
5-
from a2a.utils import get_message_text, new_agent_text_message
6-
7-
from messenger import Messenger
8-
9-
10-
class EvalRequest(BaseModel):
11-
"""Request format sent by the AgentBeats platform to green agents."""
12-
participants: dict[str, HttpUrl] # role -> agent URL
13-
config: dict[str, Any]
14-
15-
16-
class Agent:
17-
# Fill in: list of required participant roles, e.g. ["pro_debater", "con_debater"]
18-
required_roles: list[str] = []
19-
# Fill in: list of required config keys, e.g. ["topic", "num_rounds"]
20-
required_config_keys: list[str] = []
21-
22-
def __init__(self):
23-
self.messenger = Messenger()
24-
# Initialize other state here
25-
26-
def validate_request(self, request: EvalRequest) -> tuple[bool, str]:
27-
missing_roles = set(self.required_roles) - set(request.participants.keys())
28-
if missing_roles:
29-
return False, f"Missing roles: {missing_roles}"
30-
31-
missing_config_keys = set(self.required_config_keys) - set(request.config.keys())
32-
if missing_config_keys:
33-
return False, f"Missing config keys: {missing_config_keys}"
34-
35-
# Add additional request validation here
36-
37-
return True, "ok"
38-
39-
async def run(self, message: Message, updater: TaskUpdater) -> None:
40-
"""Implement your agent logic here.
41-
42-
Args:
43-
message: The incoming message
44-
updater: Report progress (update_status) and results (add_artifact)
45-
46-
Use self.messenger.talk_to_agent(message, url) to call other agents.
47-
"""
48-
input_text = get_message_text(message)
49-
50-
try:
51-
request: EvalRequest = EvalRequest.model_validate_json(input_text)
52-
ok, msg = self.validate_request(request)
53-
if not ok:
54-
await updater.reject(new_agent_text_message(msg))
55-
return
56-
except ValidationError as e:
57-
await updater.reject(new_agent_text_message(f"Invalid request: {e}"))
58-
return
59-
60-
# Replace example code below with your agent logic
61-
# Use request.participants to get participant agent URLs by role
62-
# Use request.config for assessment parameters
63-
64-
await updater.update_status(
65-
TaskState.working, new_agent_text_message("Thinking...")
66-
)
67-
await updater.add_artifact(
68-
parts=[
69-
Part(root=TextPart(text="The agent performed well.")),
70-
Part(root=DataPart(data={
71-
# structured assessment results
72-
}))
73-
],
74-
name="Result",
75-
)
1+
from google.adk.agents import Agent
2+
from google.adk.tools import FunctionTool
3+
from src.common import TranslatorEval
4+
from src.tool_provider import ToolProvider
5+
6+
SYSTEM_PROMPT = '''
7+
you are an expert evaluation agent specialized in evaluating code and programming languages translation and
8+
how efficient it is to run without errors, and judging a successful translation requires the following
9+
considerations:
10+
11+
- it does not produce error when it runs.
12+
- it is styled and commented in the new language method.
13+
- it is concise and does not have extra non relevant code.
14+
- it is clear and relevant to the topic.
15+
16+
the format of the output translation is as follows, containing at least two points of them with requirement for the first one:
17+
18+
1 - the translation: the translation of the code in the new language.
19+
2 - it keeps the same functionality of the original code.
20+
3 - it have the same structure and logic of the original code.
21+
22+
the translation needs to start with a note about the current language and the new language.
23+
24+
in general the translation needs to be clear, clean and error free.
25+
'''
26+
27+
def create_judge_agent(tool_provider: ToolProvider) -> Agent:
28+
return Agent(
29+
name="translator_judge_adk",
30+
model="gemini-2.5-flash",
31+
description=(
32+
"assess the quality of the programming language translation given and which one is better meeting the criteria"
33+
),
34+
instruction=SYSTEM_PROMPT,
35+
tools=[FunctionTool(func=tool_provider.talk_to_agent)],
36+
output_schema=TranslatorEval,
37+
after_agent_callback=lambda callback_context: tool_provider.reset()
38+
)

src/client.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import httpx
2+
from uuid import uuid4
3+
from a2a.client import (
4+
A2ACardResolver,
5+
ClientConfig,
6+
ClientFactory,
7+
Consumer,
8+
)
9+
from a2a.types import (
10+
Message,
11+
Part,
12+
Role,
13+
TextPart,
14+
DataPart,
15+
)
16+
17+
DEFAULT_TIMEOUT = 300
18+
19+
def create_message(*, role: Role = Role.user, text: str, context_id: str | None = None) -> Message:
20+
return Message(
21+
kind="message",
22+
role=role,
23+
parts=[Part(TextPart(kind="text", text=text))],
24+
message_id=uuid4().hex,
25+
context_id=context_id
26+
)
27+
28+
def merge_parts(parts: list[Part]) -> str:
29+
chunks = []
30+
for part in parts:
31+
if isinstance(part.root, TextPart):
32+
chunks.append(part.root.text)
33+
elif isinstance(part.root, DataPart):
34+
chunks.append(str(part.root.data))
35+
return "\n".join(chunks)
36+
37+
async def send_message(message: str, base_url: str, context_id: str | None = None, streaming=False, consumer: Consumer | None = None):
38+
"""Returns dict with context_id, response and status (if exists)"""
39+
async with httpx.AsyncClient(timeout=DEFAULT_TIMEOUT) as httpx_client:
40+
resolver = A2ACardResolver(httpx_client=httpx_client, base_url=base_url)
41+
agent_card = await resolver.get_agent_card()
42+
config = ClientConfig(
43+
httpx_client=httpx_client,
44+
streaming=streaming,
45+
)
46+
factory = ClientFactory(config)
47+
client = factory.create(agent_card)
48+
if consumer:
49+
await client.add_event_consumer(consumer)
50+
51+
outbound_msg = create_message(text=message, context_id=context_id)
52+
last_event = None
53+
outputs = {
54+
"response": "",
55+
"context_id": None
56+
}
57+
58+
async for event in client.send_message(outbound_msg):
59+
last_event = event
60+
61+
if isinstance(last_event, Message):
62+
outputs["context_id"] = last_event.context_id
63+
outputs["response"] += merge_parts(last_event.parts)
64+
65+
return outputs

0 commit comments

Comments
 (0)