agno 2.0.7__py3-none-any.whl → 2.0.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 +81 -49
- agno/db/migrations/v1_to_v2.py +140 -11
- agno/knowledge/embedder/sentence_transformer.py +3 -3
- agno/knowledge/knowledge.py +152 -31
- agno/knowledge/types.py +8 -0
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +57 -0
- agno/models/google/gemini.py +4 -8
- agno/models/ollama/chat.py +24 -1
- agno/models/openai/chat.py +2 -7
- agno/models/openai/responses.py +21 -17
- agno/os/interfaces/agui/agui.py +2 -2
- agno/os/interfaces/agui/utils.py +81 -18
- agno/os/interfaces/slack/slack.py +2 -2
- agno/os/interfaces/whatsapp/whatsapp.py +2 -2
- agno/os/utils.py +8 -0
- agno/reasoning/default.py +3 -1
- agno/session/agent.py +8 -5
- agno/session/team.py +14 -10
- agno/team/team.py +218 -104
- agno/tools/function.py +43 -4
- agno/tools/mcp.py +60 -37
- agno/utils/gemini.py +147 -19
- agno/utils/models/claude.py +9 -0
- agno/utils/print_response/agent.py +16 -0
- agno/utils/print_response/team.py +16 -0
- agno/vectordb/base.py +2 -2
- agno/vectordb/langchaindb/langchaindb.py +5 -7
- agno/vectordb/llamaindex/llamaindexdb.py +25 -6
- agno/workflow/workflow.py +31 -15
- {agno-2.0.7.dist-info → agno-2.0.8.dist-info}/METADATA +1 -1
- {agno-2.0.7.dist-info → agno-2.0.8.dist-info}/RECORD +35 -33
- {agno-2.0.7.dist-info → agno-2.0.8.dist-info}/WHEEL +0 -0
- {agno-2.0.7.dist-info → agno-2.0.8.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.7.dist-info → agno-2.0.8.dist-info}/top_level.txt +0 -0
agno/tools/function.py
CHANGED
|
@@ -124,6 +124,8 @@ class Function(BaseModel):
|
|
|
124
124
|
_team: Optional[Any] = None
|
|
125
125
|
# The session state that the function is associated with
|
|
126
126
|
_session_state: Optional[Dict[str, Any]] = None
|
|
127
|
+
# The dependencies that the function is associated with
|
|
128
|
+
_dependencies: Optional[Dict[str, Any]] = None
|
|
127
129
|
|
|
128
130
|
# Media context that the function is associated with
|
|
129
131
|
_images: Optional[Sequence[Image]] = None
|
|
@@ -165,6 +167,8 @@ class Function(BaseModel):
|
|
|
165
167
|
del type_hints["audios"]
|
|
166
168
|
if "files" in sig.parameters and "files" in type_hints:
|
|
167
169
|
del type_hints["files"]
|
|
170
|
+
if "dependencies" in sig.parameters and "dependencies" in type_hints:
|
|
171
|
+
del type_hints["dependencies"]
|
|
168
172
|
# log_info(f"Type hints for {function_name}: {type_hints}")
|
|
169
173
|
|
|
170
174
|
# Filter out return type and only process parameters
|
|
@@ -172,7 +176,8 @@ class Function(BaseModel):
|
|
|
172
176
|
name: type_hints.get(name)
|
|
173
177
|
for name in sig.parameters
|
|
174
178
|
if name != "return"
|
|
175
|
-
and name
|
|
179
|
+
and name
|
|
180
|
+
not in ["agent", "team", "session_state", "self", "images", "videos", "audios", "files", "dependencies"]
|
|
176
181
|
}
|
|
177
182
|
|
|
178
183
|
# Parse docstring for parameters
|
|
@@ -201,7 +206,18 @@ class Function(BaseModel):
|
|
|
201
206
|
parameters["required"] = [
|
|
202
207
|
name
|
|
203
208
|
for name in parameters["properties"]
|
|
204
|
-
if name
|
|
209
|
+
if name
|
|
210
|
+
not in [
|
|
211
|
+
"agent",
|
|
212
|
+
"team",
|
|
213
|
+
"session_state",
|
|
214
|
+
"self",
|
|
215
|
+
"images",
|
|
216
|
+
"videos",
|
|
217
|
+
"audios",
|
|
218
|
+
"files",
|
|
219
|
+
"dependencies",
|
|
220
|
+
]
|
|
205
221
|
]
|
|
206
222
|
else:
|
|
207
223
|
# Mark a field as required if it has no default value (this would include optional fields)
|
|
@@ -209,7 +225,18 @@ class Function(BaseModel):
|
|
|
209
225
|
name
|
|
210
226
|
for name, param in sig.parameters.items()
|
|
211
227
|
if param.default == param.empty
|
|
212
|
-
and name
|
|
228
|
+
and name
|
|
229
|
+
not in [
|
|
230
|
+
"agent",
|
|
231
|
+
"team",
|
|
232
|
+
"session_state",
|
|
233
|
+
"self",
|
|
234
|
+
"images",
|
|
235
|
+
"videos",
|
|
236
|
+
"audios",
|
|
237
|
+
"files",
|
|
238
|
+
"dependencies",
|
|
239
|
+
]
|
|
213
240
|
]
|
|
214
241
|
|
|
215
242
|
# log_debug(f"JSON schema for {function_name}: {parameters}")
|
|
@@ -268,6 +295,8 @@ class Function(BaseModel):
|
|
|
268
295
|
del type_hints["audios"]
|
|
269
296
|
if "files" in sig.parameters and "files" in type_hints:
|
|
270
297
|
del type_hints["files"]
|
|
298
|
+
if "dependencies" in sig.parameters and "dependencies" in type_hints:
|
|
299
|
+
del type_hints["dependencies"]
|
|
271
300
|
# log_info(f"Type hints for {self.name}: {type_hints}")
|
|
272
301
|
|
|
273
302
|
# Filter out return type and only process parameters
|
|
@@ -281,6 +310,7 @@ class Function(BaseModel):
|
|
|
281
310
|
"videos",
|
|
282
311
|
"audios",
|
|
283
312
|
"files",
|
|
313
|
+
"dependencies",
|
|
284
314
|
]
|
|
285
315
|
if self.requires_user_input and self.user_input_fields:
|
|
286
316
|
if len(self.user_input_fields) == 0:
|
|
@@ -396,7 +426,8 @@ class Function(BaseModel):
|
|
|
396
426
|
self.parameters["required"] = [
|
|
397
427
|
name
|
|
398
428
|
for name in self.parameters["properties"]
|
|
399
|
-
if name
|
|
429
|
+
if name
|
|
430
|
+
not in ["agent", "team", "session_state", "images", "videos", "audios", "files", "self", "dependencies"]
|
|
400
431
|
]
|
|
401
432
|
|
|
402
433
|
def _get_cache_key(self, entrypoint_args: Dict[str, Any], call_args: Optional[Dict[str, Any]] = None) -> str:
|
|
@@ -419,6 +450,8 @@ class Function(BaseModel):
|
|
|
419
450
|
del copy_entrypoint_args["audios"]
|
|
420
451
|
if "files" in copy_entrypoint_args:
|
|
421
452
|
del copy_entrypoint_args["files"]
|
|
453
|
+
if "dependencies" in copy_entrypoint_args:
|
|
454
|
+
del copy_entrypoint_args["dependencies"]
|
|
422
455
|
args_str = str(copy_entrypoint_args)
|
|
423
456
|
|
|
424
457
|
kwargs_str = str(sorted((call_args or {}).items()))
|
|
@@ -599,6 +632,9 @@ class FunctionCall(BaseModel):
|
|
|
599
632
|
# Check if the entrypoint has an session_state argument
|
|
600
633
|
if "session_state" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
601
634
|
entrypoint_args["session_state"] = self.function._session_state
|
|
635
|
+
# Check if the entrypoint has an dependencies argument
|
|
636
|
+
if "dependencies" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
637
|
+
entrypoint_args["dependencies"] = self.function._dependencies
|
|
602
638
|
# Check if the entrypoint has an fc argument
|
|
603
639
|
if "fc" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
604
640
|
entrypoint_args["fc"] = self
|
|
@@ -629,6 +665,9 @@ class FunctionCall(BaseModel):
|
|
|
629
665
|
# Check if the hook has an session_state argument
|
|
630
666
|
if "session_state" in signature(hook).parameters:
|
|
631
667
|
hook_args["session_state"] = self.function._session_state
|
|
668
|
+
# Check if the hook has an dependencies argument
|
|
669
|
+
if "dependencies" in signature(hook).parameters:
|
|
670
|
+
hook_args["dependencies"] = self.function._dependencies
|
|
632
671
|
|
|
633
672
|
if "name" in signature(hook).parameters:
|
|
634
673
|
hook_args["name"] = name
|
agno/tools/mcp.py
CHANGED
|
@@ -8,7 +8,7 @@ from typing import Any, Dict, List, Literal, Optional, Union
|
|
|
8
8
|
|
|
9
9
|
from agno.tools import Toolkit
|
|
10
10
|
from agno.tools.function import Function
|
|
11
|
-
from agno.utils.log import log_debug, log_info, log_warning
|
|
11
|
+
from agno.utils.log import log_debug, log_error, log_info, log_warning
|
|
12
12
|
from agno.utils.mcp import get_entrypoint_for_tool
|
|
13
13
|
|
|
14
14
|
try:
|
|
@@ -338,12 +338,13 @@ class MCPTools(Toolkit):
|
|
|
338
338
|
self.functions[f.name] = f
|
|
339
339
|
log_debug(f"Function: {f.name} registered with {self.name}")
|
|
340
340
|
except Exception as e:
|
|
341
|
-
|
|
341
|
+
log_error(f"Failed to register tool {tool.name}: {e}")
|
|
342
342
|
|
|
343
343
|
log_debug(f"{self.name} initialized with {len(filtered_tools)} tools")
|
|
344
344
|
self._initialized = True
|
|
345
|
+
|
|
345
346
|
except Exception as e:
|
|
346
|
-
|
|
347
|
+
log_error(f"Failed to get MCP tools: {e}")
|
|
347
348
|
raise
|
|
348
349
|
|
|
349
350
|
|
|
@@ -372,6 +373,7 @@ class MultiMCPTools(Toolkit):
|
|
|
372
373
|
client=None,
|
|
373
374
|
include_tools: Optional[list[str]] = None,
|
|
374
375
|
exclude_tools: Optional[list[str]] = None,
|
|
376
|
+
allow_partial_failure: bool = False,
|
|
375
377
|
**kwargs,
|
|
376
378
|
):
|
|
377
379
|
"""
|
|
@@ -387,6 +389,7 @@ class MultiMCPTools(Toolkit):
|
|
|
387
389
|
timeout_seconds: Timeout in seconds for managing timeouts for Client Session if Agent or Tool doesn't respond.
|
|
388
390
|
include_tools: Optional list of tool names to include (if None, includes all).
|
|
389
391
|
exclude_tools: Optional list of tool names to exclude (if None, excludes none).
|
|
392
|
+
allow_partial_failure: If True, allows toolkit to initialize even if some MCP servers fail to connect. If False, any failure will raise an exception.
|
|
390
393
|
"""
|
|
391
394
|
super().__init__(name="MultiMCPTools", **kwargs)
|
|
392
395
|
|
|
@@ -445,12 +448,16 @@ class MultiMCPTools(Toolkit):
|
|
|
445
448
|
self.server_params_list.append(StreamableHTTPClientParams(url=url))
|
|
446
449
|
|
|
447
450
|
self._async_exit_stack = AsyncExitStack()
|
|
451
|
+
self._successful_connections = 0
|
|
452
|
+
|
|
448
453
|
self._initialized = False
|
|
449
454
|
self._connection_task = None
|
|
450
455
|
self._active_contexts: list[Any] = []
|
|
451
456
|
self._used_as_context_manager = False
|
|
452
457
|
|
|
453
458
|
self._client = client
|
|
459
|
+
self._initialized = False
|
|
460
|
+
self.allow_partial_failure = allow_partial_failure
|
|
454
461
|
|
|
455
462
|
def cleanup():
|
|
456
463
|
"""Cancel active connections"""
|
|
@@ -511,39 +518,53 @@ class MultiMCPTools(Toolkit):
|
|
|
511
518
|
if self._initialized:
|
|
512
519
|
return
|
|
513
520
|
|
|
521
|
+
server_connection_errors = []
|
|
522
|
+
|
|
514
523
|
for server_params in self.server_params_list:
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
# Handle SSE connections
|
|
526
|
-
elif isinstance(server_params, SSEClientParams):
|
|
527
|
-
client_connection = await self._async_exit_stack.enter_async_context(
|
|
528
|
-
sse_client(**asdict(server_params))
|
|
529
|
-
)
|
|
530
|
-
self._active_contexts.append(client_connection)
|
|
531
|
-
read, write = client_connection
|
|
532
|
-
session = await self._async_exit_stack.enter_async_context(ClientSession(read, write))
|
|
533
|
-
self._active_contexts.append(session)
|
|
534
|
-
await self.initialize(session)
|
|
535
|
-
# Handle Streamable HTTP connections
|
|
536
|
-
elif isinstance(server_params, StreamableHTTPClientParams):
|
|
537
|
-
client_connection = await self._async_exit_stack.enter_async_context(
|
|
538
|
-
streamablehttp_client(**asdict(server_params))
|
|
539
|
-
)
|
|
540
|
-
self._active_contexts.append(client_connection)
|
|
541
|
-
read, write = client_connection[0:2]
|
|
542
|
-
session = await self._async_exit_stack.enter_async_context(ClientSession(read, write))
|
|
543
|
-
self._active_contexts.append(session)
|
|
544
|
-
await self.initialize(session)
|
|
524
|
+
try:
|
|
525
|
+
# Handle stdio connections
|
|
526
|
+
if isinstance(server_params, StdioServerParameters):
|
|
527
|
+
stdio_transport = await self._async_exit_stack.enter_async_context(stdio_client(server_params))
|
|
528
|
+
read, write = stdio_transport
|
|
529
|
+
session = await self._async_exit_stack.enter_async_context(
|
|
530
|
+
ClientSession(read, write, read_timeout_seconds=timedelta(seconds=self.timeout_seconds))
|
|
531
|
+
)
|
|
532
|
+
await self.initialize(session)
|
|
533
|
+
self._successful_connections += 1
|
|
545
534
|
|
|
546
|
-
|
|
535
|
+
# Handle SSE connections
|
|
536
|
+
elif isinstance(server_params, SSEClientParams):
|
|
537
|
+
client_connection = await self._async_exit_stack.enter_async_context(
|
|
538
|
+
sse_client(**asdict(server_params))
|
|
539
|
+
)
|
|
540
|
+
read, write = client_connection
|
|
541
|
+
session = await self._async_exit_stack.enter_async_context(ClientSession(read, write))
|
|
542
|
+
await self.initialize(session)
|
|
543
|
+
self._successful_connections += 1
|
|
544
|
+
|
|
545
|
+
# Handle Streamable HTTP connections
|
|
546
|
+
elif isinstance(server_params, StreamableHTTPClientParams):
|
|
547
|
+
client_connection = await self._async_exit_stack.enter_async_context(
|
|
548
|
+
streamablehttp_client(**asdict(server_params))
|
|
549
|
+
)
|
|
550
|
+
read, write = client_connection[0:2]
|
|
551
|
+
session = await self._async_exit_stack.enter_async_context(ClientSession(read, write))
|
|
552
|
+
await self.initialize(session)
|
|
553
|
+
self._successful_connections += 1
|
|
554
|
+
|
|
555
|
+
except Exception as e:
|
|
556
|
+
if not self.allow_partial_failure:
|
|
557
|
+
raise ValueError(f"MCP connection failed: {e}")
|
|
558
|
+
|
|
559
|
+
log_error(f"Failed to initialize MCP server with params {server_params}: {e}")
|
|
560
|
+
server_connection_errors.append(str(e))
|
|
561
|
+
continue
|
|
562
|
+
|
|
563
|
+
if self._successful_connections == 0 and server_connection_errors:
|
|
564
|
+
raise ValueError(f"All MCP connections failed: {server_connection_errors}")
|
|
565
|
+
|
|
566
|
+
if not self._initialized and self._successful_connections > 0:
|
|
567
|
+
self._initialized = True
|
|
547
568
|
|
|
548
569
|
async def close(self) -> None:
|
|
549
570
|
"""Close the MCP connections and clean up resources"""
|
|
@@ -563,6 +584,8 @@ class MultiMCPTools(Toolkit):
|
|
|
563
584
|
):
|
|
564
585
|
"""Exit the async context manager."""
|
|
565
586
|
await self._async_exit_stack.aclose()
|
|
587
|
+
self._initialized = False
|
|
588
|
+
self._successful_connections = 0
|
|
566
589
|
|
|
567
590
|
async def initialize(self, session: ClientSession) -> None:
|
|
568
591
|
"""Initialize the MCP toolkit by getting available tools from the MCP server"""
|
|
@@ -602,10 +625,10 @@ class MultiMCPTools(Toolkit):
|
|
|
602
625
|
self.functions[f.name] = f
|
|
603
626
|
log_debug(f"Function: {f.name} registered with {self.name}")
|
|
604
627
|
except Exception as e:
|
|
605
|
-
|
|
628
|
+
log_error(f"Failed to register tool {tool.name}: {e}")
|
|
606
629
|
|
|
607
|
-
log_debug(f"{self.name} initialized with {len(filtered_tools)} tools")
|
|
630
|
+
log_debug(f"{self.name} initialized with {len(filtered_tools)} tools from one MCP server")
|
|
608
631
|
self._initialized = True
|
|
609
632
|
except Exception as e:
|
|
610
|
-
|
|
633
|
+
log_error(f"Failed to get MCP tools: {e}")
|
|
611
634
|
raise
|
agno/utils/gemini.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from typing import Any, Dict, List, Optional
|
|
2
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel
|
|
3
5
|
|
|
4
6
|
from agno.media import Image
|
|
5
7
|
from agno.utils.log import log_error, log_warning
|
|
@@ -9,12 +11,119 @@ try:
|
|
|
9
11
|
FunctionDeclaration,
|
|
10
12
|
Schema,
|
|
11
13
|
Tool,
|
|
12
|
-
|
|
14
|
+
)
|
|
15
|
+
from google.genai.types import (
|
|
16
|
+
Type as GeminiType,
|
|
13
17
|
)
|
|
14
18
|
except ImportError:
|
|
15
19
|
raise ImportError("`google-genai` not installed. Please install it using `pip install google-genai`")
|
|
16
20
|
|
|
17
21
|
|
|
22
|
+
def prepare_response_schema(pydantic_model: Type[BaseModel]) -> Union[Type[BaseModel], Schema]:
|
|
23
|
+
"""
|
|
24
|
+
Prepare a Pydantic model for use as Gemini response schema.
|
|
25
|
+
|
|
26
|
+
Returns the model directly if Gemini can handle it natively,
|
|
27
|
+
otherwise converts to Gemini's Schema format.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
pydantic_model: A Pydantic model class
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Either the original Pydantic model or a converted Schema object
|
|
34
|
+
"""
|
|
35
|
+
schema_dict = pydantic_model.model_json_schema()
|
|
36
|
+
|
|
37
|
+
# Convert to Gemini Schema if the model has problematic patterns
|
|
38
|
+
if needs_conversion(schema_dict):
|
|
39
|
+
try:
|
|
40
|
+
converted = convert_schema(schema_dict)
|
|
41
|
+
except Exception as e:
|
|
42
|
+
log_warning(f"Failed to convert schema for {pydantic_model}: {e}")
|
|
43
|
+
converted = None
|
|
44
|
+
|
|
45
|
+
if converted is None:
|
|
46
|
+
# If conversion fails, let Gemini handle it directly
|
|
47
|
+
return pydantic_model
|
|
48
|
+
return converted
|
|
49
|
+
|
|
50
|
+
# Gemini can handle this model directly
|
|
51
|
+
return pydantic_model
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def needs_conversion(schema_dict: Dict[str, Any]) -> bool:
|
|
55
|
+
"""
|
|
56
|
+
Check if a schema needs conversion for Gemini.
|
|
57
|
+
|
|
58
|
+
Returns True if the schema has:
|
|
59
|
+
- Self-references or circular references
|
|
60
|
+
- Dict fields (additionalProperties) that Gemini doesn't handle well
|
|
61
|
+
- Empty object definitions that Gemini rejects
|
|
62
|
+
"""
|
|
63
|
+
# Check for dict fields (additionalProperties) anywhere in the schema
|
|
64
|
+
if has_additional_properties(schema_dict):
|
|
65
|
+
return True
|
|
66
|
+
|
|
67
|
+
# Check if schema has $defs with circular references
|
|
68
|
+
if "$defs" in schema_dict:
|
|
69
|
+
defs = schema_dict["$defs"]
|
|
70
|
+
for def_name, def_schema in defs.items():
|
|
71
|
+
ref_path = f"#/$defs/{def_name}"
|
|
72
|
+
if has_self_reference(def_schema, ref_path):
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def has_additional_properties(schema: Any) -> bool:
|
|
79
|
+
"""Check if schema has additionalProperties (Dict fields)"""
|
|
80
|
+
if isinstance(schema, dict):
|
|
81
|
+
# Direct check
|
|
82
|
+
if "additionalProperties" in schema:
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
# Check properties recursively
|
|
86
|
+
if "properties" in schema:
|
|
87
|
+
for prop_schema in schema["properties"].values():
|
|
88
|
+
if has_additional_properties(prop_schema):
|
|
89
|
+
return True
|
|
90
|
+
|
|
91
|
+
# Check array items
|
|
92
|
+
if "items" in schema:
|
|
93
|
+
if has_additional_properties(schema["items"]):
|
|
94
|
+
return True
|
|
95
|
+
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def has_self_reference(schema: Dict, target_ref: str) -> bool:
|
|
100
|
+
"""Check if a schema references itself (directly or indirectly)"""
|
|
101
|
+
if isinstance(schema, dict):
|
|
102
|
+
# Direct self-reference
|
|
103
|
+
if schema.get("$ref") == target_ref:
|
|
104
|
+
return True
|
|
105
|
+
|
|
106
|
+
# Check properties
|
|
107
|
+
if "properties" in schema:
|
|
108
|
+
for prop_schema in schema["properties"].values():
|
|
109
|
+
if has_self_reference(prop_schema, target_ref):
|
|
110
|
+
return True
|
|
111
|
+
|
|
112
|
+
# Check array items
|
|
113
|
+
if "items" in schema:
|
|
114
|
+
if has_self_reference(schema["items"], target_ref):
|
|
115
|
+
return True
|
|
116
|
+
|
|
117
|
+
# Check anyOf/oneOf/allOf
|
|
118
|
+
for key in ["anyOf", "oneOf", "allOf"]:
|
|
119
|
+
if key in schema:
|
|
120
|
+
for sub_schema in schema[key]:
|
|
121
|
+
if has_self_reference(sub_schema, target_ref):
|
|
122
|
+
return True
|
|
123
|
+
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
|
|
18
127
|
def format_image_for_message(image: Image) -> Optional[Dict[str, Any]]:
|
|
19
128
|
# Case 1: Image is a URL
|
|
20
129
|
# Download the image from the URL and add it as base64 encoded data
|
|
@@ -66,7 +175,9 @@ def format_image_for_message(image: Image) -> Optional[Dict[str, Any]]:
|
|
|
66
175
|
return None
|
|
67
176
|
|
|
68
177
|
|
|
69
|
-
def convert_schema(
|
|
178
|
+
def convert_schema(
|
|
179
|
+
schema_dict: Dict[str, Any], root_schema: Optional[Dict[str, Any]] = None, visited_refs: Optional[set] = None
|
|
180
|
+
) -> Optional[Schema]:
|
|
70
181
|
"""
|
|
71
182
|
Recursively convert a JSON-like schema dictionary to a types.Schema object.
|
|
72
183
|
|
|
@@ -74,23 +185,39 @@ def convert_schema(schema_dict: Dict[str, Any], root_schema: Optional[Dict[str,
|
|
|
74
185
|
schema_dict (dict): The JSON schema dictionary with keys like "type", "description",
|
|
75
186
|
"properties", and "required".
|
|
76
187
|
root_schema (dict, optional): The root schema containing $defs for resolving $ref
|
|
188
|
+
visited_refs (set, optional): Set of visited $ref paths to detect circular references
|
|
77
189
|
|
|
78
190
|
Returns:
|
|
79
191
|
types.Schema: The converted schema.
|
|
80
192
|
"""
|
|
81
193
|
|
|
82
|
-
# If this is the initial call, set root_schema to self
|
|
194
|
+
# If this is the initial call, set root_schema to self and initialize visited_refs
|
|
83
195
|
if root_schema is None:
|
|
84
196
|
root_schema = schema_dict
|
|
197
|
+
if visited_refs is None:
|
|
198
|
+
visited_refs = set()
|
|
85
199
|
|
|
86
|
-
# Handle $ref references
|
|
200
|
+
# Handle $ref references with cycle detection
|
|
87
201
|
if "$ref" in schema_dict:
|
|
88
202
|
ref_path = schema_dict["$ref"]
|
|
203
|
+
|
|
204
|
+
# Check for circular reference
|
|
205
|
+
if ref_path in visited_refs:
|
|
206
|
+
# Return a basic object schema to break the cycle
|
|
207
|
+
return Schema(
|
|
208
|
+
type=GeminiType.OBJECT,
|
|
209
|
+
description=f"Circular reference to {ref_path}",
|
|
210
|
+
)
|
|
211
|
+
|
|
89
212
|
if ref_path.startswith("#/$defs/"):
|
|
90
213
|
def_name = ref_path.split("/")[-1]
|
|
91
214
|
if "$defs" in root_schema and def_name in root_schema["$defs"]:
|
|
215
|
+
# Add to visited set before recursing
|
|
216
|
+
new_visited = visited_refs.copy()
|
|
217
|
+
new_visited.add(ref_path)
|
|
218
|
+
|
|
92
219
|
referenced_schema = root_schema["$defs"][def_name]
|
|
93
|
-
return convert_schema(referenced_schema, root_schema)
|
|
220
|
+
return convert_schema(referenced_schema, root_schema, new_visited)
|
|
94
221
|
# If we can't resolve the reference, return None
|
|
95
222
|
return None
|
|
96
223
|
|
|
@@ -103,7 +230,7 @@ def convert_schema(schema_dict: Dict[str, Any], root_schema: Optional[Dict[str,
|
|
|
103
230
|
# Handle enum types
|
|
104
231
|
if "enum" in schema_dict:
|
|
105
232
|
enum_values = schema_dict["enum"]
|
|
106
|
-
return Schema(type=
|
|
233
|
+
return Schema(type=GeminiType.STRING, enum=enum_values, description=description, default=default)
|
|
107
234
|
|
|
108
235
|
if schema_type == "object":
|
|
109
236
|
# Handle regular objects with properties
|
|
@@ -117,8 +244,8 @@ def convert_schema(schema_dict: Dict[str, Any], root_schema: Optional[Dict[str,
|
|
|
117
244
|
prop_def["type"] = prop_type[0]
|
|
118
245
|
is_nullable = True
|
|
119
246
|
|
|
120
|
-
# Process property schema (pass root_schema for $ref resolution)
|
|
121
|
-
converted_schema = convert_schema(prop_def, root_schema)
|
|
247
|
+
# Process property schema (pass root_schema and visited_refs for $ref resolution)
|
|
248
|
+
converted_schema = convert_schema(prop_def, root_schema, visited_refs)
|
|
122
249
|
if converted_schema is not None:
|
|
123
250
|
if is_nullable:
|
|
124
251
|
converted_schema.nullable = True
|
|
@@ -128,14 +255,14 @@ def convert_schema(schema_dict: Dict[str, Any], root_schema: Optional[Dict[str,
|
|
|
128
255
|
|
|
129
256
|
if properties:
|
|
130
257
|
return Schema(
|
|
131
|
-
type=
|
|
258
|
+
type=GeminiType.OBJECT,
|
|
132
259
|
properties=properties,
|
|
133
260
|
required=required,
|
|
134
261
|
description=description,
|
|
135
262
|
default=default,
|
|
136
263
|
)
|
|
137
264
|
else:
|
|
138
|
-
return Schema(type=
|
|
265
|
+
return Schema(type=GeminiType.OBJECT, description=description, default=default)
|
|
139
266
|
|
|
140
267
|
# Handle Dict types (objects with additionalProperties but no properties)
|
|
141
268
|
elif "additionalProperties" in schema_dict:
|
|
@@ -170,7 +297,7 @@ def convert_schema(schema_dict: Dict[str, Any], root_schema: Optional[Dict[str,
|
|
|
170
297
|
placeholder_properties["example_key"].items = {} # type: ignore
|
|
171
298
|
|
|
172
299
|
return Schema(
|
|
173
|
-
type=
|
|
300
|
+
type=GeminiType.OBJECT,
|
|
174
301
|
properties=placeholder_properties,
|
|
175
302
|
description=description
|
|
176
303
|
or f"Dictionary with {value_type.lower()} values{type_description_suffix}. Can contain any number of key-value pairs.",
|
|
@@ -178,21 +305,22 @@ def convert_schema(schema_dict: Dict[str, Any], root_schema: Optional[Dict[str,
|
|
|
178
305
|
)
|
|
179
306
|
else:
|
|
180
307
|
# additionalProperties is false or true
|
|
181
|
-
return Schema(type=
|
|
308
|
+
return Schema(type=GeminiType.OBJECT, description=description, default=default)
|
|
182
309
|
|
|
183
310
|
# Handle empty objects
|
|
184
311
|
else:
|
|
185
|
-
return Schema(type=
|
|
312
|
+
return Schema(type=GeminiType.OBJECT, description=description, default=default)
|
|
186
313
|
|
|
187
314
|
elif schema_type == "array" and "items" in schema_dict:
|
|
188
315
|
if not schema_dict["items"]: # Handle empty {}
|
|
189
|
-
items = Schema(type=
|
|
316
|
+
items = Schema(type=GeminiType.STRING)
|
|
190
317
|
else:
|
|
191
|
-
|
|
318
|
+
converted_items = convert_schema(schema_dict["items"], root_schema, visited_refs)
|
|
319
|
+
items = converted_items if converted_items is not None else Schema(type=GeminiType.STRING)
|
|
192
320
|
min_items = schema_dict.get("minItems")
|
|
193
321
|
max_items = schema_dict.get("maxItems")
|
|
194
322
|
return Schema(
|
|
195
|
-
type=
|
|
323
|
+
type=GeminiType.ARRAY,
|
|
196
324
|
description=description,
|
|
197
325
|
items=items,
|
|
198
326
|
min_items=min_items,
|
|
@@ -201,7 +329,7 @@ def convert_schema(schema_dict: Dict[str, Any], root_schema: Optional[Dict[str,
|
|
|
201
329
|
|
|
202
330
|
elif schema_type == "string":
|
|
203
331
|
schema_kwargs = {
|
|
204
|
-
"type":
|
|
332
|
+
"type": GeminiType.STRING,
|
|
205
333
|
"description": description,
|
|
206
334
|
"default": default,
|
|
207
335
|
}
|
|
@@ -224,7 +352,7 @@ def convert_schema(schema_dict: Dict[str, Any], root_schema: Optional[Dict[str,
|
|
|
224
352
|
elif schema_type == "" and "anyOf" in schema_dict:
|
|
225
353
|
any_of = []
|
|
226
354
|
for sub_schema in schema_dict["anyOf"]:
|
|
227
|
-
sub_schema_converted = convert_schema(sub_schema, root_schema)
|
|
355
|
+
sub_schema_converted = convert_schema(sub_schema, root_schema, visited_refs)
|
|
228
356
|
any_of.append(sub_schema_converted)
|
|
229
357
|
|
|
230
358
|
is_nullable = False
|
agno/utils/models/claude.py
CHANGED
|
@@ -279,6 +279,15 @@ def format_messages(messages: List[Message]) -> Tuple[List[Dict[str, str]], str]
|
|
|
279
279
|
type="tool_use",
|
|
280
280
|
)
|
|
281
281
|
)
|
|
282
|
+
elif message.role == "tool":
|
|
283
|
+
content = []
|
|
284
|
+
content.append(
|
|
285
|
+
{
|
|
286
|
+
"type": "tool_result",
|
|
287
|
+
"tool_use_id": message.tool_call_id,
|
|
288
|
+
"content": str(message.content),
|
|
289
|
+
}
|
|
290
|
+
)
|
|
282
291
|
|
|
283
292
|
# Skip empty assistant responses
|
|
284
293
|
if message.role == "assistant" and not content:
|
|
@@ -44,6 +44,8 @@ def print_response_stream(
|
|
|
44
44
|
console: Optional[Any] = None,
|
|
45
45
|
add_history_to_context: Optional[bool] = None,
|
|
46
46
|
dependencies: Optional[Dict[str, Any]] = None,
|
|
47
|
+
add_dependencies_to_context: Optional[bool] = None,
|
|
48
|
+
add_session_state_to_context: Optional[bool] = None,
|
|
47
49
|
metadata: Optional[Dict[str, Any]] = None,
|
|
48
50
|
**kwargs: Any,
|
|
49
51
|
):
|
|
@@ -90,6 +92,8 @@ def print_response_stream(
|
|
|
90
92
|
knowledge_filters=knowledge_filters,
|
|
91
93
|
debug_mode=debug_mode,
|
|
92
94
|
add_history_to_context=add_history_to_context,
|
|
95
|
+
add_dependencies_to_context=add_dependencies_to_context,
|
|
96
|
+
add_session_state_to_context=add_session_state_to_context,
|
|
93
97
|
dependencies=dependencies,
|
|
94
98
|
metadata=metadata,
|
|
95
99
|
**kwargs,
|
|
@@ -223,6 +227,8 @@ async def aprint_response_stream(
|
|
|
223
227
|
console: Optional[Any] = None,
|
|
224
228
|
add_history_to_context: Optional[bool] = None,
|
|
225
229
|
dependencies: Optional[Dict[str, Any]] = None,
|
|
230
|
+
add_dependencies_to_context: Optional[bool] = None,
|
|
231
|
+
add_session_state_to_context: Optional[bool] = None,
|
|
226
232
|
metadata: Optional[Dict[str, Any]] = None,
|
|
227
233
|
**kwargs: Any,
|
|
228
234
|
):
|
|
@@ -269,6 +275,8 @@ async def aprint_response_stream(
|
|
|
269
275
|
knowledge_filters=knowledge_filters,
|
|
270
276
|
debug_mode=debug_mode,
|
|
271
277
|
add_history_to_context=add_history_to_context,
|
|
278
|
+
add_dependencies_to_context=add_dependencies_to_context,
|
|
279
|
+
add_session_state_to_context=add_session_state_to_context,
|
|
272
280
|
dependencies=dependencies,
|
|
273
281
|
metadata=metadata,
|
|
274
282
|
**kwargs,
|
|
@@ -490,6 +498,8 @@ def print_response(
|
|
|
490
498
|
console: Optional[Any] = None,
|
|
491
499
|
add_history_to_context: Optional[bool] = None,
|
|
492
500
|
dependencies: Optional[Dict[str, Any]] = None,
|
|
501
|
+
add_dependencies_to_context: Optional[bool] = None,
|
|
502
|
+
add_session_state_to_context: Optional[bool] = None,
|
|
493
503
|
metadata: Optional[Dict[str, Any]] = None,
|
|
494
504
|
**kwargs: Any,
|
|
495
505
|
):
|
|
@@ -527,6 +537,8 @@ def print_response(
|
|
|
527
537
|
knowledge_filters=knowledge_filters,
|
|
528
538
|
debug_mode=debug_mode,
|
|
529
539
|
add_history_to_context=add_history_to_context,
|
|
540
|
+
add_dependencies_to_context=add_dependencies_to_context,
|
|
541
|
+
add_session_state_to_context=add_session_state_to_context,
|
|
530
542
|
dependencies=dependencies,
|
|
531
543
|
metadata=metadata,
|
|
532
544
|
**kwargs,
|
|
@@ -590,6 +602,8 @@ async def aprint_response(
|
|
|
590
602
|
console: Optional[Any] = None,
|
|
591
603
|
add_history_to_context: Optional[bool] = None,
|
|
592
604
|
dependencies: Optional[Dict[str, Any]] = None,
|
|
605
|
+
add_dependencies_to_context: Optional[bool] = None,
|
|
606
|
+
add_session_state_to_context: Optional[bool] = None,
|
|
593
607
|
metadata: Optional[Dict[str, Any]] = None,
|
|
594
608
|
**kwargs: Any,
|
|
595
609
|
):
|
|
@@ -627,6 +641,8 @@ async def aprint_response(
|
|
|
627
641
|
knowledge_filters=knowledge_filters,
|
|
628
642
|
debug_mode=debug_mode,
|
|
629
643
|
add_history_to_context=add_history_to_context,
|
|
644
|
+
add_dependencies_to_context=add_dependencies_to_context,
|
|
645
|
+
add_session_state_to_context=add_session_state_to_context,
|
|
630
646
|
dependencies=dependencies,
|
|
631
647
|
metadata=metadata,
|
|
632
648
|
**kwargs,
|