agno 2.2.2__py3-none-any.whl → 2.2.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.
agno/db/mongo/utils.py CHANGED
@@ -117,7 +117,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
117
117
 
118
118
  model_metrics = []
119
119
  for model, count in model_counts.items():
120
- model_id, model_provider = model.split(":")
120
+ model_id, model_provider = model.rsplit(":", 1)
121
121
  model_metrics.append({"model_id": model_id, "model_provider": model_provider, "count": count})
122
122
 
123
123
  metrics["users_count"] = len(all_user_ids)
agno/db/mysql/utils.py CHANGED
@@ -226,7 +226,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
226
226
 
227
227
  model_metrics = []
228
228
  for model, count in model_counts.items():
229
- model_id, model_provider = model.split(":")
229
+ model_id, model_provider = model.rsplit(":", 1)
230
230
  model_metrics.append({"model_id": model_id, "model_provider": model_provider, "count": count})
231
231
 
232
232
  metrics["users_count"] = len(all_user_ids)
@@ -37,7 +37,7 @@ except ImportError:
37
37
  class AsyncPostgresDb(AsyncBaseDb):
38
38
  def __init__(
39
39
  self,
40
- db_id: Optional[str] = None,
40
+ id: Optional[str] = None,
41
41
  db_url: Optional[str] = None,
42
42
  db_engine: Optional[AsyncEngine] = None,
43
43
  db_schema: Optional[str] = None,
@@ -47,6 +47,7 @@ class AsyncPostgresDb(AsyncBaseDb):
47
47
  eval_table: Optional[str] = None,
48
48
  knowledge_table: Optional[str] = None,
49
49
  culture_table: Optional[str] = None,
50
+ db_id: Optional[str] = None, # Deprecated, use id instead.
50
51
  ):
51
52
  """
52
53
  Async interface for interacting with a PostgreSQL database.
@@ -57,7 +58,7 @@ class AsyncPostgresDb(AsyncBaseDb):
57
58
  3. Raise an error if neither is provided
58
59
 
59
60
  Args:
60
- db_id (Optional[str]): The ID of the database.
61
+ id (Optional[str]): The ID of the database.
61
62
  db_url (Optional[str]): The database URL to connect to.
62
63
  db_engine (Optional[AsyncEngine]): The SQLAlchemy async database engine to use.
63
64
  db_schema (Optional[str]): The database schema to use.
@@ -67,13 +68,17 @@ class AsyncPostgresDb(AsyncBaseDb):
67
68
  eval_table (Optional[str]): Name of the table to store evaluation runs data.
68
69
  knowledge_table (Optional[str]): Name of the table to store knowledge content.
69
70
  culture_table (Optional[str]): Name of the table to store cultural knowledge.
71
+ db_id: Deprecated, use id instead.
70
72
 
71
73
  Raises:
72
74
  ValueError: If neither db_url nor db_engine is provided.
73
75
  ValueError: If none of the tables are provided.
74
76
  """
