agno 2.0.0a1__py3-none-any.whl → 2.0.0rc2__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 +416 -41
- agno/api/agent.py +2 -2
- agno/api/evals.py +2 -2
- agno/api/os.py +1 -1
- agno/api/settings.py +2 -2
- agno/api/team.py +2 -2
- agno/db/dynamo/dynamo.py +0 -6
- agno/db/firestore/firestore.py +0 -6
- agno/db/in_memory/in_memory_db.py +0 -6
- agno/db/json/json_db.py +0 -6
- agno/db/mongo/mongo.py +8 -9
- agno/db/mysql/utils.py +0 -1
- agno/db/postgres/postgres.py +0 -10
- agno/db/postgres/utils.py +0 -1
- agno/db/redis/redis.py +0 -4
- agno/db/singlestore/singlestore.py +0 -10
- agno/db/singlestore/utils.py +0 -1
- agno/db/sqlite/sqlite.py +0 -4
- agno/db/sqlite/utils.py +0 -1
- agno/eval/accuracy.py +12 -5
- agno/integrations/discord/client.py +5 -1
- agno/knowledge/chunking/strategy.py +14 -14
- agno/knowledge/embedder/aws_bedrock.py +2 -2
- agno/knowledge/knowledge.py +156 -120
- agno/knowledge/reader/arxiv_reader.py +5 -5
- agno/knowledge/reader/csv_reader.py +6 -77
- agno/knowledge/reader/docx_reader.py +5 -5
- agno/knowledge/reader/firecrawl_reader.py +5 -5
- agno/knowledge/reader/json_reader.py +5 -5
- agno/knowledge/reader/markdown_reader.py +31 -9
- agno/knowledge/reader/pdf_reader.py +10 -123
- agno/knowledge/reader/reader_factory.py +65 -72
- agno/knowledge/reader/s3_reader.py +44 -114
- agno/knowledge/reader/text_reader.py +5 -5
- agno/knowledge/reader/url_reader.py +75 -31
- agno/knowledge/reader/web_search_reader.py +6 -29
- agno/knowledge/reader/website_reader.py +5 -5
- agno/knowledge/reader/wikipedia_reader.py +5 -5
- agno/knowledge/reader/youtube_reader.py +6 -6
- agno/knowledge/utils.py +10 -10
- agno/models/anthropic/claude.py +2 -49
- agno/models/aws/bedrock.py +3 -7
- agno/models/base.py +37 -6
- agno/models/message.py +7 -6
- agno/os/app.py +168 -64
- agno/os/interfaces/agui/agui.py +1 -1
- agno/os/interfaces/agui/utils.py +16 -9
- agno/os/interfaces/slack/slack.py +2 -3
- agno/os/interfaces/whatsapp/whatsapp.py +2 -3
- agno/os/mcp.py +235 -0
- agno/os/router.py +576 -19
- agno/os/routers/evals/evals.py +201 -12
- agno/os/routers/knowledge/knowledge.py +455 -18
- agno/os/routers/memory/memory.py +260 -29
- agno/os/routers/metrics/metrics.py +127 -7
- agno/os/routers/session/session.py +398 -25
- agno/os/schema.py +55 -2
- agno/os/settings.py +0 -1
- agno/run/agent.py +96 -2
- agno/run/cancel.py +0 -2
- agno/run/team.py +93 -2
- agno/run/workflow.py +25 -12
- agno/team/team.py +863 -1053
- agno/tools/function.py +65 -7
- agno/tools/linear.py +1 -1
- agno/tools/mcp.py +1 -2
- agno/utils/gemini.py +31 -1
- agno/utils/log.py +52 -2
- agno/utils/mcp.py +55 -3
- agno/utils/models/claude.py +41 -0
- agno/utils/print_response/team.py +177 -73
- agno/utils/streamlit.py +481 -0
- agno/workflow/workflow.py +17 -1
- {agno-2.0.0a1.dist-info → agno-2.0.0rc2.dist-info}/METADATA +1 -1
- {agno-2.0.0a1.dist-info → agno-2.0.0rc2.dist-info}/RECORD +78 -77
- agno/knowledge/reader/gcs_reader.py +0 -67
- {agno-2.0.0a1.dist-info → agno-2.0.0rc2.dist-info}/WHEEL +0 -0
- {agno-2.0.0a1.dist-info → agno-2.0.0rc2.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.0a1.dist-info → agno-2.0.0rc2.dist-info}/top_level.txt +0 -0
agno/tools/function.py
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from functools import partial
|
|
3
3
|
from importlib.metadata import version
|
|
4
|
-
from typing import Any, Callable, Dict, List, Literal, Optional, Type, TypeVar, get_type_hints
|
|
4
|
+
from typing import Any, Callable, Dict, List, Literal, Optional, Sequence, Type, TypeVar, get_type_hints
|
|
5
5
|
|
|
6
6
|
from docstring_parser import parse
|
|
7
7
|
from packaging.version import Version
|
|
8
8
|
from pydantic import BaseModel, Field, validate_call
|
|
9
9
|
|
|
10
10
|
from agno.exceptions import AgentRunException
|
|
11
|
-
from agno.media import AudioArtifact, ImageArtifact, VideoArtifact
|
|
11
|
+
from agno.media import Audio, AudioArtifact, File, Image, ImageArtifact, Video, VideoArtifact
|
|
12
12
|
from agno.utils.log import log_debug, log_error, log_exception, log_warning
|
|
13
13
|
|
|
14
14
|
T = TypeVar("T")
|
|
@@ -125,6 +125,12 @@ class Function(BaseModel):
|
|
|
125
125
|
# The session state that the function is associated with
|
|
126
126
|
_session_state: Optional[Dict[str, Any]] = None
|
|
127
127
|
|
|
128
|
+
# Media context that the function is associated with
|
|
129
|
+
_images: Optional[Sequence[Image]] = None
|
|
130
|
+
_videos: Optional[Sequence[Video]] = None
|
|
131
|
+
_audios: Optional[Sequence[Audio]] = None
|
|
132
|
+
_files: Optional[Sequence[File]] = None
|
|
133
|
+
|
|
128
134
|
def to_dict(self) -> Dict[str, Any]:
|
|
129
135
|
return self.model_dump(
|
|
130
136
|
exclude_none=True,
|
|
@@ -150,13 +156,23 @@ class Function(BaseModel):
|
|
|
150
156
|
del type_hints["team"]
|
|
151
157
|
if "session_state" in sig.parameters and "session_state" in type_hints:
|
|
152
158
|
del type_hints["session_state"]
|
|
159
|
+
# Remove media parameters from type hints as they are injected automatically
|
|
160
|
+
if "images" in sig.parameters and "images" in type_hints:
|
|
161
|
+
del type_hints["images"]
|
|
162
|
+
if "videos" in sig.parameters and "videos" in type_hints:
|
|
163
|
+
del type_hints["videos"]
|
|
164
|
+
if "audios" in sig.parameters and "audios" in type_hints:
|
|
165
|
+
del type_hints["audios"]
|
|
166
|
+
if "files" in sig.parameters and "files" in type_hints:
|
|
167
|
+
del type_hints["files"]
|
|
153
168
|
# log_info(f"Type hints for {function_name}: {type_hints}")
|
|
154
169
|
|
|
155
170
|
# Filter out return type and only process parameters
|
|
156
171
|
param_type_hints = {
|
|
157
172
|
name: type_hints.get(name)
|
|
158
173
|
for name in sig.parameters
|
|
159
|
-
if name != "return"
|
|
174
|
+
if name != "return"
|
|
175
|
+
and name not in ["agent", "team", "session_state", "self", "images", "videos", "audios", "files"]
|
|
160
176
|
}
|
|
161
177
|
|
|
162
178
|
# Parse docstring for parameters
|
|
@@ -183,14 +199,17 @@ class Function(BaseModel):
|
|
|
183
199
|
# See: https://platform.openai.com/docs/guides/structured-outputs/supported-schemas#all-fields-must-be-required
|
|
184
200
|
if strict:
|
|
185
201
|
parameters["required"] = [
|
|
186
|
-
name
|
|
202
|
+
name
|
|
203
|
+
for name in parameters["properties"]
|
|
204
|
+
if name not in ["agent", "team", "session_state", "self", "images", "videos", "audios", "files"]
|
|
187
205
|
]
|
|
188
206
|
else:
|
|
189
207
|
# Mark a field as required if it has no default value (this would include optional fields)
|
|
190
208
|
parameters["required"] = [
|
|
191
209
|
name
|
|
192
210
|
for name, param in sig.parameters.items()
|
|
193
|
-
if param.default == param.empty
|
|
211
|
+
if param.default == param.empty
|
|
212
|
+
and name not in ["agent", "team", "session_state", "self", "images", "videos", "audios", "files"]
|
|
194
213
|
]
|
|
195
214
|
|
|
196
215
|
# log_debug(f"JSON schema for {function_name}: {parameters}")
|
|
@@ -241,10 +260,28 @@ class Function(BaseModel):
|
|
|
241
260
|
del type_hints["team"]
|
|
242
261
|
if "session_state" in sig.parameters and "session_state" in type_hints:
|
|
243
262
|
del type_hints["session_state"]
|
|
263
|
+
if "images" in sig.parameters and "images" in type_hints:
|
|
264
|
+
del type_hints["images"]
|
|
265
|
+
if "videos" in sig.parameters and "videos" in type_hints:
|
|
266
|
+
del type_hints["videos"]
|
|
267
|
+
if "audios" in sig.parameters and "audios" in type_hints:
|
|
268
|
+
del type_hints["audios"]
|
|
269
|
+
if "files" in sig.parameters and "files" in type_hints:
|
|
270
|
+
del type_hints["files"]
|
|
244
271
|
# log_info(f"Type hints for {self.name}: {type_hints}")
|
|
245
272
|
|
|
246
273
|
# Filter out return type and only process parameters
|
|
247
|
-
excluded_params = [
|
|
274
|
+
excluded_params = [
|
|
275
|
+
"return",
|
|
276
|
+
"agent",
|
|
277
|
+
"team",
|
|
278
|
+
"session_state",
|
|
279
|
+
"self",
|
|
280
|
+
"images",
|
|
281
|
+
"videos",
|
|
282
|
+
"audios",
|
|
283
|
+
"files",
|
|
284
|
+
]
|
|
248
285
|
if self.requires_user_input and self.user_input_fields:
|
|
249
286
|
if len(self.user_input_fields) == 0:
|
|
250
287
|
excluded_params.extend(list(type_hints.keys()))
|
|
@@ -357,7 +394,9 @@ class Function(BaseModel):
|
|
|
357
394
|
def process_schema_for_strict(self):
|
|
358
395
|
self.parameters["additionalProperties"] = False
|
|
359
396
|
self.parameters["required"] = [
|
|
360
|
-
name
|
|
397
|
+
name
|
|
398
|
+
for name in self.parameters["properties"]
|
|
399
|
+
if name not in ["agent", "team", "session_state", "images", "videos", "audios", "files", "self"]
|
|
361
400
|
]
|
|
362
401
|
|
|
363
402
|
def _get_cache_key(self, entrypoint_args: Dict[str, Any], call_args: Optional[Dict[str, Any]] = None) -> str:
|
|
@@ -372,6 +411,14 @@ class Function(BaseModel):
|
|
|
372
411
|
del copy_entrypoint_args["team"]
|
|
373
412
|
if "session_state" in copy_entrypoint_args:
|
|
374
413
|
del copy_entrypoint_args["session_state"]
|
|
414
|
+
if "images" in copy_entrypoint_args:
|
|
415
|
+
del copy_entrypoint_args["images"]
|
|
416
|
+
if "videos" in copy_entrypoint_args:
|
|
417
|
+
del copy_entrypoint_args["videos"]
|
|
418
|
+
if "audios" in copy_entrypoint_args:
|
|
419
|
+
del copy_entrypoint_args["audios"]
|
|
420
|
+
if "files" in copy_entrypoint_args:
|
|
421
|
+
del copy_entrypoint_args["files"]
|
|
375
422
|
args_str = str(copy_entrypoint_args)
|
|
376
423
|
|
|
377
424
|
kwargs_str = str(sorted((call_args or {}).items()))
|
|
@@ -554,6 +601,17 @@ class FunctionCall(BaseModel):
|
|
|
554
601
|
# Check if the entrypoint has an fc argument
|
|
555
602
|
if "fc" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
556
603
|
entrypoint_args["fc"] = self
|
|
604
|
+
|
|
605
|
+
# Check if the entrypoint has media arguments
|
|
606
|
+
if "images" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
607
|
+
entrypoint_args["images"] = self.function._images
|
|
608
|
+
if "videos" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
609
|
+
entrypoint_args["videos"] = self.function._videos
|
|
610
|
+
if "audios" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
611
|
+
entrypoint_args["audios"] = self.function._audios
|
|
612
|
+
if "files" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
613
|
+
entrypoint_args["files"] = self.function._files
|
|
614
|
+
|
|
557
615
|
return entrypoint_args
|
|
558
616
|
|
|
559
617
|
def _build_hook_args(self, hook: Callable, name: str, func: Callable, args: Dict[str, Any]) -> Dict[str, Any]:
|
agno/tools/linear.py
CHANGED
|
@@ -202,7 +202,7 @@ class LinearTools(Toolkit):
|
|
|
202
202
|
"""
|
|
203
203
|
|
|
204
204
|
query = """
|
|
205
|
-
mutation IssueCreate ($title: String!, $description: String!, $teamId: String!, $projectId: String
|
|
205
|
+
mutation IssueCreate ($title: String!, $description: String!, $teamId: String!, $projectId: String, $assigneeId: String){
|
|
206
206
|
issueCreate(
|
|
207
207
|
input: { title: $title, description: $description, teamId: $teamId, projectId: $projectId, assigneeId: $assigneeId}
|
|
208
208
|
) {
|
agno/tools/mcp.py
CHANGED
|
@@ -365,7 +365,7 @@ class MultiMCPTools(Toolkit):
|
|
|
365
365
|
*,
|
|
366
366
|
env: Optional[dict[str, str]] = None,
|
|
367
367
|
server_params_list: Optional[
|
|
368
|
-
|
|
368
|
+
list[Union[SSEClientParams, StdioServerParameters, StreamableHTTPClientParams]]
|
|
369
369
|
] = None,
|
|
370
370
|
timeout_seconds: int = 5,
|
|
371
371
|
client=None,
|
|
@@ -531,7 +531,6 @@ class MultiMCPTools(Toolkit):
|
|
|
531
531
|
session = await self._async_exit_stack.enter_async_context(ClientSession(read, write))
|
|
532
532
|
self._active_contexts.append(session)
|
|
533
533
|
await self.initialize(session)
|
|
534
|
-
|
|
535
534
|
# Handle Streamable HTTP connections
|
|
536
535
|
elif isinstance(server_params, StreamableHTTPClientParams):
|
|
537
536
|
client_connection = await self._async_exit_stack.enter_async_context(
|
agno/utils/gemini.py
CHANGED
|
@@ -175,7 +175,37 @@ def convert_schema(schema_dict: Dict[str, Any], root_schema: Optional[Dict[str,
|
|
|
175
175
|
|
|
176
176
|
elif schema_type == "array" and "items" in schema_dict:
|
|
177
177
|
items = convert_schema(schema_dict["items"], root_schema)
|
|
178
|
-
|
|
178
|
+
min_items = schema_dict.get("minItems")
|
|
179
|
+
max_items = schema_dict.get("maxItems")
|
|
180
|
+
return Schema(
|
|
181
|
+
type=Type.ARRAY,
|
|
182
|
+
description=description,
|
|
183
|
+
items=items,
|
|
184
|
+
min_items=min_items,
|
|
185
|
+
max_items=max_items,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
elif schema_type == "string":
|
|
189
|
+
schema_kwargs = {
|
|
190
|
+
"type": Type.STRING,
|
|
191
|
+
"description": description,
|
|
192
|
+
"default": default,
|
|
193
|
+
}
|
|
194
|
+
if "format" in schema_dict:
|
|
195
|
+
schema_kwargs["format"] = schema_dict["format"]
|
|
196
|
+
return Schema(**schema_kwargs)
|
|
197
|
+
|
|
198
|
+
elif schema_type in ("integer", "number"):
|
|
199
|
+
schema_kwargs = {
|
|
200
|
+
"type": schema_type.upper(),
|
|
201
|
+
"description": description,
|
|
202
|
+
"default": default,
|
|
203
|
+
}
|
|
204
|
+
if "maximum" in schema_dict:
|
|
205
|
+
schema_kwargs["maximum"] = schema_dict["maximum"]
|
|
206
|
+
if "minimum" in schema_dict:
|
|
207
|
+
schema_kwargs["minimum"] = schema_dict["minimum"]
|
|
208
|
+
return Schema(**schema_kwargs)
|
|
179
209
|
|
|
180
210
|
elif schema_type == "" and "anyOf" in schema_dict:
|
|
181
211
|
any_of = []
|
agno/utils/log.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from functools import lru_cache
|
|
2
3
|
from os import getenv
|
|
3
4
|
from typing import Any, Literal, Optional
|
|
4
5
|
|
|
@@ -64,6 +65,11 @@ class AgnoLogger(logging.Logger):
|
|
|
64
65
|
|
|
65
66
|
|
|
66
67
|
def build_logger(logger_name: str, source_type: Optional[str] = None) -> Any:
|
|
68
|
+
# If a logger with the name "agno.{source_type}" is already set, we want to use that one
|
|
69
|
+
_logger = logging.getLogger(f"agno.{logger_name}")
|
|
70
|
+
if _logger.handlers or _logger.level != logging.NOTSET:
|
|
71
|
+
return _logger
|
|
72
|
+
|
|
67
73
|
# Set the custom logger class as the default for this logger
|
|
68
74
|
logging.setLoggerClass(AgnoLogger)
|
|
69
75
|
|
|
@@ -174,6 +180,12 @@ def use_workflow_logger():
|
|
|
174
180
|
logger = workflow_logger
|
|
175
181
|
|
|
176
182
|
|
|
183
|
+
@lru_cache(maxsize=128)
|
|
184
|
+
def _using_default_logger(logger_instance: Any) -> bool:
|
|
185
|
+
"""Return True if the currently active logger is our default AgnoLogger"""
|
|
186
|
+
return isinstance(logger_instance, AgnoLogger)
|
|
187
|
+
|
|
188
|
+
|
|
177
189
|
def log_debug(msg, center: bool = False, symbol: str = "*", log_level: Literal[1, 2] = 1, *args, **kwargs):
|
|
178
190
|
global logger
|
|
179
191
|
global debug_on
|
|
@@ -181,12 +193,18 @@ def log_debug(msg, center: bool = False, symbol: str = "*", log_level: Literal[1
|
|
|
181
193
|
|
|
182
194
|
if debug_on:
|
|
183
195
|
if debug_level >= log_level:
|
|
184
|
-
logger
|
|
196
|
+
if _using_default_logger(logger):
|
|
197
|
+
logger.debug(msg, center, symbol, *args, **kwargs)
|
|
198
|
+
else:
|
|
199
|
+
logger.debug(msg, *args, **kwargs)
|
|
185
200
|
|
|
186
201
|
|
|
187
202
|
def log_info(msg, center: bool = False, symbol: str = "*", *args, **kwargs):
|
|
188
203
|
global logger
|
|
189
|
-
logger
|
|
204
|
+
if _using_default_logger(logger):
|
|
205
|
+
logger.info(msg, center, symbol, *args, **kwargs)
|
|
206
|
+
else:
|
|
207
|
+
logger.info(msg, *args, **kwargs)
|
|
190
208
|
|
|
191
209
|
|
|
192
210
|
def log_warning(msg, *args, **kwargs):
|
|
@@ -202,3 +220,35 @@ def log_error(msg, *args, **kwargs):
|
|
|
202
220
|
def log_exception(msg, *args, **kwargs):
|
|
203
221
|
global logger
|
|
204
222
|
logger.exception(msg, *args, **kwargs)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def configure_agno_logging(
|
|
226
|
+
custom_default_logger: Optional[Any] = None,
|
|
227
|
+
custom_agent_logger: Optional[Any] = None,
|
|
228
|
+
custom_team_logger: Optional[Any] = None,
|
|
229
|
+
custom_workflow_logger: Optional[Any] = None,
|
|
230
|
+
) -> None:
|
|
231
|
+
"""
|
|
232
|
+
Util to set custom loggers. These will be used everywhere across the Agno library.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
custom_default_logger: Default logger to use (overrides agent_logger for default)
|
|
236
|
+
custom_agent_logger: Custom logger for agent operations
|
|
237
|
+
custom_team_logger: Custom logger for team operations
|
|
238
|
+
custom_workflow_logger: Custom logger for workflow operations
|
|
239
|
+
"""
|
|
240
|
+
if custom_default_logger is not None:
|
|
241
|
+
global logger
|
|
242
|
+
logger = custom_default_logger
|
|
243
|
+
|
|
244
|
+
if custom_agent_logger is not None:
|
|
245
|
+
global agent_logger
|
|
246
|
+
agent_logger = custom_agent_logger
|
|
247
|
+
|
|
248
|
+
if custom_team_logger is not None:
|
|
249
|
+
global team_logger
|
|
250
|
+
team_logger = custom_team_logger
|
|
251
|
+
|
|
252
|
+
if custom_workflow_logger is not None:
|
|
253
|
+
global workflow_logger
|
|
254
|
+
workflow_logger = custom_workflow_logger
|
agno/utils/mcp.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import json
|
|
1
2
|
from functools import partial
|
|
2
3
|
from uuid import uuid4
|
|
3
4
|
|
|
@@ -43,13 +44,64 @@ def get_entrypoint_for_tool(tool: MCPTool, session: ClientSession):
|
|
|
43
44
|
|
|
44
45
|
for content_item in result.content:
|
|
45
46
|
if isinstance(content_item, TextContent):
|
|
46
|
-
|
|
47
|
+
text_content = content_item.text
|
|
48
|
+
|
|
49
|
+
# Parse as JSON to check for custom image format
|
|
50
|
+
try:
|
|
51
|
+
parsed_json = json.loads(text_content)
|
|
52
|
+
if (
|
|
53
|
+
isinstance(parsed_json, dict)
|
|
54
|
+
and parsed_json.get("type") == "image"
|
|
55
|
+
and "data" in parsed_json
|
|
56
|
+
):
|
|
57
|
+
log_debug("Found custom JSON image format in TextContent")
|
|
58
|
+
|
|
59
|
+
# Extract image data
|
|
60
|
+
image_data = parsed_json.get("data")
|
|
61
|
+
mime_type = parsed_json.get("mimeType", "image/png")
|
|
62
|
+
|
|
63
|
+
if image_data and isinstance(image_data, str):
|
|
64
|
+
import base64
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
image_bytes = base64.b64decode(image_data)
|
|
68
|
+
except Exception as e:
|
|
69
|
+
log_debug(f"Failed to decode base64 image data: {e}")
|
|
70
|
+
image_bytes = None
|
|
71
|
+
|
|
72
|
+
if image_bytes:
|
|
73
|
+
img_artifact = ImageArtifact(
|
|
74
|
+
id=str(uuid4()),
|
|
75
|
+
url=None,
|
|
76
|
+
content=image_bytes,
|
|
77
|
+
mime_type=mime_type,
|
|
78
|
+
)
|
|
79
|
+
images.append(img_artifact)
|
|
80
|
+
response_str += "Image has been generated and added to the response.\n"
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
except (json.JSONDecodeError, TypeError):
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
response_str += text_content + "\n"
|
|
87
|
+
|
|
47
88
|
elif isinstance(content_item, ImageContent):
|
|
48
|
-
# Handle
|
|
89
|
+
# Handle standard MCP ImageContent
|
|
90
|
+
image_data = getattr(content_item, "data", None)
|
|
91
|
+
|
|
92
|
+
if image_data and isinstance(image_data, str):
|
|
93
|
+
import base64
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
image_data = base64.b64decode(image_data)
|
|
97
|
+
except Exception as e:
|
|
98
|
+
log_debug(f"Failed to decode base64 image data: {e}")
|
|
99
|
+
image_data = None
|
|
100
|
+
|
|
49
101
|
img_artifact = ImageArtifact(
|
|
50
102
|
id=str(uuid4()),
|
|
51
103
|
url=getattr(content_item, "url", None),
|
|
52
|
-
content=
|
|
104
|
+
content=image_data,
|
|
53
105
|
mime_type=getattr(content_item, "mimeType", "image/png"),
|
|
54
106
|
)
|
|
55
107
|
images.append(img_artifact)
|
agno/utils/models/claude.py
CHANGED
|
@@ -286,3 +286,44 @@ def format_messages(messages: List[Message]) -> Tuple[List[Dict[str, str]], str]
|
|
|
286
286
|
|
|
287
287
|
chat_messages.append({"role": ROLE_MAP[message.role], "content": content}) # type: ignore
|
|
288
288
|
return chat_messages, " ".join(system_messages)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def format_tools_for_model(tools: Optional[List[Dict[str, Any]]] = None) -> Optional[List[Dict[str, Any]]]:
|
|
292
|
+
"""
|
|
293
|
+
Transforms function definitions into a format accepted by the Anthropic API.
|
|
294
|
+
"""
|
|
295
|
+
if not tools:
|
|
296
|
+
return None
|
|
297
|
+
|
|
298
|
+
parsed_tools: List[Dict[str, Any]] = []
|
|
299
|
+
for tool_def in tools:
|
|
300
|
+
if tool_def.get("type", "") != "function":
|
|
301
|
+
parsed_tools.append(tool_def)
|
|
302
|
+
continue
|
|
303
|
+
|
|
304
|
+
func_def = tool_def.get("function", {})
|
|
305
|
+
parameters: Dict[str, Any] = func_def.get("parameters", {})
|
|
306
|
+
properties: Dict[str, Any] = parameters.get("properties", {})
|
|
307
|
+
required: List[str] = parameters.get("required", [])
|
|
308
|
+
required_params: List[str] = required
|
|
309
|
+
|
|
310
|
+
input_properties: Dict[str, Any] = {}
|
|
311
|
+
for param_name, param_info in properties.items():
|
|
312
|
+
# Preserve the complete schema structure for complex types
|
|
313
|
+
input_properties[param_name] = param_info.copy()
|
|
314
|
+
|
|
315
|
+
# Ensure description is present (default to empty if missing)
|
|
316
|
+
if "description" not in input_properties[param_name]:
|
|
317
|
+
input_properties[param_name]["description"] = ""
|
|
318
|
+
|
|
319
|
+
tool = {
|
|
320
|
+
"name": func_def.get("name") or "",
|
|
321
|
+
"description": func_def.get("description") or "",
|
|
322
|
+
"input_schema": {
|
|
323
|
+
"type": parameters.get("type", "object"),
|
|
324
|
+
"properties": input_properties,
|
|
325
|
+
"required": required_params,
|
|
326
|
+
},
|
|
327
|
+
}
|
|
328
|
+
parsed_tools.append(tool)
|
|
329
|
+
return parsed_tools
|