agno 2.3.6__py3-none-any.whl → 2.3.7__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 +193 -26
- agno/db/postgres/async_postgres.py +23 -7
- agno/db/utils.py +2 -0
- agno/models/base.py +34 -4
- agno/models/response.py +1 -1
- agno/os/routers/evals/utils.py +13 -3
- agno/run/agent.py +17 -0
- agno/run/requirement.py +98 -0
- agno/run/team.py +10 -0
- agno/team/team.py +91 -10
- agno/tools/postgres.py +76 -36
- agno/tools/redshift.py +406 -0
- agno/tools/toolkit.py +25 -0
- agno/utils/events.py +5 -1
- {agno-2.3.6.dist-info → agno-2.3.7.dist-info}/METADATA +4 -1
- {agno-2.3.6.dist-info → agno-2.3.7.dist-info}/RECORD +19 -17
- {agno-2.3.6.dist-info → agno-2.3.7.dist-info}/WHEEL +0 -0
- {agno-2.3.6.dist-info → agno-2.3.7.dist-info}/licenses/LICENSE +0 -0
- {agno-2.3.6.dist-info → agno-2.3.7.dist-info}/top_level.txt +0 -0
|
@@ -255,47 +255,63 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
255
255
|
if table_type == "sessions":
|
|
256
256
|
if not hasattr(self, "session_table"):
|
|
257
257
|
self.session_table = await self._get_or_create_table(
|
|
258
|
-
table_name=self.session_table_name,
|
|
258
|
+
table_name=self.session_table_name,
|
|
259
|
+
table_type="sessions",
|
|
260
|
+
create_table_if_not_found=create_table_if_not_found,
|
|
259
261
|
)
|
|
260
262
|
return self.session_table
|
|
261
263
|
|
|
262
264
|
if table_type == "memories":
|
|
263
265
|
if not hasattr(self, "memory_table"):
|
|
264
266
|
self.memory_table = await self._get_or_create_table(
|
|
265
|
-
table_name=self.memory_table_name,
|
|
267
|
+
table_name=self.memory_table_name,
|
|
268
|
+
table_type="memories",
|
|
269
|
+
create_table_if_not_found=create_table_if_not_found,
|
|
266
270
|
)
|
|
267
271
|
return self.memory_table
|
|
268
272
|
|
|
269
273
|
if table_type == "metrics":
|
|
270
274
|
if not hasattr(self, "metrics_table"):
|
|
271
275
|
self.metrics_table = await self._get_or_create_table(
|
|
272
|
-
table_name=self.metrics_table_name,
|
|
276
|
+
table_name=self.metrics_table_name,
|
|
277
|
+
table_type="metrics",
|
|
278
|
+
create_table_if_not_found=create_table_if_not_found,
|
|
273
279
|
)
|
|
274
280
|
return self.metrics_table
|
|
275
281
|
|
|
276
282
|
if table_type == "evals":
|
|
277
283
|
if not hasattr(self, "eval_table"):
|
|
278
|
-
self.eval_table = await self._get_or_create_table(
|
|
284
|
+
self.eval_table = await self._get_or_create_table(
|
|
285
|
+
table_name=self.eval_table_name,
|
|
286
|
+
table_type="evals",
|
|
287
|
+
create_table_if_not_found=create_table_if_not_found,
|
|
288
|
+
)
|
|
279
289
|
return self.eval_table
|
|
280
290
|
|
|
281
291
|
if table_type == "knowledge":
|
|
282
292
|
if not hasattr(self, "knowledge_table"):
|
|
283
293
|
self.knowledge_table = await self._get_or_create_table(
|
|
284
|
-
table_name=self.knowledge_table_name,
|
|
294
|
+
table_name=self.knowledge_table_name,
|
|
295
|
+
table_type="knowledge",
|
|
296
|
+
create_table_if_not_found=create_table_if_not_found,
|
|
285
297
|
)
|
|
286
298
|
return self.knowledge_table
|
|
287
299
|
|
|
288
300
|
if table_type == "culture":
|
|
289
301
|
if not hasattr(self, "culture_table"):
|
|
290
302
|
self.culture_table = await self._get_or_create_table(
|
|
291
|
-
table_name=self.culture_table_name,
|
|
303
|
+
table_name=self.culture_table_name,
|
|
304
|
+
table_type="culture",
|
|
305
|
+
create_table_if_not_found=create_table_if_not_found,
|
|
292
306
|
)
|
|
293
307
|
return self.culture_table
|
|
294
308
|
|
|
295
309
|
if table_type == "versions":
|
|
296
310
|
if not hasattr(self, "versions_table"):
|
|
297
311
|
self.versions_table = await self._get_or_create_table(
|
|
298
|
-
table_name=self.versions_table_name,
|
|
312
|
+
table_name=self.versions_table_name,
|
|
313
|
+
table_type="versions",
|
|
314
|
+
create_table_if_not_found=create_table_if_not_found,
|
|
299
315
|
)
|
|
300
316
|
return self.versions_table
|
|
301
317
|
|
agno/db/utils.py
CHANGED
agno/models/base.py
CHANGED
|
@@ -30,6 +30,7 @@ from agno.models.message import Citations, Message
|
|
|
30
30
|
from agno.models.metrics import Metrics
|
|
31
31
|
from agno.models.response import ModelResponse, ModelResponseEvent, ToolExecution
|
|
32
32
|
from agno.run.agent import CustomEvent, RunContentEvent, RunOutput, RunOutputEvent
|
|
33
|
+
from agno.run.requirement import RunRequirement
|
|
33
34
|
from agno.run.team import RunContentEvent as TeamRunContentEvent
|
|
34
35
|
from agno.run.team import TeamRunOutput, TeamRunOutputEvent
|
|
35
36
|
from agno.run.workflow import WorkflowRunOutputEvent
|
|
@@ -423,10 +424,23 @@ class Model(ABC):
|
|
|
423
424
|
]
|
|
424
425
|
and function_call_response.tool_executions is not None
|
|
425
426
|
):
|
|
427
|
+
# Record the tool execution in the model response
|
|
426
428
|
if model_response.tool_executions is None:
|
|
427
429
|
model_response.tool_executions = []
|
|
428
430
|
model_response.tool_executions.extend(function_call_response.tool_executions)
|
|
429
431
|
|
|
432
|
+
# If the tool is currently paused (HITL flow), add the requirement to the run response
|
|
433
|
+
if (
|
|
434
|
+
function_call_response.event == ModelResponseEvent.tool_call_paused.value
|
|
435
|
+
and run_response is not None
|
|
436
|
+
):
|
|
437
|
+
current_tool_execution = function_call_response.tool_executions[-1]
|
|
438
|
+
if run_response.requirements is None:
|
|
439
|
+
run_response.requirements = []
|
|
440
|
+
run_response.requirements.append(
|
|
441
|
+
RunRequirement(tool_execution=current_tool_execution)
|
|
442
|
+
)
|
|
443
|
+
|
|
430
444
|
elif function_call_response.event not in [
|
|
431
445
|
ModelResponseEvent.tool_call_started.value,
|
|
432
446
|
ModelResponseEvent.tool_call_completed.value,
|
|
@@ -615,6 +629,19 @@ class Model(ABC):
|
|
|
615
629
|
if model_response.tool_executions is None:
|
|
616
630
|
model_response.tool_executions = []
|
|
617
631
|
model_response.tool_executions.extend(function_call_response.tool_executions)
|
|
632
|
+
|
|
633
|
+
# If the tool is currently paused (HITL flow), add the requirement to the run response
|
|
634
|
+
if (
|
|
635
|
+
function_call_response.event == ModelResponseEvent.tool_call_paused.value
|
|
636
|
+
and run_response is not None
|
|
637
|
+
):
|
|
638
|
+
current_tool_execution = function_call_response.tool_executions[-1]
|
|
639
|
+
if run_response.requirements is None:
|
|
640
|
+
run_response.requirements = []
|
|
641
|
+
run_response.requirements.append(
|
|
642
|
+
RunRequirement(tool_execution=current_tool_execution)
|
|
643
|
+
)
|
|
644
|
+
|
|
618
645
|
elif function_call_response.event not in [
|
|
619
646
|
ModelResponseEvent.tool_call_started.value,
|
|
620
647
|
ModelResponseEvent.tool_call_completed.value,
|
|
@@ -1706,7 +1733,7 @@ class Model(ABC):
|
|
|
1706
1733
|
|
|
1707
1734
|
paused_tool_executions = []
|
|
1708
1735
|
|
|
1709
|
-
# The function
|
|
1736
|
+
# The function requires user confirmation (HITL)
|
|
1710
1737
|
if fc.function.requires_confirmation:
|
|
1711
1738
|
paused_tool_executions.append(
|
|
1712
1739
|
ToolExecution(
|
|
@@ -1716,7 +1743,8 @@ class Model(ABC):
|
|
|
1716
1743
|
requires_confirmation=True,
|
|
1717
1744
|
)
|
|
1718
1745
|
)
|
|
1719
|
-
|
|
1746
|
+
|
|
1747
|
+
# The function requires user input (HITL)
|
|
1720
1748
|
if fc.function.requires_user_input:
|
|
1721
1749
|
user_input_schema = fc.function.user_input_schema
|
|
1722
1750
|
if fc.arguments and user_input_schema:
|
|
@@ -1734,7 +1762,8 @@ class Model(ABC):
|
|
|
1734
1762
|
user_input_schema=user_input_schema,
|
|
1735
1763
|
)
|
|
1736
1764
|
)
|
|
1737
|
-
|
|
1765
|
+
|
|
1766
|
+
# If the function is from the user control flow (HITL) tools, we handle it here
|
|
1738
1767
|
if fc.function.name == "get_user_input" and fc.arguments and fc.arguments.get("user_input_fields"):
|
|
1739
1768
|
user_input_schema = []
|
|
1740
1769
|
for input_field in fc.arguments.get("user_input_fields", []):
|
|
@@ -1760,7 +1789,8 @@ class Model(ABC):
|
|
|
1760
1789
|
user_input_schema=user_input_schema,
|
|
1761
1790
|
)
|
|
1762
1791
|
)
|
|
1763
|
-
|
|
1792
|
+
|
|
1793
|
+
# The function requires external execution (HITL)
|
|
1764
1794
|
if fc.function.external_execution:
|
|
1765
1795
|
paused_tool_executions.append(
|
|
1766
1796
|
ToolExecution(
|
agno/models/response.py
CHANGED
|
@@ -37,7 +37,7 @@ class ToolExecution:
|
|
|
37
37
|
|
|
38
38
|
created_at: int = field(default_factory=lambda: int(time()))
|
|
39
39
|
|
|
40
|
-
# User control flow
|
|
40
|
+
# User control flow (HITL) fields
|
|
41
41
|
requires_confirmation: Optional[bool] = None
|
|
42
42
|
confirmed: Optional[bool] = None
|
|
43
43
|
confirmation_note: Optional[str] = None
|
agno/os/routers/evals/utils.py
CHANGED
|
@@ -36,7 +36,10 @@ async def run_accuracy_eval(
|
|
|
36
36
|
model=default_model,
|
|
37
37
|
)
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
if isinstance(db, AsyncBaseDb):
|
|
40
|
+
result = await accuracy_eval.arun(print_results=False, print_summary=False)
|
|
41
|
+
else:
|
|
42
|
+
result = accuracy_eval.run(print_results=False, print_summary=False)
|
|
40
43
|
if not result:
|
|
41
44
|
raise HTTPException(status_code=500, detail="Failed to run accuracy evaluation")
|
|
42
45
|
|
|
@@ -86,7 +89,11 @@ async def run_performance_eval(
|
|
|
86
89
|
model_id=model_id,
|
|
87
90
|
model_provider=model_provider,
|
|
88
91
|
)
|
|
89
|
-
|
|
92
|
+
|
|
93
|
+
if isinstance(db, AsyncBaseDb):
|
|
94
|
+
result = await performance_eval.arun(print_results=False, print_summary=False)
|
|
95
|
+
else:
|
|
96
|
+
result = performance_eval.run(print_results=False, print_summary=False)
|
|
90
97
|
if not result:
|
|
91
98
|
raise HTTPException(status_code=500, detail="Failed to run performance evaluation")
|
|
92
99
|
|
|
@@ -141,7 +148,10 @@ async def run_reliability_eval(
|
|
|
141
148
|
model_id = team.model.id if team and team.model else None
|
|
142
149
|
model_provider = team.model.provider if team and team.model else None
|
|
143
150
|
|
|
144
|
-
|
|
151
|
+
if isinstance(db, AsyncBaseDb):
|
|
152
|
+
result = await reliability_eval.arun(print_results=False)
|
|
153
|
+
else:
|
|
154
|
+
result = reliability_eval.run(print_results=False)
|
|
145
155
|
if not result:
|
|
146
156
|
raise HTTPException(status_code=500, detail="Failed to run reliability evaluation")
|
|
147
157
|
|
agno/run/agent.py
CHANGED
|
@@ -11,6 +11,7 @@ from agno.models.metrics import Metrics
|
|
|
11
11
|
from agno.models.response import ToolExecution
|
|
12
12
|
from agno.reasoning.step import ReasoningStep
|
|
13
13
|
from agno.run.base import BaseRunOutputEvent, MessageReferences, RunStatus
|
|
14
|
+
from agno.run.requirement import RunRequirement
|
|
14
15
|
from agno.utils.log import logger
|
|
15
16
|
from agno.utils.media import (
|
|
16
17
|
reconstruct_audio_list,
|
|
@@ -273,11 +274,18 @@ class RunCompletedEvent(BaseAgentRunEvent):
|
|
|
273
274
|
class RunPausedEvent(BaseAgentRunEvent):
|
|
274
275
|
event: str = RunEvent.run_paused.value
|
|
275
276
|
tools: Optional[List[ToolExecution]] = None
|
|
277
|
+
requirements: Optional[List[RunRequirement]] = None
|
|
276
278
|
|
|
277
279
|
@property
|
|
278
280
|
def is_paused(self):
|
|
279
281
|
return True
|
|
280
282
|
|
|
283
|
+
@property
|
|
284
|
+
def active_requirements(self) -> List[RunRequirement]:
|
|
285
|
+
if not self.requirements:
|
|
286
|
+
return []
|
|
287
|
+
return [requirement for requirement in self.requirements if not requirement.is_resolved()]
|
|
288
|
+
|
|
281
289
|
|
|
282
290
|
@dataclass
|
|
283
291
|
class RunContinuedEvent(BaseAgentRunEvent):
|
|
@@ -539,11 +547,20 @@ class RunOutput:
|
|
|
539
547
|
|
|
540
548
|
status: RunStatus = RunStatus.running
|
|
541
549
|
|
|
550
|
+
# User control flow (HITL) requirements to continue a run when paused, in order of arrival
|
|
551
|
+
requirements: Optional[list[RunRequirement]] = None
|
|
552
|
+
|
|
542
553
|
# === FOREIGN KEY RELATIONSHIPS ===
|
|
543
554
|
# These fields establish relationships to parent workflow/step structures
|
|
544
555
|
# and should be treated as foreign keys for data integrity
|
|
545
556
|
workflow_step_id: Optional[str] = None # FK: Points to StepOutput.step_id
|
|
546
557
|
|
|
558
|
+
@property
|
|
559
|
+
def active_requirements(self) -> list[RunRequirement]:
|
|
560
|
+
if not self.requirements:
|
|
561
|
+
return []
|
|
562
|
+
return [requirement for requirement in self.requirements if not requirement.is_resolved()]
|
|
563
|
+
|
|
547
564
|
@property
|
|
548
565
|
def is_paused(self):
|
|
549
566
|
return self.status == RunStatus.paused
|
agno/run/requirement.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
from typing import TYPE_CHECKING, List, Optional
|
|
4
|
+
from uuid import uuid4
|
|
5
|
+
|
|
6
|
+
from agno.models.response import ToolExecution, UserInputField
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class RunRequirement:
|
|
14
|
+
"""Requirement to complete a paused run (used in HITL flows)"""
|
|
15
|
+
|
|
16
|
+
tool_execution: Optional[ToolExecution] = None
|
|
17
|
+
created_at: datetime = datetime.now(timezone.utc)
|
|
18
|
+
|
|
19
|
+
# User confirmation
|
|
20
|
+
confirmation: Optional[bool] = None
|
|
21
|
+
confirmation_note: Optional[str] = None
|
|
22
|
+
|
|
23
|
+
# User input
|
|
24
|
+
user_input_schema: Optional[List[UserInputField]] = None
|
|
25
|
+
|
|
26
|
+
# External execution
|
|
27
|
+
external_execution_result: Optional[str] = None
|
|
28
|
+
|
|
29
|
+
def __init__(self, tool_execution: ToolExecution):
|
|
30
|
+
self.id = str(uuid4())
|
|
31
|
+
self.tool_execution = tool_execution
|
|
32
|
+
self.user_input_schema = tool_execution.user_input_schema
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def needs_confirmation(self) -> bool:
|
|
36
|
+
if self.confirmation is not None:
|
|
37
|
+
return False
|
|
38
|
+
if not self.tool_execution:
|
|
39
|
+
return False
|
|
40
|
+
if self.tool_execution.confirmed is True:
|
|
41
|
+
return True
|
|
42
|
+
|
|
43
|
+
return self.tool_execution.requires_confirmation or False
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def needs_user_input(self) -> bool:
|
|
47
|
+
if not self.tool_execution:
|
|
48
|
+
return False
|
|
49
|
+
if self.tool_execution.answered is True:
|
|
50
|
+
return False
|
|
51
|
+
if self.user_input_schema and not all(field.value is not None for field in self.user_input_schema):
|
|
52
|
+
return True
|
|
53
|
+
|
|
54
|
+
return self.tool_execution.requires_user_input or False
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def needs_external_execution(self) -> bool:
|
|
58
|
+
if not self.tool_execution:
|
|
59
|
+
return False
|
|
60
|
+
if self.external_execution_result is not None:
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
return self.tool_execution.external_execution_required or False
|
|
64
|
+
|
|
65
|
+
def confirm(self):
|
|
66
|
+
if not self.needs_confirmation:
|
|
67
|
+
raise ValueError("This requirement does not require confirmation")
|
|
68
|
+
self.confirmation = True
|
|
69
|
+
if self.tool_execution:
|
|
70
|
+
self.tool_execution.confirmed = True
|
|
71
|
+
|
|
72
|
+
def reject(self):
|
|
73
|
+
if not self.needs_confirmation:
|
|
74
|
+
raise ValueError("This requirement does not require confirmation")
|
|
75
|
+
self.confirmation = False
|
|
76
|
+
if self.tool_execution:
|
|
77
|
+
self.tool_execution.confirmed = False
|
|
78
|
+
|
|
79
|
+
def set_external_execution_result(self, result: str):
|
|
80
|
+
if not self.needs_external_execution:
|
|
81
|
+
raise ValueError("This requirement does not require external execution")
|
|
82
|
+
self.external_execution_result = result
|
|
83
|
+
if self.tool_execution:
|
|
84
|
+
self.tool_execution.result = result
|
|
85
|
+
|
|
86
|
+
def update_tool(self):
|
|
87
|
+
if not self.tool_execution:
|
|
88
|
+
return
|
|
89
|
+
if self.confirmation is True:
|
|
90
|
+
self.tool_execution.confirmed = True
|
|
91
|
+
elif self.confirmation is False:
|
|
92
|
+
self.tool_execution.confirmed = False
|
|
93
|
+
else:
|
|
94
|
+
raise ValueError("This requirement does not require confirmation or user input")
|
|
95
|
+
|
|
96
|
+
def is_resolved(self) -> bool:
|
|
97
|
+
"""Return True if the requirement has been resolved"""
|
|
98
|
+
return not self.needs_confirmation and not self.needs_user_input and not self.needs_external_execution
|
agno/run/team.py
CHANGED
|
@@ -12,6 +12,7 @@ from agno.models.response import ToolExecution
|
|
|
12
12
|
from agno.reasoning.step import ReasoningStep
|
|
13
13
|
from agno.run.agent import RunEvent, RunOutput, RunOutputEvent, run_output_event_from_dict
|
|
14
14
|
from agno.run.base import BaseRunOutputEvent, MessageReferences, RunStatus
|
|
15
|
+
from agno.run.requirement import RunRequirement
|
|
15
16
|
from agno.utils.log import log_error
|
|
16
17
|
from agno.utils.media import (
|
|
17
18
|
reconstruct_audio_list,
|
|
@@ -515,11 +516,20 @@ class TeamRunOutput:
|
|
|
515
516
|
|
|
516
517
|
status: RunStatus = RunStatus.running
|
|
517
518
|
|
|
519
|
+
# User control flow (HITL) requirements to continue a run when paused, in order of arrival
|
|
520
|
+
requirements: Optional[list[RunRequirement]] = None
|
|
521
|
+
|
|
518
522
|
# === FOREIGN KEY RELATIONSHIPS ===
|
|
519
523
|
# These fields establish relationships to parent workflow/step structures
|
|
520
524
|
# and should be treated as foreign keys for data integrity
|
|
521
525
|
workflow_step_id: Optional[str] = None # FK: Points to StepOutput.step_id
|
|
522
526
|
|
|
527
|
+
@property
|
|
528
|
+
def active_requirements(self) -> list[RunRequirement]:
|
|
529
|
+
if not self.requirements:
|
|
530
|
+
return []
|
|
531
|
+
return [requirement for requirement in self.requirements if not requirement.is_resolved()]
|
|
532
|
+
|
|
523
533
|
@property
|
|
524
534
|
def is_paused(self):
|
|
525
535
|
return self.status == RunStatus.paused
|
agno/team/team.py
CHANGED
|
@@ -723,6 +723,8 @@ class Team:
|
|
|
723
723
|
|
|
724
724
|
# List of MCP tools that were initialized on the last run
|
|
725
725
|
self._mcp_tools_initialized_on_run: List[Any] = []
|
|
726
|
+
# List of connectable tools that were initialized on the last run
|
|
727
|
+
self._connectable_tools_initialized_on_run: List[Any] = []
|
|
726
728
|
|
|
727
729
|
# Lazy-initialized shared thread pool executor for background tasks (memory, cultural knowledge, etc.)
|
|
728
730
|
self._background_executor: Optional[Any] = None
|
|
@@ -1046,16 +1048,48 @@ class Team:
|
|
|
1046
1048
|
and any(c.__name__ in ["MCPTools", "MultiMCPTools"] for c in type(tool).__mro__)
|
|
1047
1049
|
and not tool.initialized # type: ignore
|
|
1048
1050
|
):
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1051
|
+
try:
|
|
1052
|
+
# Connect the MCP server
|
|
1053
|
+
await tool.connect() # type: ignore
|
|
1054
|
+
self._mcp_tools_initialized_on_run.append(tool)
|
|
1055
|
+
except Exception as e:
|
|
1056
|
+
log_warning(f"Error connecting tool: {str(e)}")
|
|
1052
1057
|
|
|
1053
1058
|
async def _disconnect_mcp_tools(self) -> None:
|
|
1054
1059
|
"""Disconnect the MCP tools from the agent."""
|
|
1055
1060
|
for tool in self._mcp_tools_initialized_on_run:
|
|
1056
|
-
|
|
1061
|
+
try:
|
|
1062
|
+
await tool.close()
|
|
1063
|
+
except Exception as e:
|
|
1064
|
+
log_warning(f"Error disconnecting tool: {str(e)}")
|
|
1057
1065
|
self._mcp_tools_initialized_on_run = []
|
|
1058
1066
|
|
|
1067
|
+
def _connect_connectable_tools(self) -> None:
|
|
1068
|
+
"""Connect tools that require connection management (e.g., database connections)."""
|
|
1069
|
+
if self.tools:
|
|
1070
|
+
for tool in self.tools:
|
|
1071
|
+
if (
|
|
1072
|
+
hasattr(tool, "requires_connect")
|
|
1073
|
+
and tool.requires_connect
|
|
1074
|
+
and hasattr(tool, "connect")
|
|
1075
|
+
and tool not in self._connectable_tools_initialized_on_run
|
|
1076
|
+
):
|
|
1077
|
+
try:
|
|
1078
|
+
tool.connect() # type: ignore
|
|
1079
|
+
self._connectable_tools_initialized_on_run.append(tool)
|
|
1080
|
+
except Exception as e:
|
|
1081
|
+
log_warning(f"Error connecting tool: {str(e)}")
|
|
1082
|
+
|
|
1083
|
+
def _disconnect_connectable_tools(self) -> None:
|
|
1084
|
+
"""Disconnect tools that require connection management."""
|
|
1085
|
+
for tool in self._connectable_tools_initialized_on_run:
|
|
1086
|
+
if hasattr(tool, "close"):
|
|
1087
|
+
try:
|
|
1088
|
+
tool.close() # type: ignore
|
|
1089
|
+
except Exception as e:
|
|
1090
|
+
log_warning(f"Error disconnecting tool: {str(e)}")
|
|
1091
|
+
self._connectable_tools_initialized_on_run = []
|
|
1092
|
+
|
|
1059
1093
|
def _execute_pre_hooks(
|
|
1060
1094
|
self,
|
|
1061
1095
|
hooks: Optional[List[Callable[..., Any]]],
|
|
@@ -1625,6 +1659,8 @@ class Team:
|
|
|
1625
1659
|
self._cleanup_and_store(run_response=run_response, session=session)
|
|
1626
1660
|
return run_response
|
|
1627
1661
|
finally:
|
|
1662
|
+
# Always disconnect connectable tools
|
|
1663
|
+
self._disconnect_connectable_tools()
|
|
1628
1664
|
cleanup_run(run_response.run_id) # type: ignore
|
|
1629
1665
|
|
|
1630
1666
|
def _run_stream(
|
|
@@ -1911,6 +1947,8 @@ class Team:
|
|
|
1911
1947
|
# Add the RunOutput to Team Session even when cancelled
|
|
1912
1948
|
self._cleanup_and_store(run_response=run_response, session=session)
|
|
1913
1949
|
finally:
|
|
1950
|
+
# Always disconnect connectable tools
|
|
1951
|
+
self._disconnect_connectable_tools()
|
|
1914
1952
|
# Always clean up the run tracking
|
|
1915
1953
|
cleanup_run(run_response.run_id) # type: ignore
|
|
1916
1954
|
|
|
@@ -2475,6 +2513,8 @@ class Team:
|
|
|
2475
2513
|
|
|
2476
2514
|
return run_response
|
|
2477
2515
|
finally:
|
|
2516
|
+
# Always disconnect connectable tools
|
|
2517
|
+
self._disconnect_connectable_tools()
|
|
2478
2518
|
await self._disconnect_mcp_tools()
|
|
2479
2519
|
# Cancel the memory task if it's still running
|
|
2480
2520
|
if memory_task is not None and not memory_task.done():
|
|
@@ -2801,6 +2841,8 @@ class Team:
|
|
|
2801
2841
|
await self._acleanup_and_store(run_response=run_response, session=team_session)
|
|
2802
2842
|
|
|
2803
2843
|
finally:
|
|
2844
|
+
# Always disconnect connectable tools
|
|
2845
|
+
self._disconnect_connectable_tools()
|
|
2804
2846
|
await self._disconnect_mcp_tools()
|
|
2805
2847
|
# Cancel the memory task if it's still running
|
|
2806
2848
|
if memory_task is not None and not memory_task.done():
|
|
@@ -5333,6 +5375,9 @@ class Team:
|
|
|
5333
5375
|
add_session_state_to_context: Optional[bool] = None,
|
|
5334
5376
|
check_mcp_tools: bool = True,
|
|
5335
5377
|
) -> List[Union[Function, dict]]:
|
|
5378
|
+
# Connect tools that require connection management
|
|
5379
|
+
self._connect_connectable_tools()
|
|
5380
|
+
|
|
5336
5381
|
# Prepare tools
|
|
5337
5382
|
_tools: List[Union[Toolkit, Callable, Function, Dict]] = []
|
|
5338
5383
|
|
|
@@ -5382,6 +5427,7 @@ class Team:
|
|
|
5382
5427
|
run_response=run_response,
|
|
5383
5428
|
knowledge_filters=run_context.knowledge_filters,
|
|
5384
5429
|
async_mode=async_mode,
|
|
5430
|
+
run_context=run_context,
|
|
5385
5431
|
)
|
|
5386
5432
|
)
|
|
5387
5433
|
else:
|
|
@@ -5390,6 +5436,7 @@ class Team:
|
|
|
5390
5436
|
run_response=run_response,
|
|
5391
5437
|
knowledge_filters=run_context.knowledge_filters,
|
|
5392
5438
|
async_mode=async_mode,
|
|
5439
|
+
run_context=run_context,
|
|
5393
5440
|
)
|
|
5394
5441
|
)
|
|
5395
5442
|
|
|
@@ -6579,7 +6626,10 @@ class Team:
|
|
|
6579
6626
|
retrieval_timer = Timer()
|
|
6580
6627
|
retrieval_timer.start()
|
|
6581
6628
|
docs_from_knowledge = self.get_relevant_docs_from_knowledge(
|
|
6582
|
-
query=user_msg_content,
|
|
6629
|
+
query=user_msg_content,
|
|
6630
|
+
filters=run_context.knowledge_filters,
|
|
6631
|
+
run_context=run_context,
|
|
6632
|
+
**kwargs,
|
|
6583
6633
|
)
|
|
6584
6634
|
if docs_from_knowledge is not None:
|
|
6585
6635
|
references = MessageReferences(
|
|
@@ -6734,7 +6784,10 @@ class Team:
|
|
|
6734
6784
|
retrieval_timer = Timer()
|
|
6735
6785
|
retrieval_timer.start()
|
|
6736
6786
|
docs_from_knowledge = await self.aget_relevant_docs_from_knowledge(
|
|
6737
|
-
query=user_msg_content,
|
|
6787
|
+
query=user_msg_content,
|
|
6788
|
+
filters=run_context.knowledge_filters,
|
|
6789
|
+
run_context=run_context,
|
|
6790
|
+
**kwargs,
|
|
6738
6791
|
)
|
|
6739
6792
|
if docs_from_knowledge is not None:
|
|
6740
6793
|
references = MessageReferences(
|
|
@@ -9000,11 +9053,15 @@ class Team:
|
|
|
9000
9053
|
query: str,
|
|
9001
9054
|
num_documents: Optional[int] = None,
|
|
9002
9055
|
filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
|
|
9056
|
+
run_context: Optional[RunContext] = None,
|
|
9003
9057
|
**kwargs,
|
|
9004
9058
|
) -> Optional[List[Union[Dict[str, Any], str]]]:
|
|
9005
9059
|
"""Return a list of references from the knowledge base"""
|
|
9006
9060
|
from agno.knowledge.document import Document
|
|
9007
9061
|
|
|
9062
|
+
# Extract dependencies from run_context if available
|
|
9063
|
+
dependencies = run_context.dependencies if run_context else None
|
|
9064
|
+
|
|
9008
9065
|
if num_documents is None and self.knowledge is not None:
|
|
9009
9066
|
num_documents = self.knowledge.max_results
|
|
9010
9067
|
|
|
@@ -9036,6 +9093,11 @@ class Team:
|
|
|
9036
9093
|
knowledge_retriever_kwargs = {"team": self}
|
|
9037
9094
|
if "filters" in sig.parameters:
|
|
9038
9095
|
knowledge_retriever_kwargs["filters"] = filters
|
|
9096
|
+
if "run_context" in sig.parameters:
|
|
9097
|
+
knowledge_retriever_kwargs["run_context"] = run_context
|
|
9098
|
+
elif "dependencies" in sig.parameters:
|
|
9099
|
+
# Backward compatibility: support dependencies parameter
|
|
9100
|
+
knowledge_retriever_kwargs["dependencies"] = dependencies
|
|
9039
9101
|
knowledge_retriever_kwargs.update({"query": query, "num_documents": num_documents, **kwargs})
|
|
9040
9102
|
return self.knowledge_retriever(**knowledge_retriever_kwargs)
|
|
9041
9103
|
except Exception as e:
|
|
@@ -9067,11 +9129,15 @@ class Team:
|
|
|
9067
9129
|
query: str,
|
|
9068
9130
|
num_documents: Optional[int] = None,
|
|
9069
9131
|
filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
|
|
9132
|
+
run_context: Optional[RunContext] = None,
|
|
9070
9133
|
**kwargs,
|
|
9071
9134
|
) -> Optional[List[Union[Dict[str, Any], str]]]:
|
|
9072
9135
|
"""Get relevant documents from knowledge base asynchronously."""
|
|
9073
9136
|
from agno.knowledge.document import Document
|
|
9074
9137
|
|
|
9138
|
+
# Extract dependencies from run_context if available
|
|
9139
|
+
dependencies = run_context.dependencies if run_context else None
|
|
9140
|
+
|
|
9075
9141
|
if num_documents is None and self.knowledge is not None:
|
|
9076
9142
|
num_documents = self.knowledge.max_results
|
|
9077
9143
|
|
|
@@ -9103,6 +9169,11 @@ class Team:
|
|
|
9103
9169
|
knowledge_retriever_kwargs = {"team": self}
|
|
9104
9170
|
if "filters" in sig.parameters:
|
|
9105
9171
|
knowledge_retriever_kwargs["filters"] = filters
|
|
9172
|
+
if "run_context" in sig.parameters:
|
|
9173
|
+
knowledge_retriever_kwargs["run_context"] = run_context
|
|
9174
|
+
elif "dependencies" in sig.parameters:
|
|
9175
|
+
# Backward compatibility: support dependencies parameter
|
|
9176
|
+
knowledge_retriever_kwargs["dependencies"] = dependencies
|
|
9106
9177
|
knowledge_retriever_kwargs.update({"query": query, "num_documents": num_documents, **kwargs})
|
|
9107
9178
|
|
|
9108
9179
|
result = self.knowledge_retriever(**knowledge_retriever_kwargs)
|
|
@@ -9187,6 +9258,7 @@ class Team:
|
|
|
9187
9258
|
run_response: TeamRunOutput,
|
|
9188
9259
|
knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
|
|
9189
9260
|
async_mode: bool = False,
|
|
9261
|
+
run_context: Optional[RunContext] = None,
|
|
9190
9262
|
) -> Function:
|
|
9191
9263
|
"""Factory function to create a search_knowledge_base function with filters."""
|
|
9192
9264
|
|
|
@@ -9202,7 +9274,9 @@ class Team:
|
|
|
9202
9274
|
# Get the relevant documents from the knowledge base, passing filters
|
|
9203
9275
|
retrieval_timer = Timer()
|
|
9204
9276
|
retrieval_timer.start()
|
|
9205
|
-
docs_from_knowledge = self.get_relevant_docs_from_knowledge(
|
|
9277
|
+
docs_from_knowledge = self.get_relevant_docs_from_knowledge(
|
|
9278
|
+
query=query, filters=knowledge_filters, run_context=run_context
|
|
9279
|
+
)
|
|
9206
9280
|
if docs_from_knowledge is not None:
|
|
9207
9281
|
references = MessageReferences(
|
|
9208
9282
|
query=query, references=docs_from_knowledge, time=round(retrieval_timer.elapsed, 4)
|
|
@@ -9229,7 +9303,9 @@ class Team:
|
|
|
9229
9303
|
"""
|
|
9230
9304
|
retrieval_timer = Timer()
|
|
9231
9305
|
retrieval_timer.start()
|
|
9232
|
-
docs_from_knowledge = await self.aget_relevant_docs_from_knowledge(
|
|
9306
|
+
docs_from_knowledge = await self.aget_relevant_docs_from_knowledge(
|
|
9307
|
+
query=query, filters=knowledge_filters, run_context=run_context
|
|
9308
|
+
)
|
|
9233
9309
|
if docs_from_knowledge is not None:
|
|
9234
9310
|
references = MessageReferences(
|
|
9235
9311
|
query=query, references=docs_from_knowledge, time=round(retrieval_timer.elapsed, 4)
|
|
@@ -9256,6 +9332,7 @@ class Team:
|
|
|
9256
9332
|
run_response: TeamRunOutput,
|
|
9257
9333
|
knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
|
|
9258
9334
|
async_mode: bool = False,
|
|
9335
|
+
run_context: Optional[RunContext] = None,
|
|
9259
9336
|
) -> Function:
|
|
9260
9337
|
"""Factory function to create a search_knowledge_base function with filters."""
|
|
9261
9338
|
|
|
@@ -9275,7 +9352,9 @@ class Team:
|
|
|
9275
9352
|
# Get the relevant documents from the knowledge base, passing filters
|
|
9276
9353
|
retrieval_timer = Timer()
|
|
9277
9354
|
retrieval_timer.start()
|
|
9278
|
-
docs_from_knowledge = self.get_relevant_docs_from_knowledge(
|
|
9355
|
+
docs_from_knowledge = self.get_relevant_docs_from_knowledge(
|
|
9356
|
+
query=query, filters=search_filters, run_context=run_context
|
|
9357
|
+
)
|
|
9279
9358
|
if docs_from_knowledge is not None:
|
|
9280
9359
|
references = MessageReferences(
|
|
9281
9360
|
query=query, references=docs_from_knowledge, time=round(retrieval_timer.elapsed, 4)
|
|
@@ -9306,7 +9385,9 @@ class Team:
|
|
|
9306
9385
|
|
|
9307
9386
|
retrieval_timer = Timer()
|
|
9308
9387
|
retrieval_timer.start()
|
|
9309
|
-
docs_from_knowledge = await self.aget_relevant_docs_from_knowledge(
|
|
9388
|
+
docs_from_knowledge = await self.aget_relevant_docs_from_knowledge(
|
|
9389
|
+
query=query, filters=search_filters, run_context=run_context
|
|
9390
|
+
)
|
|
9310
9391
|
if docs_from_knowledge is not None:
|
|
9311
9392
|
references = MessageReferences(
|
|
9312
9393
|
query=query, references=docs_from_knowledge, time=round(retrieval_timer.elapsed, 4)
|