77
+ if db_id is not None:
78
+ log_warning("db_id is deprecated and will be removed in a future version, use id instead.")
79
+
75
80
  super().__init__(
76
- id=db_id,
81
+ id=id or db_id,
77
82
  session_table=session_table,
78
83
  memory_table=memory_table,
79
84
  metrics_table=metrics_table,
agno/db/postgres/utils.py CHANGED
@@ -313,7 +313,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
313
313
 
314
314
  model_metrics = []
315
315
  for model, count in model_counts.items():
316
- model_id, model_provider = model.split(":")
316
+ model_id, model_provider = model.rsplit(":", 1)
317
317
  model_metrics.append({"model_id": model_id, "model_provider": model_provider, "count": count})
318
318
 
319
319
  metrics["users_count"] = len(all_user_ids)
agno/db/redis/utils.py CHANGED
@@ -221,7 +221,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
221
221
 
222
222
  model_metrics = []
223
223
  for model, count in model_counts.items():
224
- model_id, model_provider = model.split(":")
224
+ model_id, model_provider = model.rsplit(":", 1)
225
225
  model_metrics.append({"model_id": model_id, "model_provider": model_provider, "count": count})
226
226
 
227
227
  metrics["users_count"] = len(all_user_ids)
@@ -255,7 +255,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
255
255
 
256
256
  model_metrics = []
257
257
  for model, count in model_counts.items():
258
- model_id, model_provider = model.split(":")
258
+ model_id, model_provider = model.rsplit(":", 1)
259
259
  model_metrics.append({"model_id": model_id, "model_provider": model_provider, "count": count})
260
260
 
261
261
  metrics["users_count"] = len(all_user_ids)
agno/db/sqlite/utils.py CHANGED
@@ -298,7 +298,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
298
298
 
299
299
  model_metrics = []
300
300
  for model, count in model_counts.items():
301
- model_id, model_provider = model.split(":")
301
+ model_id, model_provider = model.rsplit(":", 1)
302
302
  model_metrics.append({"model_id": model_id, "model_provider": model_provider, "count": count})
303
303
 
304
304
  metrics["users_count"] = len(all_user_ids)
@@ -85,6 +85,10 @@ class OllamaEmbedder(Embedder):
85
85
  if self.options is not None:
86
86
  kwargs["options"] = self.options
87
87
 
88
+ # Add dimensions parameter for models that support it
89
+ if self.dimensions is not None:
90
+ kwargs["dimensions"] = self.dimensions
91
+
88
92
  response = self.client.embed(input=text, model=self.id, **kwargs)
89
93
  if response and "embeddings" in response:
90
94
  embeddings = response["embeddings"]
@@ -117,6 +121,10 @@ class OllamaEmbedder(Embedder):
117
121
  if self.options is not None:
118
122
  kwargs["options"] = self.options
119
123
 
124
+ # Add dimensions parameter for models that support it
125
+ if self.dimensions is not None:
126
+ kwargs["dimensions"] = self.dimensions
127
+
120
128
  response = await self.aclient.embed(input=text, model=self.id, **kwargs)
121
129
  if response and "embeddings" in response:
122
130
  embeddings = response["embeddings"]
agno/memory/manager.py CHANGED
@@ -843,7 +843,7 @@ class MemoryManager:
843
843
  team_id=team_id,
844
844
  enable_add_memory=add_memories,
845
845
  enable_update_memory=update_memories,
846
- enable_delete_memory=False,
846
+ enable_delete_memory=True,
847
847
  enable_clear_memory=False,
848
848
  ),
849
849
  )
@@ -854,7 +854,7 @@ class MemoryManager:
854
854
  existing_memories=existing_memories,
855
855
  enable_update_memory=update_memories,
856
856
  enable_add_memory=add_memories,
857
- enable_delete_memory=False,
857
+ enable_delete_memory=True,
858
858
  enable_clear_memory=False,
859
859
  ),
860
860
  *messages,
@@ -906,7 +906,7 @@ class MemoryManager:
906
906
  team_id=team_id,
907
907
  enable_add_memory=add_memories,
908
908
  enable_update_memory=update_memories,
909
- enable_delete_memory=False,
909
+ enable_delete_memory=True,
910
910
  enable_clear_memory=False,
911
911
  ),
912
912
  )
@@ -920,7 +920,7 @@ class MemoryManager:
920
920
  team_id=team_id,
921
921
  enable_add_memory=add_memories,
922
922
  enable_update_memory=update_memories,
923
- enable_delete_memory=False,
923
+ enable_delete_memory=True,
924
924
  enable_clear_memory=False,
925
925
  ),
926
926
  )
@@ -931,7 +931,7 @@ class MemoryManager:
931
931
  existing_memories=existing_memories,
932
932
  enable_update_memory=update_memories,
933
933
  enable_add_memory=add_memories,
934
- enable_delete_memory=False,
934
+ enable_delete_memory=True,
935
935
  enable_clear_memory=False,
936
936
  ),
937
937
  *messages,
agno/os/app.py CHANGED
@@ -187,54 +187,50 @@ class AgentOS:
187
187
  self.mcp_tools: List[Any] = []
188
188
  self._mcp_app: Optional[Any] = None
