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/agent/agent.py +26 -5
- agno/db/dynamo/utils.py +1 -1
- agno/db/firestore/utils.py +1 -1
- agno/db/gcs_json/utils.py +1 -1
- agno/db/in_memory/utils.py +1 -1
- agno/db/json/utils.py +1 -1
- agno/db/mongo/__init__.py +15 -1
- agno/db/mongo/async_mongo.py +1999 -0
- agno/db/mongo/utils.py +1 -1
- agno/db/mysql/utils.py +1 -1
- agno/db/postgres/async_postgres.py +8 -3
- agno/db/postgres/utils.py +1 -1
- agno/db/redis/utils.py +1 -1
- agno/db/singlestore/utils.py +1 -1
- agno/db/sqlite/utils.py +1 -1
- agno/knowledge/embedder/ollama.py +8 -0
- agno/memory/manager.py +5 -5
- agno/os/app.py +105 -42
- agno/os/schema.py +0 -1
- agno/run/agent.py +27 -0
- agno/run/team.py +27 -0
- agno/session/agent.py +57 -17
- agno/session/team.py +57 -18
- agno/team/team.py +37 -11
- {agno-2.2.2.dist-info → agno-2.2.4.dist-info}/METADATA +1 -1
- {agno-2.2.2.dist-info → agno-2.2.4.dist-info}/RECORD +29 -28
- {agno-2.2.2.dist-info → agno-2.2.4.dist-info}/WHEEL +0 -0
- {agno-2.2.2.dist-info → agno-2.2.4.dist-info}/licenses/LICENSE +0 -0
- {agno-2.2.2.dist-info → agno-2.2.4.dist-info}/top_level.txt +0 -0
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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)
|
agno/db/singlestore/utils.py
CHANGED
|
@@ -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.
|
|
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.
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
204
|
-
|
|
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
|
-
|
|
197
|
+
log_os_telemetry(launch=OSLaunch(os_id=self.id, data=self._get_telemetry_data()))
|
|
212
198
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
221
|
-
|
|
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
|
-
|
|
224
|
-
|
|
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
|
-
|
|
229
|
-
|
|
214
|
+
# Inspecting the lifespan function signature to find its parameters
|
|
215
|
+
sig = signature(lifespan_function)
|
|
216
|
+
params = list(sig.parameters.keys())
|
|
230
217
|
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
235
|
-
|
|
224
|
+
except (ValueError, TypeError):
|
|
225
|
+
return lifespan
|
|
236
226
|
|
|
237
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
163
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|