agno 2.3.21__py3-none-any.whl → 2.3.23__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.
- agno/agent/agent.py +48 -2
- agno/agent/remote.py +234 -73
- agno/client/a2a/__init__.py +10 -0
- agno/client/a2a/client.py +554 -0
- agno/client/a2a/schemas.py +112 -0
- agno/client/a2a/utils.py +369 -0
- agno/db/migrations/utils.py +19 -0
- agno/db/migrations/v1_to_v2.py +54 -16
- agno/db/migrations/versions/v2_3_0.py +92 -53
- agno/db/mysql/async_mysql.py +5 -7
- agno/db/mysql/mysql.py +5 -7
- agno/db/mysql/schemas.py +39 -21
- agno/db/postgres/async_postgres.py +172 -42
- agno/db/postgres/postgres.py +186 -38
- agno/db/postgres/schemas.py +39 -21
- agno/db/postgres/utils.py +6 -2
- agno/db/singlestore/schemas.py +41 -21
- agno/db/singlestore/singlestore.py +14 -3
- agno/db/sqlite/async_sqlite.py +7 -2
- agno/db/sqlite/schemas.py +36 -21
- agno/db/sqlite/sqlite.py +3 -7
- agno/knowledge/chunking/document.py +3 -2
- agno/knowledge/chunking/markdown.py +8 -3
- agno/knowledge/chunking/recursive.py +2 -2
- agno/models/base.py +4 -0
- agno/models/google/gemini.py +27 -4
- agno/models/openai/chat.py +1 -1
- agno/models/openai/responses.py +14 -7
- agno/os/middleware/jwt.py +66 -27
- agno/os/routers/agents/router.py +3 -3
- agno/os/routers/evals/evals.py +2 -2
- agno/os/routers/knowledge/knowledge.py +5 -5
- agno/os/routers/knowledge/schemas.py +1 -1
- agno/os/routers/memory/memory.py +4 -4
- agno/os/routers/session/session.py +2 -2
- agno/os/routers/teams/router.py +4 -4
- agno/os/routers/traces/traces.py +3 -3
- agno/os/routers/workflows/router.py +3 -3
- agno/os/schema.py +1 -1
- agno/reasoning/deepseek.py +11 -1
- agno/reasoning/gemini.py +6 -2
- agno/reasoning/groq.py +8 -3
- agno/reasoning/openai.py +2 -0
- agno/remote/base.py +106 -9
- agno/skills/__init__.py +17 -0
- agno/skills/agent_skills.py +370 -0
- agno/skills/errors.py +32 -0
- agno/skills/loaders/__init__.py +4 -0
- agno/skills/loaders/base.py +27 -0
- agno/skills/loaders/local.py +216 -0
- agno/skills/skill.py +65 -0
- agno/skills/utils.py +107 -0
- agno/skills/validator.py +277 -0
- agno/team/remote.py +220 -60
- agno/team/team.py +41 -3
- agno/tools/brandfetch.py +27 -18
- agno/tools/browserbase.py +150 -13
- agno/tools/function.py +6 -1
- agno/tools/mcp/mcp.py +300 -17
- agno/tools/mcp/multi_mcp.py +269 -14
- agno/tools/toolkit.py +89 -21
- agno/utils/mcp.py +49 -8
- agno/utils/string.py +43 -1
- agno/workflow/condition.py +4 -2
- agno/workflow/loop.py +20 -1
- agno/workflow/remote.py +173 -33
- agno/workflow/router.py +4 -1
- agno/workflow/steps.py +4 -0
- agno/workflow/workflow.py +14 -0
- {agno-2.3.21.dist-info → agno-2.3.23.dist-info}/METADATA +13 -14
- {agno-2.3.21.dist-info → agno-2.3.23.dist-info}/RECORD +74 -60
- {agno-2.3.21.dist-info → agno-2.3.23.dist-info}/WHEEL +0 -0
- {agno-2.3.21.dist-info → agno-2.3.23.dist-info}/licenses/LICENSE +0 -0
- {agno-2.3.21.dist-info → agno-2.3.23.dist-info}/top_level.txt +0 -0
|
@@ -156,7 +156,7 @@ class VectorSearchRequestSchema(BaseModel):
|
|
|
156
156
|
class Meta(BaseModel):
|
|
157
157
|
"""Inline metadata schema for pagination."""
|
|
158
158
|
|
|
159
|
-
limit: int = Field(20, description="Number of results per page", ge=1
|
|
159
|
+
limit: int = Field(20, description="Number of results per page", ge=1)
|
|
160
160
|
page: int = Field(1, description="Page number", ge=1)
|
|
161
161
|
|
|
162
162
|
query: str = Field(..., description="The search query text")
|
agno/os/routers/memory/memory.py
CHANGED
|
@@ -256,8 +256,8 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
256
256
|
team_id: Optional[str] = Query(default=None, description="Filter memories by team ID"),
|
|
257
257
|
topics: Optional[List[str]] = Depends(parse_topics),
|
|
258
258
|
search_content: Optional[str] = Query(default=None, description="Fuzzy search within memory content"),
|
|
259
|
-
limit: Optional[int] = Query(default=20, description="Number of memories to return per page"),
|
|
260
|
-
page: Optional[int] = Query(default=1, description="Page number for pagination"),
|
|
259
|
+
limit: Optional[int] = Query(default=20, description="Number of memories to return per page", ge=1),
|
|
260
|
+
page: Optional[int] = Query(default=1, description="Page number for pagination", ge=0),
|
|
261
261
|
sort_by: Optional[str] = Query(default="updated_at", description="Field to sort memories by"),
|
|
262
262
|
sort_order: Optional[SortOrder] = Query(default="desc", description="Sort order (asc or desc)"),
|
|
263
263
|
db_id: Optional[str] = Query(default=None, description="Database ID to query memories from"),
|
|
@@ -558,8 +558,8 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
558
558
|
)
|
|
559
559
|
async def get_user_memory_stats(
|
|
560
560
|
request: Request,
|
|
561
|
-
limit: Optional[int] = Query(default=20, description="Number of user statistics to return per page"),
|
|
562
|
-
page: Optional[int] = Query(default=1, description="Page number for pagination"),
|
|
561
|
+
limit: Optional[int] = Query(default=20, description="Number of user statistics to return per page", ge=1),
|
|
562
|
+
page: Optional[int] = Query(default=1, description="Page number for pagination", ge=0),
|
|
563
563
|
db_id: Optional[str] = Query(default=None, description="Database ID to query statistics from"),
|
|
564
564
|
table: Optional[str] = Query(default=None, description="Table to query statistics from"),
|
|
565
565
|
) -> PaginatedResponse[UserStatsSchema]:
|
|
@@ -107,8 +107,8 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
107
107
|
),
|
|
108
108
|
user_id: Optional[str] = Query(default=None, description="Filter sessions by user ID"),
|
|
109
109
|
session_name: Optional[str] = Query(default=None, description="Filter sessions by name (partial match)"),
|
|
110
|
-
limit: Optional[int] = Query(default=20, description="Number of sessions to return per page"),
|
|
111
|
-
page: Optional[int] = Query(default=1, description="Page number for pagination"),
|
|
110
|
+
limit: Optional[int] = Query(default=20, description="Number of sessions to return per page", ge=1),
|
|
111
|
+
page: Optional[int] = Query(default=1, description="Page number for pagination", ge=0),
|
|
112
112
|
sort_by: Optional[str] = Query(default="created_at", description="Field to sort sessions by"),
|
|
113
113
|
sort_order: Optional[SortOrder] = Query(default="desc", description="Sort order (asc or desc)"),
|
|
114
114
|
db_id: Optional[str] = Query(default=None, description="Database ID to query sessions from"),
|
agno/os/routers/teams/router.py
CHANGED
|
@@ -95,7 +95,7 @@ async def team_response_streamer(
|
|
|
95
95
|
)
|
|
96
96
|
yield format_sse_event(error_response)
|
|
97
97
|
|
|
98
|
-
except
|
|
98
|
+
except BaseException as e:
|
|
99
99
|
import traceback
|
|
100
100
|
|
|
101
101
|
traceback.print_exc()
|
|
@@ -169,11 +169,11 @@ def get_team_router(
|
|
|
169
169
|
kwargs = await get_request_kwargs(request, create_team_run)
|
|
170
170
|
|
|
171
171
|
if hasattr(request.state, "user_id") and request.state.user_id is not None:
|
|
172
|
-
if user_id:
|
|
172
|
+
if user_id and user_id != request.state.user_id:
|
|
173
173
|
log_warning("User ID parameter passed in both request state and kwargs, using request state")
|
|
174
174
|
user_id = request.state.user_id
|
|
175
175
|
if hasattr(request.state, "session_id") and request.state.session_id is not None:
|
|
176
|
-
if session_id:
|
|
176
|
+
if session_id and session_id != request.state.session_id:
|
|
177
177
|
log_warning("Session ID parameter passed in both request state and kwargs, using request state")
|
|
178
178
|
session_id = request.state.session_id
|
|
179
179
|
if hasattr(request.state, "session_state") and request.state.session_state is not None:
|
|
@@ -325,7 +325,7 @@ def get_team_router(
|
|
|
325
325
|
if team is None:
|
|
326
326
|
raise HTTPException(status_code=404, detail="Team not found")
|
|
327
327
|
|
|
328
|
-
cancelled = team.
|
|
328
|
+
cancelled = await team.acancel_run(run_id=run_id)
|
|
329
329
|
if not cancelled:
|
|
330
330
|
raise HTTPException(status_code=500, detail="Failed to cancel run - run not found or already completed")
|
|
331
331
|
|
agno/os/routers/traces/traces.py
CHANGED
|
@@ -126,8 +126,8 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
126
126
|
default=None,
|
|
127
127
|
description="Filter traces ending before this time (ISO 8601 format with timezone, e.g., '2025-11-19T11:00:00Z' or '2025-11-19T16:30:00+05:30'). Times are converted to UTC for comparison.",
|
|
128
128
|
),
|
|
129
|
-
page: int = Query(default=1, description="Page number (1-indexed)", ge=
|
|
130
|
-
limit: int = Query(default=20, description="Number of traces per page", ge=1
|
|
129
|
+
page: int = Query(default=1, description="Page number (1-indexed)", ge=0),
|
|
130
|
+
limit: int = Query(default=20, description="Number of traces per page", ge=1),
|
|
131
131
|
db_id: Optional[str] = Query(default=None, description="Database ID to query traces from"),
|
|
132
132
|
):
|
|
133
133
|
"""Get list of traces with optional filters and pagination"""
|
|
@@ -455,7 +455,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBase
|
|
|
455
455
|
description="Filter sessions with traces created before this time (ISO 8601 format with timezone, e.g., '2025-11-19T11:00:00Z' or '2025-11-19T16:30:00+05:30'). Times are converted to UTC for comparison.",
|
|
456
456
|
),
|
|
457
457
|
page: int = Query(default=1, description="Page number (1-indexed)", ge=1),
|
|
458
|
-
limit: int = Query(default=20, description="Number of sessions per page", ge=1
|
|
458
|
+
limit: int = Query(default=20, description="Number of sessions per page", ge=1),
|
|
459
459
|
db_id: Optional[str] = Query(default=None, description="Database ID to query statistics from"),
|
|
460
460
|
):
|
|
461
461
|
"""Get trace statistics grouped by session"""
|
|
@@ -626,11 +626,11 @@ def get_workflow_router(
|
|
|
626
626
|
kwargs = await get_request_kwargs(request, create_workflow_run)
|
|
627
627
|
|
|
628
628
|
if hasattr(request.state, "user_id") and request.state.user_id is not None:
|
|
629
|
-
if user_id:
|
|
629
|
+
if user_id and user_id != request.state.user_id:
|
|
630
630
|
log_warning("User ID parameter passed in both request state and kwargs, using request state")
|
|
631
631
|
user_id = request.state.user_id
|
|
632
632
|
if hasattr(request.state, "session_id") and request.state.session_id is not None:
|
|
633
|
-
if session_id:
|
|
633
|
+
if session_id and session_id != request.state.session_id:
|
|
634
634
|
log_warning("Session ID parameter passed in both request state and kwargs, using request state")
|
|
635
635
|
session_id = request.state.session_id
|
|
636
636
|
if hasattr(request.state, "session_state") and request.state.session_state is not None:
|
|
@@ -721,7 +721,7 @@ def get_workflow_router(
|
|
|
721
721
|
if workflow is None:
|
|
722
722
|
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
723
723
|
|
|
724
|
-
cancelled = workflow.
|
|
724
|
+
cancelled = await workflow.acancel_run(run_id=run_id)
|
|
725
725
|
if not cancelled:
|
|
726
726
|
raise HTTPException(status_code=500, detail="Failed to cancel run - run not found or already completed")
|
|
727
727
|
|
agno/os/schema.py
CHANGED
|
@@ -549,7 +549,7 @@ class SortOrder(str, Enum):
|
|
|
549
549
|
|
|
550
550
|
class PaginationInfo(BaseModel):
|
|
551
551
|
page: int = Field(0, description="Current page number (0-indexed)", ge=0)
|
|
552
|
-
limit: int = Field(20, description="Number of items per page", ge=1
|
|
552
|
+
limit: int = Field(20, description="Number of items per page", ge=1)
|
|
553
553
|
total_pages: int = Field(0, description="Total number of pages", ge=0)
|
|
554
554
|
total_count: int = Field(0, description="Total count of items", ge=0)
|
|
555
555
|
search_time_ms: float = Field(0, description="Search execution time in milliseconds", ge=0)
|
agno/reasoning/deepseek.py
CHANGED
|
@@ -8,7 +8,17 @@ from agno.utils.log import logger
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def is_deepseek_reasoning_model(reasoning_model: Model) -> bool:
|
|
11
|
-
|
|
11
|
+
"""Check if the model is a DeepSeek reasoning model.
|
|
12
|
+
|
|
13
|
+
Matches:
|
|
14
|
+
- deepseek-reasoner
|
|
15
|
+
- deepseek-r1 and variants (deepseek-r1-distill-*, etc.)
|
|
16
|
+
"""
|
|
17
|
+
if reasoning_model.__class__.__name__ != "DeepSeek":
|
|
18
|
+
return False
|
|
19
|
+
|
|
20
|
+
model_id = reasoning_model.id.lower()
|
|
21
|
+
return "reasoner" in model_id or "r1" in model_id
|
|
12
22
|
|
|
13
23
|
|
|
14
24
|
def get_deepseek_reasoning(reasoning_agent: "Agent", messages: List[Message]) -> Optional[Message]: # type: ignore # noqa: F821
|
agno/reasoning/gemini.py
CHANGED
|
@@ -13,9 +13,13 @@ def is_gemini_reasoning_model(reasoning_model: Model) -> bool:
|
|
|
13
13
|
if not is_gemini_class:
|
|
14
14
|
return False
|
|
15
15
|
|
|
16
|
-
# Check if it's a Gemini
|
|
16
|
+
# Check if it's a Gemini model with thinking support
|
|
17
|
+
# - Gemini 2.5+ models support thinking
|
|
18
|
+
# - Gemini 3+ models support thinking (including DeepThink variants)
|
|
17
19
|
model_id = reasoning_model.id.lower()
|
|
18
|
-
has_thinking_support =
|
|
20
|
+
has_thinking_support = (
|
|
21
|
+
"2.5" in model_id or "3.0" in model_id or "3.5" in model_id or "deepthink" in model_id or "gemini-3" in model_id
|
|
22
|
+
)
|
|
19
23
|
|
|
20
24
|
# Also check if thinking parameters are set
|
|
21
25
|
# Note: thinking_budget=0 explicitly disables thinking mode per Google's API docs
|
agno/reasoning/groq.py
CHANGED
|
@@ -8,7 +8,12 @@ from agno.utils.log import logger
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def is_groq_reasoning_model(reasoning_model: Model) -> bool:
|
|
11
|
-
return reasoning_model.__class__.__name__ == "Groq" and
|
|
11
|
+
return reasoning_model.__class__.__name__ == "Groq" and (
|
|
12
|
+
"deepseek" in reasoning_model.id.lower()
|
|
13
|
+
or "openai/gpt-oss-20b" in reasoning_model.id.lower()
|
|
14
|
+
or "openai/gpt-oss-120b" in reasoning_model.id.lower()
|
|
15
|
+
or "qwen/qwen3-32b" in reasoning_model.id.lower()
|
|
16
|
+
)
|
|
12
17
|
|
|
13
18
|
|
|
14
19
|
def get_groq_reasoning(reasoning_agent: "Agent", messages: List[Message]) -> Optional[Message]: # type: ignore # noqa: F821
|
|
@@ -95,7 +100,7 @@ def get_groq_reasoning_stream(
|
|
|
95
100
|
reasoning_content: str = ""
|
|
96
101
|
|
|
97
102
|
try:
|
|
98
|
-
for event in reasoning_agent.run(input=messages, stream=True,
|
|
103
|
+
for event in reasoning_agent.run(input=messages, stream=True, stream_events=True):
|
|
99
104
|
if hasattr(event, "event"):
|
|
100
105
|
if event.event == RunEvent.run_content:
|
|
101
106
|
# Check for reasoning_content attribute first (native reasoning)
|
|
@@ -146,7 +151,7 @@ async def aget_groq_reasoning_stream(
|
|
|
146
151
|
reasoning_content: str = ""
|
|
147
152
|
|
|
148
153
|
try:
|
|
149
|
-
async for event in reasoning_agent.arun(input=messages, stream=True,
|
|
154
|
+
async for event in reasoning_agent.arun(input=messages, stream=True, stream_events=True):
|
|
150
155
|
if hasattr(event, "event"):
|
|
151
156
|
if event.event == RunEvent.run_content:
|
|
152
157
|
# Check for reasoning_content attribute first (native reasoning)
|
agno/reasoning/openai.py
CHANGED
|
@@ -21,6 +21,8 @@ def is_openai_reasoning_model(reasoning_model: Model) -> bool:
|
|
|
21
21
|
or ("o1" in reasoning_model.id)
|
|
22
22
|
or ("4.1" in reasoning_model.id)
|
|
23
23
|
or ("4.5" in reasoning_model.id)
|
|
24
|
+
or ("5.1" in reasoning_model.id)
|
|
25
|
+
or ("5.2" in reasoning_model.id)
|
|
24
26
|
)
|
|
25
27
|
) or (isinstance(reasoning_model, OpenAILike) and "deepseek-r1" in reasoning_model.id.lower())
|
|
26
28
|
|
agno/remote/base.py
CHANGED
|
@@ -2,7 +2,7 @@ import time
|
|
|
2
2
|
from abc import abstractmethod
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
4
|
from datetime import date
|
|
5
|
-
from typing import TYPE_CHECKING, Any, AsyncIterator, Dict, List, Optional, Sequence, Tuple, Union
|
|
5
|
+
from typing import TYPE_CHECKING, Any, AsyncIterator, Dict, List, Literal, Optional, Sequence, Tuple, Union
|
|
6
6
|
|
|
7
7
|
from pydantic import BaseModel
|
|
8
8
|
|
|
@@ -18,6 +18,8 @@ if TYPE_CHECKING:
|
|
|
18
18
|
from fastapi import UploadFile
|
|
19
19
|
|
|
20
20
|
from agno.client import AgentOSClient
|
|
21
|
+
from agno.client.a2a import A2AClient
|
|
22
|
+
from agno.client.a2a.schemas import AgentCard
|
|
21
23
|
from agno.os.routers.evals.schemas import EvalSchema
|
|
22
24
|
from agno.os.routers.knowledge.schemas import (
|
|
23
25
|
ConfigResponseSchema,
|
|
@@ -64,7 +66,7 @@ class RemoteDb:
|
|
|
64
66
|
"""Create a RemoteDb instance from an AgentResponse/TeamResponse/WorkflowResponse and ConfigResponse.
|
|
65
67
|
|
|
66
68
|
Args:
|
|
67
|
-
|
|
69
|
+
db_id (str): The id of the remote database
|
|
68
70
|
client: The AgentOSClient for remote operations.
|
|
69
71
|
config: The ConfigResponse containing database table information.
|
|
70
72
|
|
|
@@ -344,31 +346,52 @@ class RemoteKnowledge:
|
|
|
344
346
|
class BaseRemote:
|
|
345
347
|
# Private cache for OS config with TTL: (config, timestamp)
|
|
346
348
|
_cached_config: Optional[Tuple["ConfigResponse", float]] = field(default=None, init=False, repr=False)
|
|
349
|
+
# Private cache for agent card with TTL: (agent_card, timestamp)
|
|
350
|
+
_cached_agent_card: Optional[Tuple[Optional["AgentCard"], float]] = field(default=None, init=False, repr=False)
|
|
347
351
|
|
|
348
352
|
def __init__(
|
|
349
353
|
self,
|
|
350
354
|
base_url: str,
|
|
351
355
|
timeout: float = 60.0,
|
|
356
|
+
protocol: Literal["agentos", "a2a"] = "agentos",
|
|
357
|
+
a2a_protocol: Literal["json-rpc", "rest"] = "rest",
|
|
352
358
|
config_ttl: float = 300.0,
|
|
353
359
|
):
|
|
354
360
|
"""Initialize BaseRemote for remote execution.
|
|
355
361
|
|
|
362
|
+
Supports two protocols:
|
|
363
|
+
- "agentos": Agno's proprietary AgentOS REST API (default)
|
|
364
|
+
- "a2a": A2A (Agent-to-Agent) protocol for cross-framework communication
|
|
365
|
+
|
|
356
366
|
For local execution, provide agent/team/workflow instances.
|
|
357
367
|
For remote execution, provide base_url.
|
|
358
368
|
|
|
359
369
|
Args:
|
|
360
370
|
base_url: Base URL for remote instance (e.g., "http://localhost:7777")
|
|
361
371
|
timeout: Request timeout in seconds (default: 60)
|
|
372
|
+
protocol: Communication protocol - "agentos" (default) or "a2a"
|
|
373
|
+
a2a_protocol: For A2A protocol only - Whether to use JSON-RPC or REST protocol.
|
|
362
374
|
config_ttl: Time-to-live for cached config in seconds (default: 300)
|
|
363
375
|
"""
|
|
364
376
|
self.base_url = base_url.rstrip("/")
|
|
365
377
|
self.timeout: float = timeout
|
|
378
|
+
self.protocol = protocol
|
|
379
|
+
self.a2a_protocol = a2a_protocol
|
|
366
380
|
self.config_ttl: float = config_ttl
|
|
367
381
|
self._cached_config = None
|
|
382
|
+
self._cached_agent_card = None
|
|
383
|
+
|
|
384
|
+
self.agentos_client = None
|
|
385
|
+
self.a2a_client = None
|
|
368
386
|
|
|
369
|
-
|
|
387
|
+
if protocol == "agentos":
|
|
388
|
+
self.agentos_client = self.get_os_client()
|
|
389
|
+
elif protocol == "a2a":
|
|
390
|
+
self.a2a_client = self.get_a2a_client()
|
|
391
|
+
else:
|
|
392
|
+
raise ValueError(f"Invalid protocol: {protocol}")
|
|
370
393
|
|
|
371
|
-
def
|
|
394
|
+
def get_os_client(self) -> "AgentOSClient":
|
|
372
395
|
"""Get an AgentOSClient for fetching remote configuration.
|
|
373
396
|
|
|
374
397
|
This is used internally by AgentOS to fetch configuration from remote
|
|
@@ -384,11 +407,31 @@ class BaseRemote:
|
|
|
384
407
|
timeout=self.timeout,
|
|
385
408
|
)
|
|
386
409
|
|
|
410
|
+
def get_a2a_client(self) -> "A2AClient":
|
|
411
|
+
"""Get an A2AClient for A2A protocol communication.
|
|
412
|
+
|
|
413
|
+
Returns cached client if available, otherwise creates a new one.
|
|
414
|
+
This method provides lazy initialization of the A2A client.
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
A2AClient: Client configured for A2A protocol communication
|
|
418
|
+
"""
|
|
419
|
+
from agno.client.a2a import A2AClient
|
|
420
|
+
|
|
421
|
+
return A2AClient(
|
|
422
|
+
base_url=self.base_url,
|
|
423
|
+
timeout=int(self.timeout),
|
|
424
|
+
protocol=self.a2a_protocol,
|
|
425
|
+
)
|
|
426
|
+
|
|
387
427
|
@property
|
|
388
|
-
def _config(self) -> "ConfigResponse":
|
|
428
|
+
def _config(self) -> Optional["ConfigResponse"]:
|
|
389
429
|
"""Get the OS config from remote, cached with TTL."""
|
|
390
430
|
from agno.os.schema import ConfigResponse
|
|
391
431
|
|
|
432
|
+
if self.protocol == "a2a":
|
|
433
|
+
return None
|
|
434
|
+
|
|
392
435
|
current_time = time.time()
|
|
393
436
|
|
|
394
437
|
# Check if cache is valid
|
|
@@ -398,15 +441,15 @@ class BaseRemote:
|
|
|
398
441
|
return config
|
|
399
442
|
|
|
400
443
|
# Fetch fresh config
|
|
401
|
-
config: ConfigResponse = self.
|
|
444
|
+
config: ConfigResponse = self.agentos_client.get_config() # type: ignore
|
|
402
445
|
self._cached_config = (config, current_time)
|
|
403
446
|
return config
|
|
404
447
|
|
|
405
|
-
def refresh_os_config(self) -> "ConfigResponse":
|
|
448
|
+
async def refresh_os_config(self) -> "ConfigResponse":
|
|
406
449
|
"""Force refresh the cached OS config."""
|
|
407
450
|
from agno.os.schema import ConfigResponse
|
|
408
451
|
|
|
409
|
-
config: ConfigResponse = self.
|
|
452
|
+
config: ConfigResponse = await self.agentos_client.aget_config() # type: ignore
|
|
410
453
|
self._cached_config = (config, time.time())
|
|
411
454
|
return config
|
|
412
455
|
|
|
@@ -437,6 +480,60 @@ class BaseRemote:
|
|
|
437
480
|
return {"Authorization": f"Bearer {auth_token}"}
|
|
438
481
|
return None
|
|
439
482
|
|
|
483
|
+
def get_agent_card(self) -> Optional["AgentCard"]:
|
|
484
|
+
"""Get agent card for A2A protocol agents, cached with TTL.
|
|
485
|
+
|
|
486
|
+
Fetches the agent card from the standard /.well-known/agent.json endpoint
|
|
487
|
+
to populate agent metadata (name, description, etc.) for A2A agents.
|
|
488
|
+
|
|
489
|
+
Returns None for non-A2A protocols or if the server doesn't support agent cards.
|
|
490
|
+
"""
|
|
491
|
+
if self.protocol != "a2a":
|
|
492
|
+
return None
|
|
493
|
+
|
|
494
|
+
current_time = time.time()
|
|
495
|
+
|
|
496
|
+
# Check if cache is valid
|
|
497
|
+
if self._cached_agent_card is not None:
|
|
498
|
+
agent_card, cached_at = self._cached_agent_card
|
|
499
|
+
if current_time - cached_at < self.config_ttl:
|
|
500
|
+
return agent_card
|
|
501
|
+
|
|
502
|
+
try:
|
|
503
|
+
agent_card = self.a2a_client.get_agent_card() # type: ignore
|
|
504
|
+
self._cached_agent_card = (agent_card, current_time)
|
|
505
|
+
return agent_card
|
|
506
|
+
except Exception:
|
|
507
|
+
self._cached_agent_card = (None, current_time)
|
|
508
|
+
return None
|
|
509
|
+
|
|
510
|
+
async def aget_agent_card(self) -> Optional["AgentCard"]:
|
|
511
|
+
"""Get agent card for A2A protocol agents, cached with TTL.
|
|
512
|
+
|
|
513
|
+
Fetches the agent card from the standard /.well-known/agent.json endpoint
|
|
514
|
+
to populate agent metadata (name, description, etc.) for A2A agents.
|
|
515
|
+
|
|
516
|
+
Returns None for non-A2A protocols or if the server doesn't support agent cards.
|
|
517
|
+
"""
|
|
518
|
+
if self.protocol != "a2a":
|
|
519
|
+
return None
|
|
520
|
+
|
|
521
|
+
current_time = time.time()
|
|
522
|
+
|
|
523
|
+
# Check if cache is valid
|
|
524
|
+
if self._cached_agent_card is not None:
|
|
525
|
+
agent_card, cached_at = self._cached_agent_card
|
|
526
|
+
if current_time - cached_at < self.config_ttl:
|
|
527
|
+
return agent_card
|
|
528
|
+
|
|
529
|
+
try:
|
|
530
|
+
agent_card = await self.a2a_client.aget_agent_card() # type: ignore
|
|
531
|
+
self._cached_agent_card = (agent_card, current_time)
|
|
532
|
+
return agent_card
|
|
533
|
+
except Exception:
|
|
534
|
+
self._cached_agent_card = (None, current_time)
|
|
535
|
+
return None
|
|
536
|
+
|
|
440
537
|
@abstractmethod
|
|
441
538
|
def arun( # type: ignore
|
|
442
539
|
self,
|
|
@@ -480,5 +577,5 @@ class BaseRemote:
|
|
|
480
577
|
raise NotImplementedError("acontinue_run method must be implemented by the subclass")
|
|
481
578
|
|
|
482
579
|
@abstractmethod
|
|
483
|
-
async def
|
|
580
|
+
async def acancel_run(self, run_id: str) -> bool:
|
|
484
581
|
raise NotImplementedError("cancel_run method must be implemented by the subclass")
|
agno/skills/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from agno.skills.agent_skills import Skills
|
|
2
|
+
from agno.skills.errors import SkillError, SkillParseError, SkillValidationError
|
|
3
|
+
from agno.skills.loaders import LocalSkills, SkillLoader
|
|
4
|
+
from agno.skills.skill import Skill
|
|
5
|
+
from agno.skills.validator import validate_metadata, validate_skill_directory
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"Skills",
|
|
9
|
+
"LocalSkills",
|
|
10
|
+
"SkillLoader",
|
|
11
|
+
"Skill",
|
|
12
|
+
"SkillError",
|
|
13
|
+
"SkillParseError",
|
|
14
|
+
"SkillValidationError",
|
|
15
|
+
"validate_metadata",
|
|
16
|
+
"validate_skill_directory",
|
|
17
|
+
]
|