189
189
 
190
- if self.agents:
191
- for agent in self.agents:
192
- # Track all MCP tools to later handle their connection
193
- if agent.tools:
194
- for tool in agent.tools:
195
- # Checking if the tool is a MCPTools or MultiMCPTools instance
196
- type_name = type(tool).__name__
197
- if type_name in ("MCPTools", "MultiMCPTools"):
198
- if tool not in self.mcp_tools:
199
- self.mcp_tools.append(tool)
200
-
201
- agent.initialize_agent()
190
+ self._initialize_agents()
191
+ self._initialize_teams()
192
+ self._initialize_workflows()
202
193
 
203
- # Required for the built-in routes to work
204
- agent.store_events = True
205
-
206
- if self.teams:
207
- for team in self.teams:
208
- # Track all MCP tools recursively
209
- collect_mcp_tools_from_team(team, self.mcp_tools)
194
+ if self.telemetry:
195
+ from agno.api.os import OSLaunch, log_os_telemetry
210
196
 
211
- team.initialize_team()
197
+ log_os_telemetry(launch=OSLaunch(os_id=self.id, data=self._get_telemetry_data()))
212
198
 
213
- for member in team.members:
214
- if isinstance(member, Agent):
215
- member.team_id = None
216
- member.initialize_agent()
217
- elif isinstance(member, Team):
218
- member.initialize_team()
199
+ def _add_agent_os_to_lifespan_function(self, lifespan):
200
+ """
201
+ Inspect a lifespan function and wrap it to pass agent_os if it accepts it.
219
202
 
220
- # Required for the built-in routes to work
221
- team.store_events = True
203
+ Returns:
204
+ A wrapped lifespan that passes agent_os if the lifespan function expects it.
205
+ """
206
+ # Getting the actual function inside the lifespan
207
+ lifespan_function = lifespan
208
+ if hasattr(lifespan, "__wrapped__"):
209
+ lifespan_function = lifespan.__wrapped__
222
210
 
223
- if self.workflows:
224
- for workflow in self.workflows:
225
- # Track MCP tools recursively in workflow members
226
- collect_mcp_tools_from_workflow(workflow, self.mcp_tools)
211
+ try:
212
+ from inspect import signature
227
213
 
228
- if not workflow.id:
229
- workflow.id = generate_id_from_name(workflow.name)
214
+ # Inspecting the lifespan function signature to find its parameters
215
+ sig = signature(lifespan_function)
216
+ params = list(sig.parameters.keys())
230
217
 
231
- # Required for the built-in routes to work
232
- workflow.store_events = True
218
+ # If the lifespan function expects the 'agent_os' parameter, add it
219
+ if "agent_os" in params:
220
+ return partial(lifespan, agent_os=self)
221
+ else:
222
+ return lifespan
233
223
 
234
- if self.telemetry:
235
- from agno.api.os import OSLaunch, log_os_telemetry
224
+ except (ValueError, TypeError):
225
+ return lifespan
236
226
 
237
- log_os_telemetry(launch=OSLaunch(os_id=self.id, data=self._get_telemetry_data()))
227
+ def resync(self) -> None:
228
+ """Resync the AgentOS to discover, initialize and configure: agents, teams, workflows, databases and knowledge bases."""
229
+ self._initialize_agents()
230
+ self._initialize_teams()
231
+ self._initialize_workflows()
232
+ self._auto_discover_databases()
233
+ self._auto_discover_knowledge_instances()
238
234
 
239
235
  def _make_app(self, lifespan: Optional[Any] = None) -> FastAPI:
240
236
  # Adjust the FastAPI app lifespan to handle MCP connections if relevant
@@ -265,6 +261,63 @@ class AgentOS:
265
261
  lifespan=app_lifespan,
266
262
  )
267
263
 
