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.
Files changed (54) hide show
  1. {smarta2a-0.2.5 → smarta2a-0.3.0}/PKG-INFO +1 -1
  2. {smarta2a-0.2.5 → smarta2a-0.3.0}/pyproject.toml +1 -1
  3. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/client/a2a_client.py +85 -51
  4. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/client/tools_manager.py +3 -2
  5. smarta2a-0.3.0/smarta2a/examples/openai_delegator_agent/main.py +41 -0
  6. smarta2a-0.3.0/smarta2a/examples/openai_weather_agent/__init__.py +0 -0
  7. {smarta2a-0.2.5/smarta2a/examples/openai_agent → smarta2a-0.3.0/smarta2a/examples/openai_weather_agent}/main.py +1 -1
  8. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/model_providers/openai_provider.py +1 -1
  9. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/utils/types.py +5 -0
  10. {smarta2a-0.2.5 → smarta2a-0.3.0}/.gitignore +0 -0
  11. {smarta2a-0.2.5 → smarta2a-0.3.0}/LICENSE +0 -0
  12. {smarta2a-0.2.5 → smarta2a-0.3.0}/README.md +0 -0
  13. {smarta2a-0.2.5 → smarta2a-0.3.0}/documentation/smarta2a_docs/docs/blog/announcements.md +0 -0
  14. {smarta2a-0.2.5 → smarta2a-0.3.0}/documentation/smarta2a_docs/docs/getting-started/index.md +0 -0
  15. {smarta2a-0.2.5 → smarta2a-0.3.0}/documentation/smarta2a_docs/docs/getting-started/installation.md +0 -0
  16. {smarta2a-0.2.5 → smarta2a-0.3.0}/documentation/smarta2a_docs/docs/getting-started/quickstart.md +0 -0
  17. {smarta2a-0.2.5 → smarta2a-0.3.0}/documentation/smarta2a_docs/docs/index.md +0 -0
  18. {smarta2a-0.2.5 → smarta2a-0.3.0}/documentation/smarta2a_docs/docs/tutorials/example1.md +0 -0
  19. {smarta2a-0.2.5 → smarta2a-0.3.0}/documentation/smarta2a_docs/docs/tutorials/example2.md +0 -0
  20. {smarta2a-0.2.5 → smarta2a-0.3.0}/documentation/smarta2a_docs/mkdocs.yml +0 -0
  21. {smarta2a-0.2.5 → smarta2a-0.3.0}/requirements.txt +0 -0
  22. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/__init__.py +0 -0
  23. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/agent/a2a_agent.py +0 -0
  24. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/agent/a2a_mcp_server.py +0 -0
  25. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/archive/smart_mcp_client.py +0 -0
  26. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/client/__init__.py +0 -0
  27. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/client/mcp_client.py +0 -0
  28. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/examples/__init__.py +0 -0
  29. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/examples/echo_server/__init__.py +0 -0
  30. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/examples/echo_server/curl.txt +0 -0
  31. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/examples/echo_server/main.py +0 -0
  32. {smarta2a-0.2.5/smarta2a/examples/openai_agent → smarta2a-0.3.0/smarta2a/examples/openai_delegator_agent}/__init__.py +0 -0
  33. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/history_update_strategies/__init__.py +0 -0
  34. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/history_update_strategies/append_strategy.py +0 -0
  35. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/history_update_strategies/history_update_strategy.py +0 -0
  36. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/model_providers/__init__.py +0 -0
  37. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/model_providers/base_llm_provider.py +0 -0
  38. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/server/__init__.py +0 -0
  39. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/server/handler_registry.py +0 -0
  40. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/server/server.py +0 -0
  41. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/server/state_manager.py +0 -0
  42. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/server/subscription_service.py +0 -0
  43. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/server/task_service.py +0 -0
  44. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/state_stores/__init__.py +0 -0
  45. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/state_stores/base_state_store.py +0 -0
  46. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/state_stores/inmemory_state_store.py +0 -0
  47. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/utils/__init__.py +0 -0
  48. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/utils/prompt_helpers.py +0 -0
  49. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/utils/task_builder.py +0 -0
  50. {smarta2a-0.2.5 → smarta2a-0.3.0}/smarta2a/utils/task_request_builder.py +0 -0
  51. {smarta2a-0.2.5 → smarta2a-0.3.0}/tests/__init__.py +0 -0
  52. {smarta2a-0.2.5 → smarta2a-0.3.0}/tests/test_server.py +0 -0
  53. {smarta2a-0.2.5 → smarta2a-0.3.0}/tests/test_server_history.py +0 -0
  54. {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.2.5
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,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "smarta2a"
7
- version = "0.2.5"
7
+ version = "0.3.0"
8
8
  authors = [
9
9
  { name = "Siddharth Ambegaonkar", email = "siddharthsma@gmail.com" },
10
10
  ]
@@ -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
- parameters = sig.parameters
198
-
199
- fields = {}
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
- annotation = param.annotation
205
- if annotation is Parameter.empty:
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
- field = Field(...)
231
+ # no default key
216
232
  else:
217
- field = Field(default=default)
218
- fields[param_name] = (annotation, field)
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
- 'name': name,
226
- 'description': description,
227
- 'inputSchema': schema
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
- # Validate arguments using the same schema as list_tools
258
+
259
+ # 2) build a minimal pydantic model for validation
238
260
  sig = signature(method)
239
- parameters = sig.parameters
240
-
241
- fields = {}
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
- annotation = param.annotation
246
- if annotation is Parameter.empty:
247
- annotation = Any
248
- # Handle Literal
249
- if get_origin(annotation) is Literal:
250
- enum_values = get_args(annotation)
251
- annotation = Literal.__getitem__(enum_values)
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
- fields[param_name] = (annotation, Field(...))
275
+ # required field
276
+ model_fields[param_name] = (ann, Field(...))
255
277
  else:
256
- fields[param_name] = (annotation, Field(default=default))
257
-
258
- # Create validation model
259
- model = create_model(f"{tool_name}_ValidationModel", **fields)
260
- validated_args = model(**arguments).dict()
261
-
262
- # Call the method
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
- tools = await a2a_client.list_tools()
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)
@@ -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
 
@@ -127,7 +127,7 @@ class OpenAIProvider(BaseLLMProvider):
127
127
  "function": {
128
128
  "name": tool.name,
129
129
  "description": tool.description,
130
- "parameters": tool.inputSchema
130
+ "parameters": tool.input_schema
131
131
  }
132
132
  })
133
133
  return openai_tools
@@ -482,3 +482,8 @@ class StateData(BaseModel):
482
482
  sessionId: str
483
483
  history: List[Message]
484
484
  metadata: Dict[str, Any]
485
+
486
+ class Tool(BaseModel):
487
+ name: str
488
+ description: str
489
+ inputSchema: Dict[str, Any]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes