agno 2.1.4__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 +1767 -535
- 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 +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/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 +8 -7
- agno/os/interfaces/a2a/router.py +3 -5
- agno/os/interfaces/agui/router.py +4 -1
- agno/os/interfaces/agui/utils.py +27 -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 +36 -0
- agno/os/utils.py +67 -12
- 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 +3 -3
- agno/team/team.py +918 -175
- 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/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 +4 -0
- agno/workflow/loop.py +4 -0
- agno/workflow/parallel.py +4 -0
- agno/workflow/router.py +4 -0
- agno/workflow/step.py +22 -14
- agno/workflow/steps.py +4 -0
- agno/workflow/types.py +2 -2
- agno/workflow/workflow.py +328 -61
- {agno-2.1.4.dist-info → agno-2.1.5.dist-info}/METADATA +100 -41
- {agno-2.1.4.dist-info → agno-2.1.5.dist-info}/RECORD +88 -81
- {agno-2.1.4.dist-info → agno-2.1.5.dist-info}/WHEEL +0 -0
- {agno-2.1.4.dist-info → agno-2.1.5.dist-info}/licenses/LICENSE +0 -0
- {agno-2.1.4.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
|
|
@@ -1133,7 +1147,11 @@ class Agent:
|
|
|
1133
1147
|
create_run_completed_event(from_run_response=run_response), run_response
|
|
1134
1148
|
)
|
|
1135
1149
|
|
|
1136
|
-
# 10.
|
|
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
|
|
1137
1155
|
self.save_session(session=session)
|
|
1138
1156
|
|
|
1139
1157
|
if stream_intermediate_steps:
|
|
@@ -1242,6 +1260,10 @@ class Agent:
|
|
|
1242
1260
|
**kwargs: Any,
|
|
1243
1261
|
) -> Union[RunOutput, Iterator[Union[RunOutputEvent, RunOutput]]]:
|
|
1244
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
|
+
)
|
|
1245
1267
|
|
|
1246
1268
|
# Create a run_id for this specific run
|
|
1247
1269
|
run_id = str(uuid4())
|
|
@@ -1451,8 +1473,9 @@ class Agent:
|
|
|
1451
1473
|
|
|
1452
1474
|
async def _arun(
|
|
1453
1475
|
self,
|
|
1476
|
+
input: Union[str, List, Dict, Message, BaseModel, List[Message]],
|
|
1454
1477
|
run_response: RunOutput,
|
|
1455
|
-
|
|
1478
|
+
session_id: str,
|
|
1456
1479
|
session_state: Optional[Dict[str, Any]] = None,
|
|
1457
1480
|
user_id: Optional[str] = None,
|
|
1458
1481
|
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
@@ -1468,27 +1491,42 @@ class Agent:
|
|
|
1468
1491
|
"""Run the Agent and yield the RunOutput.
|
|
1469
1492
|
|
|
1470
1493
|
Steps:
|
|
1471
|
-
1.
|
|
1472
|
-
2.
|
|
1473
|
-
3.
|
|
1474
|
-
4.
|
|
1475
|
-
5.
|
|
1476
|
-
6.
|
|
1477
|
-
7.
|
|
1478
|
-
8.
|
|
1479
|
-
9.
|
|
1480
|
-
10.
|
|
1481
|
-
11.
|
|
1482
|
-
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
|
|
1483
1508
|
"""
|
|
1509
|
+
log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
|
|
1510
|
+
|
|
1484
1511
|
# Register run for cancellation tracking
|
|
1485
1512
|
register_run(run_response.run_id) # type: ignore
|
|
1486
1513
|
|
|
1487
|
-
# 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
|
|
1488
1526
|
if dependencies is not None:
|
|
1489
|
-
await self._aresolve_run_dependencies(dependencies)
|
|
1527
|
+
await self._aresolve_run_dependencies(dependencies=dependencies)
|
|
1490
1528
|
|
|
1491
|
-
#
|
|
1529
|
+
# 4. Execute pre-hooks
|
|
1492
1530
|
run_input = cast(RunInput, run_response.input)
|
|
1493
1531
|
self.model = cast(Model, self.model)
|
|
1494
1532
|
if self.pre_hooks is not None:
|
|
@@ -1497,7 +1535,7 @@ class Agent:
|
|
|
1497
1535
|
hooks=self.pre_hooks, # type: ignore
|
|
1498
1536
|
run_response=run_response,
|
|
1499
1537
|
run_input=run_input,
|
|
1500
|
-
session=
|
|
1538
|
+
session=agent_session,
|
|
1501
1539
|
user_id=user_id,
|
|
1502
1540
|
debug_mode=debug_mode,
|
|
1503
1541
|
**kwargs,
|
|
@@ -1506,10 +1544,12 @@ class Agent:
|
|
|
1506
1544
|
async for _ in pre_hook_iterator:
|
|
1507
1545
|
pass
|
|
1508
1546
|
|
|
1509
|
-
|
|
1547
|
+
# 5. Determine tools for model
|
|
1548
|
+
self.model = cast(Model, self.model)
|
|
1549
|
+
await self._adetermine_tools_for_model(
|
|
1510
1550
|
model=self.model,
|
|
1511
1551
|
run_response=run_response,
|
|
1512
|
-
session=
|
|
1552
|
+
session=agent_session,
|
|
1513
1553
|
session_state=session_state,
|
|
1514
1554
|
dependencies=dependencies,
|
|
1515
1555
|
user_id=user_id,
|
|
@@ -1517,11 +1557,11 @@ class Agent:
|
|
|
1517
1557
|
knowledge_filters=knowledge_filters,
|
|
1518
1558
|
)
|
|
1519
1559
|
|
|
1520
|
-
#
|
|
1521
|
-
run_messages: RunMessages = self.
|
|
1560
|
+
# 6. Prepare run messages
|
|
1561
|
+
run_messages: RunMessages = await self._aget_run_messages(
|
|
1522
1562
|
run_response=run_response,
|
|
1523
1563
|
input=run_input.input_content,
|
|
1524
|
-
session=
|
|
1564
|
+
session=agent_session,
|
|
1525
1565
|
session_state=session_state,
|
|
1526
1566
|
user_id=user_id,
|
|
1527
1567
|
audio=run_input.audios,
|
|
@@ -1539,110 +1579,134 @@ class Agent:
|
|
|
1539
1579
|
if len(run_messages.messages) == 0:
|
|
1540
1580
|
log_error("No messages to be sent to the model.")
|
|
1541
1581
|
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
# Check for cancellation before model call
|
|
1548
|
-
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
|
|
1549
1586
|
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
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
|
|
1560
1598
|
|
|
1561
|
-
|
|
1562
|
-
|
|
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)
|
|
1563
1603
|
|
|
1564
|
-
|
|
1565
|
-
|
|
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
|
+
)
|
|
1566
1608
|
|
|
1567
|
-
|
|
1568
|
-
|
|
1609
|
+
# Optional: Store media
|
|
1610
|
+
if self.store_media:
|
|
1611
|
+
self._store_media(run_response, model_response)
|
|
1569
1612
|
|
|
1570
|
-
|
|
1571
|
-
|
|
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
|
|
1572
1619
|
|
|
1573
|
-
|
|
1574
|
-
self.
|
|
1575
|
-
else:
|
|
1576
|
-
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)
|
|
1577
1622
|
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
return self._handle_agent_run_paused(
|
|
1581
|
-
run_response=run_response, run_messages=run_messages, session=session, user_id=user_id
|
|
1582
|
-
)
|
|
1623
|
+
# Set the run status to completed
|
|
1624
|
+
run_response.status = RunStatus.completed
|
|
1583
1625
|
|
|
1584
|
-
|
|
1626
|
+
# Convert the response to the structured format if needed
|
|
1627
|
+
self._convert_response_to_structured_format(run_response)
|
|
1585
1628
|
|
|
1586
|
-
|
|
1587
|
-
|
|
1629
|
+
# Set the run duration
|
|
1630
|
+
if run_response.metrics:
|
|
1631
|
+
run_response.metrics.stop_timer()
|
|
1588
1632
|
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
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
|
+
)
|
|
1592
1643
|
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
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,
|
|
1599
1649
|
user_id=user_id,
|
|
1600
|
-
debug_mode=debug_mode,
|
|
1601
|
-
**kwargs,
|
|
1602
1650
|
)
|
|
1603
1651
|
|
|
1604
|
-
|
|
1605
|
-
|
|
1652
|
+
# 11. Add RunOutput to Agent Session
|
|
1653
|
+
agent_session.upsert_run(run=run_response)
|
|
1606
1654
|
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
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
|
|
1611
1660
|
|
|
1612
|
-
|
|
1613
|
-
|
|
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)
|
|
1614
1664
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
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)
|
|
1620
1670
|
|
|
1621
|
-
|
|
1622
|
-
|
|
1671
|
+
# Log Agent Telemetry
|
|
1672
|
+
await self._alog_agent_telemetry(session_id=agent_session.session_id, run_id=run_response.run_id)
|
|
1623
1673
|
|
|
1624
|
-
|
|
1625
|
-
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="*")
|
|
1626
1675
|
|
|
1627
|
-
|
|
1676
|
+
return run_response
|
|
1628
1677
|
|
|
1629
|
-
|
|
1630
|
-
|
|
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
|
|
1631
1683
|
|
|
1632
|
-
|
|
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
|
|
1633
1696
|
|
|
1634
1697
|
async def _arun_stream(
|
|
1635
1698
|
self,
|
|
1699
|
+
input: Union[str, List, Dict, Message, BaseModel, List[Message]],
|
|
1636
1700
|
run_response: RunOutput,
|
|
1637
|
-
|
|
1701
|
+
session_id: str,
|
|
1638
1702
|
session_state: Optional[Dict[str, Any]] = None,
|
|
1639
1703
|
user_id: Optional[str] = None,
|
|
1640
1704
|
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
1705
|
+
dependencies: Optional[Dict[str, Any]] = None,
|
|
1641
1706
|
add_history_to_context: Optional[bool] = None,
|
|
1642
1707
|
add_dependencies_to_context: Optional[bool] = None,
|
|
1643
1708
|
add_session_state_to_context: Optional[bool] = None,
|
|
1644
1709
|
metadata: Optional[Dict[str, Any]] = None,
|
|
1645
|
-
dependencies: Optional[Dict[str, Any]] = None,
|
|
1646
1710
|
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
1647
1711
|
stream_intermediate_steps: bool = False,
|
|
1648
1712
|
yield_run_response: Optional[bool] = None,
|
|
@@ -1652,33 +1716,51 @@ class Agent:
|
|
|
1652
1716
|
"""Run the Agent and yield the RunOutput.
|
|
1653
1717
|
|
|
1654
1718
|
Steps:
|
|
1655
|
-
1.
|
|
1656
|
-
2.
|
|
1657
|
-
3.
|
|
1658
|
-
4.
|
|
1659
|
-
5.
|
|
1660
|
-
6.
|
|
1661
|
-
7.
|
|
1662
|
-
8.
|
|
1663
|
-
9.
|
|
1664
|
-
10.
|
|
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
|
|
1665
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)
|
|
1666
1749
|
|
|
1667
|
-
#
|
|
1750
|
+
# 3. Resolve dependencies
|
|
1668
1751
|
if dependencies is not None:
|
|
1669
1752
|
await self._aresolve_run_dependencies(dependencies=dependencies)
|
|
1670
1753
|
|
|
1671
|
-
#
|
|
1754
|
+
# 4. Execute pre-hooks
|
|
1672
1755
|
run_input = cast(RunInput, run_response.input)
|
|
1673
1756
|
self.model = cast(Model, self.model)
|
|
1674
|
-
|
|
1675
1757
|
if self.pre_hooks is not None:
|
|
1676
1758
|
# Can modify the run input
|
|
1677
1759
|
pre_hook_iterator = self._aexecute_pre_hooks(
|
|
1678
1760
|
hooks=self.pre_hooks, # type: ignore
|
|
1679
1761
|
run_response=run_response,
|
|
1680
1762
|
run_input=run_input,
|
|
1681
|
-
session=
|
|
1763
|
+
session=agent_session,
|
|
1682
1764
|
user_id=user_id,
|
|
1683
1765
|
debug_mode=debug_mode,
|
|
1684
1766
|
**kwargs,
|
|
@@ -1686,22 +1768,24 @@ class Agent:
|
|
|
1686
1768
|
async for event in pre_hook_iterator:
|
|
1687
1769
|
yield event
|
|
1688
1770
|
|
|
1771
|
+
# 5. Determine tools for model
|
|
1772
|
+
self.model = cast(Model, self.model)
|
|
1689
1773
|
self._determine_tools_for_model(
|
|
1690
1774
|
model=self.model,
|
|
1691
1775
|
run_response=run_response,
|
|
1692
|
-
session=
|
|
1776
|
+
session=agent_session,
|
|
1693
1777
|
session_state=session_state,
|
|
1694
|
-
dependencies=dependencies,
|
|
1695
1778
|
user_id=user_id,
|
|
1696
1779
|
async_mode=True,
|
|
1697
1780
|
knowledge_filters=knowledge_filters,
|
|
1781
|
+
dependencies=dependencies,
|
|
1698
1782
|
)
|
|
1699
1783
|
|
|
1700
|
-
#
|
|
1701
|
-
run_messages: RunMessages = self.
|
|
1784
|
+
# 6. Prepare run messages
|
|
1785
|
+
run_messages: RunMessages = await self._aget_run_messages(
|
|
1702
1786
|
run_response=run_response,
|
|
1703
1787
|
input=run_input.input_content,
|
|
1704
|
-
session=
|
|
1788
|
+
session=agent_session,
|
|
1705
1789
|
session_state=session_state,
|
|
1706
1790
|
user_id=user_id,
|
|
1707
1791
|
audio=run_input.audios,
|
|
@@ -1716,29 +1800,23 @@ class Agent:
|
|
|
1716
1800
|
metadata=metadata,
|
|
1717
1801
|
**kwargs,
|
|
1718
1802
|
)
|
|
1719
|
-
|
|
1720
|
-
|
|
1803
|
+
if len(run_messages.messages) == 0:
|
|
1804
|
+
log_error("No messages to be sent to the model.")
|
|
1721
1805
|
|
|
1722
1806
|
# Register run for cancellation tracking
|
|
1723
1807
|
register_run(run_response.run_id) # type: ignore
|
|
1724
1808
|
|
|
1725
1809
|
try:
|
|
1726
|
-
#
|
|
1727
|
-
if stream_intermediate_steps:
|
|
1728
|
-
yield self._handle_event(create_run_started_event(run_response), run_response)
|
|
1729
|
-
|
|
1730
|
-
# 4. Reason about the task if reasoning is enabled
|
|
1810
|
+
# 7. Reason about the task if reasoning is enabled
|
|
1731
1811
|
async for item in self._ahandle_reasoning_stream(run_response=run_response, run_messages=run_messages):
|
|
1732
1812
|
raise_if_cancelled(run_response.run_id) # type: ignore
|
|
1733
1813
|
yield item
|
|
1734
|
-
|
|
1735
|
-
# Check for cancellation before model processing
|
|
1736
1814
|
raise_if_cancelled(run_response.run_id) # type: ignore
|
|
1737
1815
|
|
|
1738
|
-
#
|
|
1816
|
+
# 8. Generate a response from the Model
|
|
1739
1817
|
if self.output_model is None:
|
|
1740
1818
|
async for event in self._ahandle_model_response_stream(
|
|
1741
|
-
session=
|
|
1819
|
+
session=agent_session,
|
|
1742
1820
|
run_response=run_response,
|
|
1743
1821
|
run_messages=run_messages,
|
|
1744
1822
|
response_format=response_format,
|
|
@@ -1753,7 +1831,7 @@ class Agent:
|
|
|
1753
1831
|
) # type: ignore
|
|
1754
1832
|
|
|
1755
1833
|
async for event in self._ahandle_model_response_stream(
|
|
1756
|
-
session=
|
|
1834
|
+
session=agent_session,
|
|
1757
1835
|
run_response=run_response,
|
|
1758
1836
|
run_messages=run_messages,
|
|
1759
1837
|
response_format=response_format,
|
|
@@ -1771,7 +1849,7 @@ class Agent:
|
|
|
1771
1849
|
|
|
1772
1850
|
# If an output model is provided, generate output using the output model
|
|
1773
1851
|
async for event in self._agenerate_response_with_output_model_stream(
|
|
1774
|
-
session=
|
|
1852
|
+
session=agent_session,
|
|
1775
1853
|
run_response=run_response,
|
|
1776
1854
|
run_messages=run_messages,
|
|
1777
1855
|
stream_intermediate_steps=stream_intermediate_steps,
|
|
@@ -1784,51 +1862,59 @@ class Agent:
|
|
|
1784
1862
|
|
|
1785
1863
|
# If a parser model is provided, structure the response separately
|
|
1786
1864
|
async for event in self._aparse_response_with_parser_model_stream(
|
|
1787
|
-
session=
|
|
1865
|
+
session=agent_session, run_response=run_response, stream_intermediate_steps=stream_intermediate_steps
|
|
1788
1866
|
):
|
|
1789
1867
|
yield event
|
|
1790
1868
|
|
|
1791
|
-
#
|
|
1869
|
+
# Break out of the run function if a tool call is paused
|
|
1792
1870
|
if any(tool_call.is_paused for tool_call in run_response.tools or []):
|
|
1793
1871
|
for item in self._handle_agent_run_paused_stream(
|
|
1794
|
-
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
|
|
1795
1873
|
):
|
|
1796
1874
|
yield item
|
|
1797
1875
|
return
|
|
1798
1876
|
|
|
1877
|
+
# Set the run status to completed
|
|
1799
1878
|
run_response.status = RunStatus.completed
|
|
1800
1879
|
|
|
1801
1880
|
# Set the run duration
|
|
1802
1881
|
if run_response.metrics:
|
|
1803
1882
|
run_response.metrics.stop_timer()
|
|
1804
1883
|
|
|
1805
|
-
#
|
|
1806
|
-
self._update_session_metrics(session=
|
|
1884
|
+
# 9. Calculate session metrics
|
|
1885
|
+
self._update_session_metrics(session=agent_session, run_response=run_response)
|
|
1807
1886
|
|
|
1808
1887
|
# Optional: Save output to file if save_response_to_file is set
|
|
1809
1888
|
self.save_run_response_to_file(
|
|
1810
1889
|
run_response=run_response,
|
|
1811
1890
|
input=run_messages.user_message,
|
|
1812
|
-
session_id=
|
|
1891
|
+
session_id=agent_session.session_id,
|
|
1813
1892
|
user_id=user_id,
|
|
1814
1893
|
)
|
|
1815
1894
|
|
|
1816
|
-
#
|
|
1817
|
-
|
|
1895
|
+
# 10. Add RunOutput to Agent Session
|
|
1896
|
+
agent_session.upsert_run(run=run_response)
|
|
1818
1897
|
|
|
1819
|
-
#
|
|
1898
|
+
# 11. Update Agent Memory
|
|
1820
1899
|
async for event in self._amake_memories_and_summaries(
|
|
1821
|
-
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
|
|
1822
1901
|
):
|
|
1823
1902
|
yield event
|
|
1824
1903
|
|
|
1825
|
-
#
|
|
1904
|
+
# 12. Create the run completed event
|
|
1826
1905
|
completed_event = self._handle_event(
|
|
1827
1906
|
create_run_completed_event(from_run_response=run_response), run_response
|
|
1828
1907
|
)
|
|
1829
1908
|
|
|
1830
|
-
#
|
|
1831
|
-
self.
|
|
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)
|
|
1832
1918
|
|
|
1833
1919
|
if stream_intermediate_steps:
|
|
1834
1920
|
yield completed_event
|
|
@@ -1837,7 +1923,7 @@ class Agent:
|
|
|
1837
1923
|
yield run_response
|
|
1838
1924
|
|
|
1839
1925
|
# Log Agent Telemetry
|
|
1840
|
-
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)
|
|
1841
1927
|
|
|
1842
1928
|
log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
|
|
1843
1929
|
|
|
@@ -1854,8 +1940,11 @@ class Agent:
|
|
|
1854
1940
|
)
|
|
1855
1941
|
|
|
1856
1942
|
# Add the RunOutput to Agent Session even when cancelled
|
|
1857
|
-
|
|
1858
|
-
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)
|
|
1859
1948
|
finally:
|
|
1860
1949
|
# Always clean up the run tracking
|
|
1861
1950
|
cleanup_run(run_response.run_id) # type: ignore
|
|
@@ -1939,7 +2028,7 @@ class Agent:
|
|
|
1939
2028
|
# Create a run_id for this specific run
|
|
1940
2029
|
run_id = str(uuid4())
|
|
1941
2030
|
|
|
1942
|
-
# Validate input against input_schema if provided
|
|
2031
|
+
# 2. Validate input against input_schema if provided
|
|
1943
2032
|
validated_input = self._validate_input(input)
|
|
1944
2033
|
|
|
1945
2034
|
# Normalise hook & guardails
|
|
@@ -1950,6 +2039,7 @@ class Agent:
|
|
|
1950
2039
|
self.post_hooks = normalize_hooks(self.post_hooks, async_mode=True)
|
|
1951
2040
|
self._hooks_normalised = True
|
|
1952
2041
|
|
|
2042
|
+
# Initialize session
|
|
1953
2043
|
session_id, user_id, session_state = self._initialize_session(
|
|
1954
2044
|
run_id=run_id, session_id=session_id, user_id=user_id, session_state=session_state
|
|
1955
2045
|
)
|
|
@@ -1961,25 +2051,8 @@ class Agent:
|
|
|
1961
2051
|
images=images, videos=videos, audios=audio, files=files
|
|
1962
2052
|
)
|
|
1963
2053
|
|
|
1964
|
-
#
|
|
1965
|
-
run_input = RunInput(
|
|
1966
|
-
input_content=validated_input,
|
|
1967
|
-
images=image_artifacts,
|
|
1968
|
-
videos=video_artifacts,
|
|
1969
|
-
audios=audio_artifacts,
|
|
1970
|
-
files=file_artifacts,
|
|
1971
|
-
)
|
|
1972
|
-
|
|
1973
|
-
# Read existing session from storage
|
|
1974
|
-
agent_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
|
|
1975
|
-
self._update_metadata(session=agent_session)
|
|
1976
|
-
|
|
1977
|
-
# Update session state from DB
|
|
1978
|
-
session_state = self._load_session_state(session=agent_session, session_state=session_state)
|
|
1979
|
-
|
|
1980
|
-
# Determine run dependencies
|
|
2054
|
+
# Resolve variables
|
|
1981
2055
|
run_dependencies = dependencies if dependencies is not None else self.dependencies
|
|
1982
|
-
|
|
1983
2056
|
add_dependencies = (
|
|
1984
2057
|
add_dependencies_to_context if add_dependencies_to_context is not None else self.add_dependencies_to_context
|
|
1985
2058
|
)
|
|
@@ -1990,10 +2063,14 @@ class Agent:
|
|
|
1990
2063
|
)
|
|
1991
2064
|
add_history = add_history_to_context if add_history_to_context is not None else self.add_history_to_context
|
|
1992
2065
|
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
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
|
+
)
|
|
1997
2074
|
|
|
1998
2075
|
# Use stream override value when necessary
|
|
1999
2076
|
if stream is None:
|
|
@@ -2015,6 +2092,11 @@ class Agent:
|
|
|
2015
2092
|
response_format = self._get_response_format() if self.parser_model is None else None
|
|
2016
2093
|
self.model = cast(Model, self.model)
|
|
2017
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
|
+
|
|
2018
2100
|
# Merge agent metadata with run metadata
|
|
2019
2101
|
if self.metadata is not None:
|
|
2020
2102
|
if metadata is None:
|
|
@@ -2051,37 +2133,37 @@ class Agent:
|
|
|
2051
2133
|
# Pass the new run_response to _arun
|
|
2052
2134
|
if stream:
|
|
2053
2135
|
return self._arun_stream( # type: ignore
|
|
2136
|
+
input=validated_input,
|
|
2054
2137
|
run_response=run_response,
|
|
2055
|
-
session=agent_session,
|
|
2056
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,
|
|
2057
2144
|
session_state=session_state,
|
|
2058
2145
|
knowledge_filters=effective_filters,
|
|
2059
2146
|
add_history_to_context=add_history,
|
|
2060
2147
|
add_dependencies_to_context=add_dependencies,
|
|
2061
2148
|
add_session_state_to_context=add_session_state,
|
|
2062
2149
|
metadata=metadata,
|
|
2063
|
-
response_format=response_format,
|
|
2064
|
-
stream_intermediate_steps=stream_intermediate_steps,
|
|
2065
|
-
yield_run_response=yield_run_response,
|
|
2066
|
-
dependencies=run_dependencies,
|
|
2067
2150
|
debug_mode=debug_mode,
|
|
2068
2151
|
**kwargs,
|
|
2069
2152
|
) # type: ignore[assignment]
|
|
2070
2153
|
else:
|
|
2071
2154
|
return self._arun( # type: ignore
|
|
2155
|
+
input=validated_input,
|
|
2072
2156
|
run_response=run_response,
|
|
2073
2157
|
user_id=user_id,
|
|
2074
|
-
|
|
2158
|
+
response_format=response_format,
|
|
2159
|
+
dependencies=run_dependencies,
|
|
2160
|
+
session_id=session_id,
|
|
2075
2161
|
session_state=session_state,
|
|
2076
|
-
knowledge_filters=
|
|
2162
|
+
knowledge_filters=effective_filters,
|
|
2077
2163
|
add_history_to_context=add_history,
|
|
2078
2164
|
add_dependencies_to_context=add_dependencies,
|
|
2079
2165
|
add_session_state_to_context=add_session_state,
|
|
2080
2166
|
metadata=metadata,
|
|
2081
|
-
response_format=response_format,
|
|
2082
|
-
stream_intermediate_steps=stream_intermediate_steps,
|
|
2083
|
-
yield_run_response=yield_run_response,
|
|
2084
|
-
dependencies=run_dependencies,
|
|
2085
2167
|
debug_mode=debug_mode,
|
|
2086
2168
|
**kwargs,
|
|
2087
2169
|
)
|
|
@@ -2102,17 +2184,6 @@ class Agent:
|
|
|
2102
2184
|
import time
|
|
2103
2185
|
|
|
2104
2186
|
time.sleep(delay)
|
|
2105
|
-
except RunCancelledException as e:
|
|
2106
|
-
# Handle run cancellation
|
|
2107
|
-
log_info(f"Run {run_response.run_id} was cancelled")
|
|
2108
|
-
run_response.content = str(e)
|
|
2109
|
-
run_response.status = RunStatus.cancelled
|
|
2110
|
-
|
|
2111
|
-
# Add the RunOutput to Agent Session even when cancelled
|
|
2112
|
-
agent_session.upsert_run(run=run_response)
|
|
2113
|
-
self.save_session(session=agent_session)
|
|
2114
|
-
|
|
2115
|
-
return run_response
|
|
2116
2187
|
except KeyboardInterrupt:
|
|
2117
2188
|
run_response.content = "Operation cancelled by user"
|
|
2118
2189
|
run_response.status = RunStatus.cancelled
|
|
@@ -2209,6 +2280,9 @@ class Agent:
|
|
|
2209
2280
|
if run_response is None and (run_id is not None and (session_id is None and self.session_id is None)):
|
|
2210
2281
|
raise ValueError("Session ID is required to continue a run from a run_id.")
|
|
2211
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
|
+
|
|
2212
2286
|
session_id = run_response.session_id if run_response else session_id
|
|
2213
2287
|
|
|
2214
2288
|
session_id, user_id, session_state = self._initialize_session(
|
|
@@ -2589,6 +2663,7 @@ class Agent:
|
|
|
2589
2663
|
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
2590
2664
|
dependencies: Optional[Dict[str, Any]] = None,
|
|
2591
2665
|
debug_mode: Optional[bool] = None,
|
|
2666
|
+
yield_run_response: bool = False,
|
|
2592
2667
|
**kwargs,
|
|
2593
2668
|
) -> Union[RunOutput, AsyncIterator[Union[RunOutputEvent, RunOutput]]]:
|
|
2594
2669
|
"""Continue a previous run.
|
|
@@ -2621,21 +2696,8 @@ class Agent:
|
|
|
2621
2696
|
# Initialize the Agent
|
|
2622
2697
|
self.initialize_agent(debug_mode=debug_mode)
|
|
2623
2698
|
|
|
2624
|
-
# Read existing session from storage
|
|
2625
|
-
agent_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
|
|
2626
|
-
self._update_metadata(session=agent_session)
|
|
2627
|
-
|
|
2628
|
-
# Update session state from DB
|
|
2629
|
-
session_state = self._load_session_state(session=agent_session, session_state=session_state)
|
|
2630
|
-
|
|
2631
2699
|
run_dependencies = dependencies if dependencies is not None else self.dependencies
|
|
2632
2700
|
|
|
2633
|
-
effective_filters = knowledge_filters
|
|
2634
|
-
|
|
2635
|
-
# When filters are passed manually
|
|
2636
|
-
if self.knowledge_filters or knowledge_filters:
|
|
2637
|
-
effective_filters = self._get_effective_filters(knowledge_filters)
|
|
2638
|
-
|
|
2639
2701
|
# If no retries are set, use the agent's default retries
|
|
2640
2702
|
retries = retries if retries is not None else self.retries
|
|
2641
2703
|
|
|
@@ -2655,70 +2717,42 @@ class Agent:
|
|
|
2655
2717
|
self.stream = self.stream or stream
|
|
2656
2718
|
self.stream_intermediate_steps = self.stream_intermediate_steps or (stream_intermediate_steps and self.stream)
|
|
2657
2719
|
|
|
2658
|
-
#
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
elif run_id is not None:
|
|
2663
|
-
# The run is continued from a run_id. This requires the updated tools to be passed.
|
|
2664
|
-
if updated_tools is None:
|
|
2665
|
-
raise ValueError("Updated tools are required to continue a run from a run_id.")
|
|
2666
|
-
|
|
2667
|
-
runs = agent_session.runs
|
|
2668
|
-
run_response = next((r for r in runs if r.run_id == run_id), None) # type: ignore
|
|
2669
|
-
if run_response is None:
|
|
2670
|
-
raise RuntimeError(f"No runs found for run ID {run_id}")
|
|
2671
|
-
run_response.tools = updated_tools
|
|
2672
|
-
input = run_response.messages or []
|
|
2673
|
-
else:
|
|
2674
|
-
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)
|
|
2675
2724
|
|
|
2676
2725
|
# Prepare arguments for the model
|
|
2677
2726
|
response_format = self._get_response_format()
|
|
2678
2727
|
self.model = cast(Model, self.model)
|
|
2679
2728
|
|
|
2680
|
-
self._determine_tools_for_model(
|
|
2681
|
-
model=self.model,
|
|
2682
|
-
run_response=run_response,
|
|
2683
|
-
session=agent_session,
|
|
2684
|
-
session_state=session_state,
|
|
2685
|
-
user_id=user_id,
|
|
2686
|
-
async_mode=True,
|
|
2687
|
-
knowledge_filters=effective_filters,
|
|
2688
|
-
)
|
|
2689
|
-
|
|
2690
2729
|
last_exception = None
|
|
2691
2730
|
num_attempts = retries + 1
|
|
2692
2731
|
for attempt in range(num_attempts):
|
|
2693
|
-
run_response = cast(RunOutput, run_response)
|
|
2694
|
-
|
|
2695
|
-
log_debug(f"Agent Run Start: {run_response.run_id}", center=True)
|
|
2696
|
-
|
|
2697
|
-
# Prepare run messages
|
|
2698
|
-
run_messages: RunMessages = self._get_continue_run_messages(
|
|
2699
|
-
input=input,
|
|
2700
|
-
)
|
|
2701
|
-
|
|
2702
|
-
# Reset the run paused state
|
|
2703
|
-
run_response.status = RunStatus.running
|
|
2704
|
-
|
|
2705
2732
|
try:
|
|
2706
2733
|
if stream:
|
|
2707
2734
|
return self._acontinue_run_stream(
|
|
2708
2735
|
run_response=run_response,
|
|
2709
|
-
|
|
2736
|
+
updated_tools=updated_tools,
|
|
2737
|
+
knowledge_filters=effective_filters,
|
|
2738
|
+
session_state=session_state,
|
|
2739
|
+
run_id=run_id,
|
|
2710
2740
|
user_id=user_id,
|
|
2711
|
-
|
|
2741
|
+
session_id=session_id,
|
|
2712
2742
|
response_format=response_format,
|
|
2713
|
-
stream_intermediate_steps=stream_intermediate_steps,
|
|
2714
2743
|
dependencies=run_dependencies,
|
|
2744
|
+
stream_intermediate_steps=stream_intermediate_steps,
|
|
2745
|
+
yield_run_response=yield_run_response,
|
|
2715
2746
|
)
|
|
2716
2747
|
else:
|
|
2717
2748
|
return self._acontinue_run( # type: ignore
|
|
2749
|
+
session_id=session_id,
|
|
2718
2750
|
run_response=run_response,
|
|
2719
|
-
|
|
2751
|
+
updated_tools=updated_tools,
|
|
2752
|
+
knowledge_filters=effective_filters,
|
|
2753
|
+
session_state=session_state,
|
|
2754
|
+
run_id=run_id,
|
|
2720
2755
|
user_id=user_id,
|
|
2721
|
-
session=agent_session,
|
|
2722
2756
|
response_format=response_format,
|
|
2723
2757
|
dependencies=run_dependencies,
|
|
2724
2758
|
debug_mode=debug_mode,
|
|
@@ -2738,6 +2772,7 @@ class Agent:
|
|
|
2738
2772
|
|
|
2739
2773
|
time.sleep(delay)
|
|
2740
2774
|
except KeyboardInterrupt:
|
|
2775
|
+
run_response = cast(RunOutput, run_response)
|
|
2741
2776
|
if stream:
|
|
2742
2777
|
return async_generator_wrapper( # type: ignore
|
|
2743
2778
|
create_run_cancelled_event(run_response, "Operation cancelled by user")
|
|
@@ -2762,9 +2797,12 @@ class Agent:
|
|
|
2762
2797
|
|
|
2763
2798
|
async def _acontinue_run(
|
|
2764
2799
|
self,
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
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,
|
|
2768
2806
|
user_id: Optional[str] = None,
|
|
2769
2807
|
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
2770
2808
|
dependencies: Optional[Dict[str, Any]] = None,
|
|
@@ -2774,175 +2812,408 @@ class Agent:
|
|
|
2774
2812
|
"""Continue a previous run.
|
|
2775
2813
|
|
|
2776
2814
|
Steps:
|
|
2777
|
-
1.
|
|
2778
|
-
2.
|
|
2779
|
-
3.
|
|
2780
|
-
4.
|
|
2781
|
-
5.
|
|
2782
|
-
6.
|
|
2783
|
-
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
|
|
2784
2828
|
"""
|
|
2785
|
-
|
|
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
|
|
2786
2838
|
if dependencies is not None:
|
|
2787
|
-
await self._aresolve_run_dependencies(dependencies)
|
|
2839
|
+
await self._aresolve_run_dependencies(dependencies=dependencies)
|
|
2788
2840
|
|
|
2789
|
-
|
|
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)
|
|
2790
2845
|
|
|
2791
|
-
#
|
|
2792
|
-
|
|
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.")
|
|
2793
2854
|
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
tools=
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
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,
|
|
2802
2878
|
)
|
|
2803
2879
|
|
|
2804
|
-
|
|
2880
|
+
# 6. Prepare run messages
|
|
2881
|
+
run_messages: RunMessages = self._get_continue_run_messages(
|
|
2882
|
+
input=input,
|
|
2883
|
+
)
|
|
2805
2884
|
|
|
2806
|
-
#
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
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,
|
|
2810
2900
|
)
|
|
2901
|
+
# Check for cancellation after model call
|
|
2902
|
+
raise_if_cancelled(run_response.run_id) # type: ignore
|
|
2811
2903
|
|
|
2812
|
-
|
|
2813
|
-
|
|
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)
|
|
2814
2906
|
|
|
2815
|
-
|
|
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)
|
|
2816
2909
|
|
|
2817
|
-
|
|
2818
|
-
|
|
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
|
+
)
|
|
2819
2914
|
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2915
|
+
if self.store_media:
|
|
2916
|
+
self._store_media(run_response, model_response)
|
|
2917
|
+
else:
|
|
2918
|
+
self._scrub_media_from_run_output(run_response)
|
|
2823
2919
|
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
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,
|
|
2829
2960
|
user_id=user_id,
|
|
2830
|
-
debug_mode=debug_mode,
|
|
2831
|
-
**kwargs,
|
|
2832
2961
|
)
|
|
2833
2962
|
|
|
2834
|
-
|
|
2835
|
-
self.save_run_response_to_file(
|
|
2836
|
-
run_response=run_response, input=run_messages.user_message, session_id=session.session_id, user_id=user_id
|
|
2837
|
-
)
|
|
2963
|
+
agent_session.upsert_run(run=run_response)
|
|
2838
2964
|
|
|
2839
|
-
|
|
2840
|
-
|
|
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)
|
|
2841
2970
|
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
run_response=run_response, run_messages=run_messages, session=session, user_id=user_id
|
|
2845
|
-
):
|
|
2846
|
-
pass
|
|
2971
|
+
# Log Agent Telemetry
|
|
2972
|
+
await self._alog_agent_telemetry(session_id=agent_session.session_id, run_id=run_response.run_id)
|
|
2847
2973
|
|
|
2848
|
-
|
|
2849
|
-
self.save_session(session=session)
|
|
2974
|
+
log_debug(f"Agent Run End: {run_response.run_id}", center=True, symbol="*")
|
|
2850
2975
|
|
|
2851
|
-
|
|
2852
|
-
await self._alog_agent_telemetry(session_id=session.session_id, run_id=run_response.run_id)
|
|
2976
|
+
return run_response
|
|
2853
2977
|
|
|
2854
|
-
|
|
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
|
|
2855
2983
|
|
|
2856
|
-
|
|
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
|
|
2857
2995
|
|
|
2858
2996
|
async def _acontinue_run_stream(
|
|
2859
2997
|
self,
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
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,
|
|
2863
3004
|
user_id: Optional[str] = None,
|
|
2864
3005
|
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
2865
3006
|
stream_intermediate_steps: bool = False,
|
|
3007
|
+
yield_run_response: Optional[bool] = None,
|
|
2866
3008
|
dependencies: Optional[Dict[str, Any]] = None,
|
|
2867
3009
|
) -> AsyncIterator[Union[RunOutputEvent, RunOutput]]:
|
|
2868
3010
|
"""Continue a previous run.
|
|
2869
3011
|
|
|
2870
3012
|
Steps:
|
|
2871
|
-
1.
|
|
2872
|
-
2.
|
|
2873
|
-
3.
|
|
2874
|
-
4.
|
|
2875
|
-
5.
|
|
2876
|
-
6.
|
|
2877
|
-
7.
|
|
2878
|
-
8.
|
|
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
|
|
2879
3027
|
"""
|
|
2880
|
-
#
|
|
3028
|
+
log_debug(f"Agent Run Continue: {run_response.run_id}", center=True) # type: ignore
|
|
3029
|
+
|
|
3030
|
+
# 1. Resolve dependencies
|
|
2881
3031
|
if dependencies is not None:
|
|
2882
3032
|
await self._aresolve_run_dependencies(dependencies=dependencies)
|
|
2883
3033
|
|
|
2884
|
-
#
|
|
2885
|
-
|
|
2886
|
-
yield self._handle_event(create_run_continued_event(run_response), run_response)
|
|
2887
|
-
|
|
2888
|
-
# 1. Handle the updated tools
|
|
2889
|
-
async for event in self._ahandle_tool_call_updates_stream(run_response=run_response, run_messages=run_messages):
|
|
2890
|
-
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)
|
|
2891
3036
|
|
|
2892
|
-
#
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
run_messages=run_messages,
|
|
2897
|
-
response_format=response_format,
|
|
2898
|
-
stream_intermediate_steps=stream_intermediate_steps,
|
|
2899
|
-
):
|
|
2900
|
-
yield event
|
|
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)
|
|
2901
3041
|
|
|
2902
|
-
#
|
|
2903
|
-
if
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
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.")
|
|
2909
3050
|
|
|
2910
|
-
|
|
2911
|
-
|
|
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.")
|
|
2912
3059
|
|
|
2913
|
-
run_response
|
|
3060
|
+
run_response = cast(RunOutput, run_response)
|
|
3061
|
+
run_response.status = RunStatus.running
|
|
2914
3062
|
|
|
2915
|
-
#
|
|
2916
|
-
|
|
2917
|
-
|
|
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
|
+
)
|
|
2918
3075
|
|
|
2919
|
-
#
|
|
2920
|
-
self.
|
|
2921
|
-
|
|
3076
|
+
# 6. Prepare run messages
|
|
3077
|
+
run_messages: RunMessages = self._get_continue_run_messages(
|
|
3078
|
+
input=input,
|
|
2922
3079
|
)
|
|
2923
3080
|
|
|
2924
|
-
#
|
|
2925
|
-
|
|
3081
|
+
# Register run for cancellation tracking
|
|
3082
|
+
register_run(run_response.run_id) # type: ignore
|
|
2926
3083
|
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
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)
|
|
2932
3088
|
|
|
2933
|
-
|
|
2934
|
-
|
|
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
|
|
2935
3095
|
|
|
2936
|
-
|
|
2937
|
-
|
|
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
|
|
2938
3112
|
|
|
2939
|
-
|
|
2940
|
-
|
|
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
|
|
2941
3129
|
|
|
2942
|
-
|
|
2943
|
-
|
|
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
|
|
2944
3139
|
|
|
2945
|
-
|
|
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
|
|
2946
3217
|
|
|
2947
3218
|
def _execute_pre_hooks(
|
|
2948
3219
|
self,
|
|
@@ -4125,7 +4396,7 @@ class Agent:
|
|
|
4125
4396
|
|
|
4126
4397
|
tasks.append(
|
|
4127
4398
|
self.memory_manager.acreate_user_memories(
|
|
4128
|
-
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
|
|
4129
4400
|
)
|
|
4130
4401
|
)
|
|
4131
4402
|
|
|
@@ -4149,7 +4420,9 @@ class Agent:
|
|
|
4149
4420
|
continue
|
|
4150
4421
|
|
|
4151
4422
|
if len(parsed_messages) > 0:
|
|
4152
|
-
tasks.append(
|
|
4423
|
+
tasks.append(
|
|
4424
|
+
self.memory_manager.acreate_user_memories(messages=parsed_messages, user_id=user_id, agent_id=self.id)
|
|
4425
|
+
)
|
|
4153
4426
|
else:
|
|
4154
4427
|
log_warning("Unable to add messages to memory")
|
|
4155
4428
|
|
|
@@ -4298,6 +4571,100 @@ class Agent:
|
|
|
4298
4571
|
|
|
4299
4572
|
return agent_tools
|
|
4300
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
|
+
|
|
4301
4668
|
def _collect_joint_images(
|
|
4302
4669
|
self,
|
|
4303
4670
|
run_input: Optional[RunInput] = None,
|
|
@@ -4551,43 +4918,165 @@ class Agent:
|
|
|
4551
4918
|
func._audios = joint_audios
|
|
4552
4919
|
func._videos = joint_videos
|
|
4553
4920
|
|
|
4554
|
-
def
|
|
4555
|
-
self
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
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
|
|
4561
4934
|
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
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
|
+
)
|
|
4568
4942
|
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
return self.output_schema
|
|
4573
|
-
else:
|
|
4574
|
-
log_debug(
|
|
4575
|
-
"Model supports native structured outputs but it is not enabled. Using JSON mode instead."
|
|
4576
|
-
)
|
|
4577
|
-
return json_response_format
|
|
4943
|
+
self._tools_for_model = []
|
|
4944
|
+
self._functions_for_model = {}
|
|
4945
|
+
self._tool_instructions = []
|
|
4578
4946
|
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
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
|
|
5067
|
+
|
|
5068
|
+
elif model.supports_json_schema_outputs:
|
|
5069
|
+
if self.use_json_mode or (not self.structured_outputs):
|
|
5070
|
+
log_debug("Setting Model.response_format to JSON response mode")
|
|
5071
|
+
return {
|
|
5072
|
+
"type": "json_schema",
|
|
5073
|
+
"json_schema": {
|
|
5074
|
+
"name": self.output_schema.__name__,
|
|
5075
|
+
"schema": self.output_schema.model_json_schema(),
|
|
5076
|
+
},
|
|
5077
|
+
}
|
|
5078
|
+
else:
|
|
5079
|
+
return None
|
|
4591
5080
|
|
|
4592
5081
|
else:
|
|
4593
5082
|
log_debug("Model does not support structured or JSON schema outputs.")
|
|
@@ -4660,6 +5149,16 @@ class Agent:
|
|
|
4660
5149
|
log_warning(f"Error getting session from db: {e}")
|
|
4661
5150
|
return None
|
|
4662
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
|
+
|
|
4663
5162
|
def _upsert_session(self, session: AgentSession) -> Optional[AgentSession]:
|
|
4664
5163
|
"""Upsert a Session into the database."""
|
|
4665
5164
|
|
|
@@ -4671,6 +5170,16 @@ class Agent:
|
|
|
4671
5170
|
log_warning(f"Error upserting session into db: {e}")
|
|
4672
5171
|
return None
|
|
4673
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
|
+
|
|
4674
5183
|
def _load_session_state(self, session: AgentSession, session_state: Dict[str, Any]):
|
|
4675
5184
|
"""Load and return the stored session_state from the database, optionally merging it with the given one"""
|
|
4676
5185
|
|
|
@@ -4756,6 +5265,42 @@ class Agent:
|
|
|
4756
5265
|
|
|
4757
5266
|
return agent_session
|
|
4758
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
|
+
|
|
4759
5304
|
def get_run_output(self, run_id: str, session_id: Optional[str] = None) -> Optional[RunOutput]:
|
|
4760
5305
|
"""
|
|
4761
5306
|
Get a RunOutput from the database.
|
|
@@ -4876,6 +5421,27 @@ class Agent:
|
|
|
4876
5421
|
self._upsert_session(session=session)
|
|
4877
5422
|
log_debug(f"Created or updated AgentSession record: {session.session_id}")
|
|
4878
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
|
+
|
|
4879
5445
|
def get_chat_history(self, session_id: Optional[str] = None) -> List[Message]:
|
|
4880
5446
|
"""Read the chat history from the session"""
|
|
4881
5447
|
if not session_id and not self.session_id:
|
|
@@ -5034,8 +5600,7 @@ class Agent:
|
|
|
5034
5600
|
session = self.get_session(session_id=session_id) # type: ignore
|
|
5035
5601
|
|
|
5036
5602
|
if session is None:
|
|
5037
|
-
|
|
5038
|
-
return []
|
|
5603
|
+
raise Exception("Session not found")
|
|
5039
5604
|
|
|
5040
5605
|
# Only filter by agent_id if this is part of a team
|
|
5041
5606
|
return session.get_messages_from_last_n_runs(
|
|
@@ -5065,6 +5630,16 @@ class Agent:
|
|
|
5065
5630
|
|
|
5066
5631
|
return self.memory_manager.get_user_memories(user_id=user_id)
|
|
5067
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
|
+
|
|
5068
5643
|
def _format_message_with_state_variables(
|
|
5069
5644
|
self,
|
|
5070
5645
|
message: Any,
|
|
@@ -5078,40 +5653,313 @@ class Agent:
|
|
|
5078
5653
|
import string
|
|
5079
5654
|
from copy import deepcopy
|
|
5080
5655
|
|
|
5081
|
-
if not isinstance(message, str):
|
|
5082
|
-
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"
|
|
5083
5948
|
|
|
5084
|
-
#
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
{"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
|
|
5090
5954
|
)
|
|
5091
|
-
converted_msg = deepcopy(message)
|
|
5092
|
-
for var_name in format_variables.keys():
|
|
5093
|
-
# Only convert standalone {var_name} patterns, not nested ones
|
|
5094
|
-
pattern = r"\{" + re.escape(var_name) + r"\}"
|
|
5095
|
-
replacement = "${" + var_name + "}"
|
|
5096
|
-
converted_msg = re.sub(pattern, replacement, converted_msg)
|
|
5097
|
-
|
|
5098
|
-
# Use Template to safely substitute variables
|
|
5099
|
-
template = string.Template(converted_msg)
|
|
5100
|
-
try:
|
|
5101
|
-
result = template.safe_substitute(format_variables)
|
|
5102
|
-
return result
|
|
5103
|
-
except Exception as e:
|
|
5104
|
-
log_warning(f"Template substitution failed: {e}")
|
|
5105
|
-
return message
|
|
5106
5955
|
|
|
5107
|
-
def
|
|
5956
|
+
async def aget_system_message(
|
|
5108
5957
|
self,
|
|
5109
5958
|
session: AgentSession,
|
|
5110
5959
|
session_state: Optional[Dict[str, Any]] = None,
|
|
5111
5960
|
user_id: Optional[str] = None,
|
|
5112
5961
|
dependencies: Optional[Dict[str, Any]] = None,
|
|
5113
5962
|
metadata: Optional[Dict[str, Any]] = None,
|
|
5114
|
-
add_session_state_to_context: Optional[bool] = None,
|
|
5115
5963
|
) -> Optional[Message]:
|
|
5116
5964
|
"""Return the system message for the Agent.
|
|
5117
5965
|
|
|
@@ -5225,7 +6073,7 @@ class Agent:
|
|
|
5225
6073
|
|
|
5226
6074
|
# 3.2.5 Add information about agentic filters if enabled
|
|
5227
6075
|
if self.knowledge is not None and self.enable_agentic_knowledge_filters:
|
|
5228
|
-
valid_filters = self.knowledge
|
|
6076
|
+
valid_filters = getattr(self.knowledge, "valid_metadata_filters", None)
|
|
5229
6077
|
if valid_filters:
|
|
5230
6078
|
valid_filters_str = ", ".join(valid_filters)
|
|
5231
6079
|
additional_information.append(
|
|
@@ -5300,7 +6148,12 @@ class Agent:
|
|
|
5300
6148
|
if self.memory_manager is None:
|
|
5301
6149
|
self._set_memory_manager()
|
|
5302
6150
|
_memory_manager_not_set = True
|
|
5303
|
-
|
|
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
|
+
|
|
5304
6157
|
if user_memories and len(user_memories) > 0:
|
|
5305
6158
|
system_message_content += (
|
|
5306
6159
|
"You have access to memories from previous interactions with the user that you can use:\n\n"
|
|
@@ -5366,7 +6219,7 @@ class Agent:
|
|
|
5366
6219
|
system_message_content += f"{get_response_model_format_prompt(self.output_schema)}"
|
|
5367
6220
|
|
|
5368
6221
|
# 3.3.15 Add the session state to the system message
|
|
5369
|
-
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:
|
|
5370
6223
|
system_message_content += self._get_formatted_session_state_for_system_message(session_state)
|
|
5371
6224
|
|
|
5372
6225
|
# Return the system message
|
|
@@ -5463,90 +6316,291 @@ class Agent:
|
|
|
5463
6316
|
log_warning(f"Failed to validate message: {e}")
|
|
5464
6317
|
raise Exception(f"Failed to validate message: {e}")
|
|
5465
6318
|
|
|
5466
|
-
# If message is provided as a BaseModel, convert it to a Message
|
|
5467
|
-
elif isinstance(input, BaseModel):
|
|
5468
|
-
try:
|
|
5469
|
-
# Create a user message with the BaseModel content
|
|
5470
|
-
content = input.model_dump_json(indent=2, exclude_none=True)
|
|
5471
|
-
return Message(role=self.user_message_role, content=content)
|
|
5472
|
-
except Exception as e:
|
|
5473
|
-
log_warning(f"Failed to convert BaseModel to message: {e}")
|
|
5474
|
-
raise Exception(f"Failed to convert BaseModel to message: {e}")
|
|
5475
|
-
else:
|
|
5476
|
-
user_msg_content = input
|
|
5477
|
-
if self.add_knowledge_to_context:
|
|
5478
|
-
if isinstance(input, str):
|
|
5479
|
-
user_msg_content = input
|
|
5480
|
-
elif callable(input):
|
|
5481
|
-
user_msg_content = input(agent=self)
|
|
5482
|
-
else:
|
|
5483
|
-
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
|
|
5484
6551
|
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
)
|
|
5491
|
-
if docs_from_knowledge is not None:
|
|
5492
|
-
references = MessageReferences(
|
|
5493
|
-
query=user_msg_content,
|
|
5494
|
-
references=docs_from_knowledge,
|
|
5495
|
-
time=round(retrieval_timer.elapsed, 4),
|
|
5496
|
-
)
|
|
5497
|
-
# Add the references to the run_response
|
|
5498
|
-
if run_response.references is None:
|
|
5499
|
-
run_response.references = []
|
|
5500
|
-
run_response.references.append(references)
|
|
5501
|
-
retrieval_timer.stop()
|
|
5502
|
-
log_debug(f"Time to get references: {retrieval_timer.elapsed:.4f}s")
|
|
5503
|
-
except Exception as e:
|
|
5504
|
-
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
|
|
5505
6557
|
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
metadata=metadata,
|
|
5513
|
-
)
|
|
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}")
|
|
5514
6564
|
|
|
5515
|
-
|
|
5516
|
-
|
|
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}")
|
|
5517
6573
|
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
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}")
|
|
5534
6595
|
|
|
5535
|
-
|
|
5536
|
-
|
|
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)
|
|
5537
6600
|
|
|
5538
|
-
|
|
5539
|
-
return Message(
|
|
5540
|
-
role=self.user_message_role,
|
|
5541
|
-
content=user_msg_content,
|
|
5542
|
-
audio=None if not self.send_media_to_model else audio,
|
|
5543
|
-
images=None if not self.send_media_to_model else images,
|
|
5544
|
-
videos=None if not self.send_media_to_model else videos,
|
|
5545
|
-
files=None if not self.send_media_to_model else files,
|
|
5546
|
-
**kwargs,
|
|
5547
|
-
)
|
|
6601
|
+
return run_messages
|
|
5548
6602
|
|
|
5549
|
-
def
|
|
6603
|
+
async def _aget_run_messages(
|
|
5550
6604
|
self,
|
|
5551
6605
|
*,
|
|
5552
6606
|
run_response: RunOutput,
|
|
@@ -5560,7 +6614,7 @@ class Agent:
|
|
|
5560
6614
|
files: Optional[Sequence[File]] = None,
|
|
5561
6615
|
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
5562
6616
|
add_history_to_context: Optional[bool] = None,
|
|
5563
|
-
|
|
6617
|
+
run_dependencies: Optional[Dict[str, Any]] = None,
|
|
5564
6618
|
add_dependencies_to_context: Optional[bool] = None,
|
|
5565
6619
|
add_session_state_to_context: Optional[bool] = None,
|
|
5566
6620
|
metadata: Optional[Dict[str, Any]] = None,
|
|
@@ -5594,13 +6648,12 @@ class Agent:
|
|
|
5594
6648
|
run_messages = RunMessages()
|
|
5595
6649
|
|
|
5596
6650
|
# 1. Add system message to run_messages
|
|
5597
|
-
system_message = self.
|
|
6651
|
+
system_message = await self.aget_system_message(
|
|
5598
6652
|
session=session,
|
|
5599
6653
|
session_state=session_state,
|
|
5600
6654
|
user_id=user_id,
|
|
5601
|
-
dependencies=
|
|
6655
|
+
dependencies=run_dependencies,
|
|
5602
6656
|
metadata=metadata,
|
|
5603
|
-
add_session_state_to_context=add_session_state_to_context,
|
|
5604
6657
|
)
|
|
5605
6658
|
if system_message is not None:
|
|
5606
6659
|
run_messages.system_message = system_message
|
|
@@ -5637,16 +6690,9 @@ class Agent:
|
|
|
5637
6690
|
if add_history_to_context:
|
|
5638
6691
|
from copy import deepcopy
|
|
5639
6692
|
|
|
5640
|
-
# Only skip messages from history when system_message_role is NOT a standard conversation role.
|
|
5641
|
-
# Standard conversation roles ("user", "assistant", "tool") should never be filtered
|
|
5642
|
-
# to preserve conversation continuity.
|
|
5643
|
-
skip_role = (
|
|
5644
|
-
self.system_message_role if self.system_message_role not in ["user", "assistant", "tool"] else None
|
|
5645
|
-
)
|
|
5646
|
-
|
|
5647
6693
|
history: List[Message] = session.get_messages_from_last_n_runs(
|
|
5648
6694
|
last_n=self.num_history_runs,
|
|
5649
|
-
skip_role=
|
|
6695
|
+
skip_role=self.system_message_role,
|
|
5650
6696
|
agent_id=self.id if self.team_id is not None else None,
|
|
5651
6697
|
)
|
|
5652
6698
|
|
|
@@ -5686,7 +6732,7 @@ class Agent:
|
|
|
5686
6732
|
videos=videos,
|
|
5687
6733
|
files=files,
|
|
5688
6734
|
knowledge_filters=knowledge_filters,
|
|
5689
|
-
|
|
6735
|
+
run_dependencies=run_dependencies,
|
|
5690
6736
|
add_dependencies_to_context=add_dependencies_to_context,
|
|
5691
6737
|
metadata=metadata,
|
|
5692
6738
|
**kwargs,
|
|
@@ -5699,13 +6745,7 @@ class Agent:
|
|
|
5699
6745
|
# 4.3 If input is provided as a dict, try to validate it as a Message
|
|
5700
6746
|
elif isinstance(input, dict):
|
|
5701
6747
|
try:
|
|
5702
|
-
|
|
5703
|
-
import json
|
|
5704
|
-
|
|
5705
|
-
content = json.dumps(input, indent=2, ensure_ascii=False)
|
|
5706
|
-
user_message = Message(role=self.user_message_role, content=content)
|
|
5707
|
-
else:
|
|
5708
|
-
user_message = Message.model_validate(input)
|
|
6748
|
+
user_message = Message.model_validate(input)
|
|
5709
6749
|
except Exception as e:
|
|
5710
6750
|
log_warning(f"Failed to validate message: {e}")
|
|
5711
6751
|
|
|
@@ -6264,12 +7304,15 @@ class Agent:
|
|
|
6264
7304
|
|
|
6265
7305
|
# If a reasoning model is provided, use it to generate reasoning
|
|
6266
7306
|
if reasoning_model_provided:
|
|
7307
|
+
from agno.reasoning.anthropic import is_anthropic_reasoning_model
|
|
6267
7308
|
from agno.reasoning.azure_ai_foundry import is_ai_foundry_reasoning_model
|
|
6268
7309
|
from agno.reasoning.deepseek import is_deepseek_reasoning_model
|
|
7310
|
+
from agno.reasoning.gemini import is_gemini_reasoning_model
|
|
6269
7311
|
from agno.reasoning.groq import is_groq_reasoning_model
|
|
6270
7312
|
from agno.reasoning.helpers import get_reasoning_agent
|
|
6271
7313
|
from agno.reasoning.ollama import is_ollama_reasoning_model
|
|
6272
7314
|
from agno.reasoning.openai import is_openai_reasoning_model
|
|
7315
|
+
from agno.reasoning.vertexai import is_vertexai_reasoning_model
|
|
6273
7316
|
|
|
6274
7317
|
reasoning_agent = self.reasoning_agent or get_reasoning_agent(
|
|
6275
7318
|
reasoning_model=reasoning_model,
|
|
@@ -6285,8 +7328,20 @@ class Agent:
|
|
|
6285
7328
|
is_openai = is_openai_reasoning_model(reasoning_model)
|
|
6286
7329
|
is_ollama = is_ollama_reasoning_model(reasoning_model)
|
|
6287
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)
|
|
6288
7334
|
|
|
6289
|
-
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
|
+
):
|
|
6290
7345
|
reasoning_message: Optional[Message] = None
|
|
6291
7346
|
if is_deepseek:
|
|
6292
7347
|
from agno.reasoning.deepseek import get_deepseek_reasoning
|
|
@@ -6323,6 +7378,27 @@ class Agent:
|
|
|
6323
7378
|
reasoning_message = get_ai_foundry_reasoning(
|
|
6324
7379
|
reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
|
|
6325
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
|
+
)
|
|
6326
7402
|
|
|
6327
7403
|
if reasoning_message is None:
|
|
6328
7404
|
log_warning("Reasoning error. Reasoning response is None, continuing regular session...")
|
|
@@ -6496,12 +7572,15 @@ class Agent:
|
|
|
6496
7572
|
|
|
6497
7573
|
# If a reasoning model is provided, use it to generate reasoning
|
|
6498
7574
|
if reasoning_model_provided:
|
|
7575
|
+
from agno.reasoning.anthropic import is_anthropic_reasoning_model
|
|
6499
7576
|
from agno.reasoning.azure_ai_foundry import is_ai_foundry_reasoning_model
|
|
6500
7577
|
from agno.reasoning.deepseek import is_deepseek_reasoning_model
|
|
7578
|
+
from agno.reasoning.gemini import is_gemini_reasoning_model
|
|
6501
7579
|
from agno.reasoning.groq import is_groq_reasoning_model
|
|
6502
7580
|
from agno.reasoning.helpers import get_reasoning_agent
|
|
6503
7581
|
from agno.reasoning.ollama import is_ollama_reasoning_model
|
|
6504
7582
|
from agno.reasoning.openai import is_openai_reasoning_model
|
|
7583
|
+
from agno.reasoning.vertexai import is_vertexai_reasoning_model
|
|
6505
7584
|
|
|
6506
7585
|
reasoning_agent = self.reasoning_agent or get_reasoning_agent(
|
|
6507
7586
|
reasoning_model=reasoning_model,
|
|
@@ -6517,8 +7596,20 @@ class Agent:
|
|
|
6517
7596
|
is_openai = is_openai_reasoning_model(reasoning_model)
|
|
6518
7597
|
is_ollama = is_ollama_reasoning_model(reasoning_model)
|
|
6519
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)
|
|
6520
7602
|
|
|
6521
|
-
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
|
+
):
|
|
6522
7613
|
reasoning_message: Optional[Message] = None
|
|
6523
7614
|
if is_deepseek:
|
|
6524
7615
|
from agno.reasoning.deepseek import aget_deepseek_reasoning
|
|
@@ -6555,6 +7646,27 @@ class Agent:
|
|
|
6555
7646
|
reasoning_message = get_ai_foundry_reasoning(
|
|
6556
7647
|
reasoning_agent=reasoning_agent, messages=run_messages.get_input_messages()
|
|
6557
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
|
+
)
|
|
6558
7670
|
|
|
6559
7671
|
if reasoning_message is None:
|
|
6560
7672
|
log_warning("Reasoning error. Reasoning response is None, continuing regular session...")
|
|
@@ -7284,6 +8396,8 @@ class Agent:
|
|
|
7284
8396
|
if self.db is None:
|
|
7285
8397
|
return "Previous session messages not available"
|
|
7286
8398
|
|
|
8399
|
+
self.db = cast(BaseDb, self.db)
|
|
8400
|
+
|
|
7287
8401
|
selected_sessions = self.db.get_sessions(
|
|
7288
8402
|
session_type=SessionType.AGENT, limit=num_history_sessions, user_id=user_id
|
|
7289
8403
|
)
|
|
@@ -7321,6 +8435,69 @@ class Agent:
|
|
|
7321
8435
|
|
|
7322
8436
|
return get_previous_session_messages
|
|
7323
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
|
+
|
|
7324
8501
|
###########################################################################
|
|
7325
8502
|
# Print Response
|
|
7326
8503
|
###########################################################################
|
|
@@ -7354,6 +8531,11 @@ class Agent:
|
|
|
7354
8531
|
tags_to_include_in_markdown: Optional[Set[str]] = None,
|
|
7355
8532
|
**kwargs: Any,
|
|
7356
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
|
+
|
|
7357
8539
|
if not tags_to_include_in_markdown:
|
|
7358
8540
|
tags_to_include_in_markdown = {"think", "thinking"}
|
|
7359
8541
|
|
|
@@ -7685,6 +8867,56 @@ class Agent:
|
|
|
7685
8867
|
message.image_output = None
|
|
7686
8868
|
message.video_output = None
|
|
7687
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
|
+
|
|
7688
8920
|
def _validate_media_object_id(
|
|
7689
8921
|
self,
|
|
7690
8922
|
images: Optional[Sequence[Image]] = None,
|