agno 2.3.16__py3-none-any.whl → 2.3.18__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/__init__.py +2 -0
- agno/agent/agent.py +4 -53
- agno/agent/remote.py +351 -0
- agno/client/__init__.py +3 -0
- agno/client/os.py +2669 -0
- agno/db/base.py +20 -0
- agno/db/mongo/async_mongo.py +11 -0
- agno/db/mongo/mongo.py +10 -0
- agno/db/mysql/async_mysql.py +9 -0
- agno/db/mysql/mysql.py +9 -0
- agno/db/postgres/async_postgres.py +9 -0
- agno/db/postgres/postgres.py +9 -0
- agno/db/postgres/utils.py +3 -2
- agno/db/sqlite/async_sqlite.py +9 -0
- agno/db/sqlite/sqlite.py +11 -1
- agno/exceptions.py +23 -0
- agno/knowledge/chunking/semantic.py +123 -46
- agno/knowledge/reader/csv_reader.py +1 -1
- agno/knowledge/reader/field_labeled_csv_reader.py +1 -1
- agno/knowledge/reader/json_reader.py +1 -1
- agno/models/google/gemini.py +5 -0
- agno/os/app.py +108 -25
- agno/os/auth.py +25 -1
- agno/os/interfaces/a2a/a2a.py +7 -6
- agno/os/interfaces/a2a/router.py +13 -13
- agno/os/interfaces/agui/agui.py +5 -3
- agno/os/interfaces/agui/router.py +23 -16
- agno/os/interfaces/base.py +7 -7
- agno/os/interfaces/slack/router.py +6 -6
- agno/os/interfaces/slack/slack.py +7 -7
- agno/os/interfaces/whatsapp/router.py +29 -6
- agno/os/interfaces/whatsapp/whatsapp.py +11 -8
- agno/os/managers.py +326 -0
- agno/os/mcp.py +651 -79
- agno/os/router.py +125 -18
- agno/os/routers/agents/router.py +65 -22
- agno/os/routers/agents/schema.py +16 -4
- agno/os/routers/database.py +5 -0
- agno/os/routers/evals/evals.py +93 -11
- agno/os/routers/evals/utils.py +6 -6
- agno/os/routers/knowledge/knowledge.py +104 -16
- agno/os/routers/memory/memory.py +124 -7
- agno/os/routers/metrics/metrics.py +21 -4
- agno/os/routers/session/session.py +141 -12
- agno/os/routers/teams/router.py +40 -14
- agno/os/routers/teams/schema.py +12 -4
- agno/os/routers/traces/traces.py +54 -4
- agno/os/routers/workflows/router.py +223 -117
- agno/os/routers/workflows/schema.py +65 -1
- agno/os/schema.py +38 -12
- agno/os/utils.py +87 -166
- agno/remote/__init__.py +3 -0
- agno/remote/base.py +484 -0
- agno/run/workflow.py +1 -0
- agno/team/__init__.py +2 -0
- agno/team/remote.py +287 -0
- agno/team/team.py +25 -54
- agno/tracing/exporter.py +10 -6
- agno/tracing/setup.py +2 -1
- agno/utils/agent.py +58 -1
- agno/utils/http.py +68 -20
- agno/utils/os.py +0 -0
- agno/utils/remote.py +23 -0
- agno/vectordb/chroma/chromadb.py +452 -16
- agno/vectordb/pgvector/pgvector.py +7 -0
- agno/vectordb/redis/redisdb.py +1 -1
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +2 -2
- agno/workflow/remote.py +222 -0
- agno/workflow/types.py +0 -73
- agno/workflow/workflow.py +119 -68
- {agno-2.3.16.dist-info → agno-2.3.18.dist-info}/METADATA +1 -1
- {agno-2.3.16.dist-info → agno-2.3.18.dist-info}/RECORD +76 -66
- {agno-2.3.16.dist-info → agno-2.3.18.dist-info}/WHEEL +0 -0
- {agno-2.3.16.dist-info → agno-2.3.18.dist-info}/licenses/LICENSE +0 -0
- {agno-2.3.16.dist-info → agno-2.3.18.dist-info}/top_level.txt +0 -0
agno/os/utils.py
CHANGED
|
@@ -7,21 +7,21 @@ from fastapi.routing import APIRoute, APIRouter
|
|
|
7
7
|
from pydantic import BaseModel, create_model
|
|
8
8
|
from starlette.middleware.cors import CORSMiddleware
|
|
9
9
|
|
|
10
|
-
from agno.agent
|
|
10
|
+
from agno.agent import Agent, RemoteAgent
|
|
11
11
|
from agno.db.base import AsyncBaseDb, BaseDb
|
|
12
12
|
from agno.knowledge.knowledge import Knowledge
|
|
13
13
|
from agno.media import Audio, Image, Video
|
|
14
14
|
from agno.media import File as FileMedia
|
|
15
15
|
from agno.models.message import Message
|
|
16
16
|
from agno.os.config import AgentOSConfig
|
|
17
|
+
from agno.remote.base import RemoteDb, RemoteKnowledge
|
|
17
18
|
from agno.run.agent import RunOutputEvent
|
|
18
19
|
from agno.run.team import TeamRunOutputEvent
|
|
19
20
|
from agno.run.workflow import WorkflowRunOutputEvent
|
|
20
|
-
from agno.team
|
|
21
|
-
from agno.tools import Toolkit
|
|
22
|
-
from agno.tools.function import Function
|
|
21
|
+
from agno.team import RemoteTeam, Team
|
|
22
|
+
from agno.tools import Function, Toolkit
|
|
23
23
|
from agno.utils.log import log_warning, logger
|
|
24
|
-
from agno.workflow
|
|
24
|
+
from agno.workflow import RemoteWorkflow, Workflow
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
async def get_request_kwargs(request: Request, endpoint_func: Callable) -> Dict[str, Any]:
|
|
@@ -177,14 +177,14 @@ def format_sse_event(event: Union[RunOutputEvent, TeamRunOutputEvent, WorkflowRu
|
|
|
177
177
|
|
|
178
178
|
|
|
179
179
|
async def get_db(
|
|
180
|
-
dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]], db_id: Optional[str] = None, table: Optional[str] = None
|
|
181
|
-
) -> Union[BaseDb, AsyncBaseDb]:
|
|
180
|
+
dbs: dict[str, list[Union[BaseDb, AsyncBaseDb, RemoteDb]]], db_id: Optional[str] = None, table: Optional[str] = None
|
|
181
|
+
) -> Union[BaseDb, AsyncBaseDb, RemoteDb]:
|
|
182
182
|
"""Return the database with the given ID and/or table, or the first database if no ID/table is provided."""
|
|
183
183
|
|
|
184
184
|
if table and not db_id:
|
|
185
185
|
raise HTTPException(status_code=400, detail="The db_id query parameter is required when passing a table")
|
|
186
186
|
|
|
187
|
-
async def _has_table(db: Union[BaseDb, AsyncBaseDb], table_name: str) -> bool:
|
|
187
|
+
async def _has_table(db: Union[BaseDb, AsyncBaseDb, RemoteDb], table_name: str) -> bool:
|
|
188
188
|
"""Check if this database has the specified table (configured and actually exists)."""
|
|
189
189
|
# First check if table name is configured
|
|
190
190
|
is_configured = (
|
|
@@ -203,6 +203,10 @@ async def get_db(
|
|
|
203
203
|
if not is_configured:
|
|
204
204
|
return False
|
|
205
205
|
|
|
206
|
+
if isinstance(db, RemoteDb):
|
|
207
|
+
# We have to assume remote DBs are always configured and exist
|
|
208
|
+
return True
|
|
209
|
+
|
|
206
210
|
# Then check if table actually exists in the database
|
|
207
211
|
try:
|
|
208
212
|
if isinstance(db, AsyncBaseDb):
|
|
@@ -241,7 +245,9 @@ async def get_db(
|
|
|
241
245
|
return next(db for dbs in dbs.values() for db in dbs)
|
|
242
246
|
|
|
243
247
|
|
|
244
|
-
def get_knowledge_instance_by_db_id(
|
|
248
|
+
def get_knowledge_instance_by_db_id(
|
|
249
|
+
knowledge_instances: List[Union[Knowledge, RemoteKnowledge]], db_id: Optional[str] = None
|
|
250
|
+
) -> Union[Knowledge, RemoteKnowledge]:
|
|
245
251
|
"""Return the knowledge instance with the given ID, or the first knowledge instance if no ID is provided."""
|
|
246
252
|
if not db_id and len(knowledge_instances) == 1:
|
|
247
253
|
return next(iter(knowledge_instances))
|
|
@@ -350,11 +356,12 @@ def extract_input_media(run_dict: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
350
356
|
"files": [],
|
|
351
357
|
}
|
|
352
358
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
359
|
+
input_data = run_dict.get("input", {})
|
|
360
|
+
if isinstance(input_data, dict):
|
|
361
|
+
input_media["images"].extend(input_data.get("images", []))
|
|
362
|
+
input_media["videos"].extend(input_data.get("videos", []))
|
|
363
|
+
input_media["audios"].extend(input_data.get("audios", []))
|
|
364
|
+
input_media["files"].extend(input_data.get("files", []))
|
|
358
365
|
|
|
359
366
|
return input_media
|
|
360
367
|
|
|
@@ -406,37 +413,9 @@ def extract_format(file: UploadFile) -> Optional[str]:
|
|
|
406
413
|
return None
|
|
407
414
|
|
|
408
415
|
|
|
409
|
-
def
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
for tool in agent_tools:
|
|
413
|
-
if isinstance(tool, dict):
|
|
414
|
-
formatted_tools.append(tool)
|
|
415
|
-
elif isinstance(tool, Toolkit):
|
|
416
|
-
for _, f in tool.functions.items():
|
|
417
|
-
formatted_tools.append(f.to_dict())
|
|
418
|
-
elif isinstance(tool, Function):
|
|
419
|
-
formatted_tools.append(tool.to_dict())
|
|
420
|
-
elif callable(tool):
|
|
421
|
-
func = Function.from_callable(tool)
|
|
422
|
-
formatted_tools.append(func.to_dict())
|
|
423
|
-
else:
|
|
424
|
-
logger.warning(f"Unknown tool type: {type(tool)}")
|
|
425
|
-
return formatted_tools
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
def format_team_tools(team_tools: List[Union[Function, dict]]):
|
|
429
|
-
formatted_tools: List[Dict] = []
|
|
430
|
-
if team_tools is not None:
|
|
431
|
-
for tool in team_tools:
|
|
432
|
-
if isinstance(tool, dict):
|
|
433
|
-
formatted_tools.append(tool)
|
|
434
|
-
elif isinstance(tool, Function):
|
|
435
|
-
formatted_tools.append(tool.to_dict())
|
|
436
|
-
return formatted_tools
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
def get_agent_by_id(agent_id: str, agents: Optional[List[Agent]] = None) -> Optional[Agent]:
|
|
416
|
+
def get_agent_by_id(
|
|
417
|
+
agent_id: str, agents: Optional[List[Union[Agent, RemoteAgent]]] = None
|
|
418
|
+
) -> Optional[Union[Agent, RemoteAgent]]:
|
|
440
419
|
if agent_id is None or agents is None:
|
|
441
420
|
return None
|
|
442
421
|
|
|
@@ -446,7 +425,9 @@ def get_agent_by_id(agent_id: str, agents: Optional[List[Agent]] = None) -> Opti
|
|
|
446
425
|
return None
|
|
447
426
|
|
|
448
427
|
|
|
449
|
-
def get_team_by_id(
|
|
428
|
+
def get_team_by_id(
|
|
429
|
+
team_id: str, teams: Optional[List[Union[Team, RemoteTeam]]] = None
|
|
430
|
+
) -> Optional[Union[Team, RemoteTeam]]:
|
|
450
431
|
if team_id is None or teams is None:
|
|
451
432
|
return None
|
|
452
433
|
|
|
@@ -456,7 +437,9 @@ def get_team_by_id(team_id: str, teams: Optional[List[Team]] = None) -> Optional
|
|
|
456
437
|
return None
|
|
457
438
|
|
|
458
439
|
|
|
459
|
-
def get_workflow_by_id(
|
|
440
|
+
def get_workflow_by_id(
|
|
441
|
+
workflow_id: str, workflows: Optional[List[Union[Workflow, RemoteWorkflow]]] = None
|
|
442
|
+
) -> Optional[Union[Workflow, RemoteWorkflow]]:
|
|
460
443
|
if workflow_id is None or workflows is None:
|
|
461
444
|
return None
|
|
462
445
|
|
|
@@ -466,98 +449,6 @@ def get_workflow_by_id(workflow_id: str, workflows: Optional[List[Workflow]] = N
|
|
|
466
449
|
return None
|
|
467
450
|
|
|
468
451
|
|
|
469
|
-
# INPUT SCHEMA VALIDATIONS
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
def get_agent_input_schema_dict(agent: Agent) -> Optional[Dict[str, Any]]:
|
|
473
|
-
"""Get input schema as dictionary for API responses"""
|
|
474
|
-
|
|
475
|
-
if agent.input_schema is not None:
|
|
476
|
-
try:
|
|
477
|
-
return agent.input_schema.model_json_schema()
|
|
478
|
-
except Exception:
|
|
479
|
-
return None
|
|
480
|
-
|
|
481
|
-
return None
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
def get_team_input_schema_dict(team: Team) -> Optional[Dict[str, Any]]:
|
|
485
|
-
"""Get input schema as dictionary for API responses"""
|
|
486
|
-
|
|
487
|
-
if team.input_schema is not None:
|
|
488
|
-
try:
|
|
489
|
-
return team.input_schema.model_json_schema()
|
|
490
|
-
except Exception:
|
|
491
|
-
return None
|
|
492
|
-
|
|
493
|
-
return None
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
def get_workflow_input_schema_dict(workflow: Workflow) -> Optional[Dict[str, Any]]:
|
|
497
|
-
"""Get input schema as dictionary for API responses"""
|
|
498
|
-
|
|
499
|
-
# Priority 1: Explicit input_schema (Pydantic model)
|
|
500
|
-
if workflow.input_schema is not None:
|
|
501
|
-
try:
|
|
502
|
-
return workflow.input_schema.model_json_schema()
|
|
503
|
-
except Exception:
|
|
504
|
-
return None
|
|
505
|
-
|
|
506
|
-
# Priority 2: Auto-generate from custom kwargs
|
|
507
|
-
if workflow.steps and callable(workflow.steps):
|
|
508
|
-
custom_params = workflow.run_parameters
|
|
509
|
-
if custom_params and len(custom_params) > 1: # More than just 'message'
|
|
510
|
-
return _generate_schema_from_params(custom_params)
|
|
511
|
-
|
|
512
|
-
# Priority 3: No schema (expects string message)
|
|
513
|
-
return None
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
def _generate_schema_from_params(params: Dict[str, Any]) -> Dict[str, Any]:
|
|
517
|
-
"""Convert function parameters to JSON schema"""
|
|
518
|
-
properties: Dict[str, Any] = {}
|
|
519
|
-
required: List[str] = []
|
|
520
|
-
|
|
521
|
-
for param_name, param_info in params.items():
|
|
522
|
-
# Skip the default 'message' parameter for custom kwargs workflows
|
|
523
|
-
if param_name == "message":
|
|
524
|
-
continue
|
|
525
|
-
|
|
526
|
-
# Map Python types to JSON schema types
|
|
527
|
-
param_type = param_info.get("annotation", "str")
|
|
528
|
-
default_value = param_info.get("default")
|
|
529
|
-
is_required = param_info.get("required", False)
|
|
530
|
-
|
|
531
|
-
# Convert Python type annotations to JSON schema types
|
|
532
|
-
if param_type == "str":
|
|
533
|
-
properties[param_name] = {"type": "string"}
|
|
534
|
-
elif param_type == "bool":
|
|
535
|
-
properties[param_name] = {"type": "boolean"}
|
|
536
|
-
elif param_type == "int":
|
|
537
|
-
properties[param_name] = {"type": "integer"}
|
|
538
|
-
elif param_type == "float":
|
|
539
|
-
properties[param_name] = {"type": "number"}
|
|
540
|
-
elif "List" in str(param_type):
|
|
541
|
-
properties[param_name] = {"type": "array", "items": {"type": "string"}}
|
|
542
|
-
else:
|
|
543
|
-
properties[param_name] = {"type": "string"} # fallback
|
|
544
|
-
|
|
545
|
-
# Add default value if present
|
|
546
|
-
if default_value is not None:
|
|
547
|
-
properties[param_name]["default"] = default_value
|
|
548
|
-
|
|
549
|
-
# Add to required if no default value
|
|
550
|
-
if is_required and default_value is None:
|
|
551
|
-
required.append(param_name)
|
|
552
|
-
|
|
553
|
-
schema = {"type": "object", "properties": properties}
|
|
554
|
-
|
|
555
|
-
if required:
|
|
556
|
-
schema["required"] = required
|
|
557
|
-
|
|
558
|
-
return schema
|
|
559
|
-
|
|
560
|
-
|
|
561
452
|
def resolve_origins(user_origins: Optional[List[str]] = None, default_origins: Optional[List[str]] = None) -> List[str]:
|
|
562
453
|
"""
|
|
563
454
|
Get CORS origins - user-provided origins override defaults.
|
|
@@ -783,32 +674,6 @@ def collect_mcp_tools_from_workflow_step(step: Any, mcp_tools: List[Any]) -> Non
|
|
|
783
674
|
collect_mcp_tools_from_workflow(step, mcp_tools)
|
|
784
675
|
|
|
785
676
|
|
|
786
|
-
def stringify_input_content(input_content: Union[str, Dict[str, Any], List[Any], BaseModel]) -> str:
|
|
787
|
-
"""Convert any given input_content into its string representation.
|
|
788
|
-
|
|
789
|
-
This handles both serialized (dict) and live (object) input_content formats.
|
|
790
|
-
"""
|
|
791
|
-
import json
|
|
792
|
-
|
|
793
|
-
if isinstance(input_content, str):
|
|
794
|
-
return input_content
|
|
795
|
-
elif isinstance(input_content, Message):
|
|
796
|
-
return json.dumps(input_content.to_dict())
|
|
797
|
-
elif isinstance(input_content, dict):
|
|
798
|
-
return json.dumps(input_content, indent=2, default=str)
|
|
799
|
-
elif isinstance(input_content, list):
|
|
800
|
-
if input_content:
|
|
801
|
-
# Handle live Message objects
|
|
802
|
-
if isinstance(input_content[0], Message):
|
|
803
|
-
return json.dumps([m.to_dict() for m in input_content])
|
|
804
|
-
# Handle serialized Message dicts
|
|
805
|
-
elif isinstance(input_content[0], dict) and input_content[0].get("role") == "user":
|
|
806
|
-
return input_content[0].get("content", str(input_content))
|
|
807
|
-
return str(input_content)
|
|
808
|
-
else:
|
|
809
|
-
return str(input_content)
|
|
810
|
-
|
|
811
|
-
|
|
812
677
|
def _get_python_type_from_json_schema(field_schema: Dict[str, Any], field_name: str = "NestedModel") -> Type:
|
|
813
678
|
"""Map JSON schema type to Python type with recursive handling.
|
|
814
679
|
|
|
@@ -925,7 +790,7 @@ def json_schema_to_pydantic_model(schema: Dict[str, Any]) -> Type[BaseModel]:
|
|
|
925
790
|
return create_model(model_name)
|
|
926
791
|
|
|
927
792
|
|
|
928
|
-
def setup_tracing_for_os(db: Union[BaseDb, AsyncBaseDb]) -> None:
|
|
793
|
+
def setup_tracing_for_os(db: Union[BaseDb, AsyncBaseDb, RemoteDb]) -> None:
|
|
929
794
|
"""Set up OpenTelemetry tracing for this agent/team/workflow."""
|
|
930
795
|
try:
|
|
931
796
|
from agno.tracing import setup_tracing
|
|
@@ -979,3 +844,59 @@ def parse_datetime_to_utc(datetime_str: str, param_name: str = "datetime") -> "d
|
|
|
979
844
|
status_code=400,
|
|
980
845
|
detail=f"Invalid {param_name} format. Use ISO 8601 format (e.g., '2025-11-19T10:00:00Z' or '2025-11-19T10:00:00+05:30'): {e}",
|
|
981
846
|
)
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
def format_team_tools(team_tools: List[Union[Function, dict]]):
|
|
850
|
+
formatted_tools: List[Dict] = []
|
|
851
|
+
if team_tools is not None:
|
|
852
|
+
for tool in team_tools:
|
|
853
|
+
if isinstance(tool, dict):
|
|
854
|
+
formatted_tools.append(tool)
|
|
855
|
+
elif isinstance(tool, Function):
|
|
856
|
+
formatted_tools.append(tool.to_dict())
|
|
857
|
+
return formatted_tools
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
def format_tools(agent_tools: List[Union[Dict[str, Any], Toolkit, Function, Callable]]):
|
|
861
|
+
formatted_tools: List[Dict] = []
|
|
862
|
+
if agent_tools is not None:
|
|
863
|
+
for tool in agent_tools:
|
|
864
|
+
if isinstance(tool, dict):
|
|
865
|
+
formatted_tools.append(tool)
|
|
866
|
+
elif isinstance(tool, Toolkit):
|
|
867
|
+
for _, f in tool.functions.items():
|
|
868
|
+
formatted_tools.append(f.to_dict())
|
|
869
|
+
elif isinstance(tool, Function):
|
|
870
|
+
formatted_tools.append(tool.to_dict())
|
|
871
|
+
elif callable(tool):
|
|
872
|
+
func = Function.from_callable(tool)
|
|
873
|
+
formatted_tools.append(func.to_dict())
|
|
874
|
+
else:
|
|
875
|
+
logger.warning(f"Unknown tool type: {type(tool)}")
|
|
876
|
+
return formatted_tools
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
def stringify_input_content(input_content: Union[str, Dict[str, Any], List[Any], BaseModel]) -> str:
|
|
880
|
+
"""Convert any given input_content into its string representation.
|
|
881
|
+
|
|
882
|
+
This handles both serialized (dict) and live (object) input_content formats.
|
|
883
|
+
"""
|
|
884
|
+
import json
|
|
885
|
+
|
|
886
|
+
if isinstance(input_content, str):
|
|
887
|
+
return input_content
|
|
888
|
+
elif isinstance(input_content, Message):
|
|
889
|
+
return json.dumps(input_content.to_dict())
|
|
890
|
+
elif isinstance(input_content, dict):
|
|
891
|
+
return json.dumps(input_content, indent=2, default=str)
|
|
892
|
+
elif isinstance(input_content, list):
|
|
893
|
+
if input_content:
|
|
894
|
+
# Handle live Message objects
|
|
895
|
+
if isinstance(input_content[0], Message):
|
|
896
|
+
return json.dumps([m.to_dict() for m in input_content])
|
|
897
|
+
# Handle serialized Message dicts
|
|
898
|
+
elif isinstance(input_content[0], dict) and input_content[0].get("role") == "user":
|
|
899
|
+
return input_content[0].get("content", str(input_content))
|
|
900
|
+
return str(input_content)
|
|
901
|
+
else:
|
|
902
|
+
return str(input_content)
|
agno/remote/__init__.py
ADDED