agno 2.3.2__py3-none-any.whl → 2.3.4__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.
Files changed (90) hide show
  1. agno/agent/agent.py +513 -185
  2. agno/compression/__init__.py +3 -0
  3. agno/compression/manager.py +176 -0
  4. agno/db/dynamo/dynamo.py +11 -0
  5. agno/db/firestore/firestore.py +5 -1
  6. agno/db/gcs_json/gcs_json_db.py +5 -2
  7. agno/db/in_memory/in_memory_db.py +5 -2
  8. agno/db/json/json_db.py +5 -1
  9. agno/db/migrations/manager.py +4 -4
  10. agno/db/mongo/async_mongo.py +158 -34
  11. agno/db/mongo/mongo.py +6 -2
  12. agno/db/mysql/mysql.py +48 -54
  13. agno/db/postgres/async_postgres.py +66 -52
  14. agno/db/postgres/postgres.py +42 -50
  15. agno/db/redis/redis.py +5 -0
  16. agno/db/redis/utils.py +5 -5
  17. agno/db/singlestore/singlestore.py +99 -108
  18. agno/db/sqlite/async_sqlite.py +29 -27
  19. agno/db/sqlite/sqlite.py +30 -26
  20. agno/knowledge/reader/pdf_reader.py +2 -2
  21. agno/knowledge/reader/tavily_reader.py +0 -1
  22. agno/memory/__init__.py +14 -1
  23. agno/memory/manager.py +217 -4
  24. agno/memory/strategies/__init__.py +15 -0
  25. agno/memory/strategies/base.py +67 -0
  26. agno/memory/strategies/summarize.py +196 -0
  27. agno/memory/strategies/types.py +37 -0
  28. agno/models/aimlapi/aimlapi.py +18 -0
  29. agno/models/anthropic/claude.py +87 -81
  30. agno/models/aws/bedrock.py +38 -16
  31. agno/models/aws/claude.py +97 -277
  32. agno/models/azure/ai_foundry.py +8 -4
  33. agno/models/base.py +101 -14
  34. agno/models/cerebras/cerebras.py +25 -9
  35. agno/models/cerebras/cerebras_openai.py +22 -2
  36. agno/models/cohere/chat.py +18 -6
  37. agno/models/cometapi/cometapi.py +19 -1
  38. agno/models/deepinfra/deepinfra.py +19 -1
  39. agno/models/fireworks/fireworks.py +19 -1
  40. agno/models/google/gemini.py +583 -21
  41. agno/models/groq/groq.py +23 -6
  42. agno/models/huggingface/huggingface.py +22 -7
  43. agno/models/ibm/watsonx.py +21 -7
  44. agno/models/internlm/internlm.py +19 -1
  45. agno/models/langdb/langdb.py +10 -0
  46. agno/models/litellm/chat.py +17 -7
  47. agno/models/litellm/litellm_openai.py +19 -1
  48. agno/models/message.py +19 -5
  49. agno/models/meta/llama.py +25 -5
  50. agno/models/meta/llama_openai.py +18 -0
  51. agno/models/mistral/mistral.py +13 -5
  52. agno/models/nvidia/nvidia.py +19 -1
  53. agno/models/ollama/chat.py +17 -6
  54. agno/models/openai/chat.py +22 -7
  55. agno/models/openai/responses.py +28 -10
  56. agno/models/openrouter/openrouter.py +20 -0
  57. agno/models/perplexity/perplexity.py +17 -0
  58. agno/models/requesty/requesty.py +18 -0
  59. agno/models/sambanova/sambanova.py +19 -1
  60. agno/models/siliconflow/siliconflow.py +19 -1
  61. agno/models/together/together.py +19 -1
  62. agno/models/vercel/v0.py +19 -1
  63. agno/models/vertexai/claude.py +99 -5
  64. agno/models/xai/xai.py +18 -0
  65. agno/os/interfaces/agui/router.py +1 -0
  66. agno/os/interfaces/agui/utils.py +97 -57
  67. agno/os/router.py +16 -0
  68. agno/os/routers/memory/memory.py +143 -0
  69. agno/os/routers/memory/schemas.py +26 -0
  70. agno/os/schema.py +33 -6
  71. agno/os/utils.py +134 -10
  72. agno/run/base.py +2 -1
  73. agno/run/workflow.py +1 -1
  74. agno/team/team.py +566 -219
  75. agno/tools/mcp/mcp.py +1 -1
  76. agno/utils/agent.py +119 -1
  77. agno/utils/models/ai_foundry.py +9 -2
  78. agno/utils/models/claude.py +12 -5
  79. agno/utils/models/cohere.py +9 -2
  80. agno/utils/models/llama.py +9 -2
  81. agno/utils/models/mistral.py +4 -2
  82. agno/utils/print_response/agent.py +37 -2
  83. agno/utils/print_response/team.py +52 -0
  84. agno/utils/tokens.py +41 -0
  85. agno/workflow/types.py +2 -2
  86. {agno-2.3.2.dist-info → agno-2.3.4.dist-info}/METADATA +45 -40
  87. {agno-2.3.2.dist-info → agno-2.3.4.dist-info}/RECORD +90 -83
  88. {agno-2.3.2.dist-info → agno-2.3.4.dist-info}/WHEEL +0 -0
  89. {agno-2.3.2.dist-info → agno-2.3.4.dist-info}/licenses/LICENSE +0 -0
  90. {agno-2.3.2.dist-info → agno-2.3.4.dist-info}/top_level.txt +0 -0
