Skip to content

Commit 6bbb566

Browse files
committed
added simple weather sharing system code.
1 parent a0436a2 commit 6bbb566

File tree

10 files changed

+327
-166
lines changed

10 files changed

+327
-166
lines changed

model-deployment/A2A_agents_on_MD/README.md

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Agent-to-Agent (A2A) Communication on OCI Model Deployment
22

3-
This project demonstrates a sophisticated agent-to-agent communication system deployed on Oracle Cloud Infrastructure (OCI) Model Deployment service. The system consists of two specialized agents that work together to provide comprehensive OCI realm status information through collaborative AI interactions.
3+
This project demonstrates a sophisticated agent-to-agent communication system deployed on Oracle Cloud Infrastructure (OCI) Model Deployment service. The system consists of two specialized agents that work together to provide comprehensive weather information through collaborative AI interactions.
44

55
## Architecture Overview
66

@@ -16,11 +16,12 @@ This project demonstrates a sophisticated agent-to-agent communication system de
1616
│ │ Agent A │ │ Agent B │ │
1717
│ │ (Primary Agent) │ │ (Specialized) │ │
1818
│ │ │ │ │ │
19-
│ │ • Handles OC4-6 │◄─── A2A Protocol ─►│ • Handles OC1-3 │ │
20-
│ │ • Orchestrates │ │ • Status │ │
21-
│ │ Communication │ │ Reporter │ │
22-
│ │ • Aggregates │ │ • Data │ │
23-
│ │ Results │ │ Processing │ │
19+
│ │ • Bengaluru │◄─── A2A Protocol ─►│ • Mumbai │ │
20+
│ │ Weather │ │ Weather │ │
21+
│ │ • Orchestrates │ │ • Weather Info │ │
22+
│ │ Communication │ │ Return │ │
23+
│ │ • Aggregates │ │ │ │
24+
│ │ Results │ │ │ │
2425
│ └─────────────────┘ └─────────────────┘ │
2526
└─────────────────────────────────────────────────────────────────┘
2627
```
@@ -30,21 +31,21 @@ This project demonstrates a sophisticated agent-to-agent communication system de
3031
### Agent A (Primary Agent)
3132
- **Role**: Orchestrator and aggregator
3233
- **Responsibilities**:
33-
- Receives client requests for OCI realm status (OC1-OC6)
34-
- Manages its own status data for OC4-OC6
35-
- Communicates with Agent B to retrieve status for OC1-OC3
36-
- Aggregates and returns comprehensive status information
34+
- Receives client requests for weather information
35+
- Provides Bengaluru weather information
36+
- Communicates with Agent B to retrieve Mumbai weather information
37+
- Aggregates and returns comprehensive weather data from both cities
3738
- **Port**: 9999
38-
- **Skills**: OCI realm status aggregation and inter-agent communication
39+
- **Skills**: Bengaluru weather information and inter-agent communication
3940

4041
### Agent B (Specialized Agent)
41-
- **Role**: Specialized status provider
42+
- **Role**: Specialized weather provider
4243
- **Responsibilities**:
43-
- Provides status information for OC1-OC3 realms
44+
- Provides Mumbai weather information
4445
- Responds to A2A protocol requests from Agent A
45-
- Maintains focused expertise on specific realm data
46+
- Maintains focused expertise on Mumbai weather data
4647
- **Port**: 9998
47-
- **Skills**: OCI realm status reporting for older realms
48+
- **Skills**: Mumbai weather information and reporting
4849

4950
## Quick Start
5051

@@ -192,9 +193,11 @@ uv run python test_client.py
192193
### Expected Response
193194
```json
194195
{
195-
"this_agent_result": "🟩 New Realms Status 🟩: OC4 ✅, OC5 ✅, OC6 ✅",
196-
"other_agent_result": "🟨 Old Realms status 🟨: OC1 ✅, OC2 ✅, OC3 ✅"
196+
"this_agent_result": "Bengaluru Weather: 25°C, Sunny, Humidity: 60%, Wind: 8 km/h",
197+
"other_agent_result": "Mumbai Weather: 28°C, Partly Cloudy, Humidity: 75%, Wind: 12 km/h"
197198
}
198199
```
199200

200-
---
201+
---
202+
203+
**Note**: This is a demonstration system using dummy weather data. In production, replace the dummy data with real weather API integrations for accurate weather information.

model-deployment/A2A_agents_on_MD/agent_a/README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# Agent A - OCI Realm Finder Agent
1+
# Agent A - Bengaluru Weather Agent
22

3-
An agent-to-agent communication system built with the A2A SDK that enables seamless interaction between AI agents. This project demonstrates how to create an intelligent agent that can communicate with other agents to gather and process information collaboratively.
3+
An agent-to-agent communication system built with the A2A SDK that provides weather information for Bengaluru and collaborates with other agents to get Mumbai weather information. This project demonstrates how to create an intelligent agent that can communicate with other agents to gather and process information collaboratively.
44

55
## Architecture
66

@@ -9,10 +9,10 @@ An agent-to-agent communication system built with the A2A SDK that enables seaml
99
│ Agent A │ ◄─────────────────────► │ Agent B │
1010
│ (This Project) │ │ (External) │
1111
│ │ │ │
12-
│ • OCI Realm │ │ • Status
13-
Finder │ │ Reporter
14-
• Authentication│ │ • Data
15-
• Communication │ │ Processing
12+
│ • Bengaluru │ │ • Mumbai
13+
Weather │ │ Weather
14+
│ │
15+
│ │
1616
└─────────────────┘ └─────────────────┘
1717
```
1818