264
+ def _initialize_agents(self) -> None:
265
+ """Initialize and configure all agents for AgentOS usage."""
266
+ if not self.agents:
267
+ return
268
+
269
+ for agent in self.agents:
270
+ # Track all MCP tools to later handle their connection
271
+ if agent.tools:
272
+ for tool in agent.tools:
273
+ # Checking if the tool is a MCPTools or MultiMCPTools instance
274
+ type_name = type(tool).__name__
275
+ if type_name in ("MCPTools", "MultiMCPTools"):
276
+ if tool not in self.mcp_tools:
277
+ self.mcp_tools.append(tool)
278
+
279
+ agent.initialize_agent()
280
+
281
+ # Required for the built-in routes to work
282
+ agent.store_events = True
283
+
284
+ def _initialize_teams(self) -> None:
285
+ """Initialize and configure all teams for AgentOS usage."""
286
+ if not self.teams:
287
+ return
288
+
289
+ for team in self.teams:
290
+ # Track all MCP tools recursively
291
+ collect_mcp_tools_from_team(team, self.mcp_tools)
292
+
293
+ team.initialize_team()
294
+
295
+ for member in team.members:
296
+ if isinstance(member, Agent):
297
+ member.team_id = None
298
+ member.initialize_agent()
299
+ elif isinstance(member, Team):
300
+ member.initialize_team()
301
+
302
+ # Required for the built-in routes to work
303
+ team.store_events = True
304
+
305
+ def _initialize_workflows(self) -> None:
306
+ """Initialize and configure all workflows for AgentOS usage."""
307
+ if not self.workflows:
308
+ return
309
+
310
+ if self.workflows:
311
+ for workflow in self.workflows:
312
+ # Track MCP tools recursively in workflow members
313
+ collect_mcp_tools_from_workflow(workflow, self.mcp_tools)
314
+
315
+ if not workflow.id:
316
+ workflow.id = generate_id_from_name(workflow.name)
317
+
318
+ # Required for the built-in routes to work
319
+ workflow.store_events = True
320
+
268
321
  def get_app(self) -> FastAPI:
269
322
  if self.base_app:
270
323
  fastapi_app = self.base_app
@@ -288,7 +341,9 @@ class AgentOS:
288
341
  lifespans.append(self._mcp_app.lifespan)
289
342
 
290
343
  if self.lifespan:
291
- lifespans.append(self.lifespan)
344
+ # Wrap the user lifespan with agent_os parameter
345
+ wrapped_lifespan = self._add_agent_os_to_lifespan_function(self.lifespan)
346
+ lifespans.append(wrapped_lifespan)
292
347
 
293
348
  # Combine lifespans and set them in the app
294
349
  if lifespans:
@@ -304,11 +359,14 @@ class AgentOS:
304
359
 
305
360
  final_lifespan = self._mcp_app.lifespan # type: ignore
306
361
  if self.lifespan is not None:
362
+ # Wrap the user lifespan with agent_os parameter
363
+ wrapped_lifespan = self._add_agent_os_to_lifespan_function(self.lifespan)
364
+
307
365
  # Combine both lifespans
308
366
  @asynccontextmanager
309
367
  async def combined_lifespan(app: FastAPI):
310
368
  # Run both lifespans
311
- async with self.lifespan(app): # type: ignore
369
+ async with wrapped_lifespan(app): # type: ignore
312
370
  async with self._mcp_app.lifespan(app): # type: ignore
313
371
  yield
314
372
 
@@ -316,7 +374,12 @@ class AgentOS:
316
374
 
317
375
  fastapi_app = self._make_app(lifespan=final_lifespan)
318
376
  else:
319
- fastapi_app = self._make_app(lifespan=self.lifespan)
377
+ # Wrap the user lifespan with agent_os parameter
378
+ wrapped_user_lifespan = None
379
+ if self.lifespan is not None:
380
+ wrapped_user_lifespan = self._add_agent_os_to_lifespan_function(self.lifespan)
381
+
382
+ fastapi_app = self._make_app(lifespan=wrapped_user_lifespan)
320
383
 
321
384
  # Add routes
322
385
  self._add_router(fastapi_app, get_base_router(self, settings=self.settings))
agno/os/schema.py CHANGED
@@ -475,7 +475,6 @@ class TeamResponse(BaseModel):
475
475
  team_run_context={},
