agno 2.4.7__py3-none-any.whl → 2.4.8__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 (45) hide show
  1. agno/agent/agent.py +5 -1
  2. agno/db/base.py +2 -0
  3. agno/db/postgres/postgres.py +5 -5
  4. agno/db/sqlite/sqlite.py +4 -4
  5. agno/knowledge/knowledge.py +83 -1853
  6. agno/knowledge/loaders/__init__.py +29 -0
  7. agno/knowledge/loaders/azure_blob.py +423 -0
  8. agno/knowledge/loaders/base.py +187 -0
  9. agno/knowledge/loaders/gcs.py +267 -0
  10. agno/knowledge/loaders/github.py +415 -0
  11. agno/knowledge/loaders/s3.py +281 -0
  12. agno/knowledge/loaders/sharepoint.py +439 -0
  13. agno/knowledge/reader/website_reader.py +2 -2
  14. agno/knowledge/remote_knowledge.py +151 -0
  15. agno/learn/stores/session_context.py +10 -2
  16. agno/models/azure/openai_chat.py +6 -11
  17. agno/models/neosantara/__init__.py +5 -0
  18. agno/models/neosantara/neosantara.py +42 -0
  19. agno/models/utils.py +5 -0
  20. agno/os/app.py +4 -1
  21. agno/os/interfaces/agui/router.py +1 -1
  22. agno/os/routers/components/components.py +2 -0
  23. agno/os/routers/knowledge/knowledge.py +0 -1
  24. agno/os/routers/registry/registry.py +340 -192
  25. agno/os/routers/workflows/router.py +7 -1
  26. agno/os/schema.py +104 -0
  27. agno/registry/registry.py +4 -0
  28. agno/session/workflow.py +1 -1
  29. agno/skills/utils.py +100 -2
  30. agno/team/team.py +6 -3
  31. agno/vectordb/lancedb/lance_db.py +22 -7
  32. agno/workflow/__init__.py +4 -0
  33. agno/workflow/cel.py +299 -0
  34. agno/workflow/condition.py +145 -2
  35. agno/workflow/loop.py +177 -46
  36. agno/workflow/parallel.py +75 -4
  37. agno/workflow/router.py +260 -44
  38. agno/workflow/step.py +14 -7
  39. agno/workflow/steps.py +43 -0
  40. agno/workflow/workflow.py +104 -46
  41. {agno-2.4.7.dist-info → agno-2.4.8.dist-info}/METADATA +24 -36
  42. {agno-2.4.7.dist-info → agno-2.4.8.dist-info}/RECORD +45 -34
  43. {agno-2.4.7.dist-info → agno-2.4.8.dist-info}/WHEEL +0 -0
  44. {agno-2.4.7.dist-info → agno-2.4.8.dist-info}/licenses/LICENSE +0 -0
  45. {agno-2.4.7.dist-info → agno-2.4.8.dist-info}/top_level.txt +0 -0