model-deployment/A2A_agents_on_MD/agent_a/__main__.py

Lines changed: 233 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,143 @@
11
import uvicorn
22
import os
3+
import logging
4+
from typing import Any
5+
from uuid import uuid4
6+
import httpx
7+
import oci
8+
import json
9+
import asyncio
310
from a2a.server.apps import A2AStarletteApplication
411
from a2a.server.request_handlers import DefaultRequestHandler
512
from a2a.server.tasks import InMemoryTaskStore
13+
from a2a.server.agent_execution import AgentExecutor, RequestContext
14+
from a2a.server.events import EventQueue
15+
from a2a.utils import new_agent_text_message
16+
from a2a.client import A2ACardResolver, A2AClient
617
from a2a.types import (
718
AgentCapabilities,
819
AgentCard,
920
AgentSkill,
10-
)
11-
from agent_executor import (
12-
OCIAllRealmFinderAgentExecutor,
21+
MessageSendParams,
22+
SendMessageRequest,
23+
SendStreamingMessageRequest,
1324
)
1425
from starlette.responses import JSONResponse
1526
from starlette.applications import Starlette
1627
from starlette.requests import Request
1728

29+
# OCI Authentication Classes
30+
class OCISignerAuth(httpx.Auth):
31+
def __init__(self, signer):
32+
self.signer = signer
33+
def auth_flow(self, request):
34+
import requests
35+
req = requests.Request(
36+
method=request.method,
37+
url=str(request.url),
38+
headers=dict(request.headers),
39+
data=request.content
40+
).prepare()
41+
self.signer(req)
42+
for k, v in req.headers.items():
43+
request.headers[k] = v
44+
yield request
45+
46+
def get_auth():
47+
PROFILE_NAME = 'default'
48+
SECURITY_TOKEN_FILE_KEY = 'security_token_file'
49+
KEY_FILE_KEY = 'key_file'
50+
config = oci.config.from_file(profile_name=PROFILE_NAME)
51+
token_file = config[SECURITY_TOKEN_FILE_KEY]
52+
with open(token_file, 'r') as f:
53+
token = f.read()
54+
private_key = oci.signer.load_private_key_from_file(config[KEY_FILE_KEY])
55+
signer = oci.auth.signers.SecurityTokenSigner(token, private_key)
56+
return OCISignerAuth(signer)
57+
58+
def get_auth_rps():
59+
print(f'[DEBUG] Getting RPS auth')
60+
rps = oci.auth.signers.get_resource_principals_signer()
61+
print(f'[DEBUG] RPS auth: {rps}')
62+
print(f'[DEBUG] RPS token: {rps.get_security_token()}')
63+
return OCISignerAuth(rps)
64+
65+
# Agent Communication Function
66+
async def get_agent_answer(base_url: str, question: str) -> str:
67+
print(f'[DEBUG] Sending request to other agent: {base_url}')
68+
PUBLIC_AGENT_CARD_PATH = '/.well-known/agent.json'
69+
async with httpx.AsyncClient(auth=get_auth_rps(), verify=False, headers={"Content-Length": "0"}) as httpx_client:
70+
resolver = A2ACardResolver(
71+
httpx_client=httpx_client,
72+
base_url=base_url,
73+
)
74+
_public_card = await resolver.get_agent_card()
75+
print(f'[DEBUG] Resolved agent card: {_public_card}')
76+
client = A2AClient(
77+
httpx_client=httpx_client, agent_card=_public_card
78+
)
79+
send_message_payload: dict[str, Any] = {
80+
'message': {
81+
'role': 'user',
82+
'parts': [
83+
{'kind': 'text', 'text': question}
84+
],
85+
'messageId': uuid4().hex,
86+
},
87+
}
88+
request = SendMessageRequest(
89+
id=str(uuid4()), params=MessageSendParams(**send_message_payload)
90+
)
91+
response = await client.send_message(request)
92+
print(f'[DEBUG] Response: {response}')
93+
try:
94+
parts = response.result.message.parts
95+
for part in parts:
96+
if 'text' in part:
97+
return part['text']
98+
return str(response.model_dump(mode='json', exclude_none=True))
99+
except Exception:
100+
return str(response.model_dump(mode='json', exclude_none=True))
101+
102+
# Agent Classes
103+
class WeatherAgent:
104+
"""Gets weather information for cities, with one city from this agent and another from the other agent."""
105+
106+
def __init__(self):
107+
agent_b_url = os.getenv('AGENT_B_URL')
108+
self.other_agent_url = agent_b_url
109+
110+
async def invoke(self) -> dict:
111+
# Get string information from the other agent
112+
other_agent_result = await get_agent_answer(self.other_agent_url, "Please provide some information")
113+
# Weather information for Bengaluru from this agent
114+
this_agent_result = '🌤️ Bengaluru Weather: 25°C, Partly Cloudy, Humidity: 70%, Wind: 8 km/h'
115+
return {
116+
"this_agent_result": this_agent_result,
117+
"other_agent_result": other_agent_result
118+
}
119+
120+
class WeatherAgentExecutor(AgentExecutor):
121+
"""Test AgentProxy Implementation that delegates to the agent's invoke method."""
122+
123+
def __init__(self):
124+
self.agent = WeatherAgent()
125+
126+
async def execute(
127+
self,
128+
context: RequestContext,
129+
event_queue: EventQueue,
130+
) -> None:
131+
result = await self.agent.invoke()
132+
# Serialize the result dict for output, preserving Unicode (emojis)
133+
await event_queue.enqueue_event(new_agent_text_message(json.dumps(result, indent=2, ensure_ascii=False)))
134+
135+
async def cancel(
136+
self, context: RequestContext, event_queue: EventQueue
137+
) -> None:
138+
raise Exception('cancel not supported')
139+
140+
# Application Setup Classes
18141
class PrefixDispatcher:
19142
def __init__(self, app, prefix="/a2a"):
20143
self.app = app
@@ -28,23 +151,119 @@ async def __call__(self, scope, receive, send):
28151
scope["path"] = "/"
29152
await self.app(scope, receive, send)
30153

