agno 2.1.3__py3-none-any.whl → 2.1.5__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 +1779 -577
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/async_postgres/async_postgres.py +1668 -0
- agno/db/async_postgres/schemas.py +124 -0
- agno/db/async_postgres/utils.py +289 -0
- agno/db/base.py +237 -2
- agno/db/dynamo/dynamo.py +10 -8
- agno/db/dynamo/schemas.py +1 -10
- agno/db/dynamo/utils.py +2 -2
- agno/db/firestore/firestore.py +2 -2
- agno/db/firestore/utils.py +4 -2
- agno/db/gcs_json/gcs_json_db.py +2 -2
- agno/db/in_memory/in_memory_db.py +2 -2
- agno/db/json/json_db.py +2 -2
- agno/db/migrations/v1_to_v2.py +30 -13
- agno/db/mongo/mongo.py +18 -6
- agno/db/mysql/mysql.py +35 -13
- agno/db/postgres/postgres.py +29 -6
- agno/db/redis/redis.py +2 -2
- agno/db/singlestore/singlestore.py +2 -2
- agno/db/sqlite/sqlite.py +34 -12
- agno/db/sqlite/utils.py +8 -3
- agno/eval/accuracy.py +50 -43
- agno/eval/performance.py +6 -3
- agno/eval/reliability.py +6 -3
- agno/eval/utils.py +33 -16
- agno/exceptions.py +8 -2
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/knowledge.py +260 -46
- agno/knowledge/reader/pdf_reader.py +4 -6
- agno/knowledge/reader/reader_factory.py +2 -3
- agno/memory/manager.py +241 -33
- agno/models/anthropic/claude.py +37 -0
- agno/os/app.py +15 -10
- agno/os/interfaces/a2a/router.py +3 -5
- agno/os/interfaces/agui/router.py +4 -1
- agno/os/interfaces/agui/utils.py +33 -6
- agno/os/interfaces/slack/router.py +2 -4
- agno/os/mcp.py +98 -41
- agno/os/router.py +23 -0
- agno/os/routers/evals/evals.py +52 -20
- agno/os/routers/evals/utils.py +14 -14
- agno/os/routers/knowledge/knowledge.py +130 -9
- agno/os/routers/knowledge/schemas.py +57 -0
- agno/os/routers/memory/memory.py +116 -44
- agno/os/routers/metrics/metrics.py +16 -6
- agno/os/routers/session/session.py +65 -22
- agno/os/schema.py +38 -0
- agno/os/utils.py +69 -13
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/openai.py +5 -0
- agno/reasoning/vertexai.py +76 -0
- agno/session/workflow.py +69 -1
- agno/team/team.py +934 -241
- agno/tools/function.py +36 -18
- agno/tools/google_drive.py +270 -0
- agno/tools/googlesheets.py +20 -5
- agno/tools/mcp_toolbox.py +3 -3
- agno/tools/scrapegraph.py +1 -1
- agno/utils/models/claude.py +3 -1
- agno/utils/print_response/workflow.py +112 -12
- agno/utils/streamlit.py +1 -1
- agno/vectordb/base.py +22 -1
- agno/vectordb/cassandra/cassandra.py +9 -0
- agno/vectordb/chroma/chromadb.py +26 -6
- agno/vectordb/clickhouse/clickhousedb.py +9 -1
- agno/vectordb/couchbase/couchbase.py +11 -0
- agno/vectordb/lancedb/lance_db.py +20 -0
- agno/vectordb/langchaindb/langchaindb.py +11 -0
- agno/vectordb/lightrag/lightrag.py +9 -0
- agno/vectordb/llamaindex/llamaindexdb.py +15 -1
- agno/vectordb/milvus/milvus.py +23 -0
- agno/vectordb/mongodb/mongodb.py +22 -0
- agno/vectordb/pgvector/pgvector.py +19 -0
- agno/vectordb/pineconedb/pineconedb.py +35 -4
- agno/vectordb/qdrant/qdrant.py +24 -0
- agno/vectordb/singlestore/singlestore.py +25 -17
- agno/vectordb/surrealdb/surrealdb.py +18 -1
- agno/vectordb/upstashdb/upstashdb.py +26 -1
- agno/vectordb/weaviate/weaviate.py +18 -0
- agno/workflow/condition.py +29 -0
- agno/workflow/loop.py +29 -0
- agno/workflow/parallel.py +141 -113
- agno/workflow/router.py +29 -0
- agno/workflow/step.py +146 -25
- agno/workflow/steps.py +29 -0
- agno/workflow/types.py +26 -1
- agno/workflow/workflow.py +507 -22
- {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/METADATA +100 -41
- {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/RECORD +94 -86
- {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/WHEEL +0 -0
- {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/licenses/LICENSE +0 -0
- {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/top_level.txt +0 -0
agno/agent/agent.py
CHANGED
|
@@ -27,7 +27,7 @@ from uuid import uuid4
|
|
|
27
27
|
|
|
28
28
|
from pydantic import BaseModel
|
|
29
29
|
|
|
30
|
-
from agno.db.base import BaseDb, SessionType, UserMemory
|
|
30
|
+
from agno.db.base import AsyncBaseDb, BaseDb, SessionType, UserMemory
|
|
31
31
|
from agno.exceptions import (
|
|
32
32
|
InputCheckError,
|
|
33
33
|
ModelProviderError,
|
|
@@ -177,7 +177,7 @@ class Agent:
|
|
|
177
177
|
|
|
178
178
|
# --- Database ---
|
|
179
179
|
# Database to use for this agent
|
|
180
|
-
db: Optional[BaseDb] = None
|
|
180
|
+
db: Optional[Union[BaseDb, AsyncBaseDb]] = None
|
|
181
181
|
|
|
182
182
|
# --- Agent History ---
|
|
183
183
|
# add_history_to_context=true adds messages from the chat history to the messages list sent to the Model.
|
|
@@ -247,6 +247,10 @@ class Agent:
|
|
|
247
247
|
send_media_to_model: bool = True
|
|
248
248
|
# If True, store media in run output
|
|
249
249
|
store_media: bool = True
|
|
250
|
+
# If True, store tool results in run output
|
|
251
|
+
store_tool_results: bool = True
|
|
252
|
+
# If True, store history messages in run output
|
|
253
|
+
store_history_messages: bool = True
|
|
250
254
|
|
|
251
255
|
# --- System message settings ---
|
|
252
256
|
# Provide the system message as a string or function
|
|
@@ -373,7 +377,7 @@ class Agent:
|
|
|
373
377
|
num_history_sessions: Optional[int] = None,
|
|
374
378
|
dependencies: Optional[Dict[str, Any]] = None,
|
|
375
379
|
add_dependencies_to_context: bool = False,
|
|
376
|
-
db: Optional[BaseDb] = None,
|
|
380
|
+
db: Optional[Union[BaseDb, AsyncBaseDb]] = None,
|
|
377
381
|
memory_manager: Optional[MemoryManager] = None,
|
|
378
382
|
enable_agentic_memory: bool = False,
|
|
379
383
|
enable_user_memories: bool = False,
|
|
@@ -384,6 +388,8 @@ class Agent:
|
|
|
384
388
|
add_history_to_context: bool = False,
|
|
385
389
|
num_history_runs: int = 3,
|
|
386
390
|
store_media: bool = True,
|
|
391
|
+
store_tool_results: bool = True,
|
|
392
|
+
store_history_messages: bool = True,
|
|
387
393
|
knowledge: Optional[Knowledge] = None,
|
|
388
394
|
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
389
395
|
enable_agentic_knowledge_filters: Optional[bool] = None,
|
|
@@ -484,6 +490,8 @@ class Agent:
|
|
|
484
490
|
)
|
|
485
491
|
|
|
486
492
|
self.store_media = store_media
|
|
493
|
+
self.store_tool_results = store_tool_results
|
|
494
|
+
self.store_history_messages = store_history_messages
|
|
487
495
|
|
|
488
496
|
self.knowledge = knowledge
|
|
489
497
|
self.knowledge_filters = knowledge_filters
|
|
@@ -691,6 +699,10 @@ class Agent:
|
|
|
691
699
|
self.enable_session_summaries or self.session_summary_manager is not None
|
|
692
700
|
)
|
|
693
701
|
|
|
702
|
+
def _has_async_db(self) -> bool:
|
|
703
|
+
"""Return True if the db the agent is equipped with is an Async implementation"""
|
|
704
|
+
return self.db is not None and isinstance(self.db, AsyncBaseDb)
|
|
705
|
+
|
|
694
706
|
def initialize_agent(self, debug_mode: Optional[bool] = None) -> None:
|
|
695
707
|
self._set_default_model()
|
|
696
708
|
self._set_debug(debug_mode=debug_mode)
|
|
@@ -883,8 +895,6 @@ class Agent:
|
|
|
883
895
|
|
|
884
896
|
if self.store_media:
|
|
885
897
|
self._store_media(run_response, model_response)
|
|
886
|
-
else:
|
|
887
|
-
self._scrub_media_from_run_output(run_response)
|
|
888
898
|
|
|
889
899
|
# We should break out of the run function
|
|
890
900
|
if any(tool_call.is_paused for tool_call in run_response.tools or []):
|
|
@@ -930,7 +940,11 @@ class Agent:
|
|
|
930
940
|
# Consume the response iterator to ensure the memory is updated before the run is completed
|
|
931
941
|
deque(response_iterator, maxlen=0)
|
|
932
942
|
|
|
933
|
-
# 11.
|
|
943
|
+
# 11. Scrub the stored run based on storage flags
|
|
944
|
+
if self._scrub_run_output_for_storage(run_response):
|
|
945
|
+
session.upsert_run(run=run_response)
|
|
946
|
+
|
|
947
|
+
# 12. Save session to memory
|
|
934
948
|
self.save_session(session=session)
|
|
935
949
|
|
|
936
950
|
# Log Agent Telemetry
|
|
@@ -957,7 +971,6 @@ class Agent:
|
|
|
957
971
|
dependencies: Optional[Dict[str, Any]] = None,
|
|
958
972
|
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
959
973
|
stream_intermediate_steps: bool = False,
|
|
960
|
-
workflow_context: Optional[Dict] = None,
|
|
961
974
|
yield_run_response: bool = False,
|
|
962
975
|
debug_mode: Optional[bool] = None,
|
|
963
976
|
**kwargs: Any,
|
|
@@ -973,7 +986,8 @@ class Agent:
|
|
|
973
986
|
6. Optional: Save output to file if save_response_to_file is set
|
|
974
987
|
7. Add the RunOutput to the Agent Session
|
|
975
988
|
8. Update Agent Memory
|
|
976
|
-
9.
|
|
989
|
+
9. Create the run completed event
|
|
990
|
+
10. Save session to storage
|
|
977
991
|
"""
|
|
978
992
|
|
|
979
993
|
# Register run for cancellation tracking
|
|
@@ -1034,7 +1048,7 @@ class Agent:
|
|
|
1034
1048
|
try:
|
|
1035
1049
|
# Start the Run by yielding a RunStarted event
|
|
1036
1050
|
if stream_intermediate_steps:
|
|
1037
|
-
yield self._handle_event(create_run_started_event(run_response), run_response
|
|
1051
|
+
yield self._handle_event(create_run_started_event(run_response), run_response)
|
|
1038
1052
|
|
|
1039
1053
|
# 3. Reason about the task if reasoning is enabled
|
|
1040
1054
|
yield from self._handle_reasoning_stream(run_response=run_response, run_messages=run_messages)
|
|
@@ -1050,7 +1064,6 @@ class Agent:
|
|
|
1050
1064
|
run_messages=run_messages,
|
|
1051
1065
|
response_format=response_format,
|
|
1052
1066
|
stream_intermediate_steps=stream_intermediate_steps,
|
|
1053
|
-
workflow_context=workflow_context,
|
|
1054
1067
|
):
|
|
1055
1068
|
raise_if_cancelled(run_response.run_id) # type: ignore
|
|
1056
1069
|
yield event
|
|
@@ -1066,7 +1079,6 @@ class Agent:
|
|
|
1066
1079
|
run_messages=run_messages,
|
|
1067
1080
|
response_format=response_format,
|
|
1068
1081
|
stream_intermediate_steps=stream_intermediate_steps,
|
|
1069
|
-
workflow_context=workflow_context,
|
|
1070
1082
|
):
|
|
1071
1083
|
raise_if_cancelled(run_response.run_id) # type: ignore
|
|
1072
1084
|
if isinstance(event, RunContentEvent):
|
|
@@ -1084,7 +1096,6 @@ class Agent:
|
|
|
1084
1096
|
run_response=run_response,
|
|
1085
1097
|
run_messages=run_messages,
|
|
1086
1098
|
stream_intermediate_steps=stream_intermediate_steps,
|
|
1087
|
-
workflow_context=workflow_context,
|
|
1088
1099
|
):
|
|
1089
1100
|
raise_if_cancelled(run_response.run_id) # type: ignore
|
|
1090
1101
|
yield event
|
|
@@ -1115,9 +1126,6 @@ class Agent:
|
|
|
1115
1126
|
# 5. Calculate session metrics
|
|
1116
1127
|
self._update_session_metrics(session=session, run_response=run_response)
|
|
1117
1128
|
|
|
1118
|
-
completed_event = self._handle_event(
|
|
1119
|
-
create_run_completed_event(from_run_response=run_response), run_response, workflow_context
|
|
1120
|
-
)
|
|
1121
1129
|
# 6. Optional: Save output to file if save_response_to_file is set
|
|
1122
1130
|
self.save_run_response_to_file(
|
|
1123
1131
|
run_response=run_response,
|
|
@@ -1134,7 +1142,16 @@ class Agent:
|
|
|
1134
1142
|
run_response=run_response, run_messages=run_messages, session=session, user_id=user_id
|
|
1135
1143
|
)
|
|
1136
1144
|
|
|
1137
|
-
# 9.
|
|
1145
|
+
# 9. Create the run completed event
|
|
1146
|
+
completed_event = self._handle_event(
|
|
1147
|
+
create_run_completed_event(from_run_response=run_response), run_response
|
|
1148
|
+
)
|
|
1149
|
+
|
|
1150
|
+
# 10. Scrub the stored run based on storage flags
|
|
1151
|
+
if self._scrub_run_output_for_storage(run_response):
|
|
1152
|
+
session.upsert_run(run=run_response)
|
|
1153
|
+
|
|
1154
|
+
# 11. Save session to storage
|
|
1138
1155
|
self.save_session(session=session)
|
|
1139
1156
|
|
|
1140
1157
|
if stream_intermediate_steps:
|
|
@@ -1158,7 +1175,6 @@ class Agent:
|
|
|
1158
1175
|
yield self._handle_event(
|
|
1159
1176
|
create_run_cancelled_event(from_run_response=run_response, reason=str(e)),
|
|
1160
1177
|
run_response,
|
|
1161
|
-
workflow_context,
|
|
1162
1178
|
)
|
|
1163
1179
|
|
|
1164
1180
|
# Add the RunOutput to Agent Session even when cancelled
|
|
@@ -1244,6 +1260,10 @@ class Agent:
|
|
|
1244
1260
|
**kwargs: Any,
|
|
1245
1261
|
) -> Union[RunOutput, Iterator[Union[RunOutputEvent, RunOutput]]]:
|
|
1246
1262
|
"""Run the Agent and return the response."""
|
|
1263
|
+
if self._has_async_db():
|
|
1264
|
+
raise RuntimeError(
|
|
1265
|
+
"`run` method is not supported with an async database. Please use `arun` method instead."
|
|
1266
|
+
)
|
|
1247
1267
|
|
|
1248
1268
|
# Create a run_id for this specific run
|
|
1249
1269
|
run_id = str(uuid4())
|
|
@@ -1302,9 +1322,6 @@ class Agent:
|
|
|
1302
1322
|
)
|
|
1303
1323
|
add_history = add_history_to_context if add_history_to_context is not None else self.add_history_to_context
|
|
1304
1324
|
|
|
1305
|
-
# Extract workflow context from kwargs if present
|
|
1306
|
-
workflow_context = kwargs.pop("workflow_context", None)
|
|
1307
|
-
|
|
1308
1325
|
# Initialize Knowledge Filters
|
|
1309
1326
|
effective_filters = knowledge_filters
|
|
1310
1327
|
|
|
@@ -1380,7 +1397,6 @@ class Agent:
|
|
|
1380
1397
|
dependencies=run_dependencies,
|
|
1381
1398
|
response_format=response_format,
|
|
1382
1399
|
stream_intermediate_steps=stream_intermediate_steps,
|
|
1383
|
-
workflow_context=workflow_context,
|
|
1384
1400
|
yield_run_response=yield_run_response,
|
|
1385
1401
|
debug_mode=debug_mode,
|
|
1386
1402
|
**kwargs,
|
|
@@ -1457,8 +1473,9 @@ class Agent:
|
|
|
1457
1473
|
|
|
1458
1474
|
async def _arun(
|
|
1459
1475
|
self,
|
|
1476
|
+
input: Union[str, List, Dict, Message, BaseModel, List[Message]],
|
|
1460
1477
|
run_response: RunOutput,
|
|
1461
|
-
|
|
1478
|
+
session_id: str,
|
|
1462
1479
|
session_state: Optional[Dict[str, Any]] = None,
|
|
1463
1480
|
user_id: Optional[str] = None,
|
|
1464
1481
|
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
@@ -1474,27 +1491,42 @@ class Agent:
|
|
|
1474
1491
|
"""Run the Agent and yield the RunOutput.
|
|
1475
1492
|
|
|
1476
1493
|
Steps:
|
|
1477
|
-
1.
|
|
1478
|
-
2.
|
|
1479
|
-
3.
|
|
1480
|
-
4.
|
|
1481
|
-
5.
|
|
1482
|
-
6.
|
|
1483
|
-
7.
|
|
1484
|
-
8.
|
|
1485
|
-
9.
|
|
1486
|
-
10.
|
|
1487
|
-
11.
|
|
1488
|
-
12.
|
|
1494
|
+
1. Read or create session
|
|
1495
|
+
2. Update metadata and session state
|
|
1496
|
+
3. Resolve dependencies
|
|
1497
|
+
4. Execute pre-hooks
|
|
1498
|
+
5. Determine tools for model
|
|
1499
|
+
6. Prepare run messages
|
|
1500
|
+
7. Reason about the task if reasoning is enabled
|
|
1501
|
+
8. Generate a response from the Model (includes running function calls)
|
|
1502
|
+
9. Update the RunOutput with the model response
|
|
1503
|
+
10. Execute post-hooks
|
|
1504
|
+
11. Add RunOutput to Agent Session
|
|
1505
|
+
12. Update Agent Memory
|
|
1506
|
+
13. Scrub the stored run if needed
|
|
1507
|
+
14. Save session to storage
|
|
1489
1508
|
"""
|
|
1509
|
+
log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
|
|
1510
|
+
|
|
1490
1511
|
# Register run for cancellation tracking
|
|
1491
1512
|
register_run(run_response.run_id) # type: ignore
|
|
1492
1513
|
|
|
1493
|
-
# 1.
|
|
1514
|
+
# 1. Read or create session. Reads from the database if provided.
|
|
1515
|
+
if self._has_async_db():
|
|
1516
|
+
agent_session = await self._aread_or_create_session(session_id=session_id, user_id=user_id)
|
|
1517
|
+
else:
|
|
1518
|
+
agent_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
|
|
1519
|
+
|
|
1520
|
+
# 2. Update metadata and session state
|
|
1521
|
+
self._update_metadata(session=agent_session)
|
|
1522
|
+
if session_state is not None:
|
|
1523
|
+
session_state = self._load_session_state(session=agent_session, session_state=session_state)
|
|
1524
|
+
|
|
1525
|
+
# 3. Resolve dependencies
|
|
1494
1526
|
if dependencies is not None:
|
|
1495
|
-
await self._aresolve_run_dependencies(dependencies)
|
|
1527
|
+
await self._aresolve_run_dependencies(dependencies=dependencies)
|
|
1496
1528
|
|
|
1497
|
-
#
|
|
1529
|
+
# 4. Execute pre-hooks
|
|
1498
1530
|
run_input = cast(RunInput, run_response.input)
|
|
1499
1531
|
self.model = cast(Model, self.model)
|
|
1500
1532
|
if self.pre_hooks is not None:
|
|
@@ -1503,7 +1535,7 @@ class Agent:
|
|
|
1503
1535
|
hooks=self.pre_hooks, # type: ignore
|
|
1504
1536
|
run_response=run_response,
|
|
1505
1537
|
run_input=run_input,
|
|
1506
|
-
session=
|
|
1538
|
+
session=agent_session,
|
|
1507
1539
|
user_id=user_id,
|
|
1508
1540
|
debug_mode=debug_mode,
|
|
1509
1541
|
**kwargs,
|
|
@@ -1512,10 +1544,12 @@ class Agent:
|
|
|
1512
1544
|
async for _ in pre_hook_iterator:
|
|
1513
1545
|
pass
|
|
1514
1546
|
|
|
1515
|
-
|
|
1547
|
+
# 5. Determine tools for model
|
|
1548
|
+
self.model = cast(Model, self.model)
|
|
1549
|
+
await self._adetermine_tools_for_model(
|
|
1516
1550
|
model=self.model,
|
|
1517
1551
|
run_response=run_response,
|
|
1518
|
-
session=
|
|
1552
|
+
session=agent_session,
|
|
1519
1553
|
session_state=session_state,
|
|
1520
1554
|
dependencies=dependencies,
|
|
1521
1555
|
user_id=user_id,
|
|
@@ -1523,11 +1557,11 @@ class Agent:
|
|
|
1523
1557
|
knowledge_filters=knowledge_filters,
|
|
1524
1558
|
)
|
|
1525
1559
|
|
|
1526
|
-
#
|
|
1527
|
-
run_messages: RunMessages = self.
|
|
1560
|
+
# 6. Prepare run messages
|
|
1561
|
+
run_messages: RunMessages = await self._aget_run_messages(
|
|
1528
1562
|
run_response=run_response,
|
|
1529
1563
|
input=run_input.input_content,
|
|
1530
|
-
session=
|
|
1564
|
+
session=agent_session,
|
|
1531
1565
|
session_state=session_state,
|
|
1532
1566
|
user_id=user_id,
|
|
1533
1567
|
audio=run_input.audios,
|
|
@@ -1545,113 +1579,136 @@ class Agent:
|
|
|
1545
1579
|
if len(run_messages.messages) == 0:
|
|
1546
1580
|
log_error("No messages to be sent to the model.")
|
|
1547
1581
|
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
# Check for cancellation before model call
|
|
1554
|
-
raise_if_cancelled(run_response.run_id) # type: ignore
|
|
1582
|
+
try:
|
|
1583
|
+
# 7. Reason about the task if reasoning is enabled
|
|
1584
|
+
await self._ahandle_reasoning(run_response=run_response, run_messages=run_messages)
|
|
1585
|
+
raise_if_cancelled(run_response.run_id) # type: ignore
|
|
1555
1586
|
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1587
|
+
# 8. Generate a response from the Model (includes running function calls)
|
|
1588
|
+
model_response: ModelResponse = await self.model.aresponse(
|
|
1589
|
+
messages=run_messages.messages,
|
|
1590
|
+
tools=self._tools_for_model,
|
|
1591
|
+
functions=self._functions_for_model,
|
|
1592
|
+
tool_choice=self.tool_choice,
|
|
1593
|
+
tool_call_limit=self.tool_call_limit,
|
|
1594
|
+
response_format=response_format,
|
|
1595
|
+
send_media_to_model=self.send_media_to_model,
|
|
1596
|
+
)
|
|
1597
|
+
raise_if_cancelled(run_response.run_id) # type: ignore
|
|
1566
1598
|
|
|
1567
|
-
|
|
1568
|
-
|
|
1599
|
+
# If an output model is provided, generate output using the output model
|
|
1600
|
+
await self._agenerate_response_with_output_model(model_response=model_response, run_messages=run_messages)
|
|
1601
|
+
# If a parser model is provided, structure the response separately
|
|
1602
|
+
await self._aparse_response_with_parser_model(model_response=model_response, run_messages=run_messages)
|
|
1569
1603
|
|
|
1570
|
-
|
|
1571
|
-
|
|
1604
|
+
# 9. Update the RunOutput with the model response
|
|
1605
|
+
self._update_run_response(
|
|
1606
|
+
model_response=model_response, run_response=run_response, run_messages=run_messages
|
|
1607
|
+
)
|
|
1572
1608
|
|
|
1573
|
-
|
|
1574
|
-
|
|
1609
|
+
# Optional: Store media
|
|
1610
|
+
if self.store_media:
|
|
1611
|
+
self._store_media(run_response, model_response)
|
|
1575
1612
|
|
|
1576
|
-
|
|
1577
|
-
|
|
1613
|
+
# Break out of the run function if a tool call is paused
|
|
1614
|
+
if any(tool_call.is_paused for tool_call in run_response.tools or []):
|
|
1615
|
+
return self._handle_agent_run_paused(
|
|
1616
|
+
run_response=run_response, run_messages=run_messages, session=agent_session, user_id=user_id
|
|
1617
|
+
)
|
|
1618
|
+
raise_if_cancelled(run_response.run_id) # type: ignore
|
|
1578
1619
|
|
|
1579
|
-
|
|
1580
|
-
self.
|
|
1581
|
-
else:
|
|
1582
|
-
self._scrub_media_from_run_output(run_response)
|
|
1620
|
+
# 10. Calculate session metrics
|
|
1621
|
+
self._update_session_metrics(session=agent_session, run_response=run_response)
|
|
1583
1622
|
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
return self._handle_agent_run_paused(
|
|
1587
|
-
run_response=run_response, run_messages=run_messages, session=session, user_id=user_id
|
|
1588
|
-
)
|
|
1623
|
+
# Set the run status to completed
|
|
1624
|
+
run_response.status = RunStatus.completed
|
|
1589
1625
|
|
|
1590
|
-
|
|
1626
|
+
# Convert the response to the structured format if needed
|
|
1627
|
+
self._convert_response_to_structured_format(run_response)
|
|
1591
1628
|
|
|
1592
|
-
|
|
1593
|
-
|
|
1629
|
+
# Set the run duration
|
|
1630
|
+
if run_response.metrics:
|
|
1631
|
+
run_response.metrics.stop_timer()
|
|
1594
1632
|
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1633
|
+
# 10. Execute post-hooks (after output is generated but before response is returned)
|
|
1634
|
+
if self.post_hooks is not None:
|
|
1635
|
+
await self._aexecute_post_hooks(
|
|
1636
|
+
hooks=self.post_hooks, # type: ignore
|
|
1637
|
+
run_output=run_response,
|
|
1638
|
+
session=agent_session,
|
|
1639
|
+
user_id=user_id,
|
|
1640
|
+
debug_mode=debug_mode,
|
|
1641
|
+
**kwargs,
|
|
1642
|
+
)
|
|
1598
1643
|
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
session=session,
|
|
1644
|
+
# Optional: Save output to file if save_response_to_file is set
|
|
1645
|
+
self.save_run_response_to_file(
|
|
1646
|
+
run_response=run_response,
|
|
1647
|
+
input=run_messages.user_message,
|
|
1648
|
+
session_id=agent_session.session_id,
|
|
1605
1649
|
user_id=user_id,
|
|
1606
|
-
debug_mode=debug_mode,
|
|
1607
|
-
**kwargs,
|
|
1608
1650
|
)
|
|
1609
1651
|
|
|
1610
|
-
|
|
1611
|
-
|
|
1652
|
+
# 11. Add RunOutput to Agent Session
|
|
1653
|
+
agent_session.upsert_run(run=run_response)
|
|
1612
1654
|
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1655
|
+
# 12. Update Agent Memory
|
|
1656
|
+
async for _ in self._amake_memories_and_summaries(
|
|
1657
|
+
run_response=run_response, run_messages=run_messages, session=agent_session, user_id=user_id
|
|
1658
|
+
):
|
|
1659
|
+
pass
|
|
1617
1660
|
|
|
1618
|
-
|
|
1619
|
-
|
|
1661
|
+
# 13. Scrub the stored run based on storage flags
|
|
1662
|
+
if self._scrub_run_output_for_storage(run_response):
|
|
1663
|
+
agent_session.upsert_run(run=run_response)
|
|
1620
1664
|
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1665
|
+
# 14. Save session to storage
|
|
1666
|
+
if self._has_async_db():
|
|
1667
|
+
await self.asave_session(session=agent_session)
|
|
1668
|
+
else:
|
|
1669
|
+
self.save_session(session=agent_session)
|
|
1626
1670
|
|
|
1627
|
-
|
|
1628
|
-
|
|
1671
|
+
# Log Agent Telemetry
|
|
1672
|
+
await self._alog_agent_telemetry(session_id=agent_session.session_id, run_id=run_response.run_id)
|
|
1629
1673
|
|
|
1630
|
-
|
|
1631
|
-
await self._alog_agent_telemetry(session_id=session.session_id, run_id=run_response.run_id)
|
|
1674
|
+
log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
|
|
1632
1675
|
|
|
1633
|
-
|
|
1676
|
+
return run_response
|
|
1634
1677
|
|
|
1635
|
-
|
|
1636
|
-
|
|
1678
|
+
except RunCancelledException as e:
|
|
1679
|
+
# Handle run cancellation
|
|
1680
|
+
log_info(f"Run {run_response.run_id} was cancelled")
|
|
1681
|
+
run_response.content = str(e)
|
|
1682
|
+
run_response.status = RunStatus.cancelled
|
|
1637
1683
|
|
|
1638
|
-
|
|
1684
|
+
# Update the Agent Session before exiting
|
|
1685
|
+
agent_session.upsert_run(run=run_response)
|
|
1686
|
+
if self._has_async_db():
|
|
1687
|
+
await self.asave_session(session=agent_session)
|
|
1688
|
+
else:
|
|
1689
|
+
self.save_session(session=agent_session)
|
|
1690
|
+
|
|
1691
|
+
return run_response
|
|
1692
|
+
|
|
1693
|
+
finally:
|
|
1694
|
+
# Always clean up the run tracking
|
|
1695
|
+
cleanup_run(run_response.run_id) # type: ignore
|
|
1639
1696
|
|
|
1640
1697
|
async def _arun_stream(
|
|
1641
1698
|
self,
|
|
1699
|
+
input: Union[str, List, Dict, Message, BaseModel, List[Message]],
|
|
1642
1700
|
run_response: RunOutput,
|
|
1643
|
-
|
|
1701
|
+
session_id: str,
|
|
1644
1702
|
session_state: Optional[Dict[str, Any]] = None,
|
|
1645
1703
|
user_id: Optional[str] = None,
|
|
1646
1704
|
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
1705
|
+
dependencies: Optional[Dict[str, Any]] = None,
|
|
1647
1706
|
add_history_to_context: Optional[bool] = None,
|
|
1648
1707
|
add_dependencies_to_context: Optional[bool] = None,
|
|
1649
1708
|
add_session_state_to_context: Optional[bool] = None,
|
|
1650
1709
|
metadata: Optional[Dict[str, Any]] = None,
|
|
1651
|
-
dependencies: Optional[Dict[str, Any]] = None,
|
|
1652
1710
|
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
1653
1711
|
stream_intermediate_steps: bool = False,
|
|
1654
|
-
workflow_context: Optional[Dict] = None,
|
|
1655
1712
|
yield_run_response: Optional[bool] = None,
|
|
1656
1713
|
debug_mode: Optional[bool] = None,
|
|
1657
1714
|
**kwargs: Any,
|
|
@@ -1659,32 +1716,51 @@ class Agent:
|
|
|
1659
1716
|
"""Run the Agent and yield the RunOutput.
|
|
1660
1717
|
|
|
1661
1718
|
Steps:
|
|
1662
|
-
1.
|
|
1663
|
-
2.
|
|
1664
|
-
3.
|
|
1665
|
-
4.
|
|
1666
|
-
5.
|
|
1667
|
-
6.
|
|
1668
|
-
7.
|
|
1669
|
-
8.
|
|
1670
|
-
9.
|
|
1719
|
+
1. Read or create session
|
|
1720
|
+
2. Update metadata and session state
|
|
1721
|
+
3. Resolve dependencies
|
|
1722
|
+
4. Execute pre-hooks
|
|
1723
|
+
5. Determine tools for model
|
|
1724
|
+
6. Prepare run messages
|
|
1725
|
+
7. Reason about the task if reasoning is enabled
|
|
1726
|
+
8. Generate a response from the Model (includes running function calls)
|
|
1727
|
+
9. Calculate session metrics
|
|
1728
|
+
10. Add RunOutput to Agent Session
|
|
1729
|
+
11. Update Agent Memory
|
|
1730
|
+
12. Create the run completed event
|
|
1731
|
+
13. Save session to storage
|
|
1671
1732
|
"""
|
|
1733
|
+
log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
|
|
1734
|
+
|
|
1735
|
+
# Start the Run by yielding a RunStarted event
|
|
1736
|
+
if stream_intermediate_steps:
|
|
1737
|
+
yield self._handle_event(create_run_started_event(run_response), run_response)
|
|
1738
|
+
|
|
1739
|
+
# 1. Read or create session. Reads from the database if provided.
|
|
1740
|
+
if self._has_async_db():
|
|
1741
|
+
agent_session = await self._aread_or_create_session(session_id=session_id, user_id=user_id)
|
|
1742
|
+
else:
|
|
1743
|
+
agent_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
|
|
1744
|
+
|
|
1745
|
+
# 2. Update metadata and session state
|
|
1746
|
+
self._update_metadata(session=agent_session)
|
|
1747
|
+
if session_state is not None:
|
|
1748
|
+
session_state = self._load_session_state(session=agent_session, session_state=session_state)
|
|
1672
1749
|
|
|
1673
|
-
#
|
|
1750
|
+
# 3. Resolve dependencies
|
|
1674
1751
|
if dependencies is not None:
|
|
1675
1752
|
await self._aresolve_run_dependencies(dependencies=dependencies)
|
|
1676
1753
|
|
|
1677
|
-
#
|
|
1754
|
+
# 4. Execute pre-hooks
|
|
1678
1755
|
run_input = cast(RunInput, run_response.input)
|
|
1679
1756
|
self.model = cast(Model, self.model)
|
|
1680
|
-
|
|
1681
1757
|
if self.pre_hooks is not None:
|
|
1682
1758
|
# Can modify the run input
|
|
1683
1759
|
pre_hook_iterator = self._aexecute_pre_hooks(
|
|
1684
1760
|
hooks=self.pre_hooks, # type: ignore
|
|
1685
1761
|
run_response=run_response,
|
|
1686
1762
|
run_input=run_input,
|
|
1687
|
-
session=
|
|
1763
|
+
session=agent_session,
|
|
1688
1764
|
user_id=user_id,
|
|
1689
1765
|
debug_mode=debug_mode,
|
|
1690
1766
|
**kwargs,
|
|
@@ -1692,22 +1768,24 @@ class Agent:
|
|
|
1692
1768
|
async for event in pre_hook_iterator:
|
|
1693
1769
|
yield event
|
|
1694
1770
|
|
|
1771
|
+
# 5. Determine tools for model
|
|
1772
|
+
self.model = cast(Model, self.model)
|
|
1695
1773
|
self._determine_tools_for_model(
|
|
1696
1774
|
model=self.model,
|
|
1697
1775
|
run_response=run_response,
|
|
1698
|
-
session=
|
|
1776
|
+
session=agent_session,
|
|
1699
1777
|
session_state=session_state,
|
|
1700
|
-
dependencies=dependencies,
|
|
1701
1778
|
user_id=user_id,
|
|
1702
1779
|
async_mode=True,
|
|
1703
1780
|
knowledge_filters=knowledge_filters,
|
|
1781
|
+
dependencies=dependencies,
|
|
1704
1782
|
)
|
|
1705
1783
|
|
|
1706
|
-
#
|
|
1707
|
-
run_messages: RunMessages = self.
|
|
1784
|
+
# 6. Prepare run messages
|
|
1785
|
+
run_messages: RunMessages = await self._aget_run_messages(
|
|
1708
1786
|
run_response=run_response,
|
|
1709
1787
|
input=run_input.input_content,
|
|
1710
|
-
session=
|
|
1788
|
+
session=agent_session,
|
|
1711
1789
|
session_state=session_state,
|
|
1712
1790
|
user_id=user_id,
|
|
1713
1791
|
audio=run_input.audios,
|
|
@@ -1722,34 +1800,27 @@ class Agent:
|
|
|
1722
1800
|
metadata=metadata,
|
|
1723
1801
|
**kwargs,
|
|
1724
1802
|
)
|
|
1725
|
-
|
|
1726
|
-
|
|
1803
|
+
if len(run_messages.messages) == 0:
|
|
1804
|
+
log_error("No messages to be sent to the model.")
|
|
1727
1805
|
|
|
1728
1806
|
# Register run for cancellation tracking
|
|
1729
1807
|
register_run(run_response.run_id) # type: ignore
|
|
1730
1808
|
|
|
1731
1809
|
try:
|
|
1732
|
-
#
|
|
1733
|
-
if stream_intermediate_steps:
|
|
1734
|
-
yield self._handle_event(create_run_started_event(run_response), run_response, workflow_context)
|
|
1735
|
-
|
|
1736
|
-
# 4. Reason about the task if reasoning is enabled
|
|
1810
|
+
# 7. Reason about the task if reasoning is enabled
|
|
1737
1811
|
async for item in self._ahandle_reasoning_stream(run_response=run_response, run_messages=run_messages):
|
|
1738
1812
|
raise_if_cancelled(run_response.run_id) # type: ignore
|
|
1739
1813
|
yield item
|
|
1740
|
-
|
|
1741
|
-
# Check for cancellation before model processing
|
|
1742
1814
|
raise_if_cancelled(run_response.run_id) # type: ignore
|
|
1743
1815
|
|
|
1744
|
-
#
|
|
1816
|
+
# 8. Generate a response from the Model
|
|
1745
1817
|
if self.output_model is None:
|
|
1746
1818
|
async for event in self._ahandle_model_response_stream(
|
|
1747
|
-
session=
|
|
1819
|
+
session=agent_session,
|
|
1748
1820
|
run_response=run_response,
|
|
1749
1821
|
run_messages=run_messages,
|
|
1750
1822
|
response_format=response_format,
|
|
1751
1823
|
stream_intermediate_steps=stream_intermediate_steps,
|
|
1752
|
-
workflow_context=workflow_context,
|
|
1753
1824
|
):
|
|
1754
1825
|
raise_if_cancelled(run_response.run_id) # type: ignore
|
|
1755
1826
|
yield event
|
|
@@ -1760,12 +1831,11 @@ class Agent:
|
|
|
1760
1831
|
) # type: ignore
|
|
1761
1832
|
|
|
1762
1833
|
async for event in self._ahandle_model_response_stream(
|
|
1763
|
-
session=
|
|
1834
|
+
session=agent_session,
|
|
1764
1835
|
run_response=run_response,
|
|
1765
1836
|
run_messages=run_messages,
|
|
1766
1837
|
response_format=response_format,
|
|
1767
1838
|
stream_intermediate_steps=stream_intermediate_steps,
|
|
1768
|
-
workflow_context=workflow_context,
|
|
1769
1839
|
):
|
|
1770
1840
|
raise_if_cancelled(run_response.run_id) # type: ignore
|
|
1771
1841
|
if isinstance(event, RunContentEvent):
|
|
@@ -1779,10 +1849,9 @@ class Agent:
|
|
|
1779
1849
|
|
|
1780
1850
|
# If an output model is provided, generate output using the output model
|
|
1781
1851
|
async for event in self._agenerate_response_with_output_model_stream(
|
|
1782
|
-
session=
|
|
1852
|
+
session=agent_session,
|
|
1783
1853
|
run_response=run_response,
|
|
1784
1854
|
run_messages=run_messages,
|
|
1785
|
-
workflow_context=workflow_context,
|
|
1786
1855
|
stream_intermediate_steps=stream_intermediate_steps,
|
|
1787
1856
|
):
|
|
1788
1857
|
raise_if_cancelled(run_response.run_id) # type: ignore
|
|
@@ -1793,50 +1862,59 @@ class Agent:
|
|
|
1793
1862
|
|
|
1794
1863
|
# If a parser model is provided, structure the response separately
|
|
1795
1864
|
async for event in self._aparse_response_with_parser_model_stream(
|
|
1796
|
-
session=
|
|
1865
|
+
session=agent_session, run_response=run_response, stream_intermediate_steps=stream_intermediate_steps
|
|
1797
1866
|
):
|
|
1798
1867
|
yield event
|
|
1799
1868
|
|
|
1800
|
-
#
|
|
1869
|
+
# Break out of the run function if a tool call is paused
|
|
1801
1870
|
if any(tool_call.is_paused for tool_call in run_response.tools or []):
|
|
1802
1871
|
for item in self._handle_agent_run_paused_stream(
|
|
1803
|
-
run_response=run_response, run_messages=run_messages, session=
|
|
1872
|
+
run_response=run_response, run_messages=run_messages, session=agent_session, user_id=user_id
|
|
1804
1873
|
):
|
|
1805
1874
|
yield item
|
|
1806
1875
|
return
|
|
1807
1876
|
|
|
1877
|
+
# Set the run status to completed
|
|
1808
1878
|
run_response.status = RunStatus.completed
|
|
1809
1879
|
|
|
1810
1880
|
# Set the run duration
|
|
1811
1881
|
if run_response.metrics:
|
|
1812
1882
|
run_response.metrics.stop_timer()
|
|
1813
1883
|
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
)
|
|
1817
|
-
|
|
1818
|
-
# 6. Calculate session metrics
|
|
1819
|
-
self._update_session_metrics(session=session, run_response=run_response)
|
|
1884
|
+
# 9. Calculate session metrics
|
|
1885
|
+
self._update_session_metrics(session=agent_session, run_response=run_response)
|
|
1820
1886
|
|
|
1821
1887
|
# Optional: Save output to file if save_response_to_file is set
|
|
1822
1888
|
self.save_run_response_to_file(
|
|
1823
1889
|
run_response=run_response,
|
|
1824
1890
|
input=run_messages.user_message,
|
|
1825
|
-
session_id=
|
|
1891
|
+
session_id=agent_session.session_id,
|
|
1826
1892
|
user_id=user_id,
|
|
1827
1893
|
)
|
|
1828
1894
|
|
|
1829
|
-
#
|
|
1830
|
-
|
|
1895
|
+
# 10. Add RunOutput to Agent Session
|
|
1896
|
+
agent_session.upsert_run(run=run_response)
|
|
1831
1897
|
|
|
1832
|
-
#
|
|
1898
|
+
# 11. Update Agent Memory
|
|
1833
1899
|
async for event in self._amake_memories_and_summaries(
|
|
1834
|
-
run_response=run_response, run_messages=run_messages, session=
|
|
1900
|
+
run_response=run_response, run_messages=run_messages, session=agent_session, user_id=user_id
|
|
1835
1901
|
):
|
|
1836
1902
|
yield event
|
|
1837
1903
|
|
|
1838
|
-
#
|
|
1839
|
-
self.
|
|
1904
|
+
# 12. Create the run completed event
|
|
1905
|
+
completed_event = self._handle_event(
|
|
1906
|
+
create_run_completed_event(from_run_response=run_response), run_response
|
|
1907
|
+
)
|
|
1908
|
+
|
|
1909
|
+
# 13. Scrub the stored run based on storage flags
|
|
1910
|
+
if self._scrub_run_output_for_storage(run_response):
|
|
1911
|
+
agent_session.upsert_run(run=run_response)
|
|
1912
|
+
|
|
1913
|
+
# 14. Save session to storage
|
|
1914
|
+
if self._has_async_db():
|
|
1915
|
+
await self.asave_session(session=agent_session)
|
|
1916
|
+
else:
|
|
1917
|
+
self.save_session(session=agent_session)
|
|
1840
1918
|
|
|
1841
1919
|
if stream_intermediate_steps:
|
|
1842
1920
|
yield completed_event
|
|
@@ -1845,7 +1923,7 @@ class Agent:
|
|
|
1845
1923
|
yield run_response
|
|
1846
1924
|
|
|
1847
1925
|
# Log Agent Telemetry
|
|
1848
|
-
await self._alog_agent_telemetry(session_id=
|
|
1926
|
+
await self._alog_agent_telemetry(session_id=agent_session.session_id, run_id=run_response.run_id)
|
|
1849
1927
|
|
|
1850
1928
|
log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
|
|
1851
1929
|
|
|
@@ -1859,12 +1937,14 @@ class Agent:
|
|
|
1859
1937
|
yield self._handle_event(
|
|
1860
1938
|
create_run_cancelled_event(from_run_response=run_response, reason=str(e)),
|
|
1861
1939
|
run_response,
|
|
1862
|
-
workflow_context,
|
|
1863
1940
|
)
|
|
1864
1941
|
|
|
1865
1942
|
# Add the RunOutput to Agent Session even when cancelled
|
|
1866
|
-
|
|
1867
|
-
self.
|
|
1943
|
+
agent_session.upsert_run(run=run_response)
|
|
1944
|
+
if self._has_async_db():
|
|
1945
|
+
await self.asave_session(session=agent_session)
|
|
1946
|
+
else:
|
|
1947
|
+
self.save_session(session=agent_session)
|
|
1868
1948
|
finally:
|
|
1869
1949
|
# Always clean up the run tracking
|
|
1870
1950
|
cleanup_run(run_response.run_id) # type: ignore
|
|
@@ -1948,7 +2028,7 @@ class Agent:
|
|
|
1948
2028
|
# Create a run_id for this specific run
|
|
1949
2029
|
run_id = str(uuid4())
|
|
1950
2030
|
|
|
1951
|
-
# Validate input against input_schema if provided
|
|
2031
|
+
# 2. Validate input against input_schema if provided
|
|
1952
2032
|
validated_input = self._validate_input(input)
|
|
1953
2033
|
|
|
1954
2034
|
# Normalise hook & guardails
|
|
@@ -1959,6 +2039,7 @@ class Agent:
|
|
|
1959
2039
|
self.post_hooks = normalize_hooks(self.post_hooks, async_mode=True)
|
|
1960
2040
|
self._hooks_normalised = True
|
|
1961
2041
|
|
|
2042
|
+
# Initialize session
|
|
1962
2043
|
session_id, user_id, session_state = self._initialize_session(
|
|
1963
2044
|
run_id=run_id, session_id=session_id, user_id=user_id, session_state=session_state
|
|
1964
2045
|
)
|
|
@@ -1970,25 +2051,8 @@ class Agent:
|
|
|
1970
2051
|
images=images, videos=videos, audios=audio, files=files
|
|
1971
2052
|
)
|
|
1972
2053
|
|
|
1973
|
-
#
|
|
1974
|
-
run_input = RunInput(
|
|
1975
|
-
input_content=validated_input,
|
|
1976
|
-
images=image_artifacts,
|
|
1977
|
-
videos=video_artifacts,
|
|
1978
|
-
audios=audio_artifacts,
|
|
1979
|
-
files=file_artifacts,
|
|
1980
|
-
)
|
|
1981
|
-
|
|
1982
|
-
# Read existing session from storage
|
|
1983
|
-
agent_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
|
|
1984
|
-
self._update_metadata(session=agent_session)
|
|
1985
|
-
|
|
1986
|
-
# Update session state from DB
|
|
1987
|
-
session_state = self._load_session_state(session=agent_session, session_state=session_state)
|
|
1988
|
-
|
|
1989
|
-
# Determine run dependencies
|
|
2054
|
+
# Resolve variables
|
|
1990
2055
|
run_dependencies = dependencies if dependencies is not None else self.dependencies
|
|
1991
|
-
|
|
1992
2056
|
add_dependencies = (
|
|
1993
2057
|
add_dependencies_to_context if add_dependencies_to_context is not None else self.add_dependencies_to_context
|
|
1994
2058
|
)
|
|
@@ -1999,13 +2063,14 @@ class Agent:
|
|
|
1999
2063
|
)
|
|
2000
2064
|
add_history = add_history_to_context if add_history_to_context is not None else self.add_history_to_context
|
|
2001
2065
|
|
|
2002
|
-
#
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2066
|
+
# Create RunInput to capture the original user input
|
|
2067
|
+
run_input = RunInput(
|
|
2068
|
+
input_content=validated_input,
|
|
2069
|
+
images=image_artifacts,
|
|
2070
|
+
videos=video_artifacts,
|
|
2071
|
+
audios=audio_artifacts,
|
|
2072
|
+
files=files,
|
|
2073
|
+
)
|
|
2009
2074
|
|
|
2010
2075
|
# Use stream override value when necessary
|
|
2011
2076
|
if stream is None:
|
|
@@ -2027,6 +2092,11 @@ class Agent:
|
|
|
2027
2092
|
response_format = self._get_response_format() if self.parser_model is None else None
|
|
2028
2093
|
self.model = cast(Model, self.model)
|
|
2029
2094
|
|
|
2095
|
+
# Get knowledge filters
|
|
2096
|
+
effective_filters = knowledge_filters
|
|
2097
|
+
if self.knowledge_filters or knowledge_filters:
|
|
2098
|
+
effective_filters = self._get_effective_filters(knowledge_filters)
|
|
2099
|
+
|
|
2030
2100
|
# Merge agent metadata with run metadata
|
|
2031
2101
|
if self.metadata is not None:
|
|
2032
2102
|
if metadata is None:
|
|
@@ -2063,39 +2133,37 @@ class Agent:
|
|
|
2063
2133
|
# Pass the new run_response to _arun
|
|
2064
2134
|
if stream:
|
|
2065
2135
|
return self._arun_stream( # type: ignore
|
|
2136
|
+
input=validated_input,
|
|
2066
2137
|
run_response=run_response,
|
|
2067
|
-
session=agent_session,
|
|
2068
2138
|
user_id=user_id,
|
|
2139
|
+
response_format=response_format,
|
|
2140
|
+
stream_intermediate_steps=stream_intermediate_steps,
|
|
2141
|
+
yield_run_response=yield_run_response,
|
|
2142
|
+
dependencies=run_dependencies,
|
|
2143
|
+
session_id=session_id,
|
|
2069
2144
|
session_state=session_state,
|
|
2070
2145
|
knowledge_filters=effective_filters,
|
|
2071
2146
|
add_history_to_context=add_history,
|
|
2072
2147
|
add_dependencies_to_context=add_dependencies,
|
|
2073
2148
|
add_session_state_to_context=add_session_state,
|
|
2074
2149
|
metadata=metadata,
|
|
2075
|
-
response_format=response_format,
|
|
2076
|
-
stream_intermediate_steps=stream_intermediate_steps,
|
|
2077
|
-
workflow_context=workflow_context,
|
|
2078
|
-
yield_run_response=yield_run_response,
|
|
2079
|
-
dependencies=run_dependencies,
|
|
2080
2150
|
debug_mode=debug_mode,
|
|
2081
2151
|
**kwargs,
|
|
2082
2152
|
) # type: ignore[assignment]
|
|
2083
2153
|
else:
|
|
2084
2154
|
return self._arun( # type: ignore
|
|
2155
|
+
input=validated_input,
|
|
2085
2156
|
run_response=run_response,
|
|
2086
2157
|
user_id=user_id,
|
|
2087
|
-
|
|
2158
|
+
response_format=response_format,
|
|
2159
|
+
dependencies=run_dependencies,
|
|
2160
|
+
session_id=session_id,
|
|
2088
2161
|
session_state=session_state,
|
|
2089
|
-
knowledge_filters=
|
|
2162
|
+
knowledge_filters=effective_filters,
|
|
2090
2163
|
add_history_to_context=add_history,
|
|
2091
2164
|
add_dependencies_to_context=add_dependencies,
|
|
2092
2165
|
add_session_state_to_context=add_session_state,
|
|
2093
2166
|
metadata=metadata,
|
|
2094
|
-
response_format=response_format,
|
|
2095
|
-
stream_intermediate_steps=stream_intermediate_steps,
|
|
2096
|
-
workflow_context=workflow_context,
|
|
2097
|
-
yield_run_response=yield_run_response,
|
|
2098
|
-
dependencies=run_dependencies,
|
|
2099
2167
|
debug_mode=debug_mode,
|
|
2100
2168
|
**kwargs,
|
|
2101
2169
|
)
|
|
@@ -2116,17 +2184,6 @@ class Agent:
|
|
|
2116
2184
|
import time
|
|
2117
2185
|
|
|
2118
2186
|
time.sleep(delay)
|
|
2119
|
-
except RunCancelledException as e:
|
|
2120
|
-
# Handle run cancellation
|
|
2121
|
-
log_info(f"Run {run_response.run_id} was cancelled")
|
|
2122
|
-
run_response.content = str(e)
|
|
2123
|
-
run_response.status = RunStatus.cancelled
|
|
2124
|
-
|
|
2125
|
-
# Add the RunOutput to Agent Session even when cancelled
|
|
2126
|
-
agent_session.upsert_run(run=run_response)
|
|
2127
|
-
self.save_session(session=agent_session)
|
|
2128
|
-
|
|
2129
|
-
return run_response
|
|
2130
2187
|
except KeyboardInterrupt:
|
|
2131
2188
|
run_response.content = "Operation cancelled by user"
|
|
2132
2189
|
run_response.status = RunStatus.cancelled
|
|
@@ -2223,6 +2280,9 @@ class Agent:
|
|
|
2223
2280
|
if run_response is None and (run_id is not None and (session_id is None and self.session_id is None)):
|
|
2224
2281
|
raise ValueError("Session ID is required to continue a run from a run_id.")
|
|
2225
2282
|
|
|
2283
|
+
if self._has_async_db():
|
|
2284
|
+
raise Exception("continue_run() is not supported with an async DB. Please use acontinue_arun() instead.")
|
|
2285
|
+
|
|
2226
2286
|
session_id = run_response.session_id if run_response else session_id
|
|
2227
2287
|
|
|
2228
2288
|
session_id, user_id, session_state = self._initialize_session(
|
|
@@ -2484,11 +2544,12 @@ class Agent:
|
|
|
2484
2544
|
Steps:
|
|
2485
2545
|
1. Handle any updated tools
|
|
2486
2546
|
2. Generate a response from the Model
|
|
2487
|
-
3.
|
|
2488
|
-
4.
|
|
2489
|
-
5.
|
|
2490
|
-
6.
|
|
2491
|
-
7.
|
|
2547
|
+
3. Calculate session metrics
|
|
2548
|
+
4. Save output to file if save_response_to_file is set
|
|
2549
|
+
5. Add the run to memory
|
|
2550
|
+
6. Update Agent Memory
|
|
2551
|
+
7. Create the run completed event
|
|
2552
|
+
8. Save session to storage
|
|
2492
2553
|
"""
|
|
2493
2554
|
|
|
2494
2555
|
if dependencies is not None:
|
|
@@ -2523,8 +2584,6 @@ class Agent:
|
|
|
2523
2584
|
|
|
2524
2585
|
run_response.status = RunStatus.completed
|
|
2525
2586
|
|
|
2526
|
-
completed_event = self._handle_event(create_run_completed_event(run_response), run_response)
|
|
2527
|
-
|
|
2528
2587
|
# Set the run duration
|
|
2529
2588
|
if run_response.metrics:
|
|
2530
2589
|
run_response.metrics.stop_timer()
|
|
@@ -2542,7 +2601,10 @@ class Agent:
|
|
|
2542
2601
|
run_response=run_response, run_messages=run_messages, session=session, user_id=user_id
|
|
2543
2602
|
)
|
|
2544
2603
|
|
|
2545
|
-
# 7.
|
|
2604
|
+
# 7. Create the run completed event
|
|
2605
|
+
completed_event = self._handle_event(create_run_completed_event(run_response), run_response)
|
|
2606
|
+
|
|
2607
|
+
# 8. Save session to storage
|
|
2546
2608
|
self.save_session(session=session)
|
|
2547
2609
|
|
|
2548
2610
|
if stream_intermediate_steps:
|
|
@@ -2601,6 +2663,7 @@ class Agent:
|
|
|
2601
2663
|
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
2602
2664
|
dependencies: Optional[Dict[str, Any]] = None,
|
|
2603
2665
|
debug_mode: Optional[bool] = None,
|
|
2666
|
+
yield_run_response: bool = False,
|
|
2604
2667
|
**kwargs,
|
|
2605
2668
|
) -> Union[RunOutput, AsyncIterator[Union[RunOutputEvent, RunOutput]]]:
|
|
2606
2669
|
"""Continue a previous run.
|
|
@@ -2633,21 +2696,8 @@ class Agent:
|
|
|
2633
2696
|
# Initialize the Agent
|
|
2634
2697
|
self.initialize_agent(debug_mode=debug_mode)
|
|
2635
2698
|
|
|
2636
|
-
# Read existing session from storage
|
|
2637
|
-
agent_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
|
|
2638
|
-
self._update_metadata(session=agent_session)
|
|
2639
|
-
|
|
2640
|
-
# Update session state from DB
|
|
2641
|
-
session_state = self._load_session_state(session=agent_session, session_state=session_state)
|
|
2642
|
-
|
|
2643
2699
|
run_dependencies = dependencies if dependencies is not None else self.dependencies
|
|
2644
2700
|
|
|
2645
|
-
effective_filters = knowledge_filters
|
|
2646
|
-
|
|
2647
|
-
# When filters are passed manually
|
|
2648
|
-
if self.knowledge_filters or knowledge_filters:
|
|
2649
|
-
effective_filters = self._get_effective_filters(knowledge_filters)
|
|
2650
|
-
|
|
2651
2701
|
# If no retries are set, use the agent's default retries
|
|
2652
2702
|
retries = retries if retries is not None else self.retries
|
|
2653
2703
|
|
|
@@ -2667,70 +2717,42 @@ class Agent:
|
|
|
2667
2717
|
self.stream = self.stream or stream
|
|
2668
2718
|
self.stream_intermediate_steps = self.stream_intermediate_steps or (stream_intermediate_steps and self.stream)
|
|
2669
2719
|
|
|
2670
|
-
#
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
elif run_id is not None:
|
|
2675
|
-
# The run is continued from a run_id. This requires the updated tools to be passed.
|
|
2676
|
-
if updated_tools is None:
|
|
2677
|
-
raise ValueError("Updated tools are required to continue a run from a run_id.")
|
|
2678
|
-
|
|
2679
|
-
runs = agent_session.runs
|
|
2680
|
-
run_response = next((r for r in runs if r.run_id == run_id), None) # type: ignore
|
|
2681
|
-
if run_response is None:
|
|
2682
|
-
raise RuntimeError(f"No runs found for run ID {run_id}")
|
|
2683
|
-
run_response.tools = updated_tools
|
|
2684
|
-
input = run_response.messages or []
|
|
2685
|
-
else:
|
|
2686
|
-
raise ValueError("Either run_response or run_id must be provided.")
|
|
2720
|
+
# Get knowledge filters
|
|
2721
|
+
effective_filters = knowledge_filters
|
|
2722
|
+
if self.knowledge_filters or knowledge_filters:
|
|
2723
|
+
effective_filters = self._get_effective_filters(knowledge_filters)
|
|
2687
2724
|
|
|
2688
2725
|
# Prepare arguments for the model
|
|
2689
2726
|
response_format = self._get_response_format()
|
|
2690
2727
|
self.model = cast(Model, self.model)
|
|
2691
2728
|
|
|
2692
|
-
self._determine_tools_for_model(
|
|
2693
|
-
model=self.model,
|
|
2694
|
-
run_response=run_response,
|
|
2695
|
-
session=agent_session,
|
|
2696
|
-
session_state=session_state,
|
|
2697
|
-
user_id=user_id,
|
|
2698
|
-
async_mode=True,
|
|
2699
|
-
knowledge_filters=effective_filters,
|
|
2700
|
-
)
|
|
2701
|
-
|
|
2702
2729
|
last_exception = None
|
|
2703
2730
|
num_attempts = retries + 1
|
|
2704
2731
|
for attempt in range(num_attempts):
|
|
2705
|
-
run_response = cast(RunOutput, run_response)
|
|
2706
|
-
|
|
2707
|
-
log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
|
|
2708
|
-
|
|
2709
|
-
# Prepare run messages
|
|
2710
|
-
run_messages: RunMessages = self._get_continue_run_messages(
|
|
2711
|
-
input=input,
|
|
2712
|
-
)
|
|
2713
|
-
|
|
2714
|
-
# Reset the run paused state
|
|
2715
|
-
run_response.status = RunStatus.running
|
|
2716
|
-
|
|
2717
2732
|
try:
|
|
2718
2733
|
if stream:
|
|
2719
2734
|
return self._acontinue_run_stream(
|
|
2720
2735
|
run_response=run_response,
|
|
2721
|
-
|
|
2736
|
+
updated_tools=updated_tools,
|
|
2737
|
+
knowledge_filters=effective_filters,
|
|
2738
|
+
session_state=session_state,
|
|
2739
|
+
run_id=run_id,
|
|
2722
2740
|
user_id=user_id,
|
|
2723
|
-
|
|
2741
|
+
session_id=session_id,
|
|
2724
2742
|
response_format=response_format,
|
|
2725
|
-
stream_intermediate_steps=stream_intermediate_steps,
|
|
2726
2743
|
dependencies=run_dependencies,
|
|
2744
|
+
stream_intermediate_steps=stream_intermediate_steps,
|
|
2745
|
+
yield_run_response=yield_run_response,
|
|
2727
2746
|
)
|
|
2728
2747
|
else:
|
|
2729
2748
|
return self._acontinue_run( # type: ignore
|
|
2749
|
+
session_id=session_id,
|
|
2730
2750
|
run_response=run_response,
|
|
2731
|
-
|
|
2751
|
+
updated_tools=updated_tools,
|
|
2752
|
+
knowledge_filters=effective_filters,
|
|
2753
|
+
session_state=session_state,
|
|
2754
|
+
run_id=run_id,
|
|
2732
2755
|
user_id=user_id,
|
|
2733
|
-
session=agent_session,
|
|
2734
2756
|
response_format=response_format,
|
|
2735
2757
|
dependencies=run_dependencies,
|
|
2736
2758
|
debug_mode=debug_mode,
|
|
@@ -2750,6 +2772,7 @@ class Agent:
|
|
|
2750
2772
|
|
|
2751
2773
|
time.sleep(delay)
|
|
2752
2774
|
except KeyboardInterrupt:
|
|
2775
|
+
run_response = cast(RunOutput, run_response)
|
|
2753
2776
|
if stream:
|
|
2754
2777
|
return async_generator_wrapper( # type: ignore
|
|
2755
2778
|
create_run_cancelled_event(run_response, "Operation cancelled by user")
|
|
@@ -2774,9 +2797,12 @@ class Agent:
|
|
|
2774
2797
|
|
|
2775
2798
|
async def _acontinue_run(
|
|
2776
2799
|
self,
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2800
|
+
session_id: str,
|
|
2801
|
+
run_response: Optional[RunOutput] = None,
|
|
2802
|
+
updated_tools: Optional[List[ToolExecution]] = None,
|
|
2803
|
+
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
2804
|
+
session_state: Optional[Dict[str, Any]] = None,
|
|
2805
|
+
run_id: Optional[str] = None,
|
|
2780
2806
|
user_id: Optional[str] = None,
|
|
2781
2807
|
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
2782
2808
|
dependencies: Optional[Dict[str, Any]] = None,
|
|
@@ -2786,173 +2812,408 @@ class Agent:
|
|
|
2786
2812
|
"""Continue a previous run.
|
|
2787
2813
|
|
|
2788
2814
|
Steps:
|
|
2789
|
-
1.
|
|
2790
|
-
2.
|
|
2791
|
-
3.
|
|
2792
|
-
4.
|
|
2793
|
-
5.
|
|
2794
|
-
6.
|
|
2795
|
-
7.
|
|
2815
|
+
1. Read existing session from db
|
|
2816
|
+
2. Resolve dependencies
|
|
2817
|
+
3. Update metadata and session state
|
|
2818
|
+
4. Prepare run response
|
|
2819
|
+
5. Determine tools for model
|
|
2820
|
+
6. Prepare run messages
|
|
2821
|
+
7. Handle the updated tools
|
|
2822
|
+
8. Get model response
|
|
2823
|
+
9. Update the RunOutput with the model response
|
|
2824
|
+
10. Calculate session metrics
|
|
2825
|
+
11. Execute post-hooks
|
|
2826
|
+
12. Update Agent Memory
|
|
2827
|
+
13. Save session to storage
|
|
2796
2828
|
"""
|
|
2797
|
-
|
|
2829
|
+
log_debug(f"Agent Run Continue: {run_response.run_id}", center=True) # type: ignore
|
|
2830
|
+
|
|
2831
|
+
# 1. Read existing session from db
|
|
2832
|
+
if self._has_async_db():
|
|
2833
|
+
agent_session = await self._aread_or_create_session(session_id=session_id, user_id=user_id)
|
|
2834
|
+
else:
|
|
2835
|
+
agent_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
|
|
2836
|
+
|
|
2837
|
+
# 2. Resolve dependencies
|
|
2798
2838
|
if dependencies is not None:
|
|
2799
|
-
await self._aresolve_run_dependencies(dependencies)
|
|
2839
|
+
await self._aresolve_run_dependencies(dependencies=dependencies)
|
|
2800
2840
|
|
|
2801
|
-
|
|
2841
|
+
# 3. Update metadata and session state
|
|
2842
|
+
self._update_metadata(session=agent_session)
|
|
2843
|
+
if session_state is not None:
|
|
2844
|
+
session_state = self._load_session_state(session=agent_session, session_state=session_state)
|
|
2802
2845
|
|
|
2803
|
-
#
|
|
2804
|
-
|
|
2846
|
+
# 4. Prepare run response
|
|
2847
|
+
if run_response is not None:
|
|
2848
|
+
# The run is continued from a provided run_response. This contains the updated tools.
|
|
2849
|
+
input = run_response.messages or []
|
|
2850
|
+
elif run_id is not None:
|
|
2851
|
+
# The run is continued from a run_id. This requires the updated tools to be passed.
|
|
2852
|
+
if updated_tools is None:
|
|
2853
|
+
raise ValueError("Updated tools are required to continue a run from a run_id.")
|
|
2805
2854
|
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
tools=
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2855
|
+
runs = agent_session.runs
|
|
2856
|
+
run_response = next((r for r in runs if r.run_id == run_id), None) # type: ignore
|
|
2857
|
+
if run_response is None:
|
|
2858
|
+
raise RuntimeError(f"No runs found for run ID {run_id}")
|
|
2859
|
+
run_response.tools = updated_tools
|
|
2860
|
+
input = run_response.messages or []
|
|
2861
|
+
else:
|
|
2862
|
+
raise ValueError("Either run_response or run_id must be provided.")
|
|
2863
|
+
|
|
2864
|
+
run_response = cast(RunOutput, run_response)
|
|
2865
|
+
run_response.status = RunStatus.running
|
|
2866
|
+
|
|
2867
|
+
# 5. Determine tools for model
|
|
2868
|
+
self.model = cast(Model, self.model)
|
|
2869
|
+
await self._adetermine_tools_for_model(
|
|
2870
|
+
model=self.model,
|
|
2871
|
+
run_response=run_response,
|
|
2872
|
+
session=agent_session,
|
|
2873
|
+
session_state=session_state,
|
|
2874
|
+
dependencies=dependencies,
|
|
2875
|
+
user_id=user_id,
|
|
2876
|
+
async_mode=True,
|
|
2877
|
+
knowledge_filters=knowledge_filters,
|
|
2814
2878
|
)
|
|
2815
2879
|
|
|
2816
|
-
|
|
2880
|
+
# 6. Prepare run messages
|
|
2881
|
+
run_messages: RunMessages = self._get_continue_run_messages(
|
|
2882
|
+
input=input,
|
|
2883
|
+
)
|
|
2817
2884
|
|
|
2818
|
-
#
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2885
|
+
# Register run for cancellation tracking
|
|
2886
|
+
register_run(run_response.run_id) # type: ignore
|
|
2887
|
+
|
|
2888
|
+
try:
|
|
2889
|
+
# 7. Handle the updated tools
|
|
2890
|
+
await self._ahandle_tool_call_updates(run_response=run_response, run_messages=run_messages)
|
|
2891
|
+
|
|
2892
|
+
# 8. Get model response
|
|
2893
|
+
model_response: ModelResponse = await self.model.aresponse(
|
|
2894
|
+
messages=run_messages.messages,
|
|
2895
|
+
response_format=response_format,
|
|
2896
|
+
tools=self._tools_for_model,
|
|
2897
|
+
functions=self._functions_for_model,
|
|
2898
|
+
tool_choice=self.tool_choice,
|
|
2899
|
+
tool_call_limit=self.tool_call_limit,
|
|
2822
2900
|
)
|
|
2901
|
+
# Check for cancellation after model call
|
|
2902
|
+
raise_if_cancelled(run_response.run_id) # type: ignore
|
|
2823
2903
|
|
|
2824
|
-
|
|
2825
|
-
|
|
2904
|
+
# If an output model is provided, generate output using the output model
|
|
2905
|
+
await self._agenerate_response_with_output_model(model_response=model_response, run_messages=run_messages)
|
|
2826
2906
|
|
|
2827
|
-
|
|
2907
|
+
# If a parser model is provided, structure the response separately
|
|
2908
|
+
await self._aparse_response_with_parser_model(model_response=model_response, run_messages=run_messages)
|
|
2828
2909
|
|
|
2829
|
-
|
|
2830
|
-
|
|
2910
|
+
# 9. Update the RunOutput with the model response
|
|
2911
|
+
self._update_run_response(
|
|
2912
|
+
model_response=model_response, run_response=run_response, run_messages=run_messages
|
|
2913
|
+
)
|
|
2831
2914
|
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2915
|
+
if self.store_media:
|
|
2916
|
+
self._store_media(run_response, model_response)
|
|
2917
|
+
else:
|
|
2918
|
+
self._scrub_media_from_run_output(run_response)
|
|
2835
2919
|
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2920
|
+
# Break out of the run function if a tool call is paused
|
|
2921
|
+
if any(tool_call.is_paused for tool_call in run_response.tools or []):
|
|
2922
|
+
return self._handle_agent_run_paused(
|
|
2923
|
+
run_response=run_response, run_messages=run_messages, session=agent_session, user_id=user_id
|
|
2924
|
+
)
|
|
2925
|
+
raise_if_cancelled(run_response.run_id) # type: ignore
|
|
2926
|
+
|
|
2927
|
+
# 10. Calculate session metrics
|
|
2928
|
+
self._update_session_metrics(session=agent_session, run_response=run_response)
|
|
2929
|
+
|
|
2930
|
+
run_response.status = RunStatus.completed
|
|
2931
|
+
|
|
2932
|
+
# 11. Execute post-hooks
|
|
2933
|
+
if self.post_hooks is not None:
|
|
2934
|
+
await self._aexecute_post_hooks(
|
|
2935
|
+
hooks=self.post_hooks, # type: ignore
|
|
2936
|
+
run_output=run_response,
|
|
2937
|
+
session=agent_session,
|
|
2938
|
+
user_id=user_id,
|
|
2939
|
+
debug_mode=debug_mode,
|
|
2940
|
+
**kwargs,
|
|
2941
|
+
)
|
|
2942
|
+
|
|
2943
|
+
# Convert the response to the structured format if needed
|
|
2944
|
+
self._convert_response_to_structured_format(run_response)
|
|
2945
|
+
|
|
2946
|
+
if run_response.metrics:
|
|
2947
|
+
run_response.metrics.stop_timer()
|
|
2948
|
+
|
|
2949
|
+
# 12. Update Agent Memory
|
|
2950
|
+
async for _ in self._amake_memories_and_summaries(
|
|
2951
|
+
run_response=run_response, run_messages=run_messages, session=agent_session, user_id=user_id
|
|
2952
|
+
):
|
|
2953
|
+
pass
|
|
2954
|
+
|
|
2955
|
+
# Optional: Save output to file if save_response_to_file is set
|
|
2956
|
+
self.save_run_response_to_file(
|
|
2957
|
+
run_response=run_response,
|
|
2958
|
+
input=run_messages.user_message,
|
|
2959
|
+
session_id=agent_session.session_id,
|
|
2841
2960
|
user_id=user_id,
|
|
2842
|
-
debug_mode=debug_mode,
|
|
2843
|
-
**kwargs,
|
|
2844
2961
|
)
|
|
2845
2962
|
|
|
2846
|
-
|
|
2847
|
-
self.save_run_response_to_file(
|
|
2848
|
-
run_response=run_response, input=run_messages.user_message, session_id=session.session_id, user_id=user_id
|
|
2849
|
-
)
|
|
2963
|
+
agent_session.upsert_run(run=run_response)
|
|
2850
2964
|
|
|
2851
|
-
|
|
2852
|
-
|
|
2965
|
+
# 13. Save session to storage
|
|
2966
|
+
if self._has_async_db():
|
|
2967
|
+
await self.asave_session(session=agent_session)
|
|
2968
|
+
else:
|
|
2969
|
+
self.save_session(session=agent_session)
|
|
2853
2970
|
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
run_response=run_response, run_messages=run_messages, session=session, user_id=user_id
|
|
2857
|
-
):
|
|
2858
|
-
pass
|
|
2971
|
+
# Log Agent Telemetry
|
|
2972
|
+
await self._alog_agent_telemetry(session_id=agent_session.session_id, run_id=run_response.run_id)
|
|
2859
2973
|
|
|
2860
|
-
|
|
2861
|
-
self.save_session(session=session)
|
|
2974
|
+
log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
|
|
2862
2975
|
|
|
2863
|
-
|
|
2864
|
-
await self._alog_agent_telemetry(session_id=session.session_id, run_id=run_response.run_id)
|
|
2976
|
+
return run_response
|
|
2865
2977
|
|
|
2866
|
-
|
|
2978
|
+
except RunCancelledException as e:
|
|
2979
|
+
# Handle run cancellation
|
|
2980
|
+
log_info(f"Run {run_response.run_id} was cancelled")
|
|
2981
|
+
run_response.content = str(e)
|
|
2982
|
+
run_response.status = RunStatus.cancelled
|
|
2867
2983
|
|
|
2868
|
-
|
|
2984
|
+
# Update the Agent Session before exiting
|
|
2985
|
+
agent_session.upsert_run(run=run_response)
|
|
2986
|
+
if self._has_async_db():
|
|
2987
|
+
await self.asave_session(session=agent_session)
|
|
2988
|
+
else:
|
|
2989
|
+
self.save_session(session=agent_session)
|
|
2990
|
+
|
|
2991
|
+
return run_response
|
|
2992
|
+
finally:
|
|
2993
|
+
# Always clean up the run tracking
|
|
2994
|
+
cleanup_run(run_response.run_id) # type: ignore
|
|
2869
2995
|
|
|
2870
2996
|
async def _acontinue_run_stream(
|
|
2871
2997
|
self,
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2998
|
+
session_id: str,
|
|
2999
|
+
run_response: Optional[RunOutput] = None,
|
|
3000
|
+
updated_tools: Optional[List[ToolExecution]] = None,
|
|
3001
|
+
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
3002
|
+
session_state: Optional[Dict[str, Any]] = None,
|
|
3003
|
+
run_id: Optional[str] = None,
|
|
2875
3004
|
user_id: Optional[str] = None,
|
|
2876
3005
|
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
2877
3006
|
stream_intermediate_steps: bool = False,
|
|
3007
|
+
yield_run_response: Optional[bool] = None,
|
|
2878
3008
|
dependencies: Optional[Dict[str, Any]] = None,
|
|
2879
3009
|
) -> AsyncIterator[Union[RunOutputEvent, RunOutput]]:
|
|
2880
3010
|
"""Continue a previous run.
|
|
2881
3011
|
|
|
2882
3012
|
Steps:
|
|
2883
|
-
1.
|
|
2884
|
-
2.
|
|
2885
|
-
3.
|
|
2886
|
-
4.
|
|
2887
|
-
5.
|
|
2888
|
-
6.
|
|
2889
|
-
7.
|
|
3013
|
+
1. Resolve dependencies
|
|
3014
|
+
2. Read existing session from db
|
|
3015
|
+
3. Update session state and metadata
|
|
3016
|
+
4. Prepare run response
|
|
3017
|
+
5. Determine tools for model
|
|
3018
|
+
6. Prepare run messages
|
|
3019
|
+
7. Handle the updated tools
|
|
3020
|
+
8. Process model response
|
|
3021
|
+
9. Add the run to memory
|
|
3022
|
+
10. Update Agent Memory
|
|
3023
|
+
11. Calculate session metrics
|
|
3024
|
+
12. Create the run completed event
|
|
3025
|
+
13. Add the RunOutput to Agent Session
|
|
3026
|
+
14. Save session to storage
|
|
2890
3027
|
"""
|
|
2891
|
-
#
|
|
3028
|
+
log_debug(f"Agent Run Continue: {run_response.run_id}", center=True) # type: ignore
|
|
3029
|
+
|
|
3030
|
+
# 1. Resolve dependencies
|
|
2892
3031
|
if dependencies is not None:
|
|
2893
3032
|
await self._aresolve_run_dependencies(dependencies=dependencies)
|
|
2894
3033
|
|
|
2895
|
-
#
|
|
2896
|
-
|
|
2897
|
-
yield self._handle_event(create_run_continued_event(run_response), run_response)
|
|
2898
|
-
|
|
2899
|
-
# 1. Handle the updated tools
|
|
2900
|
-
async for event in self._ahandle_tool_call_updates_stream(run_response=run_response, run_messages=run_messages):
|
|
2901
|
-
yield event
|
|
2902
|
-
|
|
2903
|
-
# 2. Process model response
|
|
2904
|
-
async for event in self._ahandle_model_response_stream(
|
|
2905
|
-
session=session,
|
|
2906
|
-
run_response=run_response,
|
|
2907
|
-
run_messages=run_messages,
|
|
2908
|
-
response_format=response_format,
|
|
2909
|
-
stream_intermediate_steps=stream_intermediate_steps,
|
|
2910
|
-
):
|
|
2911
|
-
yield event
|
|
3034
|
+
# 2. Read existing session from db
|
|
3035
|
+
agent_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
|
|
2912
3036
|
|
|
2913
|
-
#
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
):
|
|
2918
|
-
yield item
|
|
2919
|
-
return
|
|
3037
|
+
# 3. Update session state and metadata
|
|
3038
|
+
self._update_metadata(session=agent_session)
|
|
3039
|
+
if session_state is not None:
|
|
3040
|
+
session_state = self._load_session_state(session=agent_session, session_state=session_state)
|
|
2920
3041
|
|
|
2921
|
-
#
|
|
2922
|
-
|
|
3042
|
+
# 4. Prepare run response
|
|
3043
|
+
if run_response is not None:
|
|
3044
|
+
# The run is continued from a provided run_response. This contains the updated tools.
|
|
3045
|
+
input = run_response.messages or []
|
|
3046
|
+
elif run_id is not None:
|
|
3047
|
+
# The run is continued from a run_id. This requires the updated tools to be passed.
|
|
3048
|
+
if updated_tools is None:
|
|
3049
|
+
raise ValueError("Updated tools are required to continue a run from a run_id.")
|
|
2923
3050
|
|
|
2924
|
-
|
|
3051
|
+
runs = agent_session.runs
|
|
3052
|
+
run_response = next((r for r in runs if r.run_id == run_id), None) # type: ignore
|
|
3053
|
+
if run_response is None:
|
|
3054
|
+
raise RuntimeError(f"No runs found for run ID {run_id}")
|
|
3055
|
+
run_response.tools = updated_tools
|
|
3056
|
+
input = run_response.messages or []
|
|
3057
|
+
else:
|
|
3058
|
+
raise ValueError("Either run_response or run_id must be provided.")
|
|
2925
3059
|
|
|
2926
|
-
|
|
3060
|
+
run_response = cast(RunOutput, run_response)
|
|
3061
|
+
run_response.status = RunStatus.running
|
|
2927
3062
|
|
|
2928
|
-
#
|
|
2929
|
-
|
|
2930
|
-
|
|
3063
|
+
# 5. Determine tools for model
|
|
3064
|
+
self.model = cast(Model, self.model)
|
|
3065
|
+
await self._adetermine_tools_for_model(
|
|
3066
|
+
model=self.model,
|
|
3067
|
+
run_response=run_response,
|
|
3068
|
+
session=agent_session,
|
|
3069
|
+
session_state=session_state,
|
|
3070
|
+
dependencies=dependencies,
|
|
3071
|
+
user_id=user_id,
|
|
3072
|
+
async_mode=True,
|
|
3073
|
+
knowledge_filters=knowledge_filters,
|
|
3074
|
+
)
|
|
2931
3075
|
|
|
2932
|
-
#
|
|
2933
|
-
self.
|
|
2934
|
-
|
|
3076
|
+
# 6. Prepare run messages
|
|
3077
|
+
run_messages: RunMessages = self._get_continue_run_messages(
|
|
3078
|
+
input=input,
|
|
2935
3079
|
)
|
|
2936
3080
|
|
|
2937
|
-
#
|
|
2938
|
-
|
|
3081
|
+
# Register run for cancellation tracking
|
|
3082
|
+
register_run(run_response.run_id) # type: ignore
|
|
2939
3083
|
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
yield event
|
|
3084
|
+
try:
|
|
3085
|
+
# Start the Run by yielding a RunContinued event
|
|
3086
|
+
if stream_intermediate_steps:
|
|
3087
|
+
yield self._handle_event(create_run_continued_event(run_response), run_response)
|
|
2945
3088
|
|
|
2946
|
-
|
|
2947
|
-
|
|
3089
|
+
# 7. Handle the updated tools
|
|
3090
|
+
async for event in self._ahandle_tool_call_updates_stream(
|
|
3091
|
+
run_response=run_response, run_messages=run_messages
|
|
3092
|
+
):
|
|
3093
|
+
raise_if_cancelled(run_response.run_id) # type: ignore
|
|
3094
|
+
yield event
|
|
2948
3095
|
|
|
2949
|
-
|
|
2950
|
-
|
|
3096
|
+
# 8. Process model response
|
|
3097
|
+
if self.output_model is None:
|
|
3098
|
+
async for event in self._ahandle_model_response_stream(
|
|
3099
|
+
session=agent_session,
|
|
3100
|
+
run_response=run_response,
|
|
3101
|
+
run_messages=run_messages,
|
|
3102
|
+
response_format=response_format,
|
|
3103
|
+
stream_intermediate_steps=stream_intermediate_steps,
|
|
3104
|
+
):
|
|
3105
|
+
raise_if_cancelled(run_response.run_id) # type: ignore
|
|
3106
|
+
yield event
|
|
3107
|
+
else:
|
|
3108
|
+
from agno.run.agent import (
|
|
3109
|
+
IntermediateRunContentEvent,
|
|
3110
|
+
RunContentEvent,
|
|
3111
|
+
) # type: ignore
|
|
2951
3112
|
|
|
2952
|
-
|
|
2953
|
-
|
|
3113
|
+
async for event in self._ahandle_model_response_stream(
|
|
3114
|
+
session=agent_session,
|
|
3115
|
+
run_response=run_response,
|
|
3116
|
+
run_messages=run_messages,
|
|
3117
|
+
response_format=response_format,
|
|
3118
|
+
stream_intermediate_steps=stream_intermediate_steps,
|
|
3119
|
+
):
|
|
3120
|
+
raise_if_cancelled(run_response.run_id) # type: ignore
|
|
3121
|
+
if isinstance(event, RunContentEvent):
|
|
3122
|
+
if stream_intermediate_steps:
|
|
3123
|
+
yield IntermediateRunContentEvent(
|
|
3124
|
+
content=event.content,
|
|
3125
|
+
content_type=event.content_type,
|
|
3126
|
+
)
|
|
3127
|
+
else:
|
|
3128
|
+
yield event
|
|
2954
3129
|
|
|
2955
|
-
|
|
3130
|
+
# If an output model is provided, generate output using the output model
|
|
3131
|
+
async for event in self._agenerate_response_with_output_model_stream(
|
|
3132
|
+
session=agent_session,
|
|
3133
|
+
run_response=run_response,
|
|
3134
|
+
run_messages=run_messages,
|
|
3135
|
+
stream_intermediate_steps=stream_intermediate_steps,
|
|
3136
|
+
):
|
|
3137
|
+
raise_if_cancelled(run_response.run_id) # type: ignore
|
|
3138
|
+
yield event
|
|
3139
|
+
|
|
3140
|
+
# Check for cancellation after model processing
|
|
3141
|
+
raise_if_cancelled(run_response.run_id) # type: ignore
|
|
3142
|
+
|
|
3143
|
+
# Break out of the run function if a tool call is paused
|
|
3144
|
+
if any(tool_call.is_paused for tool_call in run_response.tools or []):
|
|
3145
|
+
for item in self._handle_agent_run_paused_stream(
|
|
3146
|
+
run_response=run_response, run_messages=run_messages, session=agent_session, user_id=user_id
|
|
3147
|
+
):
|
|
3148
|
+
yield item
|
|
3149
|
+
return
|
|
3150
|
+
|
|
3151
|
+
run_response.status = RunStatus.completed
|
|
3152
|
+
|
|
3153
|
+
# 9. Create the run completed event
|
|
3154
|
+
completed_event = self._handle_event(create_run_completed_event(run_response), run_response)
|
|
3155
|
+
|
|
3156
|
+
# Set the run duration
|
|
3157
|
+
if run_response.metrics:
|
|
3158
|
+
run_response.metrics.stop_timer()
|
|
3159
|
+
|
|
3160
|
+
# 10. Add the run to memory
|
|
3161
|
+
agent_session.upsert_run(run=run_response)
|
|
3162
|
+
|
|
3163
|
+
# Optional: Save output to file if save_response_to_file is set
|
|
3164
|
+
self.save_run_response_to_file(
|
|
3165
|
+
run_response=run_response,
|
|
3166
|
+
input=run_messages.user_message,
|
|
3167
|
+
session_id=agent_session.session_id,
|
|
3168
|
+
user_id=user_id,
|
|
3169
|
+
)
|
|
3170
|
+
|
|
3171
|
+
# 11. Calculate session metrics
|
|
3172
|
+
self._update_session_metrics(session=agent_session, run_response=run_response)
|
|
3173
|
+
|
|
3174
|
+
# 12. Update Agent Memory
|
|
3175
|
+
async for event in self._amake_memories_and_summaries(
|
|
3176
|
+
run_response=run_response, run_messages=run_messages, session=agent_session, user_id=user_id
|
|
3177
|
+
):
|
|
3178
|
+
yield event
|
|
3179
|
+
|
|
3180
|
+
# 13. Save session to storage
|
|
3181
|
+
if self._has_async_db():
|
|
3182
|
+
await self.asave_session(session=agent_session)
|
|
3183
|
+
else:
|
|
3184
|
+
self.save_session(session=agent_session)
|
|
3185
|
+
|
|
3186
|
+
if stream_intermediate_steps:
|
|
3187
|
+
yield completed_event
|
|
3188
|
+
|
|
3189
|
+
if yield_run_response:
|
|
3190
|
+
yield run_response
|
|
3191
|
+
|
|
3192
|
+
# Log Agent Telemetry
|
|
3193
|
+
await self._alog_agent_telemetry(session_id=agent_session.session_id, run_id=run_response.run_id)
|
|
3194
|
+
|
|
3195
|
+
log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
|
|
3196
|
+
except RunCancelledException as e:
|
|
3197
|
+
# Handle run cancellation during streaming
|
|
3198
|
+
log_info(f"Run {run_response.run_id} was cancelled during streaming")
|
|
3199
|
+
run_response.status = RunStatus.cancelled
|
|
3200
|
+
run_response.content = str(e)
|
|
3201
|
+
|
|
3202
|
+
# Yield the cancellation event
|
|
3203
|
+
yield self._handle_event(
|
|
3204
|
+
create_run_cancelled_event(from_run_response=run_response, reason=str(e)),
|
|
3205
|
+
run_response,
|
|
3206
|
+
)
|
|
3207
|
+
|
|
3208
|
+
# Add the RunOutput to Agent Session even when cancelled
|
|
3209
|
+
agent_session.upsert_run(run=run_response)
|
|
3210
|
+
if self._has_async_db():
|
|
3211
|
+
await self.asave_session(session=agent_session)
|
|
3212
|
+
else:
|
|
3213
|
+
self.save_session(session=agent_session)
|
|
3214
|
+
finally:
|
|
3215
|
+
# Always clean up the run tracking
|
|
3216
|
+
cleanup_run(run_response.run_id) # type: ignore
|
|
2956
3217
|
|
|
2957
3218
|
def _execute_pre_hooks(
|
|
2958
3219
|
self,
|
|
@@ -3608,7 +3869,6 @@ class Agent:
|
|
|
3608
3869
|
run_messages: RunMessages,
|
|
3609
3870
|
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
3610
3871
|
stream_intermediate_steps: bool = False,
|
|
3611
|
-
workflow_context: Optional[Dict] = None,
|
|
3612
3872
|
) -> Iterator[RunOutputEvent]:
|
|
3613
3873
|
self.model = cast(Model, self.model)
|
|
3614
3874
|
|
|
@@ -3642,7 +3902,6 @@ class Agent:
|
|
|
3642
3902
|
reasoning_state=reasoning_state,
|
|
3643
3903
|
parse_structured_output=self.should_parse_structured_output,
|
|
3644
3904
|
stream_intermediate_steps=stream_intermediate_steps,
|
|
3645
|
-
workflow_context=workflow_context,
|
|
3646
3905
|
)
|
|
3647
3906
|
|
|
3648
3907
|
# Determine reasoning completed
|
|
@@ -3685,7 +3944,6 @@ class Agent:
|
|
|
3685
3944
|
run_messages: RunMessages,
|
|
3686
3945
|
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
3687
3946
|
stream_intermediate_steps: bool = False,
|
|
3688
|
-
workflow_context: Optional[Dict] = None,
|
|
3689
3947
|
) -> AsyncIterator[RunOutputEvent]:
|
|
3690
3948
|
self.model = cast(Model, self.model)
|
|
3691
3949
|
|
|
@@ -3721,7 +3979,6 @@ class Agent:
|
|
|
3721
3979
|
reasoning_state=reasoning_state,
|
|
3722
3980
|
parse_structured_output=self.should_parse_structured_output,
|
|
3723
3981
|
stream_intermediate_steps=stream_intermediate_steps,
|
|
3724
|
-
workflow_context=workflow_context,
|
|
3725
3982
|
):
|
|
3726
3983
|
yield event
|
|
3727
3984
|
|
|
@@ -3766,7 +4023,6 @@ class Agent:
|
|
|
3766
4023
|
reasoning_state: Optional[Dict[str, Any]] = None,
|
|
3767
4024
|
parse_structured_output: bool = False,
|
|
3768
4025
|
stream_intermediate_steps: bool = False,
|
|
3769
|
-
workflow_context: Optional[Dict] = None,
|
|
3770
4026
|
) -> Iterator[RunOutputEvent]:
|
|
3771
4027
|
if isinstance(model_response_event, tuple(get_args(RunOutputEvent))) or isinstance(
|
|
3772
4028
|
model_response_event, tuple(get_args(TeamRunOutputEvent))
|
|
@@ -3830,7 +4086,6 @@ class Agent:
|
|
|
3830
4086
|
content_type=content_type,
|
|
3831
4087
|
),
|
|
3832
4088
|
run_response,
|
|
3833
|
-
workflow_context=workflow_context,
|
|
3834
4089
|
)
|
|
3835
4090
|
elif (
|
|
3836
4091
|
model_response_event.content is not None
|
|
@@ -3849,7 +4104,6 @@ class Agent:
|
|
|
3849
4104
|
model_provider_data=model_response_event.provider_data,
|
|
3850
4105
|
),
|
|
3851
4106
|
run_response,
|
|
3852
|
-
workflow_context=workflow_context,
|
|
3853
4107
|
)
|
|
3854
4108
|
|
|
3855
4109
|
# Process audio
|
|
@@ -3910,7 +4164,6 @@ class Agent:
|
|
|
3910
4164
|
response_audio=run_response.response_audio,
|
|
3911
4165
|
),
|
|
3912
4166
|
run_response,
|
|
3913
|
-
workflow_context=workflow_context,
|
|
3914
4167
|
)
|
|
3915
4168
|
|
|
3916
4169
|
if model_response_event.images is not None:
|
|
@@ -3920,7 +4173,6 @@ class Agent:
|
|
|
3920
4173
|
image=model_response_event.images[-1],
|
|
3921
4174
|
),
|
|
3922
4175
|
run_response,
|
|
3923
|
-
workflow_context=workflow_context,
|
|
3924
4176
|
)
|
|
3925
4177
|
|
|
3926
4178
|
if model_response.images is None:
|
|
@@ -4144,7 +4396,7 @@ class Agent:
|
|
|
4144
4396
|
|
|
4145
4397
|
tasks.append(
|
|
4146
4398
|
self.memory_manager.acreate_user_memories(
|
|
4147
|
-
message=run_messages.user_message.get_content_string(), user_id=user_id
|
|
4399
|
+
message=run_messages.user_message.get_content_string(), user_id=user_id, agent_id=self.id
|
|
4148
4400
|
)
|
|
4149
4401
|
)
|
|
4150
4402
|
|
|
@@ -4168,7 +4420,9 @@ class Agent:
|
|
|
4168
4420
|
continue
|
|
4169
4421
|
|
|
4170
4422
|
if len(parsed_messages) > 0:
|
|
4171
|
-
tasks.append(
|
|
4423
|
+
tasks.append(
|
|
4424
|
+
self.memory_manager.acreate_user_memories(messages=parsed_messages, user_id=user_id, agent_id=self.id)
|
|
4425
|
+
)
|
|
4172
4426
|
else:
|
|
4173
4427
|
log_warning("Unable to add messages to memory")
|
|
4174
4428
|
|
|
@@ -4317,6 +4571,100 @@ class Agent:
|
|
|
4317
4571
|
|
|
4318
4572
|
return agent_tools
|
|
4319
4573
|
|
|
4574
|
+
async def aget_tools(
|
|
4575
|
+
self,
|
|
4576
|
+
run_response: RunOutput,
|
|
4577
|
+
session: AgentSession,
|
|
4578
|
+
async_mode: bool = False,
|
|
4579
|
+
user_id: Optional[str] = None,
|
|
4580
|
+
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
4581
|
+
) -> Optional[List[Union[Toolkit, Callable, Function, Dict]]]:
|
|
4582
|
+
agent_tools: List[Union[Toolkit, Callable, Function, Dict]] = []
|
|
4583
|
+
|
|
4584
|
+
# Add provided tools
|
|
4585
|
+
if self.tools is not None:
|
|
4586
|
+
agent_tools.extend(self.tools)
|
|
4587
|
+
|
|
4588
|
+
# If any of the tools has "agent" as parameter, set _rebuild_tools to True
|
|
4589
|
+
for tool in agent_tools:
|
|
4590
|
+
if isinstance(tool, Function):
|
|
4591
|
+
if "agent" in tool.parameters:
|
|
4592
|
+
self._rebuild_tools = True
|
|
4593
|
+
break
|
|
4594
|
+
if "team" in tool.parameters:
|
|
4595
|
+
self._rebuild_tools = True
|
|
4596
|
+
break
|
|
4597
|
+
if isinstance(tool, Toolkit):
|
|
4598
|
+
for func in tool.functions.values():
|
|
4599
|
+
if "agent" in func.parameters:
|
|
4600
|
+
self._rebuild_tools = True
|
|
4601
|
+
break
|
|
4602
|
+
if "team" in func.parameters:
|
|
4603
|
+
self._rebuild_tools = True
|
|
4604
|
+
break
|
|
4605
|
+
if callable(tool):
|
|
4606
|
+
from inspect import signature
|
|
4607
|
+
|
|
4608
|
+
sig = signature(tool)
|
|
4609
|
+
if "agent" in sig.parameters:
|
|
4610
|
+
self._rebuild_tools = True
|
|
4611
|
+
break
|
|
4612
|
+
if "team" in sig.parameters:
|
|
4613
|
+
self._rebuild_tools = True
|
|
4614
|
+
break
|
|
4615
|
+
|
|
4616
|
+
# Add tools for accessing memory
|
|
4617
|
+
if self.read_chat_history:
|
|
4618
|
+
agent_tools.append(self._get_chat_history_function(session=session))
|
|
4619
|
+
self._rebuild_tools = True
|
|
4620
|
+
if self.read_tool_call_history:
|
|
4621
|
+
agent_tools.append(self._get_tool_call_history_function(session=session))
|
|
4622
|
+
self._rebuild_tools = True
|
|
4623
|
+
if self.search_session_history:
|
|
4624
|
+
agent_tools.append(
|
|
4625
|
+
await self._aget_previous_sessions_messages_function(num_history_sessions=self.num_history_sessions)
|
|
4626
|
+
)
|
|
4627
|
+
self._rebuild_tools = True
|
|
4628
|
+
|
|
4629
|
+
if self.enable_agentic_memory:
|
|
4630
|
+
agent_tools.append(self._get_update_user_memory_function(user_id=user_id, async_mode=async_mode))
|
|
4631
|
+
self._rebuild_tools = True
|
|
4632
|
+
|
|
4633
|
+
if self.enable_agentic_state:
|
|
4634
|
+
agent_tools.append(self.update_session_state)
|
|
4635
|
+
|
|
4636
|
+
# Add tools for accessing knowledge
|
|
4637
|
+
if self.knowledge is not None or self.knowledge_retriever is not None:
|
|
4638
|
+
# Check if knowledge retriever is an async function but used in sync mode
|
|
4639
|
+
from inspect import iscoroutinefunction
|
|
4640
|
+
|
|
4641
|
+
if not async_mode and self.knowledge_retriever and iscoroutinefunction(self.knowledge_retriever):
|
|
4642
|
+
log_warning(
|
|
4643
|
+
"Async knowledge retriever function is being used with synchronous agent.run() or agent.print_response(). "
|
|
4644
|
+
"It is recommended to use agent.arun() or agent.aprint_response() instead."
|
|
4645
|
+
)
|
|
4646
|
+
|
|
4647
|
+
if self.search_knowledge:
|
|
4648
|
+
# Use async or sync search based on async_mode
|
|
4649
|
+
if self.enable_agentic_knowledge_filters:
|
|
4650
|
+
agent_tools.append(
|
|
4651
|
+
self._search_knowledge_base_with_agentic_filters_function(
|
|
4652
|
+
run_response=run_response, async_mode=async_mode, knowledge_filters=knowledge_filters
|
|
4653
|
+
)
|
|
4654
|
+
)
|
|
4655
|
+
else:
|
|
4656
|
+
agent_tools.append(
|
|
4657
|
+
self._get_search_knowledge_base_function(
|
|
4658
|
+
run_response=run_response, async_mode=async_mode, knowledge_filters=knowledge_filters
|
|
4659
|
+
)
|
|
4660
|
+
)
|
|
4661
|
+
self._rebuild_tools = True
|
|
4662
|
+
|
|
4663
|
+
if self.update_knowledge:
|
|
4664
|
+
agent_tools.append(self.add_to_knowledge)
|
|
4665
|
+
|
|
4666
|
+
return agent_tools
|
|
4667
|
+
|
|
4320
4668
|
def _collect_joint_images(
|
|
4321
4669
|
self,
|
|
4322
4670
|
run_input: Optional[RunInput] = None,
|
|
@@ -4570,30 +4918,152 @@ class Agent:
|
|
|
4570
4918
|
func._audios = joint_audios
|
|
4571
4919
|
func._videos = joint_videos
|
|
4572
4920
|
|
|
4573
|
-
def
|
|
4574
|
-
self
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4921
|
+
async def _adetermine_tools_for_model(
|
|
4922
|
+
self,
|
|
4923
|
+
model: Model,
|
|
4924
|
+
run_response: RunOutput,
|
|
4925
|
+
session: AgentSession,
|
|
4926
|
+
session_state: Optional[Dict[str, Any]] = None,
|
|
4927
|
+
dependencies: Optional[Dict[str, Any]] = None,
|
|
4928
|
+
user_id: Optional[str] = None,
|
|
4929
|
+
async_mode: bool = False,
|
|
4930
|
+
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
4931
|
+
) -> None:
|
|
4932
|
+
if self._rebuild_tools:
|
|
4933
|
+
self._rebuild_tools = False
|
|
4580
4934
|
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4935
|
+
agent_tools = await self.aget_tools(
|
|
4936
|
+
run_response=run_response,
|
|
4937
|
+
session=session,
|
|
4938
|
+
async_mode=async_mode,
|
|
4939
|
+
user_id=user_id,
|
|
4940
|
+
knowledge_filters=knowledge_filters,
|
|
4941
|
+
)
|
|
4587
4942
|
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4943
|
+
self._tools_for_model = []
|
|
4944
|
+
self._functions_for_model = {}
|
|
4945
|
+
self._tool_instructions = []
|
|
4946
|
+
|
|
4947
|
+
# Get Agent tools
|
|
4948
|
+
if agent_tools is not None and len(agent_tools) > 0:
|
|
4949
|
+
log_debug("Processing tools for model")
|
|
4950
|
+
|
|
4951
|
+
# Check if we need strict mode for the functions for the model
|
|
4952
|
+
strict = False
|
|
4953
|
+
if (
|
|
4954
|
+
self.output_schema is not None
|
|
4955
|
+
and (self.structured_outputs or (not self.use_json_mode))
|
|
4956
|
+
and model.supports_native_structured_outputs
|
|
4957
|
+
):
|
|
4958
|
+
strict = True
|
|
4959
|
+
|
|
4960
|
+
for tool in agent_tools:
|
|
4961
|
+
if isinstance(tool, Dict):
|
|
4962
|
+
# If a dict is passed, it is a builtin tool
|
|
4963
|
+
# that is run by the model provider and not the Agent
|
|
4964
|
+
self._tools_for_model.append(tool)
|
|
4965
|
+
log_debug(f"Included builtin tool {tool}")
|
|
4966
|
+
|
|
4967
|
+
elif isinstance(tool, Toolkit):
|
|
4968
|
+
# For each function in the toolkit and process entrypoint
|
|
4969
|
+
for name, func in tool.functions.items():
|
|
4970
|
+
# If the function does not exist in self.functions
|
|
4971
|
+
if name not in self._functions_for_model:
|
|
4972
|
+
func._agent = self
|
|
4973
|
+
func.process_entrypoint(strict=strict)
|
|
4974
|
+
if strict and func.strict is None:
|
|
4975
|
+
func.strict = True
|
|
4976
|
+
if self.tool_hooks is not None:
|
|
4977
|
+
func.tool_hooks = self.tool_hooks
|
|
4978
|
+
self._functions_for_model[name] = func
|
|
4979
|
+
self._tools_for_model.append({"type": "function", "function": func.to_dict()})
|
|
4980
|
+
log_debug(f"Added tool {name} from {tool.name}")
|
|
4981
|
+
|
|
4982
|
+
# Add instructions from the toolkit
|
|
4983
|
+
if tool.add_instructions and tool.instructions is not None:
|
|
4984
|
+
self._tool_instructions.append(tool.instructions)
|
|
4985
|
+
|
|
4986
|
+
elif isinstance(tool, Function):
|
|
4987
|
+
if tool.name not in self._functions_for_model:
|
|
4988
|
+
tool._agent = self
|
|
4989
|
+
tool.process_entrypoint(strict=strict)
|
|
4990
|
+
if strict and tool.strict is None:
|
|
4991
|
+
tool.strict = True
|
|
4992
|
+
if self.tool_hooks is not None:
|
|
4993
|
+
tool.tool_hooks = self.tool_hooks
|
|
4994
|
+
self._functions_for_model[tool.name] = tool
|
|
4995
|
+
self._tools_for_model.append({"type": "function", "function": tool.to_dict()})
|
|
4996
|
+
log_debug(f"Added tool {tool.name}")
|
|
4997
|
+
|
|
4998
|
+
# Add instructions from the Function
|
|
4999
|
+
if tool.add_instructions and tool.instructions is not None:
|
|
5000
|
+
self._tool_instructions.append(tool.instructions)
|
|
5001
|
+
|
|
5002
|
+
elif callable(tool):
|
|
5003
|
+
try:
|
|
5004
|
+
function_name = tool.__name__
|
|
5005
|
+
if function_name not in self._functions_for_model:
|
|
5006
|
+
func = Function.from_callable(tool, strict=strict)
|
|
5007
|
+
func._agent = self
|
|
5008
|
+
if strict:
|
|
5009
|
+
func.strict = True
|
|
5010
|
+
if self.tool_hooks is not None:
|
|
5011
|
+
func.tool_hooks = self.tool_hooks
|
|
5012
|
+
self._functions_for_model[func.name] = func
|
|
5013
|
+
self._tools_for_model.append({"type": "function", "function": func.to_dict()})
|
|
5014
|
+
log_debug(f"Added tool {func.name}")
|
|
5015
|
+
except Exception as e:
|
|
5016
|
+
log_warning(f"Could not add tool {tool}: {e}")
|
|
5017
|
+
|
|
5018
|
+
# Update the session state for the functions
|
|
5019
|
+
if self._functions_for_model:
|
|
5020
|
+
from inspect import signature
|
|
5021
|
+
|
|
5022
|
+
# Check if any functions need media before collecting
|
|
5023
|
+
needs_media = any(
|
|
5024
|
+
any(param in signature(func.entrypoint).parameters for param in ["images", "videos", "audios", "files"])
|
|
5025
|
+
for func in self._functions_for_model.values()
|
|
5026
|
+
if func.entrypoint is not None
|
|
5027
|
+
)
|
|
5028
|
+
|
|
5029
|
+
# Only collect media if functions actually need them
|
|
5030
|
+
joint_images = self._collect_joint_images(run_response.input, session) if needs_media else None
|
|
5031
|
+
joint_files = self._collect_joint_files(run_response.input) if needs_media else None
|
|
5032
|
+
joint_audios = self._collect_joint_audios(run_response.input, session) if needs_media else None
|
|
5033
|
+
joint_videos = self._collect_joint_videos(run_response.input, session) if needs_media else None
|
|
5034
|
+
|
|
5035
|
+
for func in self._functions_for_model.values():
|
|
5036
|
+
func._session_state = session_state
|
|
5037
|
+
func._dependencies = dependencies
|
|
5038
|
+
func._images = joint_images
|
|
5039
|
+
func._files = joint_files
|
|
5040
|
+
func._audios = joint_audios
|
|
5041
|
+
func._videos = joint_videos
|
|
5042
|
+
|
|
5043
|
+
def _model_should_return_structured_output(self):
|
|
5044
|
+
self.model = cast(Model, self.model)
|
|
5045
|
+
return bool(
|
|
5046
|
+
self.model.supports_native_structured_outputs
|
|
5047
|
+
and self.output_schema is not None
|
|
5048
|
+
and (not self.use_json_mode or self.structured_outputs)
|
|
5049
|
+
)
|
|
5050
|
+
|
|
5051
|
+
def _get_response_format(self, model: Optional[Model] = None) -> Optional[Union[Dict, Type[BaseModel]]]:
|
|
5052
|
+
model = cast(Model, model or self.model)
|
|
5053
|
+
if self.output_schema is None:
|
|
5054
|
+
return None
|
|
5055
|
+
else:
|
|
5056
|
+
json_response_format = {"type": "json_object"}
|
|
5057
|
+
|
|
5058
|
+
if model.supports_native_structured_outputs:
|
|
5059
|
+
if not self.use_json_mode or self.structured_outputs:
|
|
5060
|
+
log_debug("Setting Model.response_format to Agent.output_schema")
|
|
5061
|
+
return self.output_schema
|
|
5062
|
+
else:
|
|
5063
|
+
log_debug(
|
|
5064
|
+
"Model supports native structured outputs but it is not enabled. Using JSON mode instead."
|
|
5065
|
+
)
|
|
5066
|
+
return json_response_format
|
|
4597
5067
|
|
|
4598
5068
|
elif model.supports_json_schema_outputs:
|
|
4599
5069
|
if self.use_json_mode or (not self.structured_outputs):
|
|
@@ -4679,6 +5149,16 @@ class Agent:
|
|
|
4679
5149
|
log_warning(f"Error getting session from db: {e}")
|
|
4680
5150
|
return None
|
|
4681
5151
|
|
|
5152
|
+
async def _aread_session(self, session_id: str) -> Optional[AgentSession]:
|
|
5153
|
+
"""Get a Session from the database."""
|
|
5154
|
+
try:
|
|
5155
|
+
if not self.db:
|
|
5156
|
+
raise ValueError("Db not initialized")
|
|
5157
|
+
return await self.db.get_session(session_id=session_id, session_type=SessionType.AGENT) # type: ignore
|
|
5158
|
+
except Exception as e:
|
|
5159
|
+
log_warning(f"Error getting session from db: {e}")
|
|
5160
|
+
return None
|
|
5161
|
+
|
|
4682
5162
|
def _upsert_session(self, session: AgentSession) -> Optional[AgentSession]:
|
|
4683
5163
|
"""Upsert a Session into the database."""
|
|
4684
5164
|
|
|
@@ -4690,6 +5170,16 @@ class Agent:
|
|
|
4690
5170
|
log_warning(f"Error upserting session into db: {e}")
|
|
4691
5171
|
return None
|
|
4692
5172
|
|
|
5173
|
+
async def _aupsert_session(self, session: AgentSession) -> Optional[AgentSession]:
|
|
5174
|
+
"""Upsert a Session into the database."""
|
|
5175
|
+
try:
|
|
5176
|
+
if not self.db:
|
|
5177
|
+
raise ValueError("Db not initialized")
|
|
5178
|
+
return await self.db.upsert_session(session=session) # type: ignore
|
|
5179
|
+
except Exception as e:
|
|
5180
|
+
log_warning(f"Error upserting session into db: {e}")
|
|
5181
|
+
return None
|
|
5182
|
+
|
|
4693
5183
|
def _load_session_state(self, session: AgentSession, session_state: Dict[str, Any]):
|
|
4694
5184
|
"""Load and return the stored session_state from the database, optionally merging it with the given one"""
|
|
4695
5185
|
|
|
@@ -4775,6 +5265,42 @@ class Agent:
|
|
|
4775
5265
|
|
|
4776
5266
|
return agent_session
|
|
4777
5267
|
|
|
5268
|
+
async def _aread_or_create_session(
|
|
5269
|
+
self,
|
|
5270
|
+
session_id: str,
|
|
5271
|
+
user_id: Optional[str] = None,
|
|
5272
|
+
) -> AgentSession:
|
|
5273
|
+
from time import time
|
|
5274
|
+
|
|
5275
|
+
# Returning cached session if we have one
|
|
5276
|
+
if self._agent_session is not None and self._agent_session.session_id == session_id:
|
|
5277
|
+
return self._agent_session
|
|
5278
|
+
|
|
5279
|
+
# Try to load from database
|
|
5280
|
+
agent_session = None
|
|
5281
|
+
if self.db is not None and self.team_id is None and self.workflow_id is None:
|
|
5282
|
+
log_debug(f"Reading AgentSession: {session_id}")
|
|
5283
|
+
|
|
5284
|
+
agent_session = cast(AgentSession, await self._aread_session(session_id=session_id))
|
|
5285
|
+
|
|
5286
|
+
if agent_session is None:
|
|
5287
|
+
# Creating new session if none found
|
|
5288
|
+
log_debug(f"Creating new AgentSession: {session_id}")
|
|
5289
|
+
agent_session = AgentSession(
|
|
5290
|
+
session_id=session_id,
|
|
5291
|
+
agent_id=self.id,
|
|
5292
|
+
user_id=user_id,
|
|
5293
|
+
agent_data=self._get_agent_data(),
|
|
5294
|
+
session_data={},
|
|
5295
|
+
metadata=self.metadata,
|
|
5296
|
+
created_at=int(time()),
|
|
5297
|
+
)
|
|
5298
|
+
|
|
5299
|
+
if self.cache_session:
|
|
5300
|
+
self._agent_session = agent_session
|
|
5301
|
+
|
|
5302
|
+
return agent_session
|
|
5303
|
+
|
|
4778
5304
|
def get_run_output(self, run_id: str, session_id: Optional[str] = None) -> Optional[RunOutput]:
|
|
4779
5305
|
"""
|
|
4780
5306
|
Get a RunOutput from the database.
|
|
@@ -4895,6 +5421,27 @@ class Agent:
|
|
|
4895
5421
|
self._upsert_session(session=session)
|
|
4896
5422
|
log_debug(f"Created or updated AgentSession record: {session.session_id}")
|
|
4897
5423
|
|
|
5424
|
+
async def asave_session(self, session: AgentSession) -> None:
|
|
5425
|
+
"""Save the AgentSession to storage
|
|
5426
|
+
|
|
5427
|
+
Returns:
|
|
5428
|
+
Optional[AgentSession]: The saved AgentSession or None if not saved.
|
|
5429
|
+
"""
|
|
5430
|
+
# If the agent is a member of a team, do not save the session to the database
|
|
5431
|
+
if (
|
|
5432
|
+
self.db is not None
|
|
5433
|
+
and self.team_id is None
|
|
5434
|
+
and self.workflow_id is None
|
|
5435
|
+
and session.session_data is not None
|
|
5436
|
+
):
|
|
5437
|
+
if session.session_data is not None and "session_state" in session.session_data:
|
|
5438
|
+
session.session_data["session_state"].pop("current_session_id", None)
|
|
5439
|
+
session.session_data["session_state"].pop("current_user_id", None)
|
|
5440
|
+
session.session_data["session_state"].pop("current_run_id", None)
|
|
5441
|
+
|
|
5442
|
+
await self._aupsert_session(session=session)
|
|
5443
|
+
log_debug(f"Created or updated AgentSession record: {session.session_id}")
|
|
5444
|
+
|
|
4898
5445
|
def get_chat_history(self, session_id: Optional[str] = None) -> List[Message]:
|
|
4899
5446
|
"""Read the chat history from the session"""
|
|
4900
5447
|
if not session_id and not self.session_id:
|
|
@@ -5053,8 +5600,7 @@ class Agent:
|
|
|
5053
5600
|
session = self.get_session(session_id=session_id) # type: ignore
|
|
5054
5601
|
|
|
5055
5602
|
if session is None:
|
|
5056
|
-
|
|
5057
|
-
return []
|
|
5603
|
+
raise Exception("Session not found")
|
|
5058
5604
|
|
|
5059
5605
|
# Only filter by agent_id if this is part of a team
|
|
5060
5606
|
return session.get_messages_from_last_n_runs(
|
|
@@ -5084,6 +5630,16 @@ class Agent:
|
|
|
5084
5630
|
|
|
5085
5631
|
return self.memory_manager.get_user_memories(user_id=user_id)
|
|
5086
5632
|
|
|
5633
|
+
async def aget_user_memories(self, user_id: Optional[str] = None) -> Optional[List[UserMemory]]:
|
|
5634
|
+
"""Get the user memories for the given user ID."""
|
|
5635
|
+
if self.memory_manager is None:
|
|
5636
|
+
return None
|
|
5637
|
+
user_id = user_id if user_id is not None else self.user_id
|
|
5638
|
+
if user_id is None:
|
|
5639
|
+
user_id = "default"
|
|
5640
|
+
|
|
5641
|
+
return await self.memory_manager.aget_user_memories(user_id=user_id)
|
|
5642
|
+
|
|
5087
5643
|
def _format_message_with_state_variables(
|
|
5088
5644
|
self,
|
|
5089
5645
|
message: Any,
|
|
@@ -5097,40 +5653,313 @@ class Agent:
|
|
|
5097
5653
|
import string
|
|
5098
5654
|
from copy import deepcopy
|
|
5099
5655
|
|
|
5100
|
-
if not isinstance(message, str):
|
|
5101
|
-
return message
|
|
5656
|
+
if not isinstance(message, str):
|
|
5657
|
+
return message
|
|
5658
|
+
|
|
5659
|
+
# Should already be resolved and passed from run() method
|
|
5660
|
+
format_variables = ChainMap(
|
|
5661
|
+
session_state or {},
|
|
5662
|
+
dependencies or {},
|
|
5663
|
+
metadata or {},
|
|
5664
|
+
{"user_id": user_id} if user_id is not None else {},
|
|
5665
|
+
)
|
|
5666
|
+
converted_msg = deepcopy(message)
|
|
5667
|
+
for var_name in format_variables.keys():
|
|
5668
|
+
# Only convert standalone {var_name} patterns, not nested ones
|
|
5669
|
+
pattern = r"\{" + re.escape(var_name) + r"\}"
|
|
5670
|
+
replacement = "${" + var_name + "}"
|
|
5671
|
+
converted_msg = re.sub(pattern, replacement, converted_msg)
|
|
5672
|
+
|
|
5673
|
+
# Use Template to safely substitute variables
|
|
5674
|
+
template = string.Template(converted_msg)
|
|
5675
|
+
try:
|
|
5676
|
+
result = template.safe_substitute(format_variables)
|
|
5677
|
+
return result
|
|
5678
|
+
except Exception as e:
|
|
5679
|
+
log_warning(f"Template substitution failed: {e}")
|
|
5680
|
+
return message
|
|
5681
|
+
|
|
5682
|
+
def get_system_message(
|
|
5683
|
+
self,
|
|
5684
|
+
session: AgentSession,
|
|
5685
|
+
session_state: Optional[Dict[str, Any]] = None,
|
|
5686
|
+
user_id: Optional[str] = None,
|
|
5687
|
+
dependencies: Optional[Dict[str, Any]] = None,
|
|
5688
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
5689
|
+
add_session_state_to_context: Optional[bool] = None,
|
|
5690
|
+
) -> Optional[Message]:
|
|
5691
|
+
"""Return the system message for the Agent.
|
|
5692
|
+
|
|
5693
|
+
1. If the system_message is provided, use that.
|
|
5694
|
+
2. If build_context is False, return None.
|
|
5695
|
+
3. Build and return the default system message for the Agent.
|
|
5696
|
+
"""
|
|
5697
|
+
|
|
5698
|
+
# 1. If the system_message is provided, use that.
|
|
5699
|
+
if self.system_message is not None:
|
|
5700
|
+
if isinstance(self.system_message, Message):
|
|
5701
|
+
return self.system_message
|
|
5702
|
+
|
|
5703
|
+
sys_message_content: str = ""
|
|
5704
|
+
if isinstance(self.system_message, str):
|
|
5705
|
+
sys_message_content = self.system_message
|
|
5706
|
+
elif callable(self.system_message):
|
|
5707
|
+
sys_message_content = self.system_message(agent=self)
|
|
5708
|
+
if not isinstance(sys_message_content, str):
|
|
5709
|
+
raise Exception("system_message must return a string")
|
|
5710
|
+
|
|
5711
|
+
# Format the system message with the session state variables
|
|
5712
|
+
if self.resolve_in_context:
|
|
5713
|
+
sys_message_content = self._format_message_with_state_variables(
|
|
5714
|
+
sys_message_content,
|
|
5715
|
+
user_id=user_id,
|
|
5716
|
+
dependencies=dependencies,
|
|
5717
|
+
metadata=metadata,
|
|
5718
|
+
session_state=session_state,
|
|
5719
|
+
)
|
|
5720
|
+
|
|
5721
|
+
# type: ignore
|
|
5722
|
+
return Message(role=self.system_message_role, content=sys_message_content)
|
|
5723
|
+
|
|
5724
|
+
# 2. If build_context is False, return None.
|
|
5725
|
+
if not self.build_context:
|
|
5726
|
+
return None
|
|
5727
|
+
|
|
5728
|
+
if self.model is None:
|
|
5729
|
+
raise Exception("model not set")
|
|
5730
|
+
|
|
5731
|
+
# 3. Build and return the default system message for the Agent.
|
|
5732
|
+
# 3.1 Build the list of instructions for the system message
|
|
5733
|
+
instructions: List[str] = []
|
|
5734
|
+
if self.instructions is not None:
|
|
5735
|
+
_instructions = self.instructions
|
|
5736
|
+
if callable(self.instructions):
|
|
5737
|
+
import inspect
|
|
5738
|
+
|
|
5739
|
+
signature = inspect.signature(self.instructions)
|
|
5740
|
+
instruction_args: Dict[str, Any] = {}
|
|
5741
|
+
|
|
5742
|
+
# Check for agent parameter
|
|
5743
|
+
if "agent" in signature.parameters:
|
|
5744
|
+
instruction_args["agent"] = self
|
|
5745
|
+
|
|
5746
|
+
# Check for session_state parameter
|
|
5747
|
+
if "session_state" in signature.parameters:
|
|
5748
|
+
instruction_args["session_state"] = session_state or {}
|
|
5749
|
+
|
|
5750
|
+
_instructions = self.instructions(**instruction_args)
|
|
5751
|
+
|
|
5752
|
+
if isinstance(_instructions, str):
|
|
5753
|
+
instructions.append(_instructions)
|
|
5754
|
+
elif isinstance(_instructions, list):
|
|
5755
|
+
instructions.extend(_instructions)
|
|
5756
|
+
|
|
5757
|
+
# 3.1.1 Add instructions from the Model
|
|
5758
|
+
_model_instructions = self.model.get_instructions_for_model(self._tools_for_model)
|
|
5759
|
+
if _model_instructions is not None:
|
|
5760
|
+
instructions.extend(_model_instructions)
|
|
5761
|
+
|
|
5762
|
+
# 3.2 Build a list of additional information for the system message
|
|
5763
|
+
additional_information: List[str] = []
|
|
5764
|
+
# 3.2.1 Add instructions for using markdown
|
|
5765
|
+
if self.markdown and self.output_schema is None:
|
|
5766
|
+
additional_information.append("Use markdown to format your answers.")
|
|
5767
|
+
# 3.2.2 Add the current datetime
|
|
5768
|
+
if self.add_datetime_to_context:
|
|
5769
|
+
from datetime import datetime
|
|
5770
|
+
|
|
5771
|
+
tz = None
|
|
5772
|
+
|
|
5773
|
+
if self.timezone_identifier:
|
|
5774
|
+
try:
|
|
5775
|
+
from zoneinfo import ZoneInfo
|
|
5776
|
+
|
|
5777
|
+
tz = ZoneInfo(self.timezone_identifier)
|
|
5778
|
+
except Exception:
|
|
5779
|
+
log_warning("Invalid timezone identifier")
|
|
5780
|
+
|
|
5781
|
+
time = datetime.now(tz) if tz else datetime.now()
|
|
5782
|
+
|
|
5783
|
+
additional_information.append(f"The current time is {time}.")
|
|
5784
|
+
|
|
5785
|
+
# 3.2.3 Add the current location
|
|
5786
|
+
if self.add_location_to_context:
|
|
5787
|
+
from agno.utils.location import get_location
|
|
5788
|
+
|
|
5789
|
+
location = get_location()
|
|
5790
|
+
if location:
|
|
5791
|
+
location_str = ", ".join(
|
|
5792
|
+
filter(None, [location.get("city"), location.get("region"), location.get("country")])
|
|
5793
|
+
)
|
|
5794
|
+
if location_str:
|
|
5795
|
+
additional_information.append(f"Your approximate location is: {location_str}.")
|
|
5796
|
+
|
|
5797
|
+
# 3.2.4 Add agent name if provided
|
|
5798
|
+
if self.name is not None and self.add_name_to_context:
|
|
5799
|
+
additional_information.append(f"Your name is: {self.name}.")
|
|
5800
|
+
|
|
5801
|
+
# 3.2.5 Add information about agentic filters if enabled
|
|
5802
|
+
if self.knowledge is not None and self.enable_agentic_knowledge_filters:
|
|
5803
|
+
valid_filters = self.knowledge.get_valid_filters()
|
|
5804
|
+
if valid_filters:
|
|
5805
|
+
valid_filters_str = ", ".join(valid_filters)
|
|
5806
|
+
additional_information.append(
|
|
5807
|
+
dedent(f"""
|
|
5808
|
+
The knowledge base contains documents with these metadata filters: {valid_filters_str}.
|
|
5809
|
+
Always use filters when the user query indicates specific metadata.
|
|
5810
|
+
|
|
5811
|
+
Examples:
|
|
5812
|
+
1. If the user asks about a specific person like "Jordan Mitchell", you MUST use the search_knowledge_base tool with the filters parameter set to {{'<valid key like user_id>': '<valid value based on the user query>'}}.
|
|
5813
|
+
2. If the user asks about a specific document type like "contracts", you MUST use the search_knowledge_base tool with the filters parameter set to {{'document_type': 'contract'}}.
|
|
5814
|
+
4. If the user asks about a specific location like "documents from New York", you MUST use the search_knowledge_base tool with the filters parameter set to {{'<valid key like location>': 'New York'}}.
|
|
5815
|
+
|
|
5816
|
+
General Guidelines:
|
|
5817
|
+
- Always analyze the user query to identify relevant metadata.
|
|
5818
|
+
- Use the most specific filter(s) possible to narrow down results.
|
|
5819
|
+
- If multiple filters are relevant, combine them in the filters parameter (e.g., {{'name': 'Jordan Mitchell', 'document_type': 'contract'}}).
|
|
5820
|
+
- Ensure the filter keys match the valid metadata filters: {valid_filters_str}.
|
|
5821
|
+
|
|
5822
|
+
You can use the search_knowledge_base tool to search the knowledge base and get the most relevant documents. Make sure to pass the filters as [Dict[str: Any]] to the tool. FOLLOW THIS STRUCTURE STRICTLY.
|
|
5823
|
+
""")
|
|
5824
|
+
)
|
|
5825
|
+
|
|
5826
|
+
# 3.3 Build the default system message for the Agent.
|
|
5827
|
+
system_message_content: str = ""
|
|
5828
|
+
# 3.3.1 First add the Agent description if provided
|
|
5829
|
+
if self.description is not None:
|
|
5830
|
+
system_message_content += f"{self.description}\n"
|
|
5831
|
+
# 3.3.2 Then add the Agent role if provided
|
|
5832
|
+
if self.role is not None:
|
|
5833
|
+
system_message_content += f"\n<your_role>\n{self.role}\n</your_role>\n\n"
|
|
5834
|
+
# 3.3.4 Then add instructions for the Agent
|
|
5835
|
+
if len(instructions) > 0:
|
|
5836
|
+
system_message_content += "<instructions>"
|
|
5837
|
+
if len(instructions) > 1:
|
|
5838
|
+
for _upi in instructions:
|
|
5839
|
+
system_message_content += f"\n- {_upi}"
|
|
5840
|
+
else:
|
|
5841
|
+
system_message_content += "\n" + instructions[0]
|
|
5842
|
+
system_message_content += "\n</instructions>\n\n"
|
|
5843
|
+
# 3.3.6 Add additional information
|
|
5844
|
+
if len(additional_information) > 0:
|
|
5845
|
+
system_message_content += "<additional_information>"
|
|
5846
|
+
for _ai in additional_information:
|
|
5847
|
+
system_message_content += f"\n- {_ai}"
|
|
5848
|
+
system_message_content += "\n</additional_information>\n\n"
|
|
5849
|
+
# 3.3.7 Then add instructions for the tools
|
|
5850
|
+
if self._tool_instructions is not None:
|
|
5851
|
+
for _ti in self._tool_instructions:
|
|
5852
|
+
system_message_content += f"{_ti}\n"
|
|
5853
|
+
|
|
5854
|
+
# Format the system message with the session state variables
|
|
5855
|
+
if self.resolve_in_context:
|
|
5856
|
+
system_message_content = self._format_message_with_state_variables(
|
|
5857
|
+
system_message_content,
|
|
5858
|
+
user_id=user_id,
|
|
5859
|
+
session_state=session_state,
|
|
5860
|
+
dependencies=dependencies,
|
|
5861
|
+
metadata=metadata,
|
|
5862
|
+
)
|
|
5863
|
+
|
|
5864
|
+
# 3.3.7 Then add the expected output
|
|
5865
|
+
if self.expected_output is not None:
|
|
5866
|
+
system_message_content += f"<expected_output>\n{self.expected_output.strip()}\n</expected_output>\n\n"
|
|
5867
|
+
# 3.3.8 Then add additional context
|
|
5868
|
+
if self.additional_context is not None:
|
|
5869
|
+
system_message_content += f"{self.additional_context}\n"
|
|
5870
|
+
# 3.3.9 Then add memories to the system prompt
|
|
5871
|
+
if self.add_memories_to_context:
|
|
5872
|
+
_memory_manager_not_set = False
|
|
5873
|
+
if not user_id:
|
|
5874
|
+
user_id = "default"
|
|
5875
|
+
if self.memory_manager is None:
|
|
5876
|
+
self._set_memory_manager()
|
|
5877
|
+
_memory_manager_not_set = True
|
|
5878
|
+
|
|
5879
|
+
user_memories = self.memory_manager.get_user_memories(user_id=user_id) # type: ignore
|
|
5880
|
+
|
|
5881
|
+
if user_memories and len(user_memories) > 0:
|
|
5882
|
+
system_message_content += (
|
|
5883
|
+
"You have access to memories from previous interactions with the user that you can use:\n\n"
|
|
5884
|
+
)
|
|
5885
|
+
system_message_content += "<memories_from_previous_interactions>"
|
|
5886
|
+
for _memory in user_memories: # type: ignore
|
|
5887
|
+
system_message_content += f"\n- {_memory.memory}"
|
|
5888
|
+
system_message_content += "\n</memories_from_previous_interactions>\n\n"
|
|
5889
|
+
system_message_content += (
|
|
5890
|
+
"Note: this information is from previous interactions and may be updated in this conversation. "
|
|
5891
|
+
"You should always prefer information from this conversation over the past memories.\n"
|
|
5892
|
+
)
|
|
5893
|
+
else:
|
|
5894
|
+
system_message_content += (
|
|
5895
|
+
"You have the capability to retain memories from previous interactions with the user, "
|
|
5896
|
+
"but have not had any interactions with the user yet.\n"
|
|
5897
|
+
)
|
|
5898
|
+
if _memory_manager_not_set:
|
|
5899
|
+
self.memory_manager = None
|
|
5900
|
+
|
|
5901
|
+
if self.enable_agentic_memory:
|
|
5902
|
+
system_message_content += (
|
|
5903
|
+
"\n<updating_user_memories>\n"
|
|
5904
|
+
"- You have access to the `update_user_memory` tool that you can use to add new memories, update existing memories, delete memories, or clear all memories.\n"
|
|
5905
|
+
"- If the user's message includes information that should be captured as a memory, use the `update_user_memory` tool to update your memory database.\n"
|
|
5906
|
+
"- Memories should include details that could personalize ongoing interactions with the user.\n"
|
|
5907
|
+
"- Use this tool to add new memories or update existing memories that you identify in the conversation.\n"
|
|
5908
|
+
"- Use this tool if the user asks to update their memory, delete a memory, or clear all memories.\n"
|
|
5909
|
+
"- If you use the `update_user_memory` tool, remember to pass on the response to the user.\n"
|
|
5910
|
+
"</updating_user_memories>\n\n"
|
|
5911
|
+
)
|
|
5912
|
+
|
|
5913
|
+
# 3.3.11 Then add a summary of the interaction to the system prompt
|
|
5914
|
+
if self.add_session_summary_to_context and session.summary is not None:
|
|
5915
|
+
system_message_content += "Here is a brief summary of your previous interactions:\n\n"
|
|
5916
|
+
system_message_content += "<summary_of_previous_interactions>\n"
|
|
5917
|
+
system_message_content += session.summary.summary
|
|
5918
|
+
system_message_content += "\n</summary_of_previous_interactions>\n\n"
|
|
5919
|
+
system_message_content += (
|
|
5920
|
+
"Note: this information is from previous interactions and may be outdated. "
|
|
5921
|
+
"You should ALWAYS prefer information from this conversation over the past summary.\n\n"
|
|
5922
|
+
)
|
|
5923
|
+
|
|
5924
|
+
# 3.3.12 Add the system message from the Model
|
|
5925
|
+
system_message_from_model = self.model.get_system_message_for_model(self._tools_for_model)
|
|
5926
|
+
if system_message_from_model is not None:
|
|
5927
|
+
system_message_content += system_message_from_model
|
|
5928
|
+
|
|
5929
|
+
# 3.3.13 Add the JSON output prompt if output_schema is provided and the model does not support native structured outputs or JSON schema outputs
|
|
5930
|
+
# or if use_json_mode is True
|
|
5931
|
+
if (
|
|
5932
|
+
self.output_schema is not None
|
|
5933
|
+
and self.parser_model is None
|
|
5934
|
+
and not (
|
|
5935
|
+
(self.model.supports_native_structured_outputs or self.model.supports_json_schema_outputs)
|
|
5936
|
+
and (not self.use_json_mode or self.structured_outputs is True)
|
|
5937
|
+
)
|
|
5938
|
+
):
|
|
5939
|
+
system_message_content += f"{get_json_output_prompt(self.output_schema)}" # type: ignore
|
|
5940
|
+
|
|
5941
|
+
# 3.3.14 Add the response model format prompt if output_schema is provided
|
|
5942
|
+
if self.output_schema is not None and self.parser_model is not None:
|
|
5943
|
+
system_message_content += f"{get_response_model_format_prompt(self.output_schema)}"
|
|
5944
|
+
|
|
5945
|
+
# 3.3.15 Add the session state to the system message
|
|
5946
|
+
if self.add_session_state_to_context and session_state is not None:
|
|
5947
|
+
system_message_content += f"\n<session_state>\n{session_state}\n</session_state>\n\n"
|
|
5102
5948
|
|
|
5103
|
-
#
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
{"user_id": user_id} if user_id is not None else {},
|
|
5949
|
+
# Return the system message
|
|
5950
|
+
return (
|
|
5951
|
+
Message(role=self.system_message_role, content=system_message_content.strip()) # type: ignore
|
|
5952
|
+
if system_message_content
|
|
5953
|
+
else None
|
|
5109
5954
|
)
|
|
5110
|
-
converted_msg = deepcopy(message)
|
|
5111
|
-
for var_name in format_variables.keys():
|
|
5112
|
-
# Only convert standalone {var_name} patterns, not nested ones
|
|
5113
|
-
pattern = r"\{" + re.escape(var_name) + r"\}"
|
|
5114
|
-
replacement = "${" + var_name + "}"
|
|
5115
|
-
converted_msg = re.sub(pattern, replacement, converted_msg)
|
|
5116
|
-
|
|
5117
|
-
# Use Template to safely substitute variables
|
|
5118
|
-
template = string.Template(converted_msg)
|
|
5119
|
-
try:
|
|
5120
|
-
result = template.safe_substitute(format_variables)
|
|
5121
|
-
return result
|
|
5122
|
-
except Exception as e:
|
|
5123
|
-
log_warning(f"Template substitution failed: {e}")
|
|
5124
|
-
return message
|
|
5125
5955
|
|
|
5126
|
-
def
|
|
5956
|
+
async def aget_system_message(
|
|
5127
5957
|
self,
|
|
5128
5958
|
session: AgentSession,
|
|
5129
5959
|
session_state: Optional[Dict[str, Any]] = None,
|
|
5130
5960
|
user_id: Optional[str] = None,
|
|
5131
5961
|
dependencies: Optional[Dict[str, Any]] = None,
|
|
5132
5962
|
metadata: Optional[Dict[str, Any]] = None,
|
|
5133
|
-
add_session_state_to_context: Optional[bool] = None,
|
|
5134
5963
|
) -> Optional[Message]:
|
|
5135
5964
|
"""Return the system message for the Agent.
|
|
5136
5965
|
|
|
@@ -5244,7 +6073,7 @@ class Agent:
|
|
|
5244
6073
|
|
|
5245
6074
|
# 3.2.5 Add information about agentic filters if enabled
|
|
5246
6075
|
if self.knowledge is not None and self.enable_agentic_knowledge_filters:
|
|
5247
|
-
valid_filters = self.knowledge
|
|
6076
|
+
valid_filters = getattr(self.knowledge, "valid_metadata_filters", None)
|
|
5248
6077
|
if valid_filters:
|
|
5249
6078
|
valid_filters_str = ", ".join(valid_filters)
|
|
5250
6079
|
additional_information.append(
|
|
@@ -5319,7 +6148,12 @@ class Agent:
|
|
|
5319
6148
|
if self.memory_manager is None:
|
|
5320
6149
|
self._set_memory_manager()
|
|
5321
6150
|
_memory_manager_not_set = True
|
|
5322
|
-
|
|
6151
|
+
|
|
6152
|
+
if self._has_async_db():
|
|
6153
|
+
user_memories = await self.memory_manager.aget_user_memories(user_id=user_id) # type: ignore
|
|
6154
|
+
else:
|
|
6155
|
+
user_memories = self.memory_manager.get_user_memories(user_id=user_id) # type: ignore
|
|
6156
|
+
|
|
5323
6157
|
if user_memories and len(user_memories) > 0:
|
|
5324
6158
|
system_message_content += (
|
|
5325
6159
|
"You have access to memories from previous interactions with the user that you can use:\n\n"
|
|
@@ -5385,7 +6219,7 @@ class Agent:
|
|
|
5385
6219
|
system_message_content += f"{get_response_model_format_prompt(self.output_schema)}"
|
|
5386
6220
|
|
|
5387
6221
|
# 3.3.15 Add the session state to the system message
|
|
5388
|
-
if add_session_state_to_context and session_state is not None:
|
|
6222
|
+
if self.add_session_state_to_context and session_state is not None:
|
|
5389
6223
|
system_message_content += self._get_formatted_session_state_for_system_message(session_state)
|
|
5390
6224
|
|
|
5391
6225
|
# Return the system message
|
|
@@ -5482,90 +6316,291 @@ class Agent:
|
|
|
5482
6316
|
log_warning(f"Failed to validate message: {e}")
|
|
5483
6317
|
raise Exception(f"Failed to validate message: {e}")
|
|
5484
6318
|
|
|
5485
|
-
# If message is provided as a BaseModel, convert it to a Message
|
|
5486
|
-
elif isinstance(input, BaseModel):
|
|
5487
|
-
try:
|
|
5488
|
-
# Create a user message with the BaseModel content
|
|
5489
|
-
content = input.model_dump_json(indent=2, exclude_none=True)
|
|
5490
|
-
return Message(role=self.user_message_role, content=content)
|
|
5491
|
-
except Exception as e:
|
|
5492
|
-
log_warning(f"Failed to convert BaseModel to message: {e}")
|
|
5493
|
-
raise Exception(f"Failed to convert BaseModel to message: {e}")
|
|
5494
|
-
else:
|
|
5495
|
-
user_msg_content = input
|
|
5496
|
-
if self.add_knowledge_to_context:
|
|
5497
|
-
if isinstance(input, str):
|
|
5498
|
-
user_msg_content = input
|
|
5499
|
-
elif callable(input):
|
|
5500
|
-
user_msg_content = input(agent=self)
|
|
5501
|
-
else:
|
|
5502
|
-
raise Exception("message must be a string or a callable when add_references is True")
|
|
6319
|
+
# If message is provided as a BaseModel, convert it to a Message
|
|
6320
|
+
elif isinstance(input, BaseModel):
|
|
6321
|
+
try:
|
|
6322
|
+
# Create a user message with the BaseModel content
|
|
6323
|
+
content = input.model_dump_json(indent=2, exclude_none=True)
|
|
6324
|
+
return Message(role=self.user_message_role, content=content)
|
|
6325
|
+
except Exception as e:
|
|
6326
|
+
log_warning(f"Failed to convert BaseModel to message: {e}")
|
|
6327
|
+
raise Exception(f"Failed to convert BaseModel to message: {e}")
|
|
6328
|
+
else:
|
|
6329
|
+
user_msg_content = input
|
|
6330
|
+
if self.add_knowledge_to_context:
|
|
6331
|
+
if isinstance(input, str):
|
|
6332
|
+
user_msg_content = input
|
|
6333
|
+
elif callable(input):
|
|
6334
|
+
user_msg_content = input(agent=self)
|
|
6335
|
+
else:
|
|
6336
|
+
raise Exception("message must be a string or a callable when add_references is True")
|
|
6337
|
+
|
|
6338
|
+
try:
|
|
6339
|
+
retrieval_timer = Timer()
|
|
6340
|
+
retrieval_timer.start()
|
|
6341
|
+
docs_from_knowledge = self.get_relevant_docs_from_knowledge(
|
|
6342
|
+
query=user_msg_content, filters=knowledge_filters, **kwargs
|
|
6343
|
+
)
|
|
6344
|
+
if docs_from_knowledge is not None:
|
|
6345
|
+
references = MessageReferences(
|
|
6346
|
+
query=user_msg_content,
|
|
6347
|
+
references=docs_from_knowledge,
|
|
6348
|
+
time=round(retrieval_timer.elapsed, 4),
|
|
6349
|
+
)
|
|
6350
|
+
# Add the references to the run_response
|
|
6351
|
+
if run_response.references is None:
|
|
6352
|
+
run_response.references = []
|
|
6353
|
+
run_response.references.append(references)
|
|
6354
|
+
retrieval_timer.stop()
|
|
6355
|
+
log_debug(f"Time to get references: {retrieval_timer.elapsed:.4f}s")
|
|
6356
|
+
except Exception as e:
|
|
6357
|
+
log_warning(f"Failed to get references: {e}")
|
|
6358
|
+
|
|
6359
|
+
if self.resolve_in_context:
|
|
6360
|
+
user_msg_content = self._format_message_with_state_variables(
|
|
6361
|
+
user_msg_content,
|
|
6362
|
+
user_id=user_id,
|
|
6363
|
+
session_state=session_state,
|
|
6364
|
+
dependencies=dependencies,
|
|
6365
|
+
metadata=metadata,
|
|
6366
|
+
)
|
|
6367
|
+
|
|
6368
|
+
# Convert to string for concatenation operations
|
|
6369
|
+
user_msg_content_str = get_text_from_message(user_msg_content) if user_msg_content is not None else ""
|
|
6370
|
+
|
|
6371
|
+
# 4.1 Add knowledge references to user message
|
|
6372
|
+
if (
|
|
6373
|
+
self.add_knowledge_to_context
|
|
6374
|
+
and references is not None
|
|
6375
|
+
and references.references is not None
|
|
6376
|
+
and len(references.references) > 0
|
|
6377
|
+
):
|
|
6378
|
+
user_msg_content_str += "\n\nUse the following references from the knowledge base if it helps:\n"
|
|
6379
|
+
user_msg_content_str += "<references>\n"
|
|
6380
|
+
user_msg_content_str += self._convert_documents_to_string(references.references) + "\n"
|
|
6381
|
+
user_msg_content_str += "</references>"
|
|
6382
|
+
# 4.2 Add context to user message
|
|
6383
|
+
if add_dependencies_to_context and dependencies is not None:
|
|
6384
|
+
user_msg_content_str += "\n\n<additional context>\n"
|
|
6385
|
+
user_msg_content_str += self._convert_dependencies_to_string(dependencies) + "\n"
|
|
6386
|
+
user_msg_content_str += "</additional context>"
|
|
6387
|
+
|
|
6388
|
+
# Use the string version for the final content
|
|
6389
|
+
user_msg_content = user_msg_content_str
|
|
6390
|
+
|
|
6391
|
+
# Return the user message
|
|
6392
|
+
return Message(
|
|
6393
|
+
role=self.user_message_role,
|
|
6394
|
+
content=user_msg_content,
|
|
6395
|
+
audio=None if not self.send_media_to_model else audio,
|
|
6396
|
+
images=None if not self.send_media_to_model else images,
|
|
6397
|
+
videos=None if not self.send_media_to_model else videos,
|
|
6398
|
+
files=None if not self.send_media_to_model else files,
|
|
6399
|
+
**kwargs,
|
|
6400
|
+
)
|
|
6401
|
+
|
|
6402
|
+
def _get_run_messages(
|
|
6403
|
+
self,
|
|
6404
|
+
*,
|
|
6405
|
+
run_response: RunOutput,
|
|
6406
|
+
input: Union[str, List, Dict, Message, BaseModel, List[Message]],
|
|
6407
|
+
session: AgentSession,
|
|
6408
|
+
session_state: Optional[Dict[str, Any]] = None,
|
|
6409
|
+
user_id: Optional[str] = None,
|
|
6410
|
+
audio: Optional[Sequence[Audio]] = None,
|
|
6411
|
+
images: Optional[Sequence[Image]] = None,
|
|
6412
|
+
videos: Optional[Sequence[Video]] = None,
|
|
6413
|
+
files: Optional[Sequence[File]] = None,
|
|
6414
|
+
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
6415
|
+
add_history_to_context: Optional[bool] = None,
|
|
6416
|
+
dependencies: Optional[Dict[str, Any]] = None,
|
|
6417
|
+
add_dependencies_to_context: Optional[bool] = None,
|
|
6418
|
+
add_session_state_to_context: Optional[bool] = None,
|
|
6419
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
6420
|
+
**kwargs: Any,
|
|
6421
|
+
) -> RunMessages:
|
|
6422
|
+
"""This function returns a RunMessages object with the following attributes:
|
|
6423
|
+
- system_message: The system message for this run
|
|
6424
|
+
- user_message: The user message for this run
|
|
6425
|
+
- messages: List of messages to send to the model
|
|
6426
|
+
|
|
6427
|
+
To build the RunMessages object:
|
|
6428
|
+
1. Add system message to run_messages
|
|
6429
|
+
2. Add extra messages to run_messages if provided
|
|
6430
|
+
3. Add history to run_messages
|
|
6431
|
+
4. Add user message to run_messages (if input is single content)
|
|
6432
|
+
5. Add input messages to run_messages if provided (if input is List[Message])
|
|
6433
|
+
|
|
6434
|
+
Returns:
|
|
6435
|
+
RunMessages object with the following attributes:
|
|
6436
|
+
- system_message: The system message for this run
|
|
6437
|
+
- user_message: The user message for this run
|
|
6438
|
+
- messages: List of all messages to send to the model
|
|
6439
|
+
|
|
6440
|
+
Typical usage:
|
|
6441
|
+
run_messages = self._get_run_messages(
|
|
6442
|
+
input=input, session_id=session_id, user_id=user_id, audio=audio, images=images, videos=videos, files=files, **kwargs
|
|
6443
|
+
)
|
|
6444
|
+
"""
|
|
6445
|
+
|
|
6446
|
+
# Initialize the RunMessages object (no media here - that's in RunInput now)
|
|
6447
|
+
run_messages = RunMessages()
|
|
6448
|
+
|
|
6449
|
+
# 1. Add system message to run_messages
|
|
6450
|
+
system_message = self.get_system_message(
|
|
6451
|
+
session=session,
|
|
6452
|
+
session_state=session_state,
|
|
6453
|
+
user_id=user_id,
|
|
6454
|
+
dependencies=dependencies,
|
|
6455
|
+
metadata=metadata,
|
|
6456
|
+
add_session_state_to_context=add_session_state_to_context,
|
|
6457
|
+
)
|
|
6458
|
+
if system_message is not None:
|
|
6459
|
+
run_messages.system_message = system_message
|
|
6460
|
+
run_messages.messages.append(system_message)
|
|
6461
|
+
|
|
6462
|
+
# 2. Add extra messages to run_messages if provided
|
|
6463
|
+
if self.additional_input is not None:
|
|
6464
|
+
messages_to_add_to_run_response: List[Message] = []
|
|
6465
|
+
if run_messages.extra_messages is None:
|
|
6466
|
+
run_messages.extra_messages = []
|
|
6467
|
+
|
|
6468
|
+
for _m in self.additional_input:
|
|
6469
|
+
if isinstance(_m, Message):
|
|
6470
|
+
messages_to_add_to_run_response.append(_m)
|
|
6471
|
+
run_messages.messages.append(_m)
|
|
6472
|
+
run_messages.extra_messages.append(_m)
|
|
6473
|
+
elif isinstance(_m, dict):
|
|
6474
|
+
try:
|
|
6475
|
+
_m_parsed = Message.model_validate(_m)
|
|
6476
|
+
messages_to_add_to_run_response.append(_m_parsed)
|
|
6477
|
+
run_messages.messages.append(_m_parsed)
|
|
6478
|
+
run_messages.extra_messages.append(_m_parsed)
|
|
6479
|
+
except Exception as e:
|
|
6480
|
+
log_warning(f"Failed to validate message: {e}")
|
|
6481
|
+
# Add the extra messages to the run_response
|
|
6482
|
+
if len(messages_to_add_to_run_response) > 0:
|
|
6483
|
+
log_debug(f"Adding {len(messages_to_add_to_run_response)} extra messages")
|
|
6484
|
+
if run_response.additional_input is None:
|
|
6485
|
+
run_response.additional_input = messages_to_add_to_run_response
|
|
6486
|
+
else:
|
|
6487
|
+
run_response.additional_input.extend(messages_to_add_to_run_response)
|
|
6488
|
+
|
|
6489
|
+
# 3. Add history to run_messages
|
|
6490
|
+
if add_history_to_context:
|
|
6491
|
+
from copy import deepcopy
|
|
6492
|
+
|
|
6493
|
+
# Only skip messages from history when system_message_role is NOT a standard conversation role.
|
|
6494
|
+
# Standard conversation roles ("user", "assistant", "tool") should never be filtered
|
|
6495
|
+
# to preserve conversation continuity.
|
|
6496
|
+
skip_role = (
|
|
6497
|
+
self.system_message_role if self.system_message_role not in ["user", "assistant", "tool"] else None
|
|
6498
|
+
)
|
|
6499
|
+
|
|
6500
|
+
history: List[Message] = session.get_messages_from_last_n_runs(
|
|
6501
|
+
last_n=self.num_history_runs,
|
|
6502
|
+
skip_role=skip_role,
|
|
6503
|
+
agent_id=self.id if self.team_id is not None else None,
|
|
6504
|
+
)
|
|
6505
|
+
|
|
6506
|
+
if len(history) > 0:
|
|
6507
|
+
# Create a deep copy of the history messages to avoid modifying the original messages
|
|
6508
|
+
history_copy = [deepcopy(msg) for msg in history]
|
|
6509
|
+
|
|
6510
|
+
# Tag each message as coming from history
|
|
6511
|
+
for _msg in history_copy:
|
|
6512
|
+
_msg.from_history = True
|
|
6513
|
+
|
|
6514
|
+
log_debug(f"Adding {len(history_copy)} messages from history")
|
|
6515
|
+
|
|
6516
|
+
run_messages.messages += history_copy
|
|
6517
|
+
|
|
6518
|
+
# 4. Add user message to run_messages
|
|
6519
|
+
user_message: Optional[Message] = None
|
|
6520
|
+
|
|
6521
|
+
# 4.1 Build user message if input is None, str or list and not a list of Message/dict objects
|
|
6522
|
+
if (
|
|
6523
|
+
input is None
|
|
6524
|
+
or isinstance(input, str)
|
|
6525
|
+
or (
|
|
6526
|
+
isinstance(input, list)
|
|
6527
|
+
and not (
|
|
6528
|
+
len(input) > 0
|
|
6529
|
+
and (isinstance(input[0], Message) or (isinstance(input[0], dict) and "role" in input[0]))
|
|
6530
|
+
)
|
|
6531
|
+
)
|
|
6532
|
+
):
|
|
6533
|
+
user_message = self._get_user_message(
|
|
6534
|
+
run_response=run_response,
|
|
6535
|
+
session_state=session_state,
|
|
6536
|
+
input=input,
|
|
6537
|
+
audio=audio,
|
|
6538
|
+
images=images,
|
|
6539
|
+
videos=videos,
|
|
6540
|
+
files=files,
|
|
6541
|
+
knowledge_filters=knowledge_filters,
|
|
6542
|
+
dependencies=dependencies,
|
|
6543
|
+
add_dependencies_to_context=add_dependencies_to_context,
|
|
6544
|
+
metadata=metadata,
|
|
6545
|
+
**kwargs,
|
|
6546
|
+
)
|
|
6547
|
+
|
|
6548
|
+
# 4.2 If input is provided as a Message, use it directly
|
|
6549
|
+
elif isinstance(input, Message):
|
|
6550
|
+
user_message = input
|
|
5503
6551
|
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
)
|
|
5510
|
-
if docs_from_knowledge is not None:
|
|
5511
|
-
references = MessageReferences(
|
|
5512
|
-
query=user_msg_content,
|
|
5513
|
-
references=docs_from_knowledge,
|
|
5514
|
-
time=round(retrieval_timer.elapsed, 4),
|
|
5515
|
-
)
|
|
5516
|
-
# Add the references to the run_response
|
|
5517
|
-
if run_response.references is None:
|
|
5518
|
-
run_response.references = []
|
|
5519
|
-
run_response.references.append(references)
|
|
5520
|
-
retrieval_timer.stop()
|
|
5521
|
-
log_debug(f"Time to get references: {retrieval_timer.elapsed:.4f}s")
|
|
5522
|
-
except Exception as e:
|
|
5523
|
-
log_warning(f"Failed to get references: {e}")
|
|
6552
|
+
# 4.3 If input is provided as a dict, try to validate it as a Message
|
|
6553
|
+
elif isinstance(input, dict):
|
|
6554
|
+
try:
|
|
6555
|
+
if self.input_schema and is_typed_dict(self.input_schema):
|
|
6556
|
+
import json
|
|
5524
6557
|
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
metadata=metadata,
|
|
5532
|
-
)
|
|
6558
|
+
content = json.dumps(input, indent=2, ensure_ascii=False)
|
|
6559
|
+
user_message = Message(role=self.user_message_role, content=content)
|
|
6560
|
+
else:
|
|
6561
|
+
user_message = Message.model_validate(input)
|
|
6562
|
+
except Exception as e:
|
|
6563
|
+
log_warning(f"Failed to validate message: {e}")
|
|
5533
6564
|
|
|
5534
|
-
|
|
5535
|
-
|
|
6565
|
+
# 4.4 If input is provided as a BaseModel, convert it to a Message
|
|
6566
|
+
elif isinstance(input, BaseModel):
|
|
6567
|
+
try:
|
|
6568
|
+
# Create a user message with the BaseModel content
|
|
6569
|
+
content = input.model_dump_json(indent=2, exclude_none=True)
|
|
6570
|
+
user_message = Message(role=self.user_message_role, content=content)
|
|
6571
|
+
except Exception as e:
|
|
6572
|
+
log_warning(f"Failed to convert BaseModel to message: {e}")
|
|
5536
6573
|
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
6574
|
+
# 5. Add input messages to run_messages if provided (List[Message] or List[Dict])
|
|
6575
|
+
if (
|
|
6576
|
+
isinstance(input, list)
|
|
6577
|
+
and len(input) > 0
|
|
6578
|
+
and (isinstance(input[0], Message) or (isinstance(input[0], dict) and "role" in input[0]))
|
|
6579
|
+
):
|
|
6580
|
+
for _m in input:
|
|
6581
|
+
if isinstance(_m, Message):
|
|
6582
|
+
run_messages.messages.append(_m)
|
|
6583
|
+
if run_messages.extra_messages is None:
|
|
6584
|
+
run_messages.extra_messages = []
|
|
6585
|
+
run_messages.extra_messages.append(_m)
|
|
6586
|
+
elif isinstance(_m, dict):
|
|
6587
|
+
try:
|
|
6588
|
+
msg = Message.model_validate(_m)
|
|
6589
|
+
run_messages.messages.append(msg)
|
|
6590
|
+
if run_messages.extra_messages is None:
|
|
6591
|
+
run_messages.extra_messages = []
|
|
6592
|
+
run_messages.extra_messages.append(msg)
|
|
6593
|
+
except Exception as e:
|
|
6594
|
+
log_warning(f"Failed to validate message: {e}")
|
|
5553
6595
|
|
|
5554
|
-
|
|
5555
|
-
|
|
6596
|
+
# Add user message to run_messages
|
|
6597
|
+
if user_message is not None:
|
|
6598
|
+
run_messages.user_message = user_message
|
|
6599
|
+
run_messages.messages.append(user_message)
|
|
5556
6600
|
|
|
5557
|
-
|
|
5558
|
-
return Message(
|
|
5559
|
-
role=self.user_message_role,
|
|
5560
|
-
content=user_msg_content,
|
|
5561
|
-
audio=None if not self.send_media_to_model else audio,
|
|
5562
|
-
images=None if not self.send_media_to_model else images,
|
|
5563
|
-
videos=None if not self.send_media_to_model else videos,
|
|
5564
|
-
files=None if not self.send_media_to_model else files,
|
|
5565
|
-
**kwargs,
|
|
5566
|
-
)
|
|
6601
|
+
return run_messages
|
|
5567
6602
|
|
|
5568
|
-
def
|
|
6603
|
+
async def _aget_run_messages(
|
|
5569
6604
|
self,
|
|
5570
6605
|
*,
|
|
5571
6606
|
run_response: RunOutput,
|
|
@@ -5579,7 +6614,7 @@ class Agent:
|
|
|
5579
6614
|
files: Optional[Sequence[File]] = None,
|
|
5580
6615
|
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
5581
6616
|
add_history_to_context: Optional[bool] = None,
|
|
5582
|
-
|
|
6617
|
+
run_dependencies: Optional[Dict[str, Any]] = None,
|
|
5583
6618
|
add_dependencies_to_context: Optional[bool] = None,
|
|
5584
6619
|
add_session_state_to_context: Optional[bool] = None,
|
|
5585
6620
|
metadata: Optional[Dict[str, Any]] = None,
|
|
@@ -5613,13 +6648,12 @@ class Agent:
|
|
|
5613
6648
|
run_messages = RunMessages()
|
|
5614
6649
|
|
|
5615
6650
|
# 1. Add system message to run_messages
|
|
5616
|
-
system_message = self.
|
|
6651
|
+
system_message = await self.aget_system_message(
|
|
5617
6652
|
session=session,
|
|
5618
6653
|
session_state=session_state,
|
|
5619
6654
|
user_id=user_id,
|
|
5620
|
-
dependencies=
|
|
6655
|
+
dependencies=run_dependencies,
|
|
5621
6656
|
metadata=metadata,
|
|
5622
|
-
add_session_state_to_context=add_session_state_to_context,
|
|
5623
6657
|
)
|
|
5624
6658
|
if system_message is not None:
|
|
5625
6659
|
run_messages.system_message = system_message
|
|
@@ -5656,16 +6690,9 @@ class Agent:
|
|
|
5656
6690
|
if add_history_to_context:
|
|
5657
6691
|
from copy import deepcopy
|
|
5658
6692
|
|
|
5659
|
-
# Only skip messages from history when system_message_role is NOT a standard conversation role.
|
|
5660
|
-
# Standard conversation roles ("user", "assistant", "tool") should never be filtered
|
|
5661
|
-
# to preserve conversation continuity.
|
|
5662
|
-
skip_role = (
|
|
5663
|
-
self.system_message_role if self.system_message_role not in ["user", "assistant", "tool"] else None
|
|
5664
|
-
)
|
|
5665
|
-
|
|
5666
6693
|
history: List[Message] = session.get_messages_from_last_n_runs(
|
|
5667
6694
|
last_n=self.num_history_runs,
|
|
5668
|
-
skip_role=
|
|
6695
|
+
skip_role=self.system_message_role,
|
|
5669
6696
|
agent_id=self.id if self.team_id is not None else None,
|
|
5670
6697
|
)
|
|
5671
6698
|
|
|
@@ -5705,7 +6732,7 @@ class Agent:
|
|
|
5705
6732
|
videos=videos,
|
|
5706
6733
|
files=files,
|
|
5707
6734
|
knowledge_filters=knowledge_filters,
|
|
5708
|
-
|
|
6735
|
+
run_dependencies=run_dependencies,
|
|
5709
6736
|
add_dependencies_to_context=add_dependencies_to_context,
|
|
5710
6737
|
metadata=metadata,
|
|
5711
6738
|
**kwargs,
|
|
@@ -5718,13 +6745,7 @@ class Agent:
|
|
|
5718
6745
|
# 4.3 If input is provided as a dict, try to validate it as a Message
|
|
5719
6746
|
elif isinstance(input, dict):
|
|
5720
6747
|
try:
|
|
5721
|
-
|
|
5722
|
-
import json
|
|
5723
|
-
|
|
5724
|
-
content = json.dumps(input, indent=2, ensure_ascii=False)
|
|
5725
|
-
user_message = Message(role=self.user_message_role, content=content)
|
|
5726
|
-
else:
|
|
5727
|
-
user_message = Message.model_validate(input)
|
|
6748
|
+
user_message = Message.model_validate(input)
|
|
5728
6749
|
except Exception as e:
|
|
5729
6750
|
log_warning(f"Failed to validate message: {e}")
|
|
5730
6751
|
|
|
@@ -6283,12 +7304,15 @@ class Agent:
|
|
|
6283
7304
|
|
|
6284
7305
|
# If a reasoning model is provided, use it to generate reasoning
|
|
6285
7306
|
if reasoning_model_provided:
|
|
7307
|
+
from agno.reasoning.anthropic import is_anthropic_reasoning_model
|
|
6286
7308
|
from agno.reasoning.azure_ai_foundry import is_ai_foundry_reasoning_model
|
|
6287
7309
|
from agno.reasoning.deepseek import is_deepseek_reasoning_model
|
|
7310
|
+
from agno.reasoning.gemini import is_gemini_reasoning_model
|
|
6288
7311
|
from agno.reasoning.groq import is_groq_reasoning_model
|
|
6289
7312
|
from agno.reasoning.helpers import get_reasoning_agent
|
|
6290
7313
|
from agno.reasoning.ollama import is_ollama_reasoning_model
|
|
6291
7314
|
from agno.reasoning.openai import is_openai_reasoning_model
|
|
7315
|
+
from agno.reasoning.vertexai import is_vertexai_reasoning_model
|
|
6292
7316
|
|
|
6293
7317
|
reasoning_agent = self.reasoning_agent or get_reasoning_agent(
|
|
6294
7318
|
reasoning_model=reasoning_model,
|
|
@@ -6304,8 +7328,20 @@ class Agent:
|
|
|
6304
7328
|
is_openai = is_openai_reasoning_model(reasoning_model)
|
|
6305
7329
|
is_ollama = is_ollama_reasoning_model(reasoning_model)
|
|
6306
7330
|
is_ai_foundry = is_ai_foundry_reasoning_model(reasoning_model)
|
|
7331
|
+
is_gemini = is_gemini_reasoning_model(reasoning_model)
|
|
7332
|
+
is_anthropic = is_anthropic_reasoning_model(reasoning_model)
|
|
7333
|
+
is_vertexai = is_vertexai_reasoning_model(reasoning_model)
|
|
6307
7334
|
|
|
6308
|
-
if
|
|
7335
|
+
if (
|
|
7336
|
+
is_deepseek
|
|
7337
|
+
or is_groq
|
|
7338
|
+
or is_openai
|
|
7339
|
+
or is_ollama
|
|
7340
|
+
or is_ai_foundry
|
|
7341
|
+
or is_gemini
|
|
7342
|
+
or is_anthropic
|
|
7343
|
+
or is_vertexai
|
|
7344
|
+
):
|
|
6309
7345
|
reasoning_message: Optional[Message] = None
|
|
6310
7346
|
if is_deepseek:
|
|
6311
7347
|
from agno.reasoning.deepseek import get_deepseek_reasoning
|
|
@@ -6342,6 +7378,27 @@ class Agent:
|
|
|
6342
7378
|
reasoning_message = get_ai_foundry_reasoning(
|
|
6343
7379
|
reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
|
|
6344
7380
|
)
|
|
7381
|
+
elif is_gemini:
|
|
7382
|
+
from agno.reasoning.gemini import get_gemini_reasoning
|
|
7383
|
+
|
|
7384
|
+
log_debug("Starting Gemini Reasoning", center=True, symbol="=")
|
|
7385
|
+
reasoning_message = get_gemini_reasoning(
|
|
7386
|
+
reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
|
|
7387
|
+
)
|
|
7388
|
+
elif is_anthropic:
|
|
7389
|
+
from agno.reasoning.anthropic import get_anthropic_reasoning
|
|
7390
|
+
|
|
7391
|
+
log_debug("Starting Anthropic Claude Reasoning", center=True, symbol="=")
|
|
7392
|
+
reasoning_message = get_anthropic_reasoning(
|
|
7393
|
+
reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
|
|
7394
|
+
)
|
|
7395
|
+
elif is_vertexai:
|
|
7396
|
+
from agno.reasoning.vertexai import get_vertexai_reasoning
|
|
7397
|
+
|
|
7398
|
+
log_debug("Starting VertexAI Reasoning", center=True, symbol="=")
|
|
7399
|
+
reasoning_message = get_vertexai_reasoning(
|
|
7400
|
+
reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
|
|
7401
|
+
)
|
|
6345
7402
|
|
|
6346
7403
|
if reasoning_message is None:
|
|
6347
7404
|
log_warning("Reasoning error. Reasoning response is None, continuing regular session...")
|
|
@@ -6515,12 +7572,15 @@ class Agent:
|
|
|
6515
7572
|
|
|
6516
7573
|
# If a reasoning model is provided, use it to generate reasoning
|
|
6517
7574
|
if reasoning_model_provided:
|
|
7575
|
+
from agno.reasoning.anthropic import is_anthropic_reasoning_model
|
|
6518
7576
|
from agno.reasoning.azure_ai_foundry import is_ai_foundry_reasoning_model
|
|
6519
7577
|
from agno.reasoning.deepseek import is_deepseek_reasoning_model
|
|
7578
|
+
from agno.reasoning.gemini import is_gemini_reasoning_model
|
|
6520
7579
|
from agno.reasoning.groq import is_groq_reasoning_model
|
|
6521
7580
|
from agno.reasoning.helpers import get_reasoning_agent
|
|
6522
7581
|
from agno.reasoning.ollama import is_ollama_reasoning_model
|
|
6523
7582
|
from agno.reasoning.openai import is_openai_reasoning_model
|
|
7583
|
+
from agno.reasoning.vertexai import is_vertexai_reasoning_model
|
|
6524
7584
|
|
|
6525
7585
|
reasoning_agent = self.reasoning_agent or get_reasoning_agent(
|
|
6526
7586
|
reasoning_model=reasoning_model,
|
|
@@ -6536,8 +7596,20 @@ class Agent:
|
|
|
6536
7596
|
is_openai = is_openai_reasoning_model(reasoning_model)
|
|
6537
7597
|
is_ollama = is_ollama_reasoning_model(reasoning_model)
|
|
6538
7598
|
is_ai_foundry = is_ai_foundry_reasoning_model(reasoning_model)
|
|
7599
|
+
is_gemini = is_gemini_reasoning_model(reasoning_model)
|
|
7600
|
+
is_anthropic = is_anthropic_reasoning_model(reasoning_model)
|
|
7601
|
+
is_vertexai = is_vertexai_reasoning_model(reasoning_model)
|
|
6539
7602
|
|
|
6540
|
-
if
|
|
7603
|
+
if (
|
|
7604
|
+
is_deepseek
|
|
7605
|
+
or is_groq
|
|
7606
|
+
or is_openai
|
|
7607
|
+
or is_ollama
|
|
7608
|
+
or is_ai_foundry
|
|
7609
|
+
or is_gemini
|
|
7610
|
+
or is_anthropic
|
|
7611
|
+
or is_vertexai
|
|
7612
|
+
):
|
|
6541
7613
|
reasoning_message: Optional[Message] = None
|
|
6542
7614
|
if is_deepseek:
|
|
6543
7615
|
from agno.reasoning.deepseek import aget_deepseek_reasoning
|
|
@@ -6574,6 +7646,27 @@ class Agent:
|
|
|
6574
7646
|
reasoning_message = get_ai_foundry_reasoning(
|
|
6575
7647
|
reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
|
|
6576
7648
|
)
|
|
7649
|
+
elif is_gemini:
|
|
7650
|
+
from agno.reasoning.gemini import aget_gemini_reasoning
|
|
7651
|
+
|
|
7652
|
+
log_debug("Starting Gemini Reasoning", center=True, symbol="=")
|
|
7653
|
+
reasoning_message = await aget_gemini_reasoning(
|
|
7654
|
+
reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
|
|
7655
|
+
)
|
|
7656
|
+
elif is_anthropic:
|
|
7657
|
+
from agno.reasoning.anthropic import aget_anthropic_reasoning
|
|
7658
|
+
|
|
7659
|
+
log_debug("Starting Anthropic Claude Reasoning", center=True, symbol="=")
|
|
7660
|
+
reasoning_message = await aget_anthropic_reasoning(
|
|
7661
|
+
reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
|
|
7662
|
+
)
|
|
7663
|
+
elif is_vertexai:
|
|
7664
|
+
from agno.reasoning.vertexai import aget_vertexai_reasoning
|
|
7665
|
+
|
|
7666
|
+
log_debug("Starting VertexAI Reasoning", center=True, symbol="=")
|
|
7667
|
+
reasoning_message = await aget_vertexai_reasoning(
|
|
7668
|
+
reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
|
|
7669
|
+
)
|
|
6577
7670
|
|
|
6578
7671
|
if reasoning_message is None:
|
|
6579
7672
|
log_warning("Reasoning error. Reasoning response is None, continuing regular session...")
|
|
@@ -6892,7 +7985,6 @@ class Agent:
|
|
|
6892
7985
|
run_response: RunOutput,
|
|
6893
7986
|
run_messages: RunMessages,
|
|
6894
7987
|
stream_intermediate_steps: bool = False,
|
|
6895
|
-
workflow_context: Optional[Dict] = None,
|
|
6896
7988
|
):
|
|
6897
7989
|
"""Parse the model response using the output model."""
|
|
6898
7990
|
from agno.utils.events import (
|
|
@@ -6916,7 +8008,6 @@ class Agent:
|
|
|
6916
8008
|
run_response=run_response,
|
|
6917
8009
|
model_response=model_response,
|
|
6918
8010
|
model_response_event=model_response_event,
|
|
6919
|
-
workflow_context=workflow_context,
|
|
6920
8011
|
stream_intermediate_steps=stream_intermediate_steps,
|
|
6921
8012
|
)
|
|
6922
8013
|
|
|
@@ -6945,7 +8036,6 @@ class Agent:
|
|
|
6945
8036
|
run_response: RunOutput,
|
|
6946
8037
|
run_messages: RunMessages,
|
|
6947
8038
|
stream_intermediate_steps: bool = False,
|
|
6948
|
-
workflow_context: Optional[Dict] = None,
|
|
6949
8039
|
):
|
|
6950
8040
|
"""Parse the model response using the output model."""
|
|
6951
8041
|
from agno.utils.events import (
|
|
@@ -6971,7 +8061,6 @@ class Agent:
|
|
|
6971
8061
|
run_response=run_response,
|
|
6972
8062
|
model_response=model_response,
|
|
6973
8063
|
model_response_event=model_response_event,
|
|
6974
|
-
workflow_context=workflow_context,
|
|
6975
8064
|
stream_intermediate_steps=stream_intermediate_steps,
|
|
6976
8065
|
):
|
|
6977
8066
|
yield event
|
|
@@ -6986,14 +8075,7 @@ class Agent:
|
|
|
6986
8075
|
# Update the RunResponse metrics
|
|
6987
8076
|
run_response.metrics = self._calculate_run_metrics(messages_for_run_response)
|
|
6988
8077
|
|
|
6989
|
-
def _handle_event(self, event: RunOutputEvent, run_response: RunOutput
|
|
6990
|
-
if workflow_context:
|
|
6991
|
-
event.workflow_id = workflow_context.get("workflow_id")
|
|
6992
|
-
event.workflow_run_id = workflow_context.get("workflow_run_id")
|
|
6993
|
-
event.step_id = workflow_context.get("step_id")
|
|
6994
|
-
event.step_name = workflow_context.get("step_name")
|
|
6995
|
-
event.step_index = workflow_context.get("step_index")
|
|
6996
|
-
|
|
8078
|
+
def _handle_event(self, event: RunOutputEvent, run_response: RunOutput):
|
|
6997
8079
|
# We only store events that are not run_response_content events
|
|
6998
8080
|
events_to_skip = [event.value for event in self.events_to_skip] if self.events_to_skip else []
|
|
6999
8081
|
if self.store_events and event.event not in events_to_skip:
|
|
@@ -7314,6 +8396,8 @@ class Agent:
|
|
|
7314
8396
|
if self.db is None:
|
|
7315
8397
|
return "Previous session messages not available"
|
|
7316
8398
|
|
|
8399
|
+
self.db = cast(BaseDb, self.db)
|
|
8400
|
+
|
|
7317
8401
|
selected_sessions = self.db.get_sessions(
|
|
7318
8402
|
session_type=SessionType.AGENT, limit=num_history_sessions, user_id=user_id
|
|
7319
8403
|
)
|
|
@@ -7351,6 +8435,69 @@ class Agent:
|
|
|
7351
8435
|
|
|
7352
8436
|
return get_previous_session_messages
|
|
7353
8437
|
|
|
8438
|
+
async def _aget_previous_sessions_messages_function(self, num_history_sessions: Optional[int] = 2) -> Callable:
|
|
8439
|
+
"""Factory function to create a get_previous_session_messages function.
|
|
8440
|
+
|
|
8441
|
+
Args:
|
|
8442
|
+
num_history_sessions: The last n sessions to be taken from db
|
|
8443
|
+
|
|
8444
|
+
Returns:
|
|
8445
|
+
Callable: A function that retrieves messages from previous sessions
|
|
8446
|
+
"""
|
|
8447
|
+
|
|
8448
|
+
async def aget_previous_session_messages() -> str:
|
|
8449
|
+
"""Use this function to retrieve messages from previous chat sessions.
|
|
8450
|
+
USE THIS TOOL ONLY WHEN THE QUESTION IS EITHER "What was my last conversation?" or "What was my last question?" and similar to it.
|
|
8451
|
+
|
|
8452
|
+
Returns:
|
|
8453
|
+
str: JSON formatted list of message pairs from previous sessions
|
|
8454
|
+
"""
|
|
8455
|
+
# TODO: Review and Test this function
|
|
8456
|
+
import json
|
|
8457
|
+
|
|
8458
|
+
if self.db is None:
|
|
8459
|
+
return "Previous session messages not available"
|
|
8460
|
+
|
|
8461
|
+
if isinstance(self.db, AsyncBaseDb):
|
|
8462
|
+
selected_sessions = await self.db.get_sessions(
|
|
8463
|
+
session_type=SessionType.AGENT, limit=num_history_sessions
|
|
8464
|
+
)
|
|
8465
|
+
else:
|
|
8466
|
+
selected_sessions = self.db.get_sessions(session_type=SessionType.AGENT, limit=num_history_sessions)
|
|
8467
|
+
|
|
8468
|
+
all_messages = []
|
|
8469
|
+
seen_message_pairs = set()
|
|
8470
|
+
|
|
8471
|
+
for session in selected_sessions:
|
|
8472
|
+
if isinstance(session, AgentSession) and session.runs:
|
|
8473
|
+
message_count = 0
|
|
8474
|
+
for run in session.runs:
|
|
8475
|
+
messages = run.messages
|
|
8476
|
+
if messages is not None:
|
|
8477
|
+
for i in range(0, len(messages) - 1, 2):
|
|
8478
|
+
if i + 1 < len(messages):
|
|
8479
|
+
try:
|
|
8480
|
+
user_msg = messages[i]
|
|
8481
|
+
assistant_msg = messages[i + 1]
|
|
8482
|
+
user_content = user_msg.content
|
|
8483
|
+
assistant_content = assistant_msg.content
|
|
8484
|
+
if user_content is None or assistant_content is None:
|
|
8485
|
+
continue # Skip this pair if either message has no content
|
|
8486
|
+
|
|
8487
|
+
msg_pair_id = f"{user_content}:{assistant_content}"
|
|
8488
|
+
if msg_pair_id not in seen_message_pairs:
|
|
8489
|
+
seen_message_pairs.add(msg_pair_id)
|
|
8490
|
+
all_messages.append(Message.model_validate(user_msg))
|
|
8491
|
+
all_messages.append(Message.model_validate(assistant_msg))
|
|
8492
|
+
message_count += 1
|
|
8493
|
+
except Exception as e:
|
|
8494
|
+
log_warning(f"Error processing message pair: {e}")
|
|
8495
|
+
continue
|
|
8496
|
+
|
|
8497
|
+
return json.dumps([msg.to_dict() for msg in all_messages]) if all_messages else "No history found"
|
|
8498
|
+
|
|
8499
|
+
return aget_previous_session_messages
|
|
8500
|
+
|
|
7354
8501
|
###########################################################################
|
|
7355
8502
|
# Print Response
|
|
7356
8503
|
###########################################################################
|
|
@@ -7384,6 +8531,11 @@ class Agent:
|
|
|
7384
8531
|
tags_to_include_in_markdown: Optional[Set[str]] = None,
|
|
7385
8532
|
**kwargs: Any,
|
|
7386
8533
|
) -> None:
|
|
8534
|
+
if self._has_async_db():
|
|
8535
|
+
raise Exception(
|
|
8536
|
+
"This method is not supported with an async DB. Please use the async version of this method."
|
|
8537
|
+
)
|
|
8538
|
+
|
|
7387
8539
|
if not tags_to_include_in_markdown:
|
|
7388
8540
|
tags_to_include_in_markdown = {"think", "thinking"}
|
|
7389
8541
|
|
|
@@ -7715,6 +8867,56 @@ class Agent:
|
|
|
7715
8867
|
message.image_output = None
|
|
7716
8868
|
message.video_output = None
|
|
7717
8869
|
|
|
8870
|
+
def _scrub_tool_results_from_run_output(self, run_response: RunOutput) -> None:
|
|
8871
|
+
"""
|
|
8872
|
+
Remove all tool-related data from RunOutput when store_tool_results=False.
|
|
8873
|
+
This includes tool calls, tool results, and tool-related message fields.
|
|
8874
|
+
"""
|
|
8875
|
+
# Remove tool results (messages with role="tool")
|
|
8876
|
+
if run_response.messages:
|
|
8877
|
+
run_response.messages = [msg for msg in run_response.messages if msg.role != "tool"]
|
|
8878
|
+
# Also scrub tool-related fields from remaining messages
|
|
8879
|
+
for message in run_response.messages:
|
|
8880
|
+
self._scrub_tool_data_from_message(message)
|
|
8881
|
+
|
|
8882
|
+
def _scrub_tool_data_from_message(self, message: Message) -> None:
|
|
8883
|
+
"""Remove all tool-related data from a Message object."""
|
|
8884
|
+
message.tool_calls = None
|
|
8885
|
+
message.tool_call_id = None
|
|
8886
|
+
message.tool_name = None
|
|
8887
|
+
message.tool_args = None
|
|
8888
|
+
message.tool_call_error = None
|
|
8889
|
+
|
|
8890
|
+
def _scrub_history_messages_from_run_output(self, run_response: RunOutput) -> None:
|
|
8891
|
+
"""
|
|
8892
|
+
Remove all history messages from RunOutput when store_history_messages=False.
|
|
8893
|
+
This removes messages that were loaded from the agent's memory.
|
|
8894
|
+
"""
|
|
8895
|
+
# Remove messages with from_history=True
|
|
8896
|
+
if run_response.messages:
|
|
8897
|
+
run_response.messages = [msg for msg in run_response.messages if not msg.from_history]
|
|
8898
|
+
|
|
8899
|
+
def _scrub_run_output_for_storage(self, run_response: RunOutput) -> bool:
|
|
8900
|
+
"""
|
|
8901
|
+
Scrub run output based on storage flags before persisting to database.
|
|
8902
|
+
Returns True if any scrubbing was done, False otherwise.
|
|
8903
|
+
"""
|
|
8904
|
+
scrubbed = False
|
|
8905
|
+
|
|
8906
|
+
if not self.store_media:
|
|
8907
|
+
self._scrub_media_from_run_output(run_response)
|
|
8908
|
+
scrubbed = True
|
|
8909
|
+
|
|
8910
|
+
if not self.store_tool_results:
|
|
8911
|
+
self._scrub_tool_results_from_run_output(run_response)
|
|
8912
|
+
scrubbed = True
|
|
8913
|
+
|
|
8914
|
+
if not self.store_history_messages:
|
|
8915
|
+
self._scrub_history_messages_from_run_output(run_response)
|
|
8916
|
+
scrubbed = True
|
|
8917
|
+
|
|
8918
|
+
return scrubbed
|
|
8919
|
+
|
|
7718
8920
|
def _validate_media_object_id(
|
|
7719
8921
|
self,
|
|
7720
8922
|
images: Optional[Sequence[Image]] = None,
|