476
476
  check_mcp_tools=False,
477
477
  )
478
- print(team.tools, _tools)
479
478
  team_tools = _tools
480
479
  formatted_tools = format_team_tools(team_tools) if team_tools else None
481
480
 
agno/run/agent.py CHANGED
@@ -70,12 +70,39 @@ class RunInput:
70
70
  result["input_content"] = self.input_content.model_dump(exclude_none=True)
71
71
  elif isinstance(self.input_content, Message):
72
72
  result["input_content"] = self.input_content.to_dict()
73
+
74
+ # Handle input_content provided as a list of Message objects
73
75
  elif (
74
76
  isinstance(self.input_content, list)
75
77
  and self.input_content
76
78
  and isinstance(self.input_content[0], Message)
77
79
  ):
78
80
  result["input_content"] = [m.to_dict() for m in self.input_content]
81
+
82
+ # Handle input_content provided as a list of dicts
83
+ elif (
84
+ isinstance(self.input_content, list) and self.input_content and isinstance(self.input_content[0], dict)
85
+ ):
86
+ for content in self.input_content:
87
+ # Handle media input
88
+ if isinstance(content, dict):
89
+ if content.get("images"):
90
+ content["images"] = [
91
+ img.to_dict() if isinstance(img, Image) else img for img in content["images"]
92
+ ]
93
+ if content.get("videos"):
94
+ content["videos"] = [
95
+ vid.to_dict() if isinstance(vid, Video) else vid for vid in content["videos"]
96
+ ]
97
+ if content.get("audios"):
98
+ content["audios"] = [
99
+ aud.to_dict() if isinstance(aud, Audio) else aud for aud in content["audios"]
100
+ ]
101
+ if content.get("files"):
102
+ content["files"] = [
103
+ file.to_dict() if isinstance(file, File) else file for file in content["files"]
104
+ ]
105
+ result["input_content"] = self.input_content
79
106
  else:
80
107
  result["input_content"] = self.input_content
81
108
 
agno/run/team.py CHANGED
@@ -66,12 +66,39 @@ class TeamRunInput:
66
66
  result["input_content"] = self.input_content.model_dump(exclude_none=True)
67
67
  elif isinstance(self.input_content, Message):
68
68
  result["input_content"] = self.input_content.to_dict()
69
+
70
+ # Handle input_content provided as a list of Message objects
69
71
  elif (
70
72
  isinstance(self.input_content, list)
71
73
  and self.input_content
72
74
  and isinstance(self.input_content[0], Message)
73
75
  ):
74
76
  result["input_content"] = [m.to_dict() for m in self.input_content]
77
+
78
+ # Handle input_content provided as a list of dicts
79
+ elif (
80
+ isinstance(self.input_content, list) and self.input_content and isinstance(self.input_content[0], dict)
81
+ ):
82
+ for content in self.input_content:
83
+ # Handle media input
84
+ if isinstance(content, dict):
85
+ if content.get("images"):
86
+ content["images"] = [
87
+ img.to_dict() if isinstance(img, Image) else img for img in content["images"]
88
+ ]
89
+ if content.get("videos"):
90
+ content["videos"] = [
91
+ vid.to_dict() if isinstance(vid, Video) else vid for vid in content["videos"]
92
+ ]
93
+ if content.get("audios"):
94
+ content["audios"] = [
95
+ aud.to_dict() if isinstance(aud, Audio) else aud for aud in content["audios"]
96
+ ]
97
+ if content.get("files"):
98
+ content["files"] = [
99
+ file.to_dict() if isinstance(file, File) else file for file in content["files"]
100
+ ]
101
+ result["input_content"] = self.input_content
75
102
  else:
76
103
  result["input_content"] = self.input_content
77
104
 
agno/session/agent.py CHANGED
@@ -107,11 +107,25 @@ class AgentSession:
107
107
  return run
108
108
  return None
109
109
 
