agno 2.2.6__py3-none-any.whl → 2.2.8__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 +430 -416
- agno/api/os.py +1 -1
- agno/culture/manager.py +9 -4
- agno/guardrails/prompt_injection.py +1 -0
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/knowledge.py +37 -5
- agno/models/base.py +62 -54
- agno/models/openai/chat.py +6 -5
- agno/models/openai/responses.py +1 -58
- agno/models/requesty/requesty.py +5 -2
- agno/os/app.py +13 -12
- agno/os/schema.py +11 -8
- agno/run/__init__.py +6 -0
- agno/run/base.py +18 -1
- agno/run/team.py +11 -9
- agno/team/team.py +258 -361
- agno/tools/exa.py +21 -16
- agno/tools/function.py +93 -16
- agno/tools/mcp/mcp.py +8 -1
- agno/tools/notion.py +14 -11
- agno/utils/agent.py +78 -0
- agno/utils/hooks.py +1 -1
- agno/utils/models/claude.py +25 -8
- agno/vectordb/redis/__init__.py +5 -0
- agno/vectordb/redis/redisdb.py +687 -0
- agno/workflow/agent.py +10 -9
- agno/workflow/step.py +13 -2
- agno/workflow/workflow.py +85 -53
- {agno-2.2.6.dist-info → agno-2.2.8.dist-info}/METADATA +7 -3
- {agno-2.2.6.dist-info → agno-2.2.8.dist-info}/RECORD +33 -30
- {agno-2.2.6.dist-info → agno-2.2.8.dist-info}/WHEEL +0 -0
- {agno-2.2.6.dist-info → agno-2.2.8.dist-info}/licenses/LICENSE +0 -0
- {agno-2.2.6.dist-info → agno-2.2.8.dist-info}/top_level.txt +0 -0
agno/tools/exa.py
CHANGED
|
@@ -27,14 +27,14 @@ class ExaTools(Toolkit):
|
|
|
27
27
|
all (bool): Enable all tools. Overrides individual flags when True. Default is False.
|
|
28
28
|
text (bool): Retrieve text content from results. Default is True.
|
|
29
29
|
text_length_limit (int): Max length of text content per result. Default is 1000.
|
|
30
|
-
highlights (bool): Include highlighted snippets.
|
|
30
|
+
highlights (bool): Include highlighted snippets. Deprecated since it was removed in the Exa API. It will be removed from Agno in a future release.
|
|
31
31
|
api_key (Optional[str]): Exa API key. Retrieved from `EXA_API_KEY` env variable if not provided.
|
|
32
32
|
num_results (Optional[int]): Default number of search results. Overrides individual searches if set.
|
|
33
33
|
start_crawl_date (Optional[str]): Include results crawled on/after this date (`YYYY-MM-DD`).
|
|
34
34
|
end_crawl_date (Optional[str]): Include results crawled on/before this date (`YYYY-MM-DD`).
|
|
35
35
|
start_published_date (Optional[str]): Include results published on/after this date (`YYYY-MM-DD`).
|
|
36
36
|
end_published_date (Optional[str]): Include results published on/before this date (`YYYY-MM-DD`).
|
|
37
|
-
use_autoprompt (Optional[bool]): Enable autoprompt features in queries.
|
|
37
|
+
use_autoprompt (Optional[bool]): Enable autoprompt features in queries. Deprecated since it was removed in the Exa API. It will be removed from Agno in a future release.
|
|
38
38
|
type (Optional[str]): Specify content type (e.g., article, blog, video).
|
|
39
39
|
category (Optional[str]): Filter results by category. Options are "company", "research paper", "news", "pdf", "github", "tweet", "personal site", "linkedin profile", "financial report".
|
|
40
40
|
include_domains (Optional[List[str]]): Restrict results to these domains.
|
|
@@ -54,7 +54,7 @@ class ExaTools(Toolkit):
|
|
|
54
54
|
all: bool = False,
|
|
55
55
|
text: bool = True,
|
|
56
56
|
text_length_limit: int = 1000,
|
|
57
|
-
highlights: bool =
|
|
57
|
+
highlights: Optional[bool] = None, # Deprecated
|
|
58
58
|
summary: bool = False,
|
|
59
59
|
api_key: Optional[str] = None,
|
|
60
60
|
num_results: Optional[int] = None,
|
|
@@ -84,7 +84,24 @@ class ExaTools(Toolkit):
|
|
|
84
84
|
|
|
85
85
|
self.text: bool = text
|
|
86
86
|
self.text_length_limit: int = text_length_limit
|
|
87
|
-
|
|
87
|
+
|
|
88
|
+
if highlights:
|
|
89
|
+
import warnings
|
|
90
|
+
|
|
91
|
+
warnings.warn(
|
|
92
|
+
"The 'highlights' parameter is deprecated since it was removed in the Exa API. It will be removed from Agno in a future release.",
|
|
93
|
+
DeprecationWarning,
|
|
94
|
+
stacklevel=2,
|
|
95
|
+
)
|
|
96
|
+
if use_autoprompt:
|
|
97
|
+
import warnings
|
|
98
|
+
|
|
99
|
+
warnings.warn(
|
|
100
|
+
"The 'use_autoprompt' parameter is deprecated since it was removed in the Exa API. It will be removed from Agno in a future release.",
|
|
101
|
+
DeprecationWarning,
|
|
102
|
+
stacklevel=2,
|
|
103
|
+
)
|
|
104
|
+
|
|
88
105
|
self.summary: bool = summary
|
|
89
106
|
self.num_results: Optional[int] = num_results
|
|
90
107
|
self.livecrawl: str = livecrawl
|
|
@@ -92,7 +109,6 @@ class ExaTools(Toolkit):
|
|
|
92
109
|
self.end_crawl_date: Optional[str] = end_crawl_date
|
|
93
110
|
self.start_published_date: Optional[str] = start_published_date
|
|
94
111
|
self.end_published_date: Optional[str] = end_published_date
|
|
95
|
-
self.use_autoprompt: Optional[bool] = use_autoprompt
|
|
96
112
|
self.type: Optional[str] = type
|
|
97
113
|
self.category: Optional[str] = category
|
|
98
114
|
self.include_domains: Optional[List[str]] = include_domains
|
|
@@ -140,13 +156,6 @@ class ExaTools(Toolkit):
|
|
|
140
156
|
if self.text_length_limit:
|
|
141
157
|
_text = _text[: self.text_length_limit]
|
|
142
158
|
result_dict["text"] = _text
|
|
143
|
-
if self.highlights:
|
|
144
|
-
try:
|
|
145
|
-
if result.highlights: # type: ignore
|
|
146
|
-
result_dict["highlights"] = result.highlights # type: ignore
|
|
147
|
-
except Exception as e:
|
|
148
|
-
log_debug(f"Failed to get highlights {e}")
|
|
149
|
-
result_dict["highlights"] = f"Failed to get highlights {e}"
|
|
150
159
|
exa_results_parsed.append(result_dict)
|
|
151
160
|
return json.dumps(exa_results_parsed, indent=4, ensure_ascii=False)
|
|
152
161
|
|
|
@@ -168,14 +177,12 @@ class ExaTools(Toolkit):
|
|
|
168
177
|
log_info(f"Searching exa for: {query}")
|
|
169
178
|
search_kwargs: Dict[str, Any] = {
|
|
170
179
|
"text": self.text,
|
|
171
|
-
"highlights": self.highlights,
|
|
172
180
|
"summary": self.summary,
|
|
173
181
|
"num_results": self.num_results or num_results,
|
|
174
182
|
"start_crawl_date": self.start_crawl_date,
|
|
175
183
|
"end_crawl_date": self.end_crawl_date,
|
|
176
184
|
"start_published_date": self.start_published_date,
|
|
177
185
|
"end_published_date": self.end_published_date,
|
|
178
|
-
"use_autoprompt": self.use_autoprompt,
|
|
179
186
|
"type": self.type,
|
|
180
187
|
"category": self.category or category, # Prefer a user-set category
|
|
181
188
|
"include_domains": self.include_domains,
|
|
@@ -212,7 +219,6 @@ class ExaTools(Toolkit):
|
|
|
212
219
|
|
|
213
220
|
query_kwargs: Dict[str, Any] = {
|
|
214
221
|
"text": self.text,
|
|
215
|
-
"highlights": self.highlights,
|
|
216
222
|
"summary": self.summary,
|
|
217
223
|
}
|
|
218
224
|
|
|
@@ -249,7 +255,6 @@ class ExaTools(Toolkit):
|
|
|
249
255
|
|
|
250
256
|
query_kwargs: Dict[str, Any] = {
|
|
251
257
|
"text": self.text,
|
|
252
|
-
"highlights": self.highlights,
|
|
253
258
|
"summary": self.summary,
|
|
254
259
|
"include_domains": self.include_domains,
|
|
255
260
|
"exclude_domains": self.exclude_domains,
|
agno/tools/function.py
CHANGED
|
@@ -9,6 +9,7 @@ from pydantic import BaseModel, Field, validate_call
|
|
|
9
9
|
|
|
10
10
|
from agno.exceptions import AgentRunException
|
|
11
11
|
from agno.media import Audio, File, Image, Video
|
|
12
|
+
from agno.run import RunContext
|
|
12
13
|
from agno.utils.log import log_debug, log_error, log_exception, log_warning
|
|
13
14
|
|
|
14
15
|
T = TypeVar("T")
|
|
@@ -122,6 +123,8 @@ class Function(BaseModel):
|
|
|
122
123
|
_agent: Optional[Any] = None
|
|
123
124
|
# The team that the function is associated with
|
|
124
125
|
_team: Optional[Any] = None
|
|
126
|
+
# The run context that the function is associated with
|
|
127
|
+
_run_context: Optional[RunContext] = None
|
|
125
128
|
# The session state that the function is associated with
|
|
126
129
|
_session_state: Optional[Dict[str, Any]] = None
|
|
127
130
|
# The dependencies that the function is associated with
|
|
@@ -196,8 +199,13 @@ class Function(BaseModel):
|
|
|
196
199
|
del type_hints["agent"]
|
|
197
200
|
if "team" in sig.parameters and "team" in type_hints:
|
|
198
201
|
del type_hints["team"]
|
|
202
|
+
if "run_context" in sig.parameters and "run_context" in type_hints:
|
|
203
|
+
del type_hints["run_context"]
|
|
199
204
|
if "session_state" in sig.parameters and "session_state" in type_hints:
|
|
200
205
|
del type_hints["session_state"]
|
|
206
|
+
if "dependencies" in sig.parameters and "dependencies" in type_hints:
|
|
207
|
+
del type_hints["dependencies"]
|
|
208
|
+
|
|
201
209
|
# Remove media parameters from type hints as they are injected automatically
|
|
202
210
|
if "images" in sig.parameters and "images" in type_hints:
|
|
203
211
|
del type_hints["images"]
|
|
@@ -207,8 +215,6 @@ class Function(BaseModel):
|
|
|
207
215
|
del type_hints["audios"]
|
|
208
216
|
if "files" in sig.parameters and "files" in type_hints:
|
|
209
217
|
del type_hints["files"]
|
|
210
|
-
if "dependencies" in sig.parameters and "dependencies" in type_hints:
|
|
211
|
-
del type_hints["dependencies"]
|
|
212
218
|
# log_info(f"Type hints for {function_name}: {type_hints}")
|
|
213
219
|
|
|
214
220
|
# Filter out return type and only process parameters
|
|
@@ -217,7 +223,18 @@ class Function(BaseModel):
|
|
|
217
223
|
for name in sig.parameters
|
|
218
224
|
if name != "return"
|
|
219
225
|
and name
|
|
220
|
-
not in [
|
|
226
|
+
not in [
|
|
227
|
+
"agent",
|
|
228
|
+
"team",
|
|
229
|
+
"run_context",
|
|
230
|
+
"session_state",
|
|
231
|
+
"dependencies",
|
|
232
|
+
"self",
|
|
233
|
+
"images",
|
|
234
|
+
"videos",
|
|
235
|
+
"audios",
|
|
236
|
+
"files",
|
|
237
|
+
]
|
|
221
238
|
}
|
|
222
239
|
|
|
223
240
|
# Parse docstring for parameters
|
|
@@ -250,13 +267,14 @@ class Function(BaseModel):
|
|
|
250
267
|
not in [
|
|
251
268
|
"agent",
|
|
252
269
|
"team",
|
|
270
|
+
"run_context",
|
|
253
271
|
"session_state",
|
|
272
|
+
"dependencies",
|
|
254
273
|
"self",
|
|
255
274
|
"images",
|
|
256
275
|
"videos",
|
|
257
276
|
"audios",
|
|
258
277
|
"files",
|
|
259
|
-
"dependencies",
|
|
260
278
|
]
|
|
261
279
|
]
|
|
262
280
|
else:
|
|
@@ -269,13 +287,14 @@ class Function(BaseModel):
|
|
|
269
287
|
not in [
|
|
270
288
|
"agent",
|
|
271
289
|
"team",
|
|
290
|
+
"run_context",
|
|
272
291
|
"session_state",
|
|
292
|
+
"dependencies",
|
|
273
293
|
"self",
|
|
274
294
|
"images",
|
|
275
295
|
"videos",
|
|
276
296
|
"audios",
|
|
277
297
|
"files",
|
|
278
|
-
"dependencies",
|
|
279
298
|
]
|
|
280
299
|
]
|
|
281
300
|
|
|
@@ -325,8 +344,12 @@ class Function(BaseModel):
|
|
|
325
344
|
del type_hints["agent"]
|
|
326
345
|
if "team" in sig.parameters and "team" in type_hints:
|
|
327
346
|
del type_hints["team"]
|
|
347
|
+
if "run_context" in sig.parameters and "run_context" in type_hints:
|
|
348
|
+
del type_hints["run_context"]
|
|
328
349
|
if "session_state" in sig.parameters and "session_state" in type_hints:
|
|
329
350
|
del type_hints["session_state"]
|
|
351
|
+
if "dependencies" in sig.parameters and "dependencies" in type_hints:
|
|
352
|
+
del type_hints["dependencies"]
|
|
330
353
|
if "images" in sig.parameters and "images" in type_hints:
|
|
331
354
|
del type_hints["images"]
|
|
332
355
|
if "videos" in sig.parameters and "videos" in type_hints:
|
|
@@ -335,8 +358,6 @@ class Function(BaseModel):
|
|
|
335
358
|
del type_hints["audios"]
|
|
336
359
|
if "files" in sig.parameters and "files" in type_hints:
|
|
337
360
|
del type_hints["files"]
|
|
338
|
-
if "dependencies" in sig.parameters and "dependencies" in type_hints:
|
|
339
|
-
del type_hints["dependencies"]
|
|
340
361
|
# log_info(f"Type hints for {self.name}: {type_hints}")
|
|
341
362
|
|
|
342
363
|
# Filter out return type and only process parameters
|
|
@@ -344,13 +365,14 @@ class Function(BaseModel):
|
|
|
344
365
|
"return",
|
|
345
366
|
"agent",
|
|
346
367
|
"team",
|
|
368
|
+
"run_context",
|
|
347
369
|
"session_state",
|
|
370
|
+
"dependencies",
|
|
348
371
|
"self",
|
|
349
372
|
"images",
|
|
350
373
|
"videos",
|
|
351
374
|
"audios",
|
|
352
375
|
"files",
|
|
353
|
-
"dependencies",
|
|
354
376
|
]
|
|
355
377
|
if self.requires_user_input and self.user_input_fields:
|
|
356
378
|
if len(self.user_input_fields) == 0:
|
|
@@ -512,7 +534,18 @@ class Function(BaseModel):
|
|
|
512
534
|
name
|
|
513
535
|
for name in self.parameters["properties"]
|
|
514
536
|
if name
|
|
515
|
-
not in [
|
|
537
|
+
not in [
|
|
538
|
+
"agent",
|
|
539
|
+
"team",
|
|
540
|
+
"run_context",
|
|
541
|
+
"session_state",
|
|
542
|
+
"dependencies",
|
|
543
|
+
"images",
|
|
544
|
+
"videos",
|
|
545
|
+
"audios",
|
|
546
|
+
"files",
|
|
547
|
+
"self",
|
|
548
|
+
]
|
|
516
549
|
]
|
|
517
550
|
|
|
518
551
|
def _get_cache_key(self, entrypoint_args: Dict[str, Any], call_args: Optional[Dict[str, Any]] = None) -> str:
|
|
@@ -526,8 +559,12 @@ class Function(BaseModel):
|
|
|
526
559
|
del copy_entrypoint_args["agent"]
|
|
527
560
|
if "team" in copy_entrypoint_args:
|
|
528
561
|
del copy_entrypoint_args["team"]
|
|
562
|
+
if "run_context" in copy_entrypoint_args:
|
|
563
|
+
del copy_entrypoint_args["run_context"]
|
|
529
564
|
if "session_state" in copy_entrypoint_args:
|
|
530
565
|
del copy_entrypoint_args["session_state"]
|
|
566
|
+
if "dependencies" in copy_entrypoint_args:
|
|
567
|
+
del copy_entrypoint_args["dependencies"]
|
|
531
568
|
if "images" in copy_entrypoint_args:
|
|
532
569
|
del copy_entrypoint_args["images"]
|
|
533
570
|
if "videos" in copy_entrypoint_args:
|
|
@@ -536,8 +573,6 @@ class Function(BaseModel):
|
|
|
536
573
|
del copy_entrypoint_args["audios"]
|
|
537
574
|
if "files" in copy_entrypoint_args:
|
|
538
575
|
del copy_entrypoint_args["files"]
|
|
539
|
-
if "dependencies" in copy_entrypoint_args:
|
|
540
|
-
del copy_entrypoint_args["dependencies"]
|
|
541
576
|
# Use json.dumps with sort_keys=True to ensure consistent ordering regardless of dict key order
|
|
542
577
|
args_str = json.dumps(copy_entrypoint_args, sort_keys=True, default=str)
|
|
543
578
|
|
|
@@ -663,8 +698,14 @@ class FunctionCall(BaseModel):
|
|
|
663
698
|
if "team" in signature(self.function.pre_hook).parameters:
|
|
664
699
|
pre_hook_args["team"] = self.function._team
|
|
665
700
|
# Check if the pre-hook has an session_state argument
|
|
701
|
+
if "run_context" in signature(self.function.pre_hook).parameters:
|
|
702
|
+
pre_hook_args["run_context"] = self.function._run_context
|
|
703
|
+
# Check if the pre-hook has an session_state argument
|
|
666
704
|
if "session_state" in signature(self.function.pre_hook).parameters:
|
|
667
705
|
pre_hook_args["session_state"] = self.function._session_state
|
|
706
|
+
# Check if the pre-hook has an dependencies argument
|
|
707
|
+
if "dependencies" in signature(self.function.pre_hook).parameters:
|
|
708
|
+
pre_hook_args["dependencies"] = self.function._dependencies
|
|
668
709
|
# Check if the pre-hook has an fc argument
|
|
669
710
|
if "fc" in signature(self.function.pre_hook).parameters:
|
|
670
711
|
pre_hook_args["fc"] = self
|
|
@@ -691,8 +732,14 @@ class FunctionCall(BaseModel):
|
|
|
691
732
|
if "team" in signature(self.function.post_hook).parameters:
|
|
692
733
|
post_hook_args["team"] = self.function._team
|
|
693
734
|
# Check if the post-hook has an session_state argument
|
|
735
|
+
if "run_context" in signature(self.function.post_hook).parameters:
|
|
736
|
+
post_hook_args["run_context"] = self.function._run_context
|
|
737
|
+
# Check if the post-hook has an session_state argument
|
|
694
738
|
if "session_state" in signature(self.function.post_hook).parameters:
|
|
695
739
|
post_hook_args["session_state"] = self.function._session_state
|
|
740
|
+
# Check if the post-hook has an dependencies argument
|
|
741
|
+
if "dependencies" in signature(self.function.post_hook).parameters:
|
|
742
|
+
post_hook_args["dependencies"] = self.function._dependencies
|
|
696
743
|
# Check if the post-hook has an fc argument
|
|
697
744
|
if "fc" in signature(self.function.post_hook).parameters:
|
|
698
745
|
post_hook_args["fc"] = self
|
|
@@ -716,6 +763,9 @@ class FunctionCall(BaseModel):
|
|
|
716
763
|
# Check if the entrypoint has an team argument
|
|
717
764
|
if "team" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
718
765
|
entrypoint_args["team"] = self.function._team
|
|
766
|
+
# Check if the entrypoint has an run_context argument
|
|
767
|
+
if "run_context" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
768
|
+
entrypoint_args["run_context"] = self.function._run_context
|
|
719
769
|
# Check if the entrypoint has an session_state argument
|
|
720
770
|
if "session_state" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
721
771
|
entrypoint_args["session_state"] = self.function._session_state
|
|
@@ -748,13 +798,15 @@ class FunctionCall(BaseModel):
|
|
|
748
798
|
# Check if the hook has an team argument
|
|
749
799
|
if "team" in signature(hook).parameters:
|
|
750
800
|
hook_args["team"] = self.function._team
|
|
801
|
+
# Check if the hook has an run_context argument
|
|
802
|
+
if "run_context" in signature(hook).parameters:
|
|
803
|
+
hook_args["run_context"] = self.function._run_context
|
|
751
804
|
# Check if the hook has an session_state argument
|
|
752
805
|
if "session_state" in signature(hook).parameters:
|
|
753
806
|
hook_args["session_state"] = self.function._session_state
|
|
754
807
|
# Check if the hook has an dependencies argument
|
|
755
808
|
if "dependencies" in signature(hook).parameters:
|
|
756
809
|
hook_args["dependencies"] = self.function._dependencies
|
|
757
|
-
|
|
758
810
|
if "name" in signature(hook).parameters:
|
|
759
811
|
hook_args["name"] = name
|
|
760
812
|
if "function_name" in signature(hook).parameters:
|
|
@@ -857,8 +909,16 @@ class FunctionCall(BaseModel):
|
|
|
857
909
|
result = self.function.entrypoint(**entrypoint_args, **self.arguments) # type: ignore
|
|
858
910
|
|
|
859
911
|
updated_session_state = None
|
|
860
|
-
if entrypoint_args.get("
|
|
861
|
-
|
|
912
|
+
if entrypoint_args.get("run_context") is not None:
|
|
913
|
+
run_context = entrypoint_args.get("run_context")
|
|
914
|
+
updated_session_state = (
|
|
915
|
+
run_context.session_state
|
|
916
|
+
if run_context is not None and run_context.session_state is not None
|
|
917
|
+
else None
|
|
918
|
+
)
|
|
919
|
+
else:
|
|
920
|
+
if self.function._session_state is not None:
|
|
921
|
+
updated_session_state = self.function._session_state
|
|
862
922
|
|
|
863
923
|
# Handle generator case
|
|
864
924
|
if isgenerator(result):
|
|
@@ -906,9 +966,15 @@ class FunctionCall(BaseModel):
|
|
|
906
966
|
# Check if the pre-hook has an team argument
|
|
907
967
|
if "team" in signature(self.function.pre_hook).parameters:
|
|
908
968
|
pre_hook_args["team"] = self.function._team
|
|
969
|
+
# Check if the pre-hook has an run_context argument
|
|
970
|
+
if "run_context" in signature(self.function.pre_hook).parameters:
|
|
971
|
+
pre_hook_args["run_context"] = self.function._run_context
|
|
909
972
|
# Check if the pre-hook has an session_state argument
|
|
910
973
|
if "session_state" in signature(self.function.pre_hook).parameters:
|
|
911
974
|
pre_hook_args["session_state"] = self.function._session_state
|
|
975
|
+
# Check if the pre-hook has an dependencies argument
|
|
976
|
+
if "dependencies" in signature(self.function.pre_hook).parameters:
|
|
977
|
+
pre_hook_args["dependencies"] = self.function._dependencies
|
|
912
978
|
# Check if the pre-hook has an fc argument
|
|
913
979
|
if "fc" in signature(self.function.pre_hook).parameters:
|
|
914
980
|
pre_hook_args["fc"] = self
|
|
@@ -935,9 +1001,15 @@ class FunctionCall(BaseModel):
|
|
|
935
1001
|
# Check if the post-hook has an team argument
|
|
936
1002
|
if "team" in signature(self.function.post_hook).parameters:
|
|
937
1003
|
post_hook_args["team"] = self.function._team
|
|
1004
|
+
# Check if the post-hook has an run_context argument
|
|
1005
|
+
if "run_context" in signature(self.function.post_hook).parameters:
|
|
1006
|
+
post_hook_args["run_context"] = self.function._run_context
|
|
938
1007
|
# Check if the post-hook has an session_state argument
|
|
939
1008
|
if "session_state" in signature(self.function.post_hook).parameters:
|
|
940
1009
|
post_hook_args["session_state"] = self.function._session_state
|
|
1010
|
+
# Check if the post-hook has an dependencies argument
|
|
1011
|
+
if "dependencies" in signature(self.function.post_hook).parameters:
|
|
1012
|
+
post_hook_args["dependencies"] = self.function._dependencies
|
|
941
1013
|
|
|
942
1014
|
# Check if the post-hook has an fc argument
|
|
943
1015
|
if "fc" in signature(self.function.post_hook).parameters:
|
|
@@ -1071,8 +1143,13 @@ class FunctionCall(BaseModel):
|
|
|
1071
1143
|
self.function._save_to_cache(cache_file, self.result)
|
|
1072
1144
|
|
|
1073
1145
|
updated_session_state = None
|
|
1074
|
-
if entrypoint_args.get("
|
|
1075
|
-
|
|
1146
|
+
if entrypoint_args.get("run_context") is not None:
|
|
1147
|
+
run_context = entrypoint_args.get("run_context")
|
|
1148
|
+
updated_session_state = (
|
|
1149
|
+
run_context.session_state
|
|
1150
|
+
if run_context is not None and run_context.session_state is not None
|
|
1151
|
+
else None
|
|
1152
|
+
)
|
|
1076
1153
|
|
|
1077
1154
|
execution_result = FunctionExecutionResult(
|
|
1078
1155
|
status="success", result=self.result, updated_session_state=updated_session_state
|
agno/tools/mcp/mcp.py
CHANGED
|
@@ -43,6 +43,7 @@ class MCPTools(Toolkit):
|
|
|
43
43
|
include_tools: Optional[list[str]] = None,
|
|
44
44
|
exclude_tools: Optional[list[str]] = None,
|
|
45
45
|
refresh_connection: bool = False,
|
|
46
|
+
tool_name_prefix: Optional[str] = "",
|
|
46
47
|
**kwargs,
|
|
47
48
|
):
|
|
48
49
|
"""
|
|
@@ -71,6 +72,7 @@ class MCPTools(Toolkit):
|
|
|
71
72
|
self.include_tools = include_tools
|
|
72
73
|
self.exclude_tools = exclude_tools
|
|
73
74
|
self.refresh_connection = refresh_connection
|
|
75
|
+
self.tool_name_prefix = tool_name_prefix
|
|
74
76
|
|
|
75
77
|
if session is None and server_params is None:
|
|
76
78
|
if transport == "sse" and url is None:
|
|
@@ -279,6 +281,11 @@ class MCPTools(Toolkit):
|
|
|
279
281
|
if self.include_tools is None or tool.name in self.include_tools:
|
|
280
282
|
filtered_tools.append(tool)
|
|
281
283
|
|
|
284
|
+
# Get tool name prefix if available
|
|
285
|
+
tool_name_prefix = ""
|
|
286
|
+
if self.tool_name_prefix is not None:
|
|
287
|
+
tool_name_prefix = self.tool_name_prefix + "_"
|
|
288
|
+
|
|
282
289
|
# Register the tools with the toolkit
|
|
283
290
|
for tool in filtered_tools:
|
|
284
291
|
try:
|
|
@@ -286,7 +293,7 @@ class MCPTools(Toolkit):
|
|
|
286
293
|
entrypoint = get_entrypoint_for_tool(tool, self.session) # type: ignore
|
|
287
294
|
# Create a Function for the tool
|
|
288
295
|
f = Function(
|
|
289
|
-
name=tool.name,
|
|
296
|
+
name=tool_name_prefix + tool.name,
|
|
290
297
|
description=tool.description,
|
|
291
298
|
parameters=tool.inputSchema,
|
|
292
299
|
entrypoint=entrypoint,
|
agno/tools/notion.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
|
-
from typing import Any, List, Optional
|
|
3
|
+
from typing import Any, Dict, List, Optional, cast
|
|
4
4
|
|
|
5
5
|
from agno.tools import Toolkit
|
|
6
6
|
from agno.utils.log import log_debug, logger
|
|
@@ -73,16 +73,19 @@ class NotionTools(Toolkit):
|
|
|
73
73
|
log_debug(f"Creating Notion page with title: {title}, tag: {tag}")
|
|
74
74
|
|
|
75
75
|
# Create the page in the database
|
|
76
|
-
new_page =
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
{
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
76
|
+
new_page = cast(
|
|
77
|
+
Dict[str, Any],
|
|
78
|
+
self.client.pages.create(
|
|
79
|
+
parent={"database_id": self.database_id},
|
|
80
|
+
properties={"Name": {"title": [{"text": {"content": title}}]}, "Tag": {"select": {"name": tag}}},
|
|
81
|
+
children=[
|
|
82
|
+
{
|
|
83
|
+
"object": "block",
|
|
84
|
+
"type": "paragraph",
|
|
85
|
+
"paragraph": {"rich_text": [{"type": "text", "text": {"content": content}}]},
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
),
|
|
86
89
|
)
|
|
87
90
|
|
|
88
91
|
result = {"success": True, "page_id": new_page["id"], "url": new_page["url"], "title": title, "tag": tag}
|
agno/utils/agent.py
CHANGED
|
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Any, AsyncIterator, Dict, Iterator, List, Opti
|
|
|
4
4
|
from agno.media import Audio, File, Image, Video
|
|
5
5
|
from agno.models.message import Message
|
|
6
6
|
from agno.models.metrics import Metrics
|
|
7
|
+
from agno.models.response import ModelResponse
|
|
7
8
|
from agno.run.agent import RunEvent, RunInput, RunOutput, RunOutputEvent
|
|
8
9
|
from agno.run.team import RunOutputEvent as TeamRunOutputEvent
|
|
9
10
|
from agno.run.team import TeamRunOutput
|
|
@@ -291,6 +292,83 @@ def collect_joint_files(
|
|
|
291
292
|
return joint_files if joint_files else None
|
|
292
293
|
|
|
293
294
|
|
|
295
|
+
def store_media_util(run_response: Union[RunOutput, TeamRunOutput], model_response: ModelResponse):
|
|
296
|
+
"""Store media from model response in run_response for persistence"""
|
|
297
|
+
# Handle generated media fields from ModelResponse (generated media)
|
|
298
|
+
if model_response.images is not None:
|
|
299
|
+
for image in model_response.images:
|
|
300
|
+
if run_response.images is None:
|
|
301
|
+
run_response.images = []
|
|
302
|
+
run_response.images.append(image) # Generated images go to run_response.images
|
|
303
|
+
|
|
304
|
+
if model_response.videos is not None:
|
|
305
|
+
for video in model_response.videos:
|
|
306
|
+
if run_response.videos is None:
|
|
307
|
+
run_response.videos = []
|
|
308
|
+
run_response.videos.append(video) # Generated videos go to run_response.videos
|
|
309
|
+
|
|
310
|
+
if model_response.audios is not None:
|
|
311
|
+
for audio in model_response.audios:
|
|
312
|
+
if run_response.audio is None:
|
|
313
|
+
run_response.audio = []
|
|
314
|
+
run_response.audio.append(audio) # Generated audio go to run_response.audio
|
|
315
|
+
|
|
316
|
+
if model_response.files is not None:
|
|
317
|
+
for file in model_response.files:
|
|
318
|
+
if run_response.files is None:
|
|
319
|
+
run_response.files = []
|
|
320
|
+
run_response.files.append(file) # Generated files go to run_response.files
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def validate_media_object_id(
|
|
324
|
+
images: Optional[Sequence[Image]] = None,
|
|
325
|
+
videos: Optional[Sequence[Video]] = None,
|
|
326
|
+
audios: Optional[Sequence[Audio]] = None,
|
|
327
|
+
files: Optional[Sequence[File]] = None,
|
|
328
|
+
) -> tuple:
|
|
329
|
+
image_list = None
|
|
330
|
+
if images:
|
|
331
|
+
image_list = []
|
|
332
|
+
for img in images:
|
|
333
|
+
if not img.id:
|
|
334
|
+
from uuid import uuid4
|
|
335
|
+
|
|
336
|
+
img.id = str(uuid4())
|
|
337
|
+
image_list.append(img)
|
|
338
|
+
|
|
339
|
+
video_list = None
|
|
340
|
+
if videos:
|
|
341
|
+
video_list = []
|
|
342
|
+
for vid in videos:
|
|
343
|
+
if not vid.id:
|
|
344
|
+
from uuid import uuid4
|
|
345
|
+
|
|
346
|
+
vid.id = str(uuid4())
|
|
347
|
+
video_list.append(vid)
|
|
348
|
+
|
|
349
|
+
audio_list = None
|
|
350
|
+
if audios:
|
|
351
|
+
audio_list = []
|
|
352
|
+
for aud in audios:
|
|
353
|
+
if not aud.id:
|
|
354
|
+
from uuid import uuid4
|
|
355
|
+
|
|
356
|
+
aud.id = str(uuid4())
|
|
357
|
+
audio_list.append(aud)
|
|
358
|
+
|
|
359
|
+
file_list = None
|
|
360
|
+
if files:
|
|
361
|
+
file_list = []
|
|
362
|
+
for file in files:
|
|
363
|
+
if not file.id:
|
|
364
|
+
from uuid import uuid4
|
|
365
|
+
|
|
366
|
+
file.id = str(uuid4())
|
|
367
|
+
file_list.append(file)
|
|
368
|
+
|
|
369
|
+
return image_list, video_list, audio_list, file_list
|
|
370
|
+
|
|
371
|
+
|
|
294
372
|
def scrub_media_from_run_output(run_response: Union[RunOutput, TeamRunOutput]) -> None:
|
|
295
373
|
"""
|
|
296
374
|
Completely remove all media from RunOutput when store_media=False.
|
agno/utils/hooks.py
CHANGED
|
@@ -5,7 +5,7 @@ from agno.utils.log import log_warning
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def normalize_hooks(
|
|
8
|
-
hooks: Optional[Union[
|
|
8
|
+
hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail]]],
|
|
9
9
|
async_mode: bool = False,
|
|
10
10
|
) -> Optional[List[Callable[..., Any]]]:
|
|
11
11
|
"""Normalize hooks to a list format"""
|
agno/utils/models/claude.py
CHANGED
|
@@ -76,6 +76,17 @@ def _format_image_for_message(image: Image) -> Optional[Dict[str, Any]]:
|
|
|
76
76
|
if image.url is not None:
|
|
77
77
|
content_bytes = image.get_content_bytes() # type: ignore
|
|
78
78
|
|
|
79
|
+
# If image URL has a suffix, use it as the type (without dot)
|
|
80
|
+
import os
|
|
81
|
+
from urllib.parse import urlparse
|
|
82
|
+
|
|
83
|
+
img_type = None
|
|
84
|
+
if image.url:
|
|
85
|
+
parsed_url = urlparse(image.url)
|
|
86
|
+
_, ext = os.path.splitext(parsed_url.path)
|
|
87
|
+
if ext:
|
|
88
|
+
img_type = ext.lstrip(".").lower()
|
|
89
|
+
|
|
79
90
|
# Case 2: Image is a local file path
|
|
80
91
|
elif image.filepath is not None:
|
|
81
92
|
from pathlib import Path
|
|
@@ -84,6 +95,11 @@ def _format_image_for_message(image: Image) -> Optional[Dict[str, Any]]:
|
|
|
84
95
|
if path.exists() and path.is_file():
|
|
85
96
|
with open(image.filepath, "rb") as f:
|
|
86
97
|
content_bytes = f.read()
|
|
98
|
+
|
|
99
|
+
# If image file path has a suffix, use it as the type (without dot)
|
|
100
|
+
path_ext = path.suffix.lstrip(".")
|
|
101
|
+
if path_ext:
|
|
102
|
+
img_type = path_ext.lower()
|
|
87
103
|
else:
|
|
88
104
|
log_error(f"Image file not found: {image}")
|
|
89
105
|
return None
|
|
@@ -96,15 +112,16 @@ def _format_image_for_message(image: Image) -> Optional[Dict[str, Any]]:
|
|
|
96
112
|
log_error(f"Unsupported image type: {type(image)}")
|
|
97
113
|
return None
|
|
98
114
|
|
|
99
|
-
if
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
115
|
+
if not img_type:
|
|
116
|
+
if using_filetype:
|
|
117
|
+
kind = filetype.guess(content_bytes)
|
|
118
|
+
if not kind:
|
|
119
|
+
log_error("Unable to determine image type")
|
|
120
|
+
return None
|
|
104
121
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
122
|
+
img_type = kind.extension
|
|
123
|
+
else:
|
|
124
|
+
img_type = imghdr.what(None, h=content_bytes) # type: ignore
|
|
108
125
|
|
|
109
126
|
if not img_type:
|
|
110
127
|
log_error("Unable to determine image type")
|