Skip to content

Commit 0de948e

Browse files
Merge pull request #6 from satendrakumar/develop
Code refactoring
2 parents 1d339b4 + 3103dc1 commit 0de948e

File tree

10 files changed

+123
-89
lines changed

10 files changed

+123
-89
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# a2a-multiple-agents-on-single-host
1+
# multiple-agents-on-single-a2a-server
22

33
This repository demonstrates how to run **multiple A2A agents** on the **same host** using the A2A protocol.
44
Each agent is served at a **unique URL path**, making it possible to host different agents without requiring multiple servers or ports.
@@ -22,8 +22,8 @@ Three agents running on the same host:
2222

2323
1. Clone the repository
2424
```bash
25-
git clone https://github.com/satendrakumar/a2a-multiple-agents-on-single-host.git
26-
cd a2a-multiple-agents-on-single-host
25+
git clone https://github.com/satendrakumar/multiple-agents-on-single-a2a-server.git
26+
cd multiple-agents-on-single-a2a-server
2727
```
2828

2929
2. Install dependencies (using uv)

a2a_client_app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ async def main():
88
a2a_client: A2ASimpleClient = A2ASimpleClient()
99
agent_host_url = "http://localhost:8000/a2a"
1010

11-
trending_task = a2a_client.create_task(agent_url=f'{agent_host_url}/trending', message="What's trending today?",
11+
trending_task = a2a_client.create_task(agent_url=f'{agent_host_url}/trending_topics', message="What's trending today?",
1212
context_id=str(uuid.uuid4()))
1313
analysis_task = a2a_client.create_task(agent_url=f"{agent_host_url}/analyzer",
1414
message="Analyze the trend AI in Social Media", context_id=str(uuid.uuid4()))

main.py

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,47 +5,53 @@
55
from dotenv import load_dotenv
66
from fastapi import FastAPI
77

8-
from src.a2a.a2a_fastapi_app import A2AFastApiApp, get_agent_request_handler
9-
from src.agent.analyzer_agent import analyzer_agent, get_analyzer_agent_card
10-
from src.agent.conversation_agent import get_conversational_agent_card, conversational_agent
11-
from src.agent.trending_topics_agent import trending_topics_agent, get_trending_topics_agent_card
8+
from src.a2a.a2a_utils import A2AUtils
9+
from src.agent.analyzer_agent import get_analyzer_agent_card, get_analyzer_agent
10+
from src.agent.conversation_agent import get_conversational_agent_card, get_conversational_agent
11+
from src.agent.trending_topics_agent import get_trending_topics_agent_card, get_trending_topics_agent
1212

1313
load_dotenv()
1414

1515
logging.basicConfig(level=logging.INFO)
1616
logger = logging.getLogger()
1717

1818
AGENT_BASE_URL = os.getenv('AGENT_BASE_URL')
19+
20+
if not AGENT_BASE_URL:
21+
raise ValueError("AGENT_BASE_URL environment variable must be set")
22+
23+
MODEL_NAME = os.getenv('MODEL_NAME')
24+
25+
if not MODEL_NAME:
26+
raise ValueError("MODEL_NAME environment variable must be set")
1927
logger.info(f"AGENT BASE URL {AGENT_BASE_URL}")
2028

2129
app: FastAPI = FastAPI(title="Run multiple agents on single host using A2A protocol.",
2230
description="Run multiple agents on single host using A2A protocol.",
2331
version="1.0.0",
24-
root_path=f"/a2a")
32+
root_path="/a2a")
2533

2634

2735
@app.get("/health")
2836
async def health_check() -> dict[str, str]:
2937
return {"status": "ok"}
3038

3139

32-
conversation_agent_request_handler = get_agent_request_handler(conversational_agent)
33-
conversational_agent_card = get_conversational_agent_card(f"{AGENT_BASE_URL}/conversation/")
34-
conversational_agent_server = A2AFastApiApp(fastapi_app=app, agent_card=conversational_agent_card, http_handler=conversation_agent_request_handler)
35-
# {path:path} added into the agent card url path to handle both 'agent-card.json' and '/.well-known/agent-card.json'
36-
conversational_agent_server.build(rpc_url="/conversation/", agent_card_url="/conversation/{path:path}")
40+
# conversation agent integration with A2A server
41+
A2AUtils.build(name="conversation", get_agent=get_conversational_agent, get_agent_card=get_conversational_agent_card,
42+
model_name=MODEL_NAME,
43+
agent_base_url=AGENT_BASE_URL, app=app)
3744

