smarta2a 0.2.5__tar.gz → 0.3.0__tar.gz
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-0.2.5 → smarta2a-0.3.0}/PKG-INFO +1 -1
- {smarta2a-0.2.5 → smarta2a-0.3.0}/pyproject.toml +1 -1
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/client/a2a_client.py +85 -51
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/client/tools_manager.py +3 -2
- smarta2a-0.3.0/smarta2a/examples/openai_delegator_agent/main.py +41 -0
- smarta2a-0.3.0/smarta2a/examples/openai_weather_agent/__init__.py +0 -0
- {smarta2a-0.2.5/smarta2a/examples/openai_agent → smarta2a-0.3.0/smarta2a/examples/openai_weather_agent}/main.py +1 -1
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/model_providers/openai_provider.py +1 -1
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/utils/types.py +5 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/.gitignore +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/LICENSE +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/README.md +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/documentation/smarta2a_docs/docs/blog/announcements.md +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/documentation/smarta2a_docs/docs/getting-started/index.md +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/documentation/smarta2a_docs/docs/getting-started/installation.md +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/documentation/smarta2a_docs/docs/getting-started/quickstart.md +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/documentation/smarta2a_docs/docs/index.md +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/documentation/smarta2a_docs/docs/tutorials/example1.md +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/documentation/smarta2a_docs/docs/tutorials/example2.md +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/documentation/smarta2a_docs/mkdocs.yml +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/requirements.txt +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/__init__.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/agent/a2a_agent.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/agent/a2a_mcp_server.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/archive/smart_mcp_client.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/client/__init__.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/client/mcp_client.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/examples/__init__.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/examples/echo_server/__init__.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/examples/echo_server/curl.txt +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/examples/echo_server/main.py +0 -0
- {smarta2a-0.2.5/smarta2a/examples/openai_agent → smarta2a-0.3.0/smarta2a/examples/openai_delegator_agent}/__init__.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/history_update_strategies/__init__.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/history_update_strategies/append_strategy.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/history_update_strategies/history_update_strategy.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/model_providers/__init__.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/model_providers/base_llm_provider.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/server/__init__.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/server/handler_registry.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/server/server.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/server/state_manager.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/server/subscription_service.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/server/task_service.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/state_stores/__init__.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/state_stores/base_state_store.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/state_stores/inmemory_state_store.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/utils/__init__.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/utils/prompt_helpers.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/utils/task_builder.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/utils/task_request_builder.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/tests/__init__.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/tests/test_server.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/tests/test_server_history.py +0 -0
- {smarta2a-0.2.5 → smarta2a-0.3.0}/tests/test_task_request_builder.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: smarta2a
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.3.0
|
4
4
|
Summary: A simple Python framework (built on top of FastAPI) for creating Agents following Google's Agent2Agent protocol
|
5
5
|
Project-URL: Homepage, https://github.com/siddharthsma/smarta2a
|
6
6
|
Project-URL: Bug Tracker, https://github.com/siddharthsma/smarta2a/issues
|
@@ -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
|
+
input_schema = {
|
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": input_schema
|
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)
|
@@ -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
|
"""
|
@@ -27,7 +27,8 @@ class ToolsManager:
|
|
27
27
|
async def load_a2a_tools(self, agent_cards: List[AgentCard]) -> None:
|
28
28
|
for agent_card in agent_cards:
|
29
29
|
a2a_client = A2AClient(agent_card)
|
30
|
-
|
30
|
+
tools_list = await a2a_client.list_tools()
|
31
|
+
tools = [Tool(**tool_dict) for tool_dict in tools_list]
|
31
32
|
for tool in tools:
|
32
33
|
self.tools_list.append(tool)
|
33
34
|
self.clients[tool.name] = a2a_client
|
@@ -0,0 +1,41 @@
|
|
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
|
+
|
27
|
+
openai_provider = OpenAIProvider(
|
28
|
+
api_key=api_key,
|
29
|
+
model="gpt-4o-mini",
|
30
|
+
agent_cards=[weather_agent_card]
|
31
|
+
)
|
32
|
+
|
33
|
+
# Create the agent
|
34
|
+
agent = A2AAgent(
|
35
|
+
name="openai_agent",
|
36
|
+
model_provider=openai_provider,
|
37
|
+
)
|
38
|
+
|
39
|
+
# Entry point
|
40
|
+
if __name__ == "__main__":
|
41
|
+
uvicorn.run(agent.get_app(), host="0.0.0.0", port=8080)
|
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
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{smarta2a-0.2.5 → smarta2a-0.3.0}/documentation/smarta2a_docs/docs/getting-started/installation.md
RENAMED
File without changes
|
{smarta2a-0.2.5 → smarta2a-0.3.0}/documentation/smarta2a_docs/docs/getting-started/quickstart.md
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/history_update_strategies/history_update_strategy.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|