agno/os/schema.py CHANGED
@@ -24,6 +24,7 @@ from agno.run.agent import RunOutput
24
24
  from agno.run.team import TeamRunOutput
25
25
  from agno.session import AgentSession, TeamSession, WorkflowSession
26
26
  from agno.team.team import Team
27
+ from agno.utils.agent import aexecute_instructions, aexecute_system_message
27
28
  from agno.workflow.agent import WorkflowAgent
28
29
  from agno.workflow.workflow import Workflow
29
30
 
@@ -337,12 +338,20 @@ class AgentResponse(BaseModel):
337
338
  "read_tool_call_history": agent.read_tool_call_history,
338
339
  }
339
340
 
341
+ instructions = agent.instructions if agent.instructions else None
342
+ if instructions and callable(instructions):
343
+ instructions = await aexecute_instructions(instructions=instructions, agent=agent)
344
+
345
+ system_message = agent.system_message if agent.system_message else None
346
+ if system_message and callable(system_message):
347
+ system_message = await aexecute_system_message(system_message=system_message, agent=agent)
348
+
340
349
  system_message_info = {
341
- "system_message": str(agent.system_message) if agent.system_message else None,
350
+ "system_message": str(system_message) if system_message else None,
342
351
  "system_message_role": agent.system_message_role,
343
352
  "build_context": agent.build_context,
344
353
  "description": agent.description,
345
- "instructions": agent.instructions if agent.instructions else None,
354
+ "instructions": instructions,
346
355
  "expected_output": agent.expected_output,
347
356
  "additional_context": agent.additional_context,
348
357
  "markdown": agent.markdown,
@@ -560,12 +569,18 @@ class TeamResponse(BaseModel):
560
569
  "get_member_information_tool": team.get_member_information_tool,
561
570
  }
562
571
 
563
- team_instructions = (
564
- team.instructions() if team.instructions and callable(team.instructions) else team.instructions
565
- )
572
+ team_instructions = team.instructions if team.instructions else None
573
+ if team_instructions and callable(team_instructions):
574
+ team_instructions = await aexecute_instructions(instructions=team_instructions, agent=team, team=team)
575
+
576
+ team_system_message = team.system_message if team.system_message else None
577
+ if team_system_message and callable(team_system_message):
578
+ team_system_message = await aexecute_system_message(
579
+ system_message=team_system_message, agent=team, team=team
580
+ )
566
581
 