31-
if __name__ == '__main__':
154+
# Test Client Function (for standalone testing)
155+
async def test_client_main() -> None:
156+
PUBLIC_AGENT_CARD_PATH = '/.well-known/agent.json'
157+
EXTENDED_AGENT_CARD_PATH = '/agent/authenticatedExtendedCard'
158+
logging.basicConfig(level=logging.INFO)
159+
logger = logging.getLogger(__name__)
160+
161+
base_url = 'https://modeldeployment.us-ashburn-1.oci.customer-oci.com/ocid1.tenancy.oc1..aaaaaaaafwgqzxcwlkkpl5i334qpv62s375upsw2j4ufgcizfnnhjd4l55ia/agent-a/predict'
162+
async with httpx.AsyncClient(auth=get_auth(), verify=False, headers={"Content-Length": "0"}) as httpx_client:
163+
resolver = A2ACardResolver(
164+
httpx_client=httpx_client,
165+
base_url=base_url,
166+
)
167+
final_agent_card_to_use: AgentCard | None = None
168+
try:
169+
logger.info(
170+
f'Attempting to fetch public agent card from: {base_url}{PUBLIC_AGENT_CARD_PATH}'
171+
)
172+
_public_card = (
173+
await resolver.get_agent_card()
174+
)
175+
logger.info('Successfully fetched public agent card:')
176+
logger.info(
177+
_public_card.model_dump_json(indent=2, exclude_none=True)
178+
)
179+
final_agent_card_to_use = _public_card
180+
logger.info(
181+
'\nUsing PUBLIC agent card for client initialization (default).'
182+
)
183+
if _public_card.supportsAuthenticatedExtendedCard:
184+
try:
185+
logger.info(
186+
f'\nPublic card supports authenticated extended card. Attempting to fetch from: {base_url}{EXTENDED_AGENT_CARD_PATH}'
187+
)
188+
auth_headers_dict = {
189+
'Authorization': 'Bearer dummy-token-for-extended-card'
190+
}
191+
_extended_card = await resolver.get_agent_card(
192+
relative_card_path=EXTENDED_AGENT_CARD_PATH,
193+
http_kwargs={'headers': auth_headers_dict},
194+
)
195+
logger.info(
196+
'Successfully fetched authenticated extended agent card:'
197+
)
198+
logger.info(
199+
_extended_card.model_dump_json(
200+
indent=2, exclude_none=True
201+
)
202+
)
203+
final_agent_card_to_use = (
204+
_extended_card
205+
)
206+
logger.info(
207+
'\nUsing AUTHENTICATED EXTENDED agent card for client initialization.'
208+
)
209+
except Exception as e_extended:
210+
logger.warning(
211+
f'Failed to fetch extended agent card: {e_extended}. Will proceed with public card.',
212+
exc_info=True,
213+
)
214+
elif (
215+
_public_card
216+
):
217+
logger.info(
218+
'\nPublic card does not indicate support for an extended card. Using public card.'
219+
)
220+
except Exception as e:
221+
logger.error(
222+
f'Critical error fetching public agent card: {e}', exc_info=True
223+
)
224+
raise RuntimeError(
225+
'Failed to fetch the public agent card. Cannot continue.'
226+
) from e
227+
client = A2AClient(
228+
httpx_client=httpx_client, agent_card=final_agent_card_to_use
229+
)
230+
logger.info('A2AClient initialized.')
231+
send_message_payload: dict[str, Any] = {
232+
'message': {
233+
'role': 'user',
234+
'parts': [
235+
{'kind': 'text', 'text': 'how much is 10 USD in INR?'}
236+
],
237+
'messageId': uuid4().hex,
238+
},
239+
}
240+
request = SendMessageRequest(
241+
id=str(uuid4()), params=MessageSendParams(**send_message_payload)
242+
)
243+
response = await client.send_message(request)
244+
print(response.model_dump(mode='json', exclude_none=True))
245+
streaming_request = SendStreamingMessageRequest(
246+
id=str(uuid4()), params=MessageSendParams(**send_message_payload)
247+
)
248+
stream_response = client.send_message_streaming(streaming_request)
249+
async for chunk in stream_response:
250+
print(chunk.model_dump(mode='json', exclude_none=True))
32251

