Skip to content

Commit 0cf120b

Browse files
committed
docs: update README with correct JSON format, add test scripts and references
1 parent 904a54c commit 0cf120b

2 files changed

Lines changed: 312 additions & 10 deletions

File tree

README.md

Lines changed: 82 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ The agent is designed to be called by an orchestration layer or directly via A2A
9595
```json
9696
{
9797
"participants": {
98-
"researcher_translator": "http://url-to-purple-agent"
98+
"translator": "http://url-to-purple-agent"
9999
},
100100
"config": {
101101
"code_to_translate": "print('Hello World')",
@@ -110,35 +110,107 @@ The agent is designed to be called by an orchestration layer or directly via A2A
110110
2. It sends the `code_to_translate`, `source_language`, and `target_language` to the participant.
111111
3. It waits for the participant to return the translated code.
112112
4. Once received, the Green Agent constructs a prompt for the Gemini model (Judge), instructing it to evaluate the translation.
113-
5. It returns a result resembling:
113+
5. It returns a result that is saved to the leaderboard in the following format:
114114

115115
```json
116116
{
117-
"winner": "researcher_translator",
118-
"scores": [
117+
"participants": {
118+
"translator": "019b8933-d5b6-76a3-8e0b-930c19c10e87"
119+
},
120+
"results": [
119121
{
120-
"participant": "researcher_translator",
121-
"score": 9
122+
"winner": "translator",
123+
"execution_correctness": 10.0,
124+
"style_score": 9.0,
125+
"conciseness": 9.33,
126+
"relevance": 10.0,
127+
"overall_score": 9.58,
128+
"reasoning": "The JavaScript translation successfully replicates the functionality of the Python code..."
122129
}
123-
],
124-
"reasoning": "The translation is syntactically correct and preserves functionality..."
130+
]
125131
}
126132
```
127133

134+
### Evaluation Metrics
135+
136+
| Metric | Description | Score Range |
137+
|--------|-------------|-------------|
138+
| **Execution Correctness** | Does the translated code produce the same output/behavior? | 0-10 |
139+
| **Style Score** | Does the code follow idiomatic conventions of the target language? | 0-10 |
140+
| **Conciseness** | Is the translation efficient without unnecessary verbosity? | 0-10 |
141+
| **Relevance** | Does the translation preserve the original code's intent and logic? | 0-10 |
142+
| **Overall Score** | Average of all four metrics | 0-10 |
143+
128144
## Testing
129145
130146
To ensure the agent is functioning correctly, you can run the provided tests.
131147
148+
### Unit Tests
149+
132150
1. **Install test dependencies** (if not already installed):
133151
```bash
134152
pip install pytest pytest-asyncio
135153
```
136154
137155
2. **Run tests**:
138156
```bash
139-
pytest tests/
157+
pytest tests/test_agent.py
140158
```
141159
142160
The `test_agent.py` contains:
143161
- **Conformance Tests**: Verifies the Agent Card and A2A protocol structure (e.g., proper message formats, capabilities).
144-
- **Message Validation**: Ensures that request and response payloads adhere to the defined schemas.
162+
- **Message Validation**: Ensures that request and response payloads adhere to the defined schemas.
163+
164+
### Integration Test
165+
166+
The `run_integration_test.py` script tests the full pipeline between the Green Agent (Judge) and Purple Agent (Participant):
167+
168+
```bash
169+
python tests/run_integration_test.py
170+
```
171+
172+
This script:
173+
1. Starts both agents locally
174+
2. Sends a code translation request to the Green Agent
175+
3. The Green Agent communicates with the Purple Agent
176+
4. Returns the evaluation result
177+
178+
### Leaderboard Test
179+
180+
The `run_leaderboard_test.py` script runs a full evaluation and generates a result JSON file compatible with the AgentBeats leaderboard:
181+
182+
```bash
183+
python tests/run_leaderboard_test.py
184+
```
185+
186+
This script:
187+
1. Starts both agents locally
188+
2. Sends multiple test cases for evaluation
189+
3. Aggregates the scores
190+
4. Generates a JSON file in the `results/` directory in the correct format for the leaderboard
191+
192+
## Related Repositories
193+
194+
This project is part of the **Code Translator** multi-agent evaluation system built for the [AgentBeats Competition](https://rdi.berkeley.edu/agentx-agentbeats.html). The complete system consists of:
195+
196+
| Repository | Description |
197+
|------------|-------------|
198+
| **[Code Translator Green Agent](https://github.com/Samir-atra/code_translator_green_agent)** (this repo) | The Judge agent that evaluates code translations |
199+
| **[Code Translator Purple Agent](https://github.com/Samir-atra/code_translator_purple_agent)** | The participant agent that performs code translation |
200+
| **[Code Translator Leaderboard](https://github.com/Samir-atra/code_translator_leaderboard)** | The leaderboard repository that records evaluation results |
201+
202+
### Live Leaderboard
203+
204+
View the live leaderboard at: **[AgentBeats - Code Translator Judge](https://agentbeats.dev/Samir-atra/code-translator-judge)**
205+
206+
### Docker Images
207+
208+
- **Green Agent**: `docker.io/samiratra95/code-translator-green-agent:latest`
209+
- **Purple Agent**: `docker.io/samiratra95/code-translator-purple-agent:latest`
210+
211+
## References
212+
213+
- [AgentBeats Tutorial](https://github.com/RDI-Foundation/agentbeats-tutorial) - Official tutorial for building AgentBeats agents
214+
- [Green Agent Template](https://github.com/RDI-Foundation/green-agent-template) - Template for green (judge) agents
215+
- [Agent Template](https://github.com/RDI-Foundation/agent-template) - Template for purple (participant) agents
216+
- [Leaderboard Template](https://github.com/RDI-Foundation/agentbeats-leaderboard-template) - Template for leaderboard repositories

tests/run_leaderboard_test.py

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
import asyncio
2+
import os
3+
import signal
4+
import subprocess
5+
import sys
6+
import time
7+
import httpx
8+
import json
9+
import uuid
10+
from datetime import datetime
11+
12+
# Configuration
13+
GREEN_AGENT_DIR = os.path.abspath("code_translator_green_agent")
14+
PURPLE_AGENT_DIR = os.path.abspath("code_translator_purple_agent")
15+
LEADERBOARD_DIR = os.path.abspath("code_translator_leaderboard")
16+
RESULTS_DIR = os.path.join(LEADERBOARD_DIR, "results")
17+
18+
# Ensure results directory exists
19+
os.makedirs(RESULTS_DIR, exist_ok=True)
20+
21+
GREEN_PORT = 9009
22+
PURPLE_PORT = 9010
23+
24+
GREEN_URL = f"http://127.0.0.1:{GREEN_PORT}"
25+
PURPLE_URL = f"http://127.0.0.1:{PURPLE_PORT}"
26+
27+
def start_process(command, cwd, name, log_file):
28+
print(f"Starting {name}...")
29+
f = open(log_file, "w")
30+
process = subprocess.Popen(
31+
command,
32+
cwd=cwd,
33+
stdout=f,
34+
stderr=subprocess.STDOUT,
35+
preexec_fn=os.setsid
36+
)
37+
return process, f
38+
39+
async def wait_for_agent(url, name, timeout=30):
40+
print(f"Waiting for {name} to be ready at {url}...")
41+
start_time = time.time()
42+
async with httpx.AsyncClient() as client:
43+
while time.time() - start_time < timeout:
44+
try:
45+
response = await client.get(f"{url}/.well-known/agent-card.json")
46+
if response.status_code == 200:
47+
print(f"{name} is ready!")
48+
return True
49+
except httpx.ConnectError:
50+
pass
51+
except Exception as e:
52+
print(f"Error checking {name}: {e}")
53+
await asyncio.sleep(1)
54+
print(f"{name} failed to start within {timeout} seconds.")
55+
return False
56+
57+
async def run_leaderboard_test():
58+
# 1. Start Agents
59+
green_proc, green_log = start_process(
60+
["uv", "run", "src/server.py", "--port", str(GREEN_PORT)],
61+
GREEN_AGENT_DIR,
62+
"Green Agent",
63+
"green_agent_leaderboard.log"
64+
)
65+
66+
purple_proc, purple_log = start_process(
67+
["uv", "run", "src/server.py", "--port", str(PURPLE_PORT)],
68+
PURPLE_AGENT_DIR,
69+
"Purple Agent",
70+
"purple_agent_leaderboard.log"
71+
)
72+
73+
try:
74+
# 2. Wait for health
75+
green_ready = await wait_for_agent(GREEN_URL, "Green Agent")
76+
purple_ready = await wait_for_agent(PURPLE_URL, "Purple Agent")
77+
78+
if not (green_ready and purple_ready):
79+
print("One or more agents failed to start. Aborting.")
80+
return
81+
82+
# 3. Send Evaluation Request
83+
print("\n--- Sending Evaluation Request for Leaderboard ---")
84+
85+
participant_id = "019b8933-d5b6-76a3-8e0b-930c19c10e87" # Example ID from scenario
86+
participant_name = "translator"
87+
88+
payload = {
89+
"participants": {
90+
participant_name: PURPLE_URL
91+
},
92+
"config": {
93+
"test_cases": [
94+
"""
95+
def process_data(items):
96+
\"\"\"
97+
Filters items with length > 3 and converts them to uppercase.
98+
Returns a list of formatted strings with their original indices.
99+
\"\"\"
100+
return [
101+
f"{idx}: {item.upper()}"
102+
for idx, item in enumerate(items)
103+
if len(item) > 3
104+
]
105+
""",
106+
"""
107+
class Fibonacci:
108+
def __init__(self):
109+
self.memo = {}
110+
111+
def get_number(self, n):
112+
if n in self.memo:
113+
return self.memo[n]
114+
if n <= 1:
115+
return n
116+
self.memo[n] = self.get_number(n - 1) + self.get_number(n - 2)
117+
return self.memo[n]
118+
""",
119+
"""
120+
import re
121+
122+
def parse_log_line(line):
123+
# Format: [TIMESTAMP] LEVEL: Message
124+
pattern = r"\[(.*?)\] (\w+): (.*)"
125+
match = re.match(pattern, line)
126+
if match:
127+
timestamp, level, message = match.groups()
128+
return {"time": timestamp, "level": level, "msg": message}
129+
return None
130+
"""
131+
],
132+
"source_language": "python",
133+
"target_language": "javascript"
134+
}
135+
}
136+
137+
from a2a.client import A2ACardResolver, ClientConfig, ClientFactory
138+
from a2a.types import Message, Part, TextPart, Role, DataPart
139+
from uuid import uuid4
140+
141+
async with httpx.AsyncClient(timeout=120.0) as httpx_client:
142+
print(f"Resolving agent card from {GREEN_URL}...")
143+
resolver = A2ACardResolver(httpx_client=httpx_client, base_url=GREEN_URL)
144+
agent_card = await resolver.get_agent_card()
145+
146+
config = ClientConfig(httpx_client=httpx_client, streaming=True)
147+
factory = ClientFactory(config)
148+
client = factory.create(agent_card)
149+
150+
msg = Message(
151+
kind="message",
152+
role=Role.user,
153+
parts=[Part(TextPart(text=json.dumps(payload)))],
154+
message_id=uuid4().hex,
155+
)
156+
157+
print(f"Messaging Green Agent at {GREEN_URL}...")
158+
159+
evaluation_result = None
160+
161+
async for event in client.send_message(msg):
162+
if isinstance(event, Message):
163+
pass
164+
else:
165+
task, update = event
166+
# Check for artifacts where evaluation result is stored
167+
if task.artifacts:
168+
for artifact in task.artifacts:
169+
if artifact.name == "Evaluation Result":
170+
# Assuming it's a DataPart
171+
if isinstance(artifact.parts[0].root, DataPart):
172+
evaluation_result = artifact.parts[0].root.data
173+
print(f"[ARTIFACT] Found evaluation result: {evaluation_result}")
174+
175+
if evaluation_result:
176+
# 4. Generate Leaderboard JSON
177+
print("\n--- Generating Leaderboard Entry ---")
178+
179+
# Extract scores directly from the new schema
180+
execution_correctness = evaluation_result.get("execution_correctness", 0)
181+
style_score = evaluation_result.get("style_score", 0)
182+
conciseness = evaluation_result.get("conciseness", 0)
183+
relevance = evaluation_result.get("relevance", 0)
184+
185+
# Calculate overall score if needed, or use average
186+
overall_score = (execution_correctness + style_score + conciseness + relevance) / 4.0
187+
188+
# Format matching AgentBeats tutorial (participants + results)
189+
result_entry = {
190+
"participants": {
191+
participant_name: participant_id
192+
},
193+
"results": [
194+
{
195+
"winner": evaluation_result.get("winner", participant_name),
196+
"execution_correctness": execution_correctness,
197+
"style_score": style_score,
198+
"conciseness": conciseness,
199+
"relevance": relevance,
200+
"overall_score": overall_score,
201+
"reasoning": evaluation_result.get("reasoning", "")
202+
}
203+
]
204+
}
205+
206+
filename = f"result_{participant_name}_{int(time.time())}.json"
207+
filepath = os.path.join(RESULTS_DIR, filename)
208+
209+
with open(filepath, "w") as f:
210+
json.dump(result_entry, f, indent=2)
211+
212+
print(f"Leaderboard result saved to: {filepath}")
213+
print(json.dumps(result_entry, indent=2))
214+
215+
else:
216+
print("Failed to retrieve evaluation result artifact.")
217+
218+
except Exception as e:
219+
print(f"An error occurred during testing: {e}")
220+
221+
finally:
222+
print("\n--- Shutting down agents ---")
223+
os.killpg(os.getpgid(green_proc.pid), signal.SIGTERM)
224+
os.killpg(os.getpgid(purple_proc.pid), signal.SIGTERM)
225+
green_log.close()
226+
purple_log.close()
227+
print("Done.")
228+
229+
if __name__ == "__main__":
230+
asyncio.run(run_leaderboard_test())

0 commit comments

Comments
 (0)