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/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. Default is True.
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 = True,
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
- self.highlights: bool = highlights
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 ["agent", "team", "session_state", "self", "images", "videos", "audios", "files", "dependencies"]
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 ["agent", "team", "session_state", "images", "videos", "audios", "files", "self", "dependencies"]
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("session_state") is not None:
861
- updated_session_state = entrypoint_args.get("session_state")
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("session_state") is not None:
1075
- updated_session_state = entrypoint_args.get("session_state")
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 = self.client.pages.create(
77
- parent={"database_id": self.database_id},
78
- properties={"Name": {"title": [{"text": {"content": title}}]}, "Tag": {"select": {"name": tag}}},
79
- children=[
80
- {
81
- "object": "block",
82
- "type": "paragraph",
83
- "paragraph": {"rich_text": [{"type": "text", "text": {"content": content}}]},
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[List[Callable[..., Any]], List[BaseGuardrail]]],
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"""
@@ -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 using_filetype:
100
- kind = filetype.guess(content_bytes)
101
- if not kind:
102
- log_error("Unable to determine image type")
103
- return None
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
- img_type = kind.extension
106
- else:
107
- img_type = imghdr.what(None, h=content_bytes) # type: ignore
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")
@@ -0,0 +1,5 @@
1
+ from agno.vectordb.redis.redisdb import RedisDB
2
+
3
+ __all__ = [
4
+ "RedisDB",
5
+ ]