252+
# Main Application
253+
if __name__ == '__main__':
33254
agent_a_url = os.getenv('AGENT_A_URL')
34255

35256
skill = AgentSkill(
36-
id='oci_realm_finder',
37-
name='Returns OCI functioning realms and their status',
38-
description='just returns OCI functioning realms and their status',
39-
tags=['oci', 'realm', 'finder'],
40-
examples=['what are the functioning realms and their status?', 'what is the status of the OCI-1 realm?'],
257+
id='bengaluru_weather_agent',
258+
name='Returns weather information for Bengaluru',
259+
description='returns weather information for Bengaluru and collaborates with other agents for additional information',
260+
tags=['weather', 'bengaluru', 'temperature', 'collaboration'],
261+
examples=['what is the weather in Bengaluru?', 'get Bengaluru weather information'],
41262
)
42263

43264
public_agent_card = AgentCard(
44-
name='OCI Realm Finder Agent',
45-
description='Just a OCI realm finder agent',
46-
# url='http://localhost:9999/', # TODO: change to the actual url of MD
47-
# url='https://modeldeployment.us-ashburn-1.oci.customer-oci.com/ocid1.datasciencemodeldeployment.oc1.iad.amaaaaaay75uckqavsz3dipblcb6ckgwljls5qosxramv4osvt77tr5nnrra/predict/a2a/',
265+
name='Bengaluru Weather Agent',
266+
description='A weather information agent for Bengaluru that collaborates with other agents',
48267
url=agent_a_url,
49268
version='1.0.0',
50269
defaultInputModes=['text'],
@@ -55,7 +274,7 @@ async def __call__(self, scope, receive, send):
55274
)
56275

57276
request_handler = DefaultRequestHandler(
58-
agent_executor=OCIAllRealmFinderAgentExecutor(),
277+
agent_executor=WeatherAgentExecutor(),
59278
task_store=InMemoryTaskStore(),
60279
)
61280

0 commit comments

Comments
 (0)