110
+ def _should_skip_message(
111
+ self, message: Message, skip_role: Optional[str] = None, skip_history_messages: bool = True
112
+ ) -> bool:
113
+ """Processes a message for history"""
114
+ # Skip messages that were tagged as history in previous runs
115
+ if hasattr(message, "from_history") and message.from_history and skip_history_messages:
116
+ return True
117
+
118
+ # Skip messages with specified role
119
+ if skip_role and message.role == skip_role:
120
+ return True
121
+ return False
122
+
110
123
  def get_messages_from_last_n_runs(
111
124
  self,
112
125
  agent_id: Optional[str] = None,
113
126
  team_id: Optional[str] = None,
114
127
  last_n: Optional[int] = None,
128
+ last_n_messages: Optional[int] = None,
115
129
  skip_role: Optional[str] = None,
116
130
  skip_status: Optional[List[RunStatus]] = None,
117
131
  skip_history_messages: bool = True,
@@ -121,6 +135,7 @@ class AgentSession:
121
135
  agent_id: The id of the agent to get the messages from.
122
136
  team_id: The id of the team to get the messages from.
123
137
  last_n: The number of runs to return from the end of the conversation. Defaults to all runs.
138
+ last_n_messages: The number of messages to return from the end of the conversation. Defaults to all messages.
124
139
  skip_role: Skip messages with this role.
125
140
  skip_status: Skip messages with this status.
126
141
  skip_history_messages: Skip messages that were tagged as history in previous runs.
@@ -146,30 +161,55 @@ class AgentSession:
146
161
  # Filter by status
147
162
  session_runs = [run for run in session_runs if hasattr(run, "status") and run.status not in skip_status] # type: ignore
148
163
 
149
- # Filter by last_n
150
- runs_to_process = session_runs[-last_n:] if last_n is not None else session_runs
151
164
  messages_from_history = []
152
165
  system_message = None
153
- for run_response in runs_to_process:
154
- if not (run_response and run_response.messages):
155
- continue
156
166
 
157
- for message in run_response.messages or []:
158
- # Skip messages that were tagged as history in previous runs
159
- if hasattr(message, "from_history") and message.from_history and skip_history_messages:
167
+ # Filter by last_n_messages
168
+ if last_n_messages is not None:
169
+ for run_response in session_runs:
170
+ if not run_response or not run_response.messages:
160
171
  continue
161
172
 
162
- # Skip messages with specified role
163
- if skip_role and message.role == skip_role:
173
+ for message in run_response.messages or []:
174
+ if self._should_skip_message(message, skip_role, skip_history_messages):
175
+ continue
176
+
177
+ if message.role == "system":
178
+ # Only add the system message once
179
+ if system_message is None:
180
+ system_message = message
181
+ else:
182
+ messages_from_history.append(message)
183
+
184
+ if system_message:
185
+ messages_from_history = [system_message] + messages_from_history[
186
+ -(last_n_messages - 1) :
187
+ ] # Grab one less message then add the system message
188
+ else:
189
+ messages_from_history = messages_from_history[-last_n_messages:]
190
+
191
+ # Remove tool result messages that don't have an associated assistant message with tool calls
192
+ while len(messages_from_history) > 0 and messages_from_history[0].role == "tool":
193
+ messages_from_history.pop(0)
194
+
195
+ else:
196
+ # Filter by last_n runs
197
+ runs_to_process = session_runs[-last_n:] if last_n is not None else session_runs
198
+ for run_response in runs_to_process:
199
+ if not run_response or not run_response.messages:
164
200
  continue
165
201
 
166
- if message.role == "system":
167
- # Only add the system message once
168
- if system_message is None:
169
- system_message = message
170
- messages_from_history.append(system_message)
171
- else:
172
- messages_from_history.append(message)
202
+ for message in run_response.messages or []:
203
+ if self._should_skip_message(message, skip_role, skip_history_messages):
204
+ continue
205
+
206
+ if message.role == "system":
207
+ # Only add the system message once
208
+ if system_message is None:
209
+ system_message = message
210
+ messages_from_history.append(system_message)
211
+ else:
212
+ messages_from_history.append(message)
173
213
 
174
214
  log_debug(f"Getting messages from previous runs: {len(messages_from_history)}")
175
215
  return messages_from_history