567
582
  system_message_info = {
568
- "system_message": str(team.system_message) if team.system_message else None,
583
+ "system_message": team_system_message,
569
584
  "system_message_role": team.system_message_role,
570
585
  "description": team.description,
571
586
  "instructions": team_instructions,
@@ -883,6 +898,9 @@ class RunSchema(BaseModel):
883
898
  events: Optional[List[dict]] = Field(None, description="Events generated during the run")
884
899
  created_at: Optional[datetime] = Field(None, description="Run creation timestamp")
885
900
  references: Optional[List[dict]] = Field(None, description="References cited in the run")
901
+ citations: Optional[Dict[str, Any]] = Field(
902
+ None, description="Citations from the model (e.g., from Gemini grounding/search)"
903
+ )
886
904
  reasoning_messages: Optional[List[dict]] = Field(None, description="Reasoning process messages")
887
905
  session_state: Optional[dict] = Field(None, description="Session state at the end of the run")
888
906
  images: Optional[List[dict]] = Field(None, description="Images included in the run")
@@ -911,6 +929,7 @@ class RunSchema(BaseModel):
911
929
  tools=[tool for tool in run_dict.get("tools", [])] if run_dict.get("tools") else None,
912
930
  events=[event for event in run_dict["events"]] if run_dict.get("events") else None,
913
931
  references=run_dict.get("references", []),
932
+ citations=run_dict.get("citations", None),
914
933
  reasoning_messages=run_dict.get("reasoning_messages", []),
915
934
  session_state=run_dict.get("session_state"),
916
935
  images=run_dict.get("images", []),
@@ -940,6 +959,9 @@ class TeamRunSchema(BaseModel):
940
959
  events: Optional[List[dict]] = Field(None, description="Events generated during the run")
941
960
  created_at: Optional[datetime] = Field(None, description="Run creation timestamp")
942
961
  references: Optional[List[dict]] = Field(None, description="References cited in the run")
962
+ citations: Optional[Dict[str, Any]] = Field(
963
+ None, description="Citations from the model (e.g., from Gemini grounding/search)"
964
+ )
943
965
  reasoning_messages: Optional[List[dict]] = Field(None, description="Reasoning process messages")
944
966
  session_state: Optional[dict] = Field(None, description="Session state at the end of the run")
945
967
  input_media: Optional[Dict[str, Any]] = Field(None, description="Input media attachments")
@@ -970,6 +992,7 @@ class TeamRunSchema(BaseModel):
970
992
  if run_dict.get("created_at") is not None
971
993
  else None,
972
994
  references=run_dict.get("references", []),
995
+ citations=run_dict.get("citations", None),
973
996
  reasoning_messages=run_dict.get("reasoning_messages", []),
974
997
  session_state=run_dict.get("session_state"),
975
998
  images=run_dict.get("images", []),
@@ -997,6 +1020,9 @@ class WorkflowRunSchema(BaseModel):
997
1020
  reasoning_content: Optional[str] = Field(None, description="Reasoning content if reasoning was enabled")
998
1021
  reasoning_steps: Optional[List[dict]] = Field(None, description="List of reasoning steps")
999
1022
  references: Optional[List[dict]] = Field(None, description="References cited in the workflow")
1023
+ citations: Optional[Dict[str, Any]] = Field(
1024
+ None, description="Citations from the model (e.g., from Gemini grounding/search)"
1025
+ )
1000
1026
  reasoning_messages: Optional[List[dict]] = Field(None, description="Reasoning process messages")
1001
1027
  images: Optional[List[dict]] = Field(None, description="Images included in the workflow")
1002
1028
  videos: Optional[List[dict]] = Field(None, description="Videos included in the workflow")
@@ -1023,6 +1049,7 @@ class WorkflowRunSchema(BaseModel):
1023
1049
  reasoning_content=run_response.get("reasoning_content", ""),
1024
1050
  reasoning_steps=run_response.get("reasoning_steps", []),
1025
1051
  references=run_response.get("references", []),
1052
+ citations=run_response.get("citations", None),
1026
1053
  reasoning_messages=run_response.get("reasoning_messages", []),
1027
1054
  images=run_response.get("images", []),
1028
1055
  videos=run_response.get("videos", []),
agno/os/utils.py CHANGED
@@ -1,8 +1,8 @@
1
- from typing import Any, Callable, Dict, List, Optional, Set, Union
1
+ from typing import Any, Callable, Dict, List, Optional, Set, Type, Union
2
2
 
3
3
  from fastapi import FastAPI, HTTPException, UploadFile
4
4
  from fastapi.routing import APIRoute, APIRouter
5
- from pydantic import BaseModel
5
+ from pydantic import BaseModel, create_model
6
6
  from starlette.middleware.cors import CORSMiddleware
7
7
 
8
8
  from agno.agent.agent import Agent
@@ -511,8 +511,10 @@ def collect_mcp_tools_from_team(team: Team, mcp_tools: List[Any]) -> None:
511
511
  # Check the team tools
512
512
  if team.tools:
513
513
  for tool in team.tools:
514
- type_name = type(tool).__name__
515
- if type_name in ("MCPTools", "MultiMCPTools"):
514
+ # Alternate method of using isinstance(tool, (MCPTools, MultiMCPTools)) to avoid imports
515
+ if hasattr(type(tool), "__mro__") and any(
516
+ c.__name__ in ["MCPTools", "MultiMCPTools"] for c in type(tool).__mro__
517
+ ):
516
518
  if tool not in mcp_tools:
517
519
  mcp_tools.append(tool)
518
520
 
@@ -522,8 +524,10 @@ def collect_mcp_tools_from_team(team: Team, mcp_tools: List[Any]) -> None:
522
524
  if isinstance(member, Agent):
523
525
  if member.tools:
524
526
  for tool in member.tools:
525
- type_name = type(tool).__name__
526
- if type_name in ("MCPTools", "MultiMCPTools"):
527
+ # Alternate method of using isinstance(tool, (MCPTools, MultiMCPTools)) to avoid imports
528
+ if hasattr(type(tool), "__mro__") and any(
529
+ c.__name__ in ["MCPTools", "MultiMCPTools"] for c in type(tool).__mro__
530
+ ):
527
531
  if tool not in mcp_tools:
528
532
  mcp_tools.append(tool)
529
533
 
@@ -567,8 +571,10 @@ def collect_mcp_tools_from_workflow_step(step: Any, mcp_tools: List[Any]) -> Non
567
571
  if step.agent:
568
572
  if step.agent.tools:
569
573
  for tool in step.agent.tools:
570
- type_name = type(tool).__name__
571
- if type_name in ("MCPTools", "MultiMCPTools"):
574
+ # Alternate method of using isinstance(tool, (MCPTools, MultiMCPTools)) to avoid imports
575
+ if hasattr(type(tool), "__mro__") and any(
576
+ c.__name__ in ["MCPTools", "MultiMCPTools"] for c in type(tool).__mro__
577
+ ):
572
578
  if tool not in mcp_tools:
573
579
  mcp_tools.append(tool)
574
580
  # Check step's team
@@ -590,8 +596,10 @@ def collect_mcp_tools_from_workflow_step(step: Any, mcp_tools: List[Any]) -> Non
590
596
  # Direct agent in workflow steps
591
597
  if step.tools:
592
598
  for tool in step.tools:
593
- type_name = type(tool).__name__
594
- if type_name in ("MCPTools", "MultiMCPTools"):
599
+ # Alternate method of using isinstance(tool, (MCPTools, MultiMCPTools)) to avoid imports
600
+ if hasattr(type(tool), "__mro__") and any(
601
+ c.__name__ in ["MCPTools", "MultiMCPTools"] for c in type(tool).__mro__
602
+ ):
595
603
  if tool not in mcp_tools:
596
604
  mcp_tools.append(tool)
597
605
 
@@ -628,3 +636,119 @@ def stringify_input_content(input_content: Union[str, Dict[str, Any], List[Any],
628
636
  return str(input_content)
629
637
  else:
630
638
  return str(input_content)
639
+
640
+
641
+ def _get_python_type_from_json_schema(field_schema: Dict[str, Any], field_name: str = "NestedModel") -> Type:
642
+ """Map JSON schema type to Python type with recursive handling.
643
+
644
+ Args:
645
+ field_schema: JSON schema dictionary for a single field
646
+ field_name: Name of the field (used for nested model naming)
647
+
648
+ Returns:
649
+ Python type corresponding to the JSON schema type
650
+ """
651
+ if not isinstance(field_schema, dict):
652
+ return Any
653
+
654
+ json_type = field_schema.get("type")
655
+
656
+ # Handle basic types
657
+ if json_type == "string":
658
+ return str
659
+ elif json_type == "integer":
660
+ return int
661
+ elif json_type == "number":
662
+ return float
663
+ elif json_type == "boolean":
664
+ return bool
665
+ elif json_type == "null":
666
+ return type(None)
667
+ elif json_type == "array":
668
+ # Handle arrays with item type specification
669
+ items_schema = field_schema.get("items")
670
+ if items_schema and isinstance(items_schema, dict):
671
+ item_type = _get_python_type_from_json_schema(items_schema, f"{field_name}Item")
672
+ return List[item_type] # type: ignore
673
+ else:
674
+ # No item type specified - use generic list
675
+ return List[Any]
676
+ elif json_type == "object":
677
+ # Recursively create nested Pydantic model
678
+ nested_properties = field_schema.get("properties", {})
679
+ nested_required = field_schema.get("required", [])
680
+ nested_title = field_schema.get("title", field_name)
681
+
682
+ # Build field definitions for nested model
683
+ nested_fields = {}
684
+ for nested_field_name, nested_field_schema in nested_properties.items():
685
+ nested_field_type = _get_python_type_from_json_schema(nested_field_schema, nested_field_name)
686
+
687
+ if nested_field_name in nested_required:
688
+ nested_fields[nested_field_name] = (nested_field_type, ...)
689
+ else:
690
+ nested_fields[nested_field_name] = (Optional[nested_field_type], None) # type: ignore[assignment]
691
+
692
+ # Create nested model if it has fields
693
+ if nested_fields:
694
+ return create_model(nested_title, **nested_fields) # type: ignore
695
+ else:
696
+ # Empty object schema - use generic dict
697
+ return Dict[str, Any]
698
+ else:
699
+ # Unknown or unspecified type - fallback to Any
700
+ if json_type:
701
+ logger.warning(f"Unknown JSON schema type '{json_type}' for field '{field_name}', using Any")
702
+ return Any
703
+
704
+
705
+ def json_schema_to_pydantic_model(schema: Dict[str, Any]) -> Type[BaseModel]:
706
+ """Convert a JSON schema dictionary to a Pydantic BaseModel class.
707
+
708
+ This function dynamically creates a Pydantic model from a JSON schema specification,
709
+ handling nested objects, arrays, and optional fields.
710
+
711
+ Args:
712
+ schema: JSON schema dictionary with 'properties', 'required', 'type', etc.
713
+
714
+ Returns:
715
+ Dynamically created Pydantic BaseModel class
716
+ """
717
+ import copy
718
+
719
+ # Deep copy to avoid modifying the original schema
720
+ schema = copy.deepcopy(schema)
721
+
722
+ # Extract schema components
723
+ model_name = schema.get("title", "DynamicModel")
724
+ properties = schema.get("properties", {})
725
+ required_fields = schema.get("required", [])
726
+
727
+ # Validate schema has properties
728
+ if not properties:
729
+ logger.warning(f"JSON schema '{model_name}' has no properties, creating empty model")
730
+
731
+ # Build field definitions for create_model
732
+ field_definitions = {}
733
+ for field_name, field_schema in properties.items():
734
+ try:
735
+ field_type = _get_python_type_from_json_schema(field_schema, field_name)
736
+
737
+ if field_name in required_fields:
738
+ # Required field: (type, ...)
739
+ field_definitions[field_name] = (field_type, ...)
740
+ else:
741
+ # Optional field: (Optional[type], None)
742
+ field_definitions[field_name] = (Optional[field_type], None) # type: ignore[assignment]
743
+ except Exception as e:
744
+ logger.warning(f"Failed to process field '{field_name}' in schema '{model_name}': {e}")
745
+ # Skip problematic fields rather than failing entirely
746
+ continue
747
+
748
+ # Create and return the dynamic model
749
+ try:
750
+ return create_model(model_name, **field_definitions) # type: ignore
751
+ except Exception as e:
752
+ logger.error(f"Failed to create dynamic model '{model_name}': {e}")
753
+ # Return a minimal model as fallback
754
+ return create_model(model_name)
agno/run/base.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from dataclasses import asdict, dataclass
2
2
  from enum import Enum
3
- from typing import Any, Dict, List, Optional, Union
3
+ from typing import Any, Dict, List, Optional, Type, Union
4
4
 
5
5
  from pydantic import BaseModel
6
6
 
@@ -22,6 +22,7 @@ class RunContext:
22
22
  knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
23
23
  metadata: Optional[Dict[str, Any]] = None
24
24
  session_state: Optional[Dict[str, Any]] = None
25
+ output_schema: Optional[Type[BaseModel]] = None
25
26
 
26
27
 
27
28
  @dataclass
agno/run/workflow.py CHANGED
@@ -597,7 +597,7 @@ class WorkflowRunOutput:
597
597
  _dict["input"] = self.input
598
598
 
599
599
  if self.content and isinstance(self.content, BaseModel):
600
- _dict["content"] = self.content.model_dump(exclude_none=True)
600
+ _dict["content"] = self.content.model_dump(exclude_none=True, mode="json")
601
601
 
602
602
  if self.events is not None:
603
603
  _dict["events"] = [e.to_dict() for e in self.events]