38-
trending_agent_request_handler = get_agent_request_handler(trending_topics_agent)
39-
trending_topics_agent_card = get_trending_topics_agent_card(f"{AGENT_BASE_URL}/trending/")
40-
trending_agent_server = A2AFastApiApp(fastapi_app=app, agent_card=trending_topics_agent_card,
41-
http_handler=trending_agent_request_handler)
42-
trending_agent_server.build(rpc_url="/trending/", agent_card_url="/trending/{path:path}")
45+
# trending_topics agent integration with A2A server
46+
A2AUtils.build(name="trending_topics", get_agent=get_trending_topics_agent,
47+
get_agent_card=get_trending_topics_agent_card,
48+
model_name=MODEL_NAME,
49+
agent_base_url=AGENT_BASE_URL, app=app)
4350

44-
analyzer_agent_request_handler = get_agent_request_handler(analyzer_agent)
45-
analyzer_agent_card = get_analyzer_agent_card(f"{AGENT_BASE_URL}/analyzer/")
46-
analyzer_agent_server = A2AFastApiApp(fastapi_app=app, agent_card=analyzer_agent_card,
47-
http_handler=analyzer_agent_request_handler)
48-
analyzer_agent_server.build(rpc_url="/analyzer/", agent_card_url="/analyzer/{path:path}")
51+
# analyzer agent integration with A2A server
52+
A2AUtils.build(name="analyzer", get_agent=get_analyzer_agent, get_agent_card=get_analyzer_agent_card,
53+
model_name=MODEL_NAME,
54+
agent_base_url=AGENT_BASE_URL, app=app)
4955

5056
if __name__ == '__main__':
5157
uvicorn.run(app, host="0.0.0.0", port=8000)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[project]
2-
name = "a2a-multiple-agents-on-single-host"
2+
name = "multiple-agents-on-single-a2a-server"
33
version = "0.1.0"
44
description = "Add your description here"
55
readme = "README.md"

src/a2a/a2a_client.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,21 @@
11
from typing import Any
2-
3-
4-
import httpx
5-
from a2a.client import ClientConfig, ClientFactory, ClientCallContext
6-
from a2a.types import (AgentCard, TransportProtocol, )
7-
from a2a.types import Message, Part, Role, TextPart
8-
92
from uuid import uuid4
103

4+
import httpx
5+
from a2a.client import ClientConfig, ClientFactory
6+
from a2a.types import AgentCard, TransportProtocol
117
from a2a.types import Message, Part, Role, TextPart
128

139
AGENT_CARD_PATH = '/agent-card.json'
1410

11+
1512
class A2ASimpleClient:
1613
def __init__(self, default_timeout: float = 240.0):
1714
# Cache for agent metadata
1815
self._agent_info_cache: dict[str, dict[str, Any] | None] = {}
1916
self.default_timeout = default_timeout
2017