@@ -555,7 +555,13 @@ def get_workflow_router(
555
555
  db_workflows = get_workflows(db=os.db, registry=os.registry)
556
556
  if db_workflows:
557
557
  for db_workflow in db_workflows:
558
- workflows.append(WorkflowSummaryResponse.from_workflow(workflow=db_workflow))
558
+ try:
559
+ workflows.append(WorkflowSummaryResponse.from_workflow(workflow=db_workflow))
560
+ except Exception as e:
561
+ workflow_id = getattr(db_workflow, "id", "unknown")
562
+ logger.error(f"Error converting workflow {workflow_id} to response: {e}")
563
+ # Continue processing other workflows even if this one fails
564
+ continue
559
565
 
560
566
  return workflows
561
567
 
agno/os/schema.py CHANGED
@@ -618,6 +618,7 @@ class ComponentUpdate(BaseModel):
618
618
  description: Optional[str] = None
619
619
  component_type: Optional[str] = None
620
620
  metadata: Optional[Dict[str, Any]] = None
621
+ current_version: Optional[int] = None
621
622
 
622
623
 
623
624
  class ConfigUpdate(BaseModel):
@@ -626,3 +627,106 @@ class ConfigUpdate(BaseModel):
626
627
  stage: Optional[str] = None
627
628
  notes: Optional[str] = None
628
629
  links: Optional[List[Dict[str, Any]]] = None
630
+
631
+
632
+ class RegistryResourceType(str, Enum):
633
+ """Types of resources that can be stored in a registry."""
634
+
635
+ TOOL = "tool"
636
+ MODEL = "model"
637
+ DB = "db"
638
+ VECTOR_DB = "vector_db"
639
+ SCHEMA = "schema"
640
+ FUNCTION = "function"
641
+
642
+
643
+ class CallableMetadata(BaseModel):
644
+ """Common metadata for callable components (tools, functions)."""
645
+
646
+ name: str = Field(..., description="Callable name")
647
+ description: Optional[str] = Field(None, description="Callable description")
648
+ class_path: str = Field(..., description="Full module path to the class/function")
649
+ module: Optional[str] = Field(None, description="Module where the callable is defined")
650
+ qualname: Optional[str] = Field(None, description="Qualified name of the callable")
651
+ has_entrypoint: bool = Field(..., description="Whether the callable has an executable entrypoint")
652
+ parameters: Dict[str, Any] = Field(default_factory=dict, description="JSON schema of parameters")
653
+ requires_confirmation: Optional[bool] = Field(None, description="Whether execution requires user confirmation")
654
+ external_execution: Optional[bool] = Field(None, description="Whether execution happens externally")
655
+ signature: Optional[str] = Field(None, description="Function signature string")
656
+ return_annotation: Optional[str] = Field(None, description="Return type annotation")
657
+
658
+
659
+ class ToolMetadata(BaseModel):
660
+ """Metadata for tool registry components."""
661
+
662
+ class_path: str = Field(..., description="Full module path to the tool class")
663
+ is_toolkit: bool = Field(False, description="Whether this is a toolkit containing multiple functions")
664
+ functions: Optional[List[CallableMetadata]] = Field(
665
+ None, description="Functions in the toolkit (if is_toolkit=True)"
666
+ )
667
+
668
+ # Fields for non-toolkit tools (Function or raw callable)
669
+ module: Optional[str] = Field(None, description="Module where the callable is defined")
670
+ qualname: Optional[str] = Field(None, description="Qualified name of the callable")
671
+ has_entrypoint: Optional[bool] = Field(None, description="Whether the tool has an executable entrypoint")
672
+ parameters: Optional[Dict[str, Any]] = Field(None, description="JSON schema of parameters")
673
+ requires_confirmation: Optional[bool] = Field(None, description="Whether execution requires user confirmation")
674
+ external_execution: Optional[bool] = Field(None, description="Whether execution happens externally")
675
+ signature: Optional[str] = Field(None, description="Function signature string")
676
+ return_annotation: Optional[str] = Field(None, description="Return type annotation")
677
+
678
+
679
+ class ModelMetadata(BaseModel):
680
+ """Metadata for model registry components."""
681
+
682
+ class_path: str = Field(..., description="Full module path to the model class")
683
+ provider: Optional[str] = Field(None, description="Model provider (e.g., openai, anthropic)")
684
+ model_id: Optional[str] = Field(None, description="Model identifier")
685
+
686
+
687
+ class DbMetadata(BaseModel):
688
+ """Metadata for database registry components."""
689
+
690
+ class_path: str = Field(..., description="Full module path to the database class")
691
+ db_id: Optional[str] = Field(None, description="Database identifier")
692
+
693
+
694
+ class VectorDbMetadata(BaseModel):
695
+ """Metadata for vector database registry components."""
696
+
697
+ class_path: str = Field(..., description="Full module path to the vector database class")
698
+ vector_db_id: Optional[str] = Field(None, description="Vector database identifier")
699
+ collection: Optional[str] = Field(None, description="Collection name")
700
+ table_name: Optional[str] = Field(None, description="Table name (for SQL-based vector stores)")
701
+
702
+
703
+ class SchemaMetadata(BaseModel):
704
+ """Metadata for schema registry components."""
705
+
706
+ class_path: str = Field(..., description="Full module path to the schema class")
707
+ schema_: Optional[Dict[str, Any]] = Field(None, alias="schema", description="JSON schema definition")
708
+ schema_error: Optional[str] = Field(None, description="Error message if schema generation failed")
709
+
710
+
711
+ class FunctionMetadata(CallableMetadata):
712
+ """Metadata for function registry components (workflow conditions, selectors, etc.)."""
713
+
714
+ pass
715
+
716
+
717
+ # Union of all metadata types for type hints
718
+ RegistryMetadata = Union[
719
+ ToolMetadata,
720
+ ModelMetadata,
721
+ DbMetadata,
722
+ VectorDbMetadata,
723
+ SchemaMetadata,
724
+ FunctionMetadata,
725
+ ]
726
+
727
+
728
+ class RegistryContentResponse(BaseModel):
729
+ name: str
730
+ type: RegistryResourceType
731
+ description: Optional[str] = None
732
+ metadata: Optional[Dict[str, Any]] = None
agno/registry/registry.py CHANGED
@@ -26,6 +26,7 @@ class Registry:
26
26
  dbs: List[BaseDb] = field(default_factory=list)
27
27
  vector_dbs: List[VectorDb] = field(default_factory=list)
28
28
  schemas: List[Type[BaseModel]] = field(default_factory=list)
29
+ functions: List[Callable] = field(default_factory=list)
29
30
 
30
31
  @cached_property
31
32
  def _entrypoint_lookup(self) -> Dict[str, Callable]:
@@ -66,3 +67,6 @@ class Registry:
66
67
  if self.dbs:
67
68
  return next((db for db in self.dbs if db.id == db_id), None)
68
69
  return None
70
+
71
+ def get_function(self, name: str) -> Optional[Callable]:
72
+ return next((f for f in self.functions if f.__name__ == name), None)
agno/session/workflow.py CHANGED
@@ -16,7 +16,7 @@ from agno.utils.log import log_debug, logger
16
16
 
17
17
  @dataclass
18
18
  class WorkflowSession:
19
- """Workflow Session V2 for pipeline-based workflows"""
19
+ """Workflow Session for pipeline-based workflows"""
20
20
 
21
21
  # Session UUID - this is the workflow_session_id that gets set on agents/teams
22
22
  session_id: str
agno/skills/utils.py CHANGED
@@ -1,8 +1,10 @@
1
1
  """Utility functions for the skills module."""
2
2
 
3
3
  import os
4
+ import platform
4
5
  import stat
5
6
  import subprocess
7
+ import sys
6
8
  from dataclasses import dataclass
7
9
  from pathlib import Path
8
10
  from typing import List, Optional
@@ -41,6 +43,95 @@ def ensure_executable(file_path: Path) -> None:
41
43
  os.chmod(file_path, current_mode | stat.S_IXUSR)
42
44
 
43
45
 
46
+ def parse_shebang(script_path: Path) -> Optional[str]:
47
+ """Parse the shebang line from a script file to determine the interpreter.
48
+
49
+ Handles various shebang formats:
50
+ - #!/usr/bin/env python3 -> "python3"
51
+ - #!/usr/bin/python3 -> "python3"
52
+ - #!/bin/bash -> "bash"
53
+ - #!/usr/bin/env -S node -> "node"
54
+
55
+ Args:
56
+ script_path: Path to the script file.
57
+
58
+ Returns:
59
+ The interpreter name (e.g., "python3", "bash") or None if no valid shebang.
60
+ """
61
+ try:
62
+ with open(script_path, "r", encoding="utf-8") as f:
63
+ first_line = f.readline().strip()
64
+ except (OSError, UnicodeDecodeError):
65
+ return None
66
+
67
+ if not first_line.startswith("#!"):
68
+ return None
69
+
70
+ shebang = first_line[2:].strip()
71
+ if not shebang:
72
+ return None
73
+
74
+ parts = shebang.split()
75
+
76
+ # Handle /usr/bin/env style shebangs
77
+ if Path(parts[0]).name == "env":
78
+ # Skip any flags (like -S) and get the interpreter
79
+ for part in parts[1:]:
80
+ if not part.startswith("-"):
81
+ return part
82
+ return None
83
+
84
+ # Handle direct path shebangs like #!/bin/bash or #!/usr/bin/python3
85
+ # Extract the basename of the path
86
+ interpreter_path = parts[0]
87
+ return Path(interpreter_path).name
88
+
89
+
90
+ def get_interpreter_command(interpreter: str) -> List[str]:
91
+ """Map an interpreter name to a Windows-compatible command.
92
+
93
+ Args:
94
+ interpreter: The interpreter name from shebang (e.g., "python3", "bash").
95
+
96
+ Returns:
97
+ A list representing the command to invoke the interpreter.
98
+ """
99
+ # Normalize interpreter name
100
+ interpreter_lower = interpreter.lower()
101
+
102
+ # Python interpreters - use current Python executable
103
+ if interpreter_lower in ("python", "python3", "python2"):
104
+ return [sys.executable]
105
+
106
+ # Other interpreters - pass through as-is
107
+ # This includes: bash, sh, node, ruby, perl, etc.
108
+ # These need to be available in PATH on Windows
109
+ return [interpreter]
110
+
111
+
112
+ def _build_windows_command(script_path: Path, args: List[str]) -> List[str]:
113
+ """Build the command list for executing a script on Windows.
114
+
115
+ On Windows, shebang lines are not processed by the OS, so we need to
116
+ parse the shebang and explicitly invoke the interpreter.
117
+
118
+ Args:
119
+ script_path: Path to the script file.
120
+ args: Arguments to pass to the script.
121
+
122
+ Returns:
123
+ A list representing the full command to execute.
124
+ """
125
+ interpreter = parse_shebang(script_path)
126
+
127
+ if interpreter:
128
+ cmd_prefix = get_interpreter_command(interpreter)
129
+ return [*cmd_prefix, str(script_path), *args]
130
+
131
+ # Fallback: try direct execution (may fail, but provides clear error)
132
+ return [str(script_path), *args]
133
+
134
+
44
135
  @dataclass
45
136
  class ScriptResult:
46
137
  """Result of a script execution."""
@@ -58,6 +149,10 @@ def run_script(
58
149
  ) -> ScriptResult:
59
150
  """Execute a script and return the result.
60
151
 
152
+ On Unix-like systems, scripts are executed directly using their shebang.
153
+ On Windows, the shebang is parsed to determine the interpreter since
154
+ Windows does not natively support shebang lines.
155
+
61
156
  Args:
62
157
  script_path: Path to the script to execute.
63
158
  args: Optional list of arguments to pass to the script.
@@ -71,8 +166,11 @@ def run_script(
71
166
  subprocess.TimeoutExpired: If script exceeds timeout.
72
167
  FileNotFoundError: If script or interpreter not found.
73
168
  """
74
- ensure_executable(script_path)
75
- cmd = [str(script_path), *(args or [])]
169
+ if platform.system() == "Windows":
170
+ cmd = _build_windows_command(script_path, args or [])
171
+ else:
172
+ ensure_executable(script_path)
173
+ cmd = [str(script_path), *(args or [])]
76
174
 
77
175
  result = subprocess.run(
78
176
  cmd,
agno/team/team.py CHANGED
@@ -7094,7 +7094,7 @@ class Team:
7094
7094
  get_chat_history_func = get_chat_history # type: ignore
7095
7095
  return Function.from_callable(get_chat_history_func, name="get_chat_history")
7096
7096
 
7097
- def _update_session_state_tool(self, session_state, session_state_updates: dict) -> str:
7097
+ def _update_session_state_tool(self, run_context: RunContext, session_state_updates: dict) -> str:
7098
7098
  """
7099
7099
  Update the shared session state. Provide any updates as a dictionary of key-value pairs.
7100
7100
  Example:
@@ -7103,6 +7103,9 @@ class Team:
7103
7103
  Args:
7104
7104
  session_state_updates (dict): The updates to apply to the shared session state. Should be a dictionary of key-value pairs.
7105
7105
  """
7106
+ if run_context.session_state is None:
7107
+ run_context.session_state = {}
7108
+ session_state = run_context.session_state
7106
7109
  for key, value in session_state_updates.items():
7107
7110
  session_state[key] = value
7108
7111
 
@@ -8731,8 +8734,8 @@ class Team:
8731
8734
  tool_choice=config.get("tool_choice"),
8732
8735
  get_member_information_tool=config.get("get_member_information_tool", False),
8733
8736
  # --- Schema settings ---
8734
- # input_schema=config.get("input_schema"), # TODO
8735
- # output_schema=config.get("output_schema"), # TODO
8737
+ input_schema=config.get("input_schema"),
8738
+ output_schema=config.get("output_schema"),
8736
8739
  # --- Parser and output settings ---
8737
8740
  # parser_model=config.get("parser_model"), # TODO
8738
8741
  parser_model_prompt=config.get("parser_model_prompt"),
@@ -104,7 +104,7 @@ class LanceDb(VectorDb):
104
104
  self.async_connection: Optional[lancedb.AsyncConnection] = async_connection
105
105
  self.async_table: Optional[lancedb.db.AsyncTable] = async_table
106
106
 
107
- if table_name and table_name in self.connection.list_tables().tables:
107
+ if table_name and table_name in self._get_table_names(self.connection):
108
108
  # Open the table if it exists
109
109
  try:
110
110
  self.table = self.connection.open_table(name=table_name)
@@ -157,6 +157,21 @@ class LanceDb(VectorDb):
157
157
 
158
158
  log_debug(f"Initialized LanceDb with table: '{self.table_name}'")
159
159
 
160
+ def _get_table_names(self, conn: lancedb.DBConnection) -> List[str]:
161
+ """Get table names with backward compatibility for older LanceDB versions."""
162
+ # Prefer list_tables over table_names (deprecated in new versions)
163
+ if hasattr(conn, "list_tables"):
164
+ return conn.list_tables().tables
165
+ return conn.table_names()
166
+
167
+ async def _get_async_table_names(self, conn: lancedb.AsyncConnection) -> List[str]:
168
+ """Get table names asynchronously with backward compatibility for older LanceDB versions."""
169
+ # Prefer list_tables over table_names (deprecated in new versions)
170
+ if hasattr(conn, "list_tables"):
171
+ table_list = await conn.list_tables()
172
+ return table_list.tables
173
+ return await conn.table_names()
174
+
160
175
  def _prepare_vector(self, embedding) -> List[float]:
161
176
  """Prepare vector embedding for insertion, ensuring correct dimensions and type."""
162
177
  if embedding is not None and len(embedding) > 0:
@@ -186,8 +201,8 @@ class LanceDb(VectorDb):
186
201
  self.async_connection = await lancedb.connect_async(self.uri)
187
202
  # Only try to open table if it exists and we don't have it already
188
203
  if self.async_table is None:
189
- table_list = await self.async_connection.list_tables()
190
- if self.table_name in table_list.tables:
204
+ table_names = await self._get_async_table_names(self.async_connection)
205
+ if self.table_name in table_names:
191
206
  try:
192
207
  self.async_table = await self.async_connection.open_table(self.table_name)
193
208
  except ValueError:
@@ -199,7 +214,7 @@ class LanceDb(VectorDb):
199
214
  """Refresh the sync connection to see changes made by async operations."""
200
215
  try:
201
216
  # Re-establish sync connection to see async changes
202
- if self.connection is not None and self.table_name in self.connection.list_tables().tables:
217
+ if self.connection is not None and self.table_name in self._get_table_names(self.connection):
203
218
  self.table = self.connection.open_table(self.table_name)
204
219
  except Exception as e:
205
220
  log_debug(f"Could not refresh sync connection: {e}")
@@ -642,7 +657,7 @@ class LanceDb(VectorDb):
642
657
  if self.async_table is not None:
643
658
  return True
644
659
  if self.connection is not None:
645
- return self.table_name in self.connection.list_tables().tables
660
+ return self.table_name in self._get_table_names(self.connection)
646
661
  return False
647
662
 
648
663
  async def async_exists(self) -> bool:
@@ -653,8 +668,8 @@ class LanceDb(VectorDb):
653
668
  # Check if table exists in database without trying to open it
654
669
  if self.async_connection is None:
655
670
  self.async_connection = await lancedb.connect_async(self.uri)
656
- table_list = await self.async_connection.list_tables()
657
- return self.table_name in table_list.tables
671
+ table_names = await self._get_async_table_names(self.async_connection)
672
+ return self.table_name in table_names
658
673
 
659
674
  async def async_get_count(self) -> int:
660
675
  """Get the number of rows in the table asynchronously."""
agno/workflow/__init__.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from agno.workflow.agent import WorkflowAgent
2
+ from agno.workflow.cel import CEL_AVAILABLE, validate_cel_expression
2
3
  from agno.workflow.condition import Condition
3
4
  from agno.workflow.loop import Loop
4
5
  from agno.workflow.parallel import Parallel
@@ -24,4 +25,7 @@ __all__ = [
24
25
  "StepOutput",
25
26
  "get_workflow_by_id",
26
27
  "get_workflows",
28
+ # CEL utilities
29
+ "CEL_AVAILABLE",
30
+ "validate_cel_expression",
27
31
  ]