smarta2a 0.2.5__py3-none-any.whl → 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- smarta2a/client/a2a_client.py +85 -51
- smarta2a/client/mcp_client.py +3 -0
- smarta2a/client/tools_manager.py +30 -14
- smarta2a/examples/openai_airbnb_agent/main.py +33 -0
- smarta2a/examples/openai_delegator_agent/__init__.py +0 -0
- smarta2a/examples/openai_delegator_agent/main.py +51 -0
- smarta2a/examples/openai_weather_agent/__init__.py +0 -0
- smarta2a/examples/{openai_agent → openai_weather_agent}/main.py +1 -1
- smarta2a/model_providers/openai_provider.py +3 -3
- smarta2a/utils/types.py +6 -0
- smarta2a-0.3.1.dist-info/METADATA +399 -0
- {smarta2a-0.2.5.dist-info → smarta2a-0.3.1.dist-info}/RECORD +15 -11
- smarta2a-0.2.5.dist-info/METADATA +0 -103
- /smarta2a/examples/{openai_agent → openai_airbnb_agent}/__init__.py +0 -0
- {smarta2a-0.2.5.dist-info → smarta2a-0.3.1.dist-info}/WHEEL +0 -0
- {smarta2a-0.2.5.dist-info → smarta2a-0.3.1.dist-info}/licenses/LICENSE +0 -0
smarta2a/client/a2a_client.py
CHANGED
@@ -4,7 +4,8 @@ import httpx
|
|
4
4
|
import json
|
5
5
|
from httpx_sse import connect_sse
|
6
6
|
from inspect import signature, Parameter, iscoroutinefunction
|
7
|
-
from pydantic import create_model, Field, BaseModel
|
7
|
+
from pydantic import create_model, Field, BaseModel, ValidationError
|
8
|
+
from typing import Optional, Union
|
8
9
|
|
9
10
|
# Local imports
|
10
11
|
from smarta2a.utils.types import (
|
@@ -182,86 +183,119 @@ class A2AClient:
|
|
182
183
|
|
183
184
|
|
184
185
|
async def list_tools(self) -> list[dict[str, Any]]:
|
185
|
-
"""Return metadata for all available tools."""
|
186
|
+
"""Return metadata for all available tools with minimal inputSchema."""
|
186
187
|
tools = []
|
187
|
-
tool_names = [
|
188
|
-
'send'
|
189
|
-
]
|
188
|
+
tool_names = ['send'] # add other tool names here
|
190
189
|
for name in tool_names:
|
191
190
|
method = getattr(self, name)
|
192
191
|
doc = method.__doc__ or ""
|
193
192
|
description = doc.strip().split('\n')[0] if doc else ""
|
194
193
|
|
195
|
-
# Generate input schema
|
196
194
|
sig = signature(method)
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
required = []
|
201
|
-
for param_name, param in parameters.items():
|
195
|
+
properties: dict[str, Any] = {}
|
196
|
+
required: list[str] = []
|
197
|
+
for param_name, param in sig.parameters.items():
|
202
198
|
if param_name == 'self':
|
203
199
|
continue
|
204
|
-
|
205
|
-
|
206
|
-
annotation = Any
|
207
|
-
# Handle Literal types
|
208
|
-
if get_origin(annotation) is Literal:
|
209
|
-
enum_values = get_args(annotation)
|
210
|
-
annotation = Literal.__getitem__(enum_values)
|
211
|
-
# Handle default
|
200
|
+
|
201
|
+
ann = param.annotation
|
212
202
|
default = param.default
|
203
|
+
|
204
|
+
# Handle Literal types
|
205
|
+
if get_origin(ann) is Literal:
|
206
|
+
enum_vals = list(get_args(ann))
|
207
|
+
schema_field: dict[str, Any] = {
|
208
|
+
"title": param_name.replace('_', ' ').title(),
|
209
|
+
"enum": enum_vals
|
210
|
+
}
|
211
|
+
# For Literals we'd typically not mark required if there's a default
|
212
|
+
else:
|
213
|
+
# map basic Python types to JSON Schema types
|
214
|
+
type_map = {
|
215
|
+
str: "string",
|
216
|
+
int: "integer",
|
217
|
+
float: "number",
|
218
|
+
bool: "boolean",
|
219
|
+
dict: "object",
|
220
|
+
list: "array",
|
221
|
+
Any: None
|
222
|
+
}
|
223
|
+
json_type = type_map.get(ann, None)
|
224
|
+
schema_field = {"title": param_name.replace('_', ' ').title()}
|
225
|
+
if json_type:
|
226
|
+
schema_field["type"] = json_type
|
227
|
+
|
228
|
+
# default vs required
|
213
229
|
if default is Parameter.empty:
|
214
230
|
required.append(param_name)
|
215
|
-
|
231
|
+
# no default key
|
216
232
|
else:
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
# Create dynamic Pydantic model
|
221
|
-
model = create_model(f"{name}_Input", **fields)
|
222
|
-
schema = model.schema()
|
233
|
+
schema_field["default"] = default
|
234
|
+
|
235
|
+
properties[param_name] = schema_field
|
223
236
|
|
237
|
+
inputSchema = {
|
238
|
+
"title": f"{name}_Arguments",
|
239
|
+
"type": "object",
|
240
|
+
"properties": properties,
|
241
|
+
"required": required,
|
242
|
+
}
|
243
|
+
|
224
244
|
tools.append({
|
225
|
-
|
226
|
-
|
227
|
-
|
245
|
+
"name": name,
|
246
|
+
"description": description,
|
247
|
+
"inputSchema": inputSchema
|
228
248
|
})
|
249
|
+
|
229
250
|
return tools
|
230
251
|
|
231
252
|
async def call_tool(self, tool_name: str, arguments: dict[str, Any]) -> Any:
|
232
253
|
"""Call a tool by name with validated arguments."""
|
254
|
+
# 1) lookup
|
233
255
|
if not hasattr(self, tool_name):
|
234
256
|
raise ValueError(f"Tool {tool_name} not found")
|
235
257
|
method = getattr(self, tool_name)
|
236
|
-
|
237
|
-
#
|
258
|
+
|
259
|
+
# 2) build a minimal pydantic model for validation
|
238
260
|
sig = signature(method)
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
for param_name, param in parameters.items():
|
261
|
+
model_fields: dict[str, tuple] = {}
|
262
|
+
|
263
|
+
for param_name, param in sig.parameters.items():
|
243
264
|
if param_name == 'self':
|
244
265
|
continue
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
266
|
+
|
267
|
+
# annotation
|
268
|
+
ann = param.annotation
|
269
|
+
if ann is Parameter.empty:
|
270
|
+
ann = Any
|
271
|
+
|
272
|
+
# default
|
252
273
|
default = param.default
|
253
274
|
if default is Parameter.empty:
|
254
|
-
|
275
|
+
# required field
|
276
|
+
model_fields[param_name] = (ann, Field(...))
|
255
277
|
else:
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
278
|
+
# optional field: if default is None, widen annotation
|
279
|
+
if default is None and get_origin(ann) is not Union:
|
280
|
+
ann = Optional[ann]
|
281
|
+
model_fields[param_name] = (ann, Field(default=default))
|
282
|
+
|
283
|
+
ValidationModel = create_model(
|
284
|
+
f"{tool_name}_ValidationModel",
|
285
|
+
**model_fields
|
286
|
+
)
|
287
|
+
|
288
|
+
# 3) validate (will raise ValidationError on bad args)
|
289
|
+
try:
|
290
|
+
validated = ValidationModel(**arguments)
|
291
|
+
except ValidationError as e:
|
292
|
+
# re-raise or wrap as you like
|
293
|
+
raise ValueError(f"Invalid arguments for tool {tool_name}: {e}") from e
|
294
|
+
|
295
|
+
validated_args = validated.dict()
|
296
|
+
|
297
|
+
# 4) call
|
263
298
|
if iscoroutinefunction(method):
|
264
299
|
return await method(**validated_args)
|
265
300
|
else:
|
266
|
-
# Note: Synchronous methods (like subscribe) will block the event loop
|
267
301
|
return method(**validated_args)
|
smarta2a/client/mcp_client.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# Library imports
|
2
2
|
import re
|
3
|
+
import shlex
|
3
4
|
from typing import Dict, Any
|
4
5
|
from contextlib import AsyncExitStack
|
5
6
|
from mcp import ClientSession, StdioServerParameters
|
@@ -40,6 +41,7 @@ class MCPClient:
|
|
40
41
|
if server_script_path.startswith("@") or "/" not in server_script_path:
|
41
42
|
# Assume it's an npm package
|
42
43
|
is_javascript = True
|
44
|
+
args = shlex.split(server_script_path)
|
43
45
|
command = "npx"
|
44
46
|
else:
|
45
47
|
# It's a file path
|
@@ -55,6 +57,7 @@ class MCPClient:
|
|
55
57
|
args=args,
|
56
58
|
env=None
|
57
59
|
)
|
60
|
+
print(server_params)
|
58
61
|
|
59
62
|
# Start the server
|
60
63
|
stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
|
smarta2a/client/tools_manager.py
CHANGED
@@ -5,7 +5,7 @@ from typing import List, Dict, Any, Union, Literal
|
|
5
5
|
# Local imports
|
6
6
|
from smarta2a.client.mcp_client import MCPClient
|
7
7
|
from smarta2a.client.a2a_client import A2AClient
|
8
|
-
from smarta2a.utils.types import AgentCard
|
8
|
+
from smarta2a.utils.types import AgentCard, Tool
|
9
9
|
|
10
10
|
class ToolsManager:
|
11
11
|
"""
|
@@ -21,16 +21,28 @@ class ToolsManager:
|
|
21
21
|
mcp_client = await MCPClient.create(url)
|
22
22
|
tools = await mcp_client.list_tools()
|
23
23
|
for tool in tools:
|
24
|
-
|
25
|
-
|
24
|
+
# Generate key and ensure Tool type with key
|
25
|
+
key = f"mcp---{tool.name}"
|
26
|
+
validated_tool = Tool(
|
27
|
+
key=key,
|
28
|
+
**tool.model_dump() # Pydantic 2.x syntax (use .dict() for Pydantic 1.x)
|
29
|
+
)
|
30
|
+
self.tools_list.append(validated_tool)
|
31
|
+
self.clients[key] = mcp_client
|
26
32
|
|
27
33
|
async def load_a2a_tools(self, agent_cards: List[AgentCard]) -> None:
|
28
34
|
for agent_card in agent_cards:
|
29
35
|
a2a_client = A2AClient(agent_card)
|
30
|
-
|
31
|
-
for
|
32
|
-
|
33
|
-
|
36
|
+
tools_list = await a2a_client.list_tools()
|
37
|
+
for tool_dict in tools_list:
|
38
|
+
# Generate key from agent URL and tool name
|
39
|
+
key = f"{agent_card.name}---{tool_dict['name']}"
|
40
|
+
validated_tool = Tool(
|
41
|
+
key=key,
|
42
|
+
**tool_dict
|
43
|
+
)
|
44
|
+
self.tools_list.append(validated_tool)
|
45
|
+
self.clients[key] = a2a_client
|
34
46
|
|
35
47
|
def get_tools(self) -> List[Any]:
|
36
48
|
return self.tools_list
|
@@ -42,20 +54,24 @@ class ToolsManager:
|
|
42
54
|
schema = json.dumps(tool.inputSchema, indent=2) # Fix: use inputSchema
|
43
55
|
if client_type == "mcp":
|
44
56
|
lines.append(
|
45
|
-
f"- **{tool.
|
57
|
+
f"- **{tool.key}**: {tool.description}\n Parameters schema:\n ```json\n{schema}\n```"
|
46
58
|
)
|
47
59
|
elif client_type == "a2a":
|
48
60
|
lines.append(
|
49
|
-
f"- **{tool.
|
61
|
+
f"- **{tool.key}**: {tool.description}\n Parameters schema:\n ```json\n{schema}\n```"
|
50
62
|
)
|
51
63
|
|
52
64
|
return "\n".join(lines)
|
53
65
|
|
54
|
-
def get_client(self,
|
55
|
-
return self.clients.get(
|
66
|
+
def get_client(self, tool_key: str) -> Any:
|
67
|
+
return self.clients.get(tool_key)
|
56
68
|
|
57
|
-
async def call_tool(self,
|
58
|
-
client = self.get_client(
|
69
|
+
async def call_tool(self, tool_key: str, args: Dict[str, Any]) -> Any:
|
70
|
+
client = self.get_client(tool_key)
|
71
|
+
tool_name = self._get_tool_name(tool_key)
|
59
72
|
if not client:
|
60
73
|
raise ValueError(f"Tool not found: {tool_name}")
|
61
|
-
return await client.call_tool(tool_name, args)
|
74
|
+
return await client.call_tool(tool_name, args)
|
75
|
+
|
76
|
+
def _get_tool_name(self, tool_key: str) -> str:
|
77
|
+
return tool_key.split("---")[1]
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Imports
|
2
|
+
from dotenv import load_dotenv
|
3
|
+
import os
|
4
|
+
import uvicorn
|
5
|
+
from smarta2a.agent.a2a_agent import A2AAgent
|
6
|
+
from smarta2a.model_providers.openai_provider import OpenAIProvider
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
# Load environment variables from the .env file
|
11
|
+
load_dotenv()
|
12
|
+
|
13
|
+
# Fetch the value using os.getenv
|
14
|
+
api_key = os.getenv("OPENAI_API_KEY")
|
15
|
+
|
16
|
+
|
17
|
+
openai_provider = OpenAIProvider(
|
18
|
+
api_key=api_key,
|
19
|
+
model="gpt-4o-mini",
|
20
|
+
base_system_prompt="You are a cheerful assistant that specialises in helping with airbnb related queries",
|
21
|
+
mcp_server_urls_or_paths=["@openbnb/mcp-server-airbnb --ignore-robots-txt"]
|
22
|
+
)
|
23
|
+
# /Users/apple/.npm/_npx/1629930a2e066932/node_modules/@openbnb/mcp-server-airbnb/dist/index.js
|
24
|
+
|
25
|
+
# Create the agent
|
26
|
+
agent = A2AAgent(
|
27
|
+
name="openai_agent",
|
28
|
+
model_provider=openai_provider,
|
29
|
+
)
|
30
|
+
|
31
|
+
# Entry point
|
32
|
+
if __name__ == "__main__":
|
33
|
+
uvicorn.run(agent.get_app(), host="0.0.0.0", port=8002)
|
File without changes
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Imports
|
2
|
+
from dotenv import load_dotenv
|
3
|
+
import os
|
4
|
+
import uvicorn
|
5
|
+
from smarta2a.agent.a2a_agent import A2AAgent
|
6
|
+
from smarta2a.model_providers.openai_provider import OpenAIProvider
|
7
|
+
from smarta2a.utils.types import AgentCard, AgentCapabilities, AgentSkill
|
8
|
+
|
9
|
+
|
10
|
+
# Load environment variables from the .env file
|
11
|
+
load_dotenv()
|
12
|
+
|
13
|
+
# Fetch the value using os.getenv
|
14
|
+
api_key = os.getenv("OPENAI_API_KEY")
|
15
|
+
|
16
|
+
weather_agent_card = AgentCard(
|
17
|
+
name="weather_agent",
|
18
|
+
description="A weather agent that can help with weather related queries",
|
19
|
+
version="0.1.0",
|
20
|
+
url="http://localhost:8000",
|
21
|
+
capabilities=AgentCapabilities(),
|
22
|
+
skills=[AgentSkill(id="weather_forecasting", name="Weather Forecasting", description="Can get weather forecast for a given latitude and longitude"),
|
23
|
+
AgentSkill(id="weather_alerts", name="Weather Alerts", description="Can get weather alerts for a US state")]
|
24
|
+
)
|
25
|
+
|
26
|
+
airbnb_agent_card = AgentCard(
|
27
|
+
name="airbnb_agent",
|
28
|
+
description="An airbnb agent that can help with airbnb related queries",
|
29
|
+
version="0.1.0",
|
30
|
+
url="http://localhost:8002",
|
31
|
+
capabilities=AgentCapabilities(),
|
32
|
+
skills=[AgentSkill(id="search_listings", name="Search listings", description="Search for Airbnb listings by location, dates, guests, and more"),
|
33
|
+
AgentSkill(id="get_listing_details", name="Get listing details", description="Get detailed information about a specific Airbnb listing by listing id, dates, guests, and more")]
|
34
|
+
)
|
35
|
+
|
36
|
+
|
37
|
+
openai_provider = OpenAIProvider(
|
38
|
+
api_key=api_key,
|
39
|
+
model="gpt-4o-mini",
|
40
|
+
agent_cards=[weather_agent_card, airbnb_agent_card]
|
41
|
+
)
|
42
|
+
|
43
|
+
# Create the agent
|
44
|
+
agent = A2AAgent(
|
45
|
+
name="openai_agent",
|
46
|
+
model_provider=openai_provider,
|
47
|
+
)
|
48
|
+
|
49
|
+
# Entry point
|
50
|
+
if __name__ == "__main__":
|
51
|
+
uvicorn.run(agent.get_app(), host="0.0.0.0", port=8001)
|
File without changes
|
@@ -2,7 +2,6 @@
|
|
2
2
|
from dotenv import load_dotenv
|
3
3
|
import os
|
4
4
|
import uvicorn
|
5
|
-
import asyncio
|
6
5
|
from smarta2a.agent.a2a_agent import A2AAgent
|
7
6
|
from smarta2a.model_providers.openai_provider import OpenAIProvider
|
8
7
|
|
@@ -18,6 +17,7 @@ api_key = os.getenv("OPENAI_API_KEY")
|
|
18
17
|
openai_provider = OpenAIProvider(
|
19
18
|
api_key=api_key,
|
20
19
|
model="gpt-4o-mini",
|
20
|
+
base_system_prompt="You are a cheerful assistant that specialises in helping with weather related queries",
|
21
21
|
mcp_server_urls_or_paths=["/Users/apple/Desktop/Code/weather/weather.py"],
|
22
22
|
)
|
23
23
|
|
@@ -125,7 +125,7 @@ class OpenAIProvider(BaseLLMProvider):
|
|
125
125
|
openai_tools.append({
|
126
126
|
"type": "function",
|
127
127
|
"function": {
|
128
|
-
"name": tool.
|
128
|
+
"name": tool.key,
|
129
129
|
"description": tool.description,
|
130
130
|
"parameters": tool.inputSchema
|
131
131
|
}
|
@@ -140,7 +140,7 @@ class OpenAIProvider(BaseLLMProvider):
|
|
140
140
|
# Ensure messages are Message objects
|
141
141
|
messages = [msg if isinstance(msg, Message) else Message(**msg) for msg in messages]
|
142
142
|
converted_messages = self._convert_messages(messages)
|
143
|
-
max_iterations =
|
143
|
+
max_iterations = 30
|
144
144
|
|
145
145
|
for iteration in range(max_iterations):
|
146
146
|
response = await self.client.chat.completions.create(
|
@@ -161,7 +161,7 @@ class OpenAIProvider(BaseLLMProvider):
|
|
161
161
|
args_raw = tc.function.arguments
|
162
162
|
else:
|
163
163
|
return message.content
|
164
|
-
|
164
|
+
print(message)
|
165
165
|
# Append the assistant's intent
|
166
166
|
converted_messages.append({
|
167
167
|
"role": "assistant",
|
smarta2a/utils/types.py
CHANGED
@@ -0,0 +1,399 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: smarta2a
|
3
|
+
Version: 0.3.1
|
4
|
+
Summary: a Python framework that helps you build servers and AI agents that communicate using the A2A protocol
|
5
|
+
Project-URL: Homepage, https://github.com/siddharthsma/smarta2a
|
6
|
+
Project-URL: Bug Tracker, https://github.com/siddharthsma/smarta2a/issues
|
7
|
+
Author-email: Siddharth Ambegaonkar <siddharthsma@gmail.com>
|
8
|
+
License-File: LICENSE
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
10
|
+
Classifier: Operating System :: OS Independent
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
12
|
+
Requires-Python: >=3.8
|
13
|
+
Requires-Dist: anyio>=4.9.0
|
14
|
+
Requires-Dist: fastapi>=0.115.12
|
15
|
+
Requires-Dist: httpx>=0.28.1
|
16
|
+
Requires-Dist: mcp>=0.1.0
|
17
|
+
Requires-Dist: openai>=1.0.0
|
18
|
+
Requires-Dist: pydantic>=2.11.3
|
19
|
+
Requires-Dist: sse-starlette>=2.2.1
|
20
|
+
Requires-Dist: starlette>=0.46.2
|
21
|
+
Requires-Dist: typing-extensions>=4.13.2
|
22
|
+
Requires-Dist: uvicorn>=0.34.1
|
23
|
+
Description-Content-Type: text/markdown
|
24
|
+
|
25
|
+
<picture>
|
26
|
+
<img alt="SmartA2A Logo" src="documentation/smarta2a_docs/smarta2a_small_banner.png" width="100%">
|
27
|
+
</picture>
|
28
|
+
|
29
|
+
<div>
|
30
|
+
<br>
|
31
|
+
</div>
|
32
|
+
|
33
|
+

|
34
|
+

|
35
|
+

|
36
|
+
|
37
|
+
**SmartA2A** is a Python framework that helps you build servers and AI agents that communicate using the [A2A (Agent2Agent) protocol](https://developers.googleblog.com/en/a2a-a-new-era-of-agent-interoperability/). A2A defines a common language that enables agents to exchange information and collaborate effectively across systems.
|
38
|
+
|
39
|
+
SmartA2A abstracts the complexities of the protocol, so you can focus on what matters—your agent's logic and behavior. It supports two primary use cases:
|
40
|
+
|
41
|
+
### ✅ 1. Build A2A-Compliant Servers
|
42
|
+
|
43
|
+
Use SmartA2A to expose key A2A methods through a simple decorator-based API. This allows you to:
|
44
|
+
|
45
|
+
- Build compliant server endpoints quickly
|
46
|
+
- Integrate your own agent logic using any framework (e.g., LangGraph, Google ADK, or custom code)
|
47
|
+
- Stay fully aligned with the A2A spec without boilerplate
|
48
|
+
|
49
|
+
### 🤖 2. Create Full-Fledged Agents with Minimal Code
|
50
|
+
|
51
|
+
SmartA2A can also be used to create standalone agents that:
|
52
|
+
|
53
|
+
- Speak the A2A protocol out of the box
|
54
|
+
- Collaborate with other agents seamlessly
|
55
|
+
- Connect to any MCP server you choose
|
56
|
+
- Require only a few lines of setup
|
57
|
+
|
58
|
+
SmartA2A makes it easy to build interoperable, communication-ready AI systems—whether you're extending existing frameworks or starting from scratch.
|
59
|
+
|
60
|
+
---
|
61
|
+
|
62
|
+
## Installation
|
63
|
+
|
64
|
+
```bash
|
65
|
+
pip install -U smarta2a
|
66
|
+
```
|
67
|
+
|
68
|
+
## Examples
|
69
|
+
|
70
|
+
There are 3 cool examples for you to try out - eacher cooler than the next!
|
71
|
+
|
72
|
+
1. The first is a simple Echo server implementation that demonstrates the FastAPI'esk style in which you can create a A2A compliant server. If you're using another Agent framework like LangGraph you can easily use it it tandem with this server.
|
73
|
+
|
74
|
+
2. This is where things start to get real interesting - you can easily create an LLM and MCP powered Agent in just a few lines of code. In this case a US weather agent.
|
75
|
+
|
76
|
+
3. If you get here, this is where things things start to get really awesome. You will create a multi-agent system where a delegator agent delelgates tasks to the weather agent you created before and to an Airbnb agent. You can now plan your stay based on the weather!
|
77
|
+
|
78
|
+
Note that you can find all the examples in the examples folder.
|
79
|
+
|
80
|
+
### Simple Echo Server Implementation
|
81
|
+
|
82
|
+
Here's a simple example of an echo server that demonstrates the core features of SmartA2A:
|
83
|
+
|
84
|
+
```python
|
85
|
+
from smarta2a.server import SmartA2A
|
86
|
+
from smarta2a.utils.types import A2AResponse, TaskStatus, TaskState, TextPart, FileContent, FilePart
|
87
|
+
from smarta2a.state_stores.inmemory_state_store import InMemoryStateStore
|
88
|
+
|
89
|
+
# Initialize the server with an in-memory state store
|
90
|
+
state_store = InMemoryStateStore()
|
91
|
+
app = SmartA2A("EchoServer", state_store=state_store)
|
92
|
+
|
93
|
+
@app.on_send_task()
|
94
|
+
async def handle_task(request, state):
|
95
|
+
"""Echo the input text back as a completed task. If you were using something like LangGraph you could call your graph.invoke method here """
|
96
|
+
input_text = request.content[0].text
|
97
|
+
return f"Response to task: {input_text}"
|
98
|
+
|
99
|
+
@app.on_send_subscribe_task()
|
100
|
+
async def handle_subscribe_task(request, state):
|
101
|
+
"""Subscribe to the task and stream multiple responses"""
|
102
|
+
input_text = request.content[0].text
|
103
|
+
yield f"First response to the task: {input_text}"
|
104
|
+
yield f"Second response to the task: {input_text}"
|
105
|
+
yield f"Third response to the task: {input_text}"
|
106
|
+
|
107
|
+
@app.task_get()
|
108
|
+
def handle_get_task(request):
|
109
|
+
"""Get the task status"""
|
110
|
+
return f"Task: {request.id}"
|
111
|
+
|
112
|
+
@app.task_cancel()
|
113
|
+
def handle_cancel_task(request):
|
114
|
+
"""Cancel the task"""
|
115
|
+
return f"Task cancelled: {request.id}"
|
116
|
+
```
|
117
|
+
|
118
|
+
This example shows:
|
119
|
+
- Setting up a basic A2A server with state management
|
120
|
+
- Handling synchronous task requests with text and file responses
|
121
|
+
- Implementing streaming responses for subscription tasks
|
122
|
+
- Basic task management (get and cancel operations)
|
123
|
+
|
124
|
+
To run the echo server:
|
125
|
+
```bash
|
126
|
+
uvicorn path.to.echo_server.main.py:app
|
127
|
+
```
|
128
|
+
|
129
|
+
You can test the echo server using curl commands:
|
130
|
+
|
131
|
+
```bash
|
132
|
+
# Test sending a task
|
133
|
+
curl -X POST http://localhost:8000/ \
|
134
|
+
-H "Content-Type: application/json" \
|
135
|
+
-d '{
|
136
|
+
"jsonrpc": "2.0",
|
137
|
+
"id": "1",
|
138
|
+
"method": "tasks/send",
|
139
|
+
"params": {
|
140
|
+
"id": "task1",
|
141
|
+
"message": {
|
142
|
+
"role": "user",
|
143
|
+
"parts": [{"type": "text", "text": "Hello, echo server!"}]
|
144
|
+
}
|
145
|
+
}
|
146
|
+
}'
|
147
|
+
|
148
|
+
# Test subscribing to a task
|
149
|
+
curl -X POST http://localhost:8000/ \
|
150
|
+
-H "Content-Type: application/json" \
|
151
|
+
-d '{
|
152
|
+
"jsonrpc": "2.0",
|
153
|
+
"id": "2",
|
154
|
+
"method": "tasks/sendSubscribe",
|
155
|
+
"params": {
|
156
|
+
"id": "task2",
|
157
|
+
"message": {
|
158
|
+
"role": "user",
|
159
|
+
"parts": [{"type": "text", "text": "Hello, streaming echo!"}]
|
160
|
+
}
|
161
|
+
}
|
162
|
+
}'
|
163
|
+
```
|
164
|
+
|
165
|
+
### Weather Agent Example
|
166
|
+
|
167
|
+
Here's an example of a weather agent that uses OpenAI's GPT model to handle weather-related queries. Note that you will need to setup the weather MCP server as a pre-requisite as specified by the [MCP Quick-start server tutorial](https://modelcontextprotocol.io/quickstart/server). You will also need to add your OpenAI API key to the .env file.
|
168
|
+
|
169
|
+
```python
|
170
|
+
from dotenv import load_dotenv
|
171
|
+
import os
|
172
|
+
import uvicorn
|
173
|
+
from smarta2a.agent.a2a_agent import A2AAgent
|
174
|
+
from smarta2a.model_providers.openai_provider import OpenAIProvider
|
175
|
+
|
176
|
+
# Load environment variables from the .env file
|
177
|
+
load_dotenv()
|
178
|
+
|
179
|
+
# Initialize OpenAI provider with weather-specific configuration
|
180
|
+
openai_provider = OpenAIProvider(
|
181
|
+
api_key=os.getenv("OPENAI_API_KEY"),
|
182
|
+
model="gpt-4o-mini",
|
183
|
+
base_system_prompt="You are a cheerful assistant that specialises in helping with weather related queries",
|
184
|
+
mcp_server_urls_or_paths=["/path/to/weather.py"], # Path to your weather service
|
185
|
+
)
|
186
|
+
|
187
|
+
# Create and run the agent
|
188
|
+
agent = A2AAgent(
|
189
|
+
name="openai_agent",
|
190
|
+
model_provider=openai_provider,
|
191
|
+
)
|
192
|
+
|
193
|
+
if __name__ == "__main__":
|
194
|
+
uvicorn.run(agent.get_app(), host="0.0.0.0", port=8000)
|
195
|
+
```
|
196
|
+
|
197
|
+
This example demonstrates:
|
198
|
+
- Setting up an AI agent with OpenAI integration
|
199
|
+
- Configuring a specialized system prompt for weather queries
|
200
|
+
- Connecting to external weather services via MCP
|
201
|
+
- Running the agent as a standalone server
|
202
|
+
|
203
|
+
To run the weather agent:
|
204
|
+
```bash
|
205
|
+
python path/to/weather_agent/main.py
|
206
|
+
```
|
207
|
+
|
208
|
+
To test the weather agent, ensure:
|
209
|
+
1. Your weather MCP server is running
|
210
|
+
2. Your OpenAI API key is set in the .env file
|
211
|
+
3. The agent is running as shown above
|
212
|
+
|
213
|
+
Then test it with curl:
|
214
|
+
|
215
|
+
```bash
|
216
|
+
# Test weather query
|
217
|
+
curl -X POST http://localhost:8000/ \
|
218
|
+
-H "Content-Type: application/json" \
|
219
|
+
-d '{
|
220
|
+
"jsonrpc": "2.0",
|
221
|
+
"id": "1",
|
222
|
+
"method": "tasks/send",
|
223
|
+
"params": {
|
224
|
+
"id": "weather1",
|
225
|
+
"message": {
|
226
|
+
"role": "user",
|
227
|
+
"parts": [{"type": "text", "text": "What is the weather in New York?"}]
|
228
|
+
}
|
229
|
+
}
|
230
|
+
}'
|
231
|
+
```
|
232
|
+
|
233
|
+
### Multi-agent Weather-Aware Stay Planner
|
234
|
+
|
235
|
+
This example demonstrates how to create a multi-agent system where a delegator agent coordinates between a weather agent and an Airbnb agent to help plan stays based on weather conditions. You'll need to run three agents:
|
236
|
+
|
237
|
+
1. The weather agent (from the previous example)
|
238
|
+
2. An Airbnb agent that connects to the Airbnb MCP server
|
239
|
+
3. A delegator agent that coordinates between them
|
240
|
+
|
241
|
+
First, let's create the Airbnb agent:
|
242
|
+
|
243
|
+
```python
|
244
|
+
from dotenv import load_dotenv
|
245
|
+
import os
|
246
|
+
import uvicorn
|
247
|
+
from smarta2a.agent.a2a_agent import A2AAgent
|
248
|
+
from smarta2a.model_providers.openai_provider import OpenAIProvider
|
249
|
+
|
250
|
+
# Load environment variables
|
251
|
+
load_dotenv()
|
252
|
+
|
253
|
+
# Initialize OpenAI provider with Airbnb-specific configuration
|
254
|
+
openai_provider = OpenAIProvider(
|
255
|
+
api_key=os.getenv("OPENAI_API_KEY"),
|
256
|
+
model="gpt-4o-mini",
|
257
|
+
base_system_prompt="You are a cheerful assistant that specialises in helping with Airbnb related queries",
|
258
|
+
mcp_server_urls_or_paths=["@openbnb/mcp-server-airbnb --ignore-robots-txt"]
|
259
|
+
)
|
260
|
+
|
261
|
+
# Create and run the Airbnb agent
|
262
|
+
agent = A2AAgent(
|
263
|
+
name="airbnb_agent",
|
264
|
+
model_provider=openai_provider,
|
265
|
+
)
|
266
|
+
|
267
|
+
if __name__ == "__main__":
|
268
|
+
uvicorn.run(agent.get_app(), host="0.0.0.0", port=8002)
|
269
|
+
```
|
270
|
+
|
271
|
+
Now, let's create the delegator agent that coordinates between the weather and Airbnb agents:
|
272
|
+
|
273
|
+
```python
|
274
|
+
from dotenv import load_dotenv
|
275
|
+
import os
|
276
|
+
import uvicorn
|
277
|
+
from smarta2a.agent.a2a_agent import A2AAgent
|
278
|
+
from smarta2a.model_providers.openai_provider import OpenAIProvider
|
279
|
+
from smarta2a.utils.types import AgentCard, AgentCapabilities, AgentSkill
|
280
|
+
|
281
|
+
# Load environment variables
|
282
|
+
load_dotenv()
|
283
|
+
|
284
|
+
# Define the weather agent's capabilities
|
285
|
+
weather_agent_card = AgentCard(
|
286
|
+
name="weather_agent",
|
287
|
+
description="A weather agent that can help with weather related queries",
|
288
|
+
version="0.1.0",
|
289
|
+
url="http://localhost:8000",
|
290
|
+
capabilities=AgentCapabilities(),
|
291
|
+
skills=[
|
292
|
+
AgentSkill(
|
293
|
+
id="weather_forecasting",
|
294
|
+
name="Weather Forecasting",
|
295
|
+
description="Can get weather forecast for a given location"
|
296
|
+
),
|
297
|
+
AgentSkill(
|
298
|
+
id="weather_alerts",
|
299
|
+
name="Weather Alerts",
|
300
|
+
description="Can get weather alerts for a US state"
|
301
|
+
)
|
302
|
+
]
|
303
|
+
)
|
304
|
+
|
305
|
+
# Define the Airbnb agent's capabilities
|
306
|
+
airbnb_agent_card = AgentCard(
|
307
|
+
name="airbnb_agent",
|
308
|
+
description="An Airbnb agent that can help with accommodation queries",
|
309
|
+
version="0.1.0",
|
310
|
+
url="http://localhost:8002",
|
311
|
+
capabilities=AgentCapabilities(),
|
312
|
+
skills=[
|
313
|
+
AgentSkill(
|
314
|
+
id="search_listings",
|
315
|
+
name="Search Listings",
|
316
|
+
description="Search for Airbnb listings by location, dates, and guests"
|
317
|
+
),
|
318
|
+
AgentSkill(
|
319
|
+
id="get_listing_details",
|
320
|
+
name="Get Listing Details",
|
321
|
+
description="Get detailed information about specific Airbnb listings"
|
322
|
+
)
|
323
|
+
]
|
324
|
+
)
|
325
|
+
|
326
|
+
# Initialize OpenAI provider with both agent cards
|
327
|
+
openai_provider = OpenAIProvider(
|
328
|
+
api_key=os.getenv("OPENAI_API_KEY"),
|
329
|
+
model="gpt-4o-mini",
|
330
|
+
agent_cards=[weather_agent_card, airbnb_agent_card]
|
331
|
+
)
|
332
|
+
|
333
|
+
# Create and run the delegator agent
|
334
|
+
agent = A2AAgent(
|
335
|
+
name="delegator_agent",
|
336
|
+
model_provider=openai_provider,
|
337
|
+
)
|
338
|
+
|
339
|
+
if __name__ == "__main__":
|
340
|
+
uvicorn.run(agent.get_app(), host="0.0.0.0", port=8001)
|
341
|
+
```
|
342
|
+
|
343
|
+
To run the multi-agent system:
|
344
|
+
|
345
|
+
1. Start the weather agent:
|
346
|
+
```bash
|
347
|
+
python path/to/weather_agent/main.py
|
348
|
+
```
|
349
|
+
|
350
|
+
2. Start the Airbnb agent: No need to do anything - just ensure that node and npx are installed on your machine
|
351
|
+
|
352
|
+
|
353
|
+
3. Start the delegator agent:
|
354
|
+
```bash
|
355
|
+
python path/to/delegator_agent/main.py
|
356
|
+
```
|
357
|
+
|
358
|
+
You can then test the system with a curl command:
|
359
|
+
|
360
|
+
```bash
|
361
|
+
# Test multi-agent planning
|
362
|
+
curl -X POST http://localhost:8001/ \
|
363
|
+
-H "Content-Type: application/json" \
|
364
|
+
-d '{
|
365
|
+
"jsonrpc": "2.0",
|
366
|
+
"id": "1",
|
367
|
+
"method": "tasks/send",
|
368
|
+
"params": {
|
369
|
+
"id": "plan1",
|
370
|
+
"message": {
|
371
|
+
"role": "user",
|
372
|
+
"parts": [{"type": "text", "text": "I want to go someplace sunny in California. Just me and my wife. Can you check the weather forecast in the major cities and give me some airbnb recommendations that are appropriate. I want to stay from the 7th of May and checking out on the 10th of May. I'm not fussed about the budget."}]
|
373
|
+
}
|
374
|
+
}
|
375
|
+
}'
|
376
|
+
```
|
377
|
+
|
378
|
+
This example demonstrates:
|
379
|
+
- Creating specialized agents for different domains (weather and Airbnb)
|
380
|
+
- Defining agent capabilities and skills using AgentCard
|
381
|
+
- Setting up a delegator agent that can coordinate between multiple agents
|
382
|
+
- Running multiple agents on different ports
|
383
|
+
- Making complex queries that require coordination between agents
|
384
|
+
|
385
|
+
---
|
386
|
+
|
387
|
+
## Roadmap
|
388
|
+
|
389
|
+
There's still a lot more to come ...
|
390
|
+
|
391
|
+
- Documentation site with tutorials, explanation of concepts, blog and a contribution guide
|
392
|
+
- Support for more LLM providers
|
393
|
+
- Support for more memory types
|
394
|
+
- Authorization and observability framework built-in
|
395
|
+
- Many more !
|
396
|
+
|
397
|
+
## License
|
398
|
+
|
399
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
@@ -3,21 +3,25 @@ smarta2a/agent/a2a_agent.py,sha256=zk6ZZtq4HrnKMDuZKnQPeKploV1IGXSZ1WmVPdIZAeY,1
|
|
3
3
|
smarta2a/agent/a2a_mcp_server.py,sha256=X_mxkgYgCA_dSNtCvs0rSlOoWYc-8d3Qyxv0e-a7NKY,1015
|
4
4
|
smarta2a/archive/smart_mcp_client.py,sha256=NjyMR8xvrhy0-iFamj2XQnKp5qzkkFya7EQY4Kzl5dw,2574
|
5
5
|
smarta2a/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
-
smarta2a/client/a2a_client.py,sha256=
|
7
|
-
smarta2a/client/mcp_client.py,sha256=
|
8
|
-
smarta2a/client/tools_manager.py,sha256=
|
6
|
+
smarta2a/client/a2a_client.py,sha256=5WIincf_6Nqh61_x3f0NYUSLXlGztlnzUpo0m6ch_j0,11038
|
7
|
+
smarta2a/client/mcp_client.py,sha256=PM4D1CgOycxK5kJEJTGpKq0eXFjZ69-2720TuRUkyGc,3627
|
8
|
+
smarta2a/client/tools_manager.py,sha256=JDD2lqcDe70JVHlPoRU5oHzUYcZUkH5QzjtEkWYfrrA,2970
|
9
9
|
smarta2a/examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
10
|
smarta2a/examples/echo_server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
11
|
smarta2a/examples/echo_server/curl.txt,sha256=MYBbAgrU3PeCsksrrBc0xgaTD5Dro7xiTsCUz8Ocvt8,247
|
12
12
|
smarta2a/examples/echo_server/main.py,sha256=i_XLQbRAv6XxkJX0vLV3CxHNANG41yAxgP-V-b4Iysc,1261
|
13
|
-
smarta2a/examples/
|
14
|
-
smarta2a/examples/
|
13
|
+
smarta2a/examples/openai_airbnb_agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
|
+
smarta2a/examples/openai_airbnb_agent/main.py,sha256=CaIYi-LQD32Rm_BsXI7sLfFqx_kMpl0k8zCayFB4068,892
|
15
|
+
smarta2a/examples/openai_delegator_agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
|
+
smarta2a/examples/openai_delegator_agent/main.py,sha256=zP6YOMxTBLfMtrZOPwPLQhSVodULbDkCCAy4pB4ltto,1805
|
17
|
+
smarta2a/examples/openai_weather_agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
|
+
smarta2a/examples/openai_weather_agent/main.py,sha256=o8QqLu9JC3zRXRPGWhYdd7mVl3ooys5UwkPCPv2JHmQ,796
|
15
19
|
smarta2a/history_update_strategies/__init__.py,sha256=x5WtiE9rG5ze8d8hA6E6wJOciBhWHa_ZgGgoIAZcXEQ,213
|
16
20
|
smarta2a/history_update_strategies/append_strategy.py,sha256=j7Qbhs69Wwr-HBLB8GJ3-mEPaBSHiBV2xz9ZZi86k2w,312
|
17
21
|
smarta2a/history_update_strategies/history_update_strategy.py,sha256=n2sfIGu8ztKI7gJTwRX26m4tZr28B8Xdhrk6RlBFlI8,373
|
18
22
|
smarta2a/model_providers/__init__.py,sha256=2FhblAUiwG9Xv27yEpuuz0VrnIZ-rlpgIuPwm-UIX5U,147
|
19
23
|
smarta2a/model_providers/base_llm_provider.py,sha256=6QjTUjYEnvHZji4_VWZz6CvLYKLyutxRUfIeH3seQg4,424
|
20
|
-
smarta2a/model_providers/openai_provider.py,sha256=
|
24
|
+
smarta2a/model_providers/openai_provider.py,sha256=yTiBVJfEFOTSQsVj8mNx5qVZWvOvslYvw7TQ0tLLf7U,10870
|
21
25
|
smarta2a/server/__init__.py,sha256=f2X454Ll4vJc02V4JLJHTN-h8u0TBm4d_FkiO4t686U,53
|
22
26
|
smarta2a/server/handler_registry.py,sha256=OVRG5dTvxB7qUNXgsqWxVNxIyRljUShSYxb1gtbi5XM,820
|
23
27
|
smarta2a/server/server.py,sha256=WJGQVOprF2Hh_036eW4GJrRr3fLC0S_B3dAfONCgZrs,32180
|
@@ -31,8 +35,8 @@ smarta2a/utils/__init__.py,sha256=5db5VgDGgbMUGEF-xuyaC3qrgRQkUE9WAITkFSiNqSA,70
|
|
31
35
|
smarta2a/utils/prompt_helpers.py,sha256=jLETieoeBJLQXcGzwFeoKT5b2pS4tJ5770lPLImtKLo,1439
|
32
36
|
smarta2a/utils/task_builder.py,sha256=wqSyfVHNTaXuGESu09dhlaDi7D007gcN3-8tH-nPQ40,5159
|
33
37
|
smarta2a/utils/task_request_builder.py,sha256=6cOGOqj2Rg43xWM03GRJQzlIZHBptsMCJRp7oD-TDAQ,3362
|
34
|
-
smarta2a/utils/types.py,sha256=
|
35
|
-
smarta2a-0.
|
36
|
-
smarta2a-0.
|
37
|
-
smarta2a-0.
|
38
|
-
smarta2a-0.
|
38
|
+
smarta2a/utils/types.py,sha256=h4bQw_RmeJCDq022aEd9XJZhNpJOydhV40tqERvLeQE,13190
|
39
|
+
smarta2a-0.3.1.dist-info/METADATA,sha256=Z2Z6w1tr9o6bkmxplJ4OeG5CZ7RAk9RIjkf1CYnuwfY,13005
|
40
|
+
smarta2a-0.3.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
41
|
+
smarta2a-0.3.1.dist-info/licenses/LICENSE,sha256=ECMEVHuFkvpEmH-_A9HSxs_UnnsUqpCkiAYNHPCf2z0,1078
|
42
|
+
smarta2a-0.3.1.dist-info/RECORD,,
|
@@ -1,103 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.4
|
2
|
-
Name: smarta2a
|
3
|
-
Version: 0.2.5
|
4
|
-
Summary: A simple Python framework (built on top of FastAPI) for creating Agents following Google's Agent2Agent protocol
|
5
|
-
Project-URL: Homepage, https://github.com/siddharthsma/smarta2a
|
6
|
-
Project-URL: Bug Tracker, https://github.com/siddharthsma/smarta2a/issues
|
7
|
-
Author-email: Siddharth Ambegaonkar <siddharthsma@gmail.com>
|
8
|
-
License-File: LICENSE
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
10
|
-
Classifier: Operating System :: OS Independent
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
12
|
-
Requires-Python: >=3.8
|
13
|
-
Requires-Dist: anyio>=4.9.0
|
14
|
-
Requires-Dist: fastapi>=0.115.12
|
15
|
-
Requires-Dist: httpx>=0.28.1
|
16
|
-
Requires-Dist: mcp>=0.1.0
|
17
|
-
Requires-Dist: openai>=1.0.0
|
18
|
-
Requires-Dist: pydantic>=2.11.3
|
19
|
-
Requires-Dist: sse-starlette>=2.2.1
|
20
|
-
Requires-Dist: starlette>=0.46.2
|
21
|
-
Requires-Dist: typing-extensions>=4.13.2
|
22
|
-
Requires-Dist: uvicorn>=0.34.1
|
23
|
-
Description-Content-Type: text/markdown
|
24
|
-
|
25
|
-
# SmartA2A
|
26
|
-
|
27
|
-
A Python package for creating a server following Google's Agent2Agent protocol
|
28
|
-
|
29
|
-
## Features
|
30
|
-
|
31
|
-
✅ **Full A2A Protocol Compliance** - Implements all required endpoints and response formats
|
32
|
-
|
33
|
-
⚡ **Decorator-Driven Development** - Rapid endpoint configuration with type safety
|
34
|
-
|
35
|
-
🧩 **Automatic Protocol Conversion** - Simple returns become valid A2A responses
|
36
|
-
|
37
|
-
🔀 **Flexible Response Handling** - Support for Tasks, Artifacts, Streaming, and raw protocol types if needed!
|
38
|
-
|
39
|
-
🛡️ **Built-in Validation** - Automatic Pydantic validation of A2A schemas
|
40
|
-
|
41
|
-
⚡ **Single File Setup** - Get compliant in <10 lines of code
|
42
|
-
|
43
|
-
🌍 **Production Ready** - CORS, async support, and error handling included
|
44
|
-
|
45
|
-
## Installation
|
46
|
-
|
47
|
-
```bash
|
48
|
-
pip install smarta2a
|
49
|
-
```
|
50
|
-
|
51
|
-
## Simple Echo Server Implementation
|
52
|
-
|
53
|
-
```python
|
54
|
-
from smarta2a.server import SmartA2A
|
55
|
-
|
56
|
-
app = SmartA2A("EchoServer")
|
57
|
-
|
58
|
-
@app.on_send_task()
|
59
|
-
def handle_task(request):
|
60
|
-
"""Echo the input text back as a completed task"""
|
61
|
-
input_text = request.content[0].text
|
62
|
-
return f"Echo: {input_text}"
|
63
|
-
|
64
|
-
if __name__ == "__main__":
|
65
|
-
app.run()
|
66
|
-
```
|
67
|
-
|
68
|
-
Automatically contructs the response:
|
69
|
-
|
70
|
-
```json
|
71
|
-
{
|
72
|
-
"jsonrpc": "2.0",
|
73
|
-
"id": "test",
|
74
|
-
"result": {
|
75
|
-
"id": "echo-task",
|
76
|
-
"status": {"state": "completed"},
|
77
|
-
"artifacts": [{
|
78
|
-
"parts": [{"type": "text", "text": "Echo: Hello!"}]
|
79
|
-
}]
|
80
|
-
}
|
81
|
-
}
|
82
|
-
```
|
83
|
-
|
84
|
-
## Development
|
85
|
-
|
86
|
-
To set up the development environment:
|
87
|
-
|
88
|
-
```bash
|
89
|
-
# Clone the repository
|
90
|
-
git clone https://github.com/siddharthsma/smarta2a.git
|
91
|
-
cd smarta2a
|
92
|
-
|
93
|
-
# Create and activate virtual environment
|
94
|
-
python -m venv venv
|
95
|
-
source venv/bin/activate # On Windows: venv\Scripts\activate
|
96
|
-
|
97
|
-
# Install development dependencies
|
98
|
-
pip install -e ".[dev]"
|
99
|
-
```
|
100
|
-
|
101
|
-
## License
|
102
|
-
|
103
|
-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
File without changes
|
File without changes
|
File without changes
|