21-
async def create_task(self, agent_url: str, message: str, context_id:str) -> str:
18+
async def create_task(self, agent_url: str, message: str, context_id: str) -> str:
2219
"""Send a message following the official A2A SDK pattern."""
2320
# Configure httpx client with timeout
2421
timeout_config = httpx.Timeout(
@@ -60,7 +57,8 @@ async def create_task(self, agent_url: str, message: str, context_id:str) -> str
6057

6158
factory = ClientFactory(config)
6259
client = factory.create(agent_card)
63-
message_obj= Message(role=Role.user, parts=[Part(TextPart(text=message))], message_id=str(uuid4()), context_id=context_id)
60+
message_obj = Message(role=Role.user, parts=[Part(TextPart(text=message))], message_id=str(uuid4()),
61+
context_id=context_id)
6462
responses = []
6563
async for response in client.send_message(message_obj):
6664
responses.append(response)

src/a2a/a2a_utils.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from typing import Callable
2+
3+
from a2a.types import AgentCard
4+
from fastapi import FastAPI
5+
from google.adk.agents import LlmAgent
6+
7+
from src.a2a.a2a_fastapi_app import get_agent_request_handler, A2AFastApiApp
8+
9+
10+
class A2AUtils:
11+
@staticmethod
12+
def build(
13+
name: str,
14+
get_agent: Callable[[str], LlmAgent],
15+
get_agent_card: Callable[[str], AgentCard],
16+
model_name: str,
17+
agent_base_url: str,
18+
app: FastAPI
19+
) -> None:
20+
agent = get_agent(model_name)
21+
agent_request_handler = get_agent_request_handler(agent)
22+
agent_card = get_agent_card(f"{agent_base_url}/{name}/")
23+
agent_server = A2AFastApiApp(fastapi_app=app, agent_card=agent_card,
24+
http_handler=agent_request_handler)
25+
agent_server.build(rpc_url=f"/{name}/", agent_card_url=f"/{name}/{{path:path}}")

src/agent/analyzer_agent.py

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,34 @@
11
from a2a.types import AgentCard, AgentCapabilities, TransportProtocol, AgentSkill
2-
from google.adk import Agent
2+
from google.adk.agents import LlmAgent
33
from google.adk.tools import google_search
44

5-
analyzer_agent = Agent(
6-
model='gemini-2.5-flash',
7-
name='trend_analyzer_agent',
8-
instruction="""
9-
You are a data analyst specializing in trend analysis. When given a trending topic,
10-
perform deep research to find quantitative data and insights.
115

12-
For each trend you analyze:
13-
1. Search for statistics, numbers, and metrics related to the trend
14-
2. Look for:
15-
- Engagement metrics (views, shares, mentions)
16-
- Growth rates and timeline
17-
- Geographic distribution
18-
- Related hashtags or keywords
19-
3. Provide concrete numbers and data points
20-
21-
Keep it somehow concise
22-
23-
Always prioritize quantitative information over qualitative descriptions.
24-
""",
25-
tools=[google_search],
26-
)
6+
def get_analyzer_agent(model: str) -> LlmAgent:
7+
return LlmAgent(
8+
model=model,
9+
name='trend_analyzer_agent',
10+
instruction="""
11+
You are a data analyst specializing in trend analysis. When given a trending topic,
12+
perform deep research to find quantitative data and insights.
13+
14+
For each trend you analyze:
15+
1. Search for statistics, numbers, and metrics related to the trend
16+
2. Look for:
17+
- Engagement metrics (views, shares, mentions)
18+
- Growth rates and timeline
19+
- Geographic distribution
20+
- Related hashtags or keywords
21+
3. Provide concrete numbers and data points
22+
23+
Keep it somehow concise
24+
25+
Always prioritize quantitative information over qualitative descriptions.
26+
""",
27+
tools=[google_search],
28+
)
2729

2830

29-
def get_analyzer_agent_card(agent_url):
31+
def get_analyzer_agent_card(agent_url: str) -> AgentCard:
3032
return AgentCard(
3133
name='Trend Analyzer Agent',
3234
url=agent_url,

src/agent/conversation_agent.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from a2a.types import AgentCard, AgentCapabilities, TransportProtocol, AgentSkill
2-
from google.adk import Agent
32
from google.adk.agents import LlmAgent
43
from google.adk.tools import google_search
54

@@ -62,16 +61,18 @@
6261
- End with an offer to assist further
6362
"""
6463

65-
conversational_agent: LlmAgent = Agent(
66-
model="gemini-2.5-flash",
67-
name="conversational_agent",
68-
description="An AI assistant that enhances conversations with live web search when needed.",
69-
instruction=CONVERSATION_AGENT_INSTRUCTIONS,
70-
tools=[google_search],
71-
)
64+
65+
def get_conversational_agent(model: str) -> LlmAgent:
66+
return LlmAgent(
67+
model=model,
68+
name="conversational_agent",
69+
description="An AI assistant that enhances conversations with live web search when needed.",
70+
instruction=CONVERSATION_AGENT_INSTRUCTIONS,
71+
tools=[google_search],
72+
)
7273

7374

74-
def get_conversational_agent_card(agent_url: str):
75+
def get_conversational_agent_card(agent_url: str) -> AgentCard:
7576
return AgentCard(
7677
name="Conversational Agent",
7778
description="Smart Conversational Agent Enhanced with Web Search Capabilities",

src/agent/trending_topics_agent.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from a2a.types import AgentCard, AgentCapabilities, TransportProtocol, AgentSkill
2-
from google.adk import Agent
2+
from google.adk.agents import LlmAgent
33
from google.adk.tools import google_search
44

5-
trending_topics_agent = Agent(
6-
model='gemini-2.5-flash',
7-
name='trending_topics_agent',
8-
instruction="""
5+
6+
def get_trending_topics_agent(model: str) -> LlmAgent:
7+
return LlmAgent(
8+
model=model,
9+
name='trending_topics_agent',
10+
instruction="""
911
You are a social media trends analyst. Your job is to search the web for current trending topics,
1012
particularly from social platforms.
1113
@@ -39,11 +41,11 @@
3941
4042
Only return the JSON object, no additional text.
4143
""",
42-
tools=[google_search],
43-
)
44+
tools=[google_search],
45+
)
4446

4547

46-
def get_trending_topics_agent_card(agent_url: str):
48+
def get_trending_topics_agent_card(agent_url: str) -> AgentCard:
4749
return AgentCard(
4850
name='Trending Topics Agent',
4951
url=agent_url,

uv.lock

Lines changed: 15 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)