agno 2.1.2__py3-none-any.whl → 2.3.13__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 +5540 -2273
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/compression/__init__.py +3 -0
- agno/compression/manager.py +247 -0
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +689 -6
- agno/db/dynamo/dynamo.py +933 -37
- agno/db/dynamo/schemas.py +174 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +831 -9
- agno/db/firestore/schemas.py +51 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +660 -12
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +287 -14
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +590 -14
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +43 -13
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/__init__.py +15 -1
- agno/db/mongo/async_mongo.py +2760 -0
- agno/db/mongo/mongo.py +879 -11
- agno/db/mongo/schemas.py +42 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/__init__.py +2 -1
- agno/db/mysql/async_mysql.py +2912 -0
- agno/db/mysql/mysql.py +946 -68
- agno/db/mysql/schemas.py +72 -10
- agno/db/mysql/utils.py +198 -7
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2579 -0
- agno/db/postgres/postgres.py +942 -57
- agno/db/postgres/schemas.py +81 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +671 -7
- agno/db/redis/schemas.py +50 -0
- agno/db/redis/utils.py +65 -7
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/evals.py +1 -0
- agno/db/schemas/memory.py +17 -2
- agno/db/singlestore/schemas.py +63 -0
- agno/db/singlestore/singlestore.py +949 -83
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2911 -0
- agno/db/sqlite/schemas.py +62 -0
- agno/db/sqlite/sqlite.py +965 -46
- agno/db/sqlite/utils.py +169 -8
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +334 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1908 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +2 -0
- agno/eval/__init__.py +10 -0
- agno/eval/accuracy.py +75 -55
- agno/eval/agent_as_judge.py +861 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +16 -7
- agno/eval/reliability.py +28 -16
- agno/eval/utils.py +35 -17
- agno/exceptions.py +27 -2
- agno/filters.py +354 -0
- agno/guardrails/prompt_injection.py +1 -0
- agno/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/client.py +1 -1
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +4 -1
- agno/knowledge/chunking/semantic.py +9 -4
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +8 -0
- agno/knowledge/embedder/openai.py +8 -8
- agno/knowledge/embedder/sentence_transformer.py +6 -2
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/knowledge.py +1618 -318
- agno/knowledge/reader/base.py +6 -2
- agno/knowledge/reader/csv_reader.py +8 -10
- agno/knowledge/reader/docx_reader.py +5 -6
- agno/knowledge/reader/field_labeled_csv_reader.py +16 -20
- agno/knowledge/reader/json_reader.py +5 -4
- agno/knowledge/reader/markdown_reader.py +8 -8
- agno/knowledge/reader/pdf_reader.py +17 -19
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +32 -3
- agno/knowledge/reader/s3_reader.py +3 -3
- agno/knowledge/reader/tavily_reader.py +193 -0
- agno/knowledge/reader/text_reader.py +22 -10
- agno/knowledge/reader/web_search_reader.py +1 -48
- agno/knowledge/reader/website_reader.py +10 -10
- agno/knowledge/reader/wikipedia_reader.py +33 -1
- agno/knowledge/types.py +1 -0
- agno/knowledge/utils.py +72 -7
- agno/media.py +22 -6
- agno/memory/__init__.py +14 -1
- agno/memory/manager.py +544 -83
- agno/memory/strategies/__init__.py +15 -0
- agno/memory/strategies/base.py +66 -0
- agno/memory/strategies/summarize.py +196 -0
- agno/memory/strategies/types.py +37 -0
- agno/models/aimlapi/aimlapi.py +17 -0
- agno/models/anthropic/claude.py +515 -40
- agno/models/aws/bedrock.py +102 -21
- agno/models/aws/claude.py +131 -274
- agno/models/azure/ai_foundry.py +41 -19
- agno/models/azure/openai_chat.py +39 -8
- agno/models/base.py +1249 -525
- agno/models/cerebras/cerebras.py +91 -21
- agno/models/cerebras/cerebras_openai.py +21 -2
- agno/models/cohere/chat.py +40 -6
- agno/models/cometapi/cometapi.py +18 -1
- agno/models/dashscope/dashscope.py +2 -3
- agno/models/deepinfra/deepinfra.py +18 -1
- agno/models/deepseek/deepseek.py +69 -3
- agno/models/fireworks/fireworks.py +18 -1
- agno/models/google/gemini.py +877 -80
- agno/models/google/utils.py +22 -0
- agno/models/groq/groq.py +51 -18
- agno/models/huggingface/huggingface.py +17 -6
- agno/models/ibm/watsonx.py +16 -6
- agno/models/internlm/internlm.py +18 -1
- agno/models/langdb/langdb.py +13 -1
- agno/models/litellm/chat.py +44 -9
- agno/models/litellm/litellm_openai.py +18 -1
- agno/models/message.py +28 -5
- agno/models/meta/llama.py +47 -14
- agno/models/meta/llama_openai.py +22 -17
- agno/models/mistral/mistral.py +8 -4
- agno/models/nebius/nebius.py +6 -7
- agno/models/nvidia/nvidia.py +20 -3
- agno/models/ollama/chat.py +24 -8
- agno/models/openai/chat.py +104 -29
- agno/models/openai/responses.py +101 -81
- agno/models/openrouter/openrouter.py +60 -3
- agno/models/perplexity/perplexity.py +17 -1
- agno/models/portkey/portkey.py +7 -6
- agno/models/requesty/requesty.py +24 -4
- agno/models/response.py +73 -2
- agno/models/sambanova/sambanova.py +20 -3
- agno/models/siliconflow/siliconflow.py +19 -2
- agno/models/together/together.py +20 -3
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +20 -3
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +190 -0
- agno/models/vllm/vllm.py +19 -14
- agno/models/xai/xai.py +19 -2
- agno/os/app.py +549 -152
- agno/os/auth.py +190 -3
- agno/os/config.py +23 -0
- agno/os/interfaces/a2a/router.py +8 -11
- agno/os/interfaces/a2a/utils.py +1 -1
- agno/os/interfaces/agui/router.py +18 -3
- agno/os/interfaces/agui/utils.py +152 -39
- agno/os/interfaces/slack/router.py +55 -37
- agno/os/interfaces/slack/slack.py +9 -1
- agno/os/interfaces/whatsapp/router.py +0 -1
- agno/os/interfaces/whatsapp/security.py +3 -1
- agno/os/mcp.py +110 -52
- agno/os/middleware/__init__.py +2 -0
- agno/os/middleware/jwt.py +676 -112
- agno/os/router.py +40 -1478
- agno/os/routers/agents/__init__.py +3 -0
- agno/os/routers/agents/router.py +599 -0
- agno/os/routers/agents/schema.py +261 -0
- agno/os/routers/evals/evals.py +96 -39
- agno/os/routers/evals/schemas.py +65 -33
- agno/os/routers/evals/utils.py +80 -10
- agno/os/routers/health.py +10 -4
- agno/os/routers/knowledge/knowledge.py +196 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +279 -52
- agno/os/routers/memory/schemas.py +46 -17
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +462 -34
- agno/os/routers/teams/__init__.py +3 -0
- agno/os/routers/teams/router.py +512 -0
- agno/os/routers/teams/schema.py +257 -0
- agno/os/routers/traces/__init__.py +3 -0
- agno/os/routers/traces/schemas.py +414 -0
- agno/os/routers/traces/traces.py +499 -0
- agno/os/routers/workflows/__init__.py +3 -0
- agno/os/routers/workflows/router.py +624 -0
- agno/os/routers/workflows/schema.py +75 -0
- agno/os/schema.py +256 -693
- agno/os/scopes.py +469 -0
- agno/os/utils.py +514 -36
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/openai.py +5 -0
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +155 -32
- agno/run/base.py +55 -3
- agno/run/requirement.py +181 -0
- agno/run/team.py +125 -38
- agno/run/workflow.py +72 -18
- agno/session/agent.py +102 -89
- agno/session/summary.py +56 -15
- agno/session/team.py +164 -90
- agno/session/workflow.py +405 -40
- agno/table.py +10 -0
- agno/team/team.py +3974 -1903
- agno/tools/dalle.py +2 -4
- agno/tools/eleven_labs.py +23 -25
- agno/tools/exa.py +21 -16
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +16 -10
- agno/tools/firecrawl.py +15 -7
- agno/tools/function.py +193 -38
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +271 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- agno/tools/mcp/__init__.py +10 -0
- agno/tools/mcp/mcp.py +331 -0
- agno/tools/mcp/multi_mcp.py +347 -0
- agno/tools/mcp/params.py +24 -0
- agno/tools/mcp_toolbox.py +3 -3
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/nano_banana.py +151 -0
- agno/tools/notion.py +204 -0
- agno/tools/parallel.py +314 -0
- agno/tools/postgres.py +76 -36
- agno/tools/redshift.py +406 -0
- agno/tools/scrapegraph.py +1 -1
- agno/tools/shopify.py +1519 -0
- agno/tools/slack.py +18 -3
- agno/tools/spotify.py +919 -0
- agno/tools/tavily.py +146 -0
- agno/tools/toolkit.py +25 -0
- agno/tools/workflow.py +8 -1
- agno/tools/yfinance.py +12 -11
- agno/tracing/__init__.py +12 -0
- agno/tracing/exporter.py +157 -0
- agno/tracing/schemas.py +276 -0
- agno/tracing/setup.py +111 -0
- agno/utils/agent.py +938 -0
- agno/utils/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +151 -3
- agno/utils/gemini.py +15 -5
- agno/utils/hooks.py +118 -4
- agno/utils/http.py +113 -2
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +92 -2
- agno/utils/media.py +187 -1
- agno/utils/merge_dict.py +3 -3
- agno/utils/message.py +60 -0
- agno/utils/models/ai_foundry.py +9 -2
- agno/utils/models/claude.py +49 -14
- agno/utils/models/cohere.py +9 -2
- agno/utils/models/llama.py +9 -2
- agno/utils/models/mistral.py +4 -2
- agno/utils/print_response/agent.py +109 -16
- agno/utils/print_response/team.py +223 -30
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/streamlit.py +1 -1
- agno/utils/team.py +98 -9
- agno/utils/tokens.py +657 -0
- agno/vectordb/base.py +39 -7
- agno/vectordb/cassandra/cassandra.py +21 -5
- agno/vectordb/chroma/chromadb.py +43 -12
- agno/vectordb/clickhouse/clickhousedb.py +21 -5
- agno/vectordb/couchbase/couchbase.py +29 -5
- agno/vectordb/lancedb/lance_db.py +92 -181
- agno/vectordb/langchaindb/langchaindb.py +24 -4
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/llamaindexdb.py +25 -5
- agno/vectordb/milvus/milvus.py +50 -37
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +36 -30
- agno/vectordb/pgvector/pgvector.py +201 -77
- agno/vectordb/pineconedb/pineconedb.py +41 -23
- agno/vectordb/qdrant/qdrant.py +67 -54
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +682 -0
- agno/vectordb/singlestore/singlestore.py +50 -29
- agno/vectordb/surrealdb/surrealdb.py +31 -41
- agno/vectordb/upstashdb/upstashdb.py +34 -6
- agno/vectordb/weaviate/weaviate.py +53 -14
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +120 -18
- agno/workflow/loop.py +77 -10
- agno/workflow/parallel.py +231 -143
- agno/workflow/router.py +118 -17
- agno/workflow/step.py +609 -170
- agno/workflow/steps.py +73 -6
- agno/workflow/types.py +96 -21
- agno/workflow/workflow.py +2039 -262
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/METADATA +201 -66
- agno-2.3.13.dist-info/RECORD +613 -0
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -679
- agno/tools/memori.py +0 -339
- agno-2.1.2.dist-info/RECORD +0 -543
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +0 -0
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/licenses/LICENSE +0 -0
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
agno/utils/knowledge.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
from typing import Any, Dict, Optional
|
|
1
|
+
from typing import Any, Dict, List, Optional, Union
|
|
2
2
|
|
|
3
|
+
from agno.filters import FilterExpr
|
|
3
4
|
from agno.utils.log import log_info
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
def get_agentic_or_user_search_filters(
|
|
7
|
-
filters: Optional[Dict[str, Any]], effective_filters: Optional[Dict[str, Any]]
|
|
8
|
+
filters: Optional[Dict[str, Any]], effective_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]]
|
|
8
9
|
) -> Dict[str, Any]:
|
|
9
10
|
"""Helper function to determine the final filters to use for the search.
|
|
10
11
|
|
|
@@ -15,7 +16,7 @@ def get_agentic_or_user_search_filters(
|
|
|
15
16
|
Returns:
|
|
16
17
|
Dict[str, Any]: The final filters to use for the search.
|
|
17
18
|
"""
|
|
18
|
-
search_filters =
|
|
19
|
+
search_filters = None
|
|
19
20
|
|
|
20
21
|
# If agentic filters exist and manual filters (passed by user) do not, use agentic filters
|
|
21
22
|
if filters and not effective_filters:
|
|
@@ -23,7 +24,13 @@ def get_agentic_or_user_search_filters(
|
|
|
23
24
|
|
|
24
25
|
# If both agentic filters exist and manual filters (passed by user) exist, use manual filters (give priority to user and override)
|
|
25
26
|
if filters and effective_filters:
|
|
26
|
-
|
|
27
|
+
if isinstance(effective_filters, dict):
|
|
28
|
+
search_filters = effective_filters
|
|
29
|
+
elif isinstance(effective_filters, list):
|
|
30
|
+
# If effective_filters is a list (likely List[FilterExpr]), convert both filters and effective_filters to a dict if possible, otherwise raise
|
|
31
|
+
raise ValueError(
|
|
32
|
+
"Merging dict and list of filters is not supported; effective_filters should be a dict for search compatibility."
|
|
33
|
+
)
|
|
27
34
|
|
|
28
35
|
log_info(f"Filters used by Agent: {search_filters}")
|
|
29
|
-
return search_filters
|
|
36
|
+
return search_filters or {}
|
agno/utils/log.py
CHANGED
agno/utils/mcp.py
CHANGED
|
@@ -27,9 +27,13 @@ def get_entrypoint_for_tool(tool: MCPTool, session: ClientSession):
|
|
|
27
27
|
Returns:
|
|
28
28
|
Callable: The entrypoint function for the tool
|
|
29
29
|
"""
|
|
30
|
-
from agno.agent import Agent
|
|
31
30
|
|
|
32
|
-
async def call_tool(
|
|
31
|
+
async def call_tool(tool_name: str, **kwargs) -> ToolResult:
|
|
32
|
+
try:
|
|
33
|
+
await session.send_ping()
|
|
34
|
+
except Exception as e:
|
|
35
|
+
log_exception(e)
|
|
36
|
+
|
|
33
37
|
try:
|
|
34
38
|
log_debug(f"Calling MCP Tool '{tool_name}' with args: {kwargs}")
|
|
35
39
|
result: CallToolResult = await session.call_tool(tool_name, kwargs) # type: ignore
|
|
@@ -122,3 +126,89 @@ def get_entrypoint_for_tool(tool: MCPTool, session: ClientSession):
|
|
|
122
126
|
return ToolResult(content=f"Error: {e}")
|
|
123
127
|
|
|
124
128
|
return partial(call_tool, tool_name=tool.name)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def prepare_command(command: str) -> list[str]:
|
|
132
|
+
"""Sanitize a command and split it into parts before using it to run a MCP server."""
|
|
133
|
+
import os
|
|
134
|
+
import shutil
|
|
135
|
+
from shlex import split
|
|
136
|
+
|
|
137
|
+
# Block dangerous characters
|
|
138
|
+
if any(char in command for char in ["&", "|", ";", "`", "$", "(", ")"]):
|
|
139
|
+
raise ValueError("MCP command can't contain shell metacharacters")
|
|
140
|
+
|
|
141
|
+
parts = split(command)
|
|
142
|
+
if not parts:
|
|
143
|
+
raise ValueError("MCP command can't be empty")
|
|
144
|
+
|
|
145
|
+
# Only allow specific executables
|
|
146
|
+
ALLOWED_COMMANDS = {
|
|
147
|
+
# Python
|
|
148
|
+
"python",
|
|
149
|
+
"python3",
|
|
150
|
+
"uv",
|
|
151
|
+
"uvx",
|
|
152
|
+
"pipx",
|
|
153
|
+
# Node
|
|
154
|
+
"node",
|
|
155
|
+
"npm",
|
|
156
|
+
"npx",
|
|
157
|
+
"yarn",
|
|
158
|
+
"pnpm",
|
|
159
|
+
"bun",
|
|
160
|
+
# Other runtimes
|
|
161
|
+
"deno",
|
|
162
|
+
"java",
|
|
163
|
+
"ruby",
|
|
164
|
+
"docker",
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
executable = parts[0].split("/")[-1]
|
|
168
|
+
|
|
169
|
+
# Check if it's a relative path starting with ./ or ../
|
|
170
|
+
if executable.startswith("./") or executable.startswith("../"):
|
|
171
|
+
# Allow relative paths to binaries
|
|
172
|
+
return parts
|
|
173
|
+
|
|
174
|
+
# Check if it's an absolute path to a binary
|
|
175
|
+
if executable.startswith("/") and os.path.isfile(executable):
|
|
176
|
+
# Allow absolute paths to existing files
|
|
177
|
+
return parts
|
|
178
|
+
|
|
179
|
+
# Check if it's a binary in current directory without ./
|
|
180
|
+
if "/" not in executable and os.path.isfile(executable):
|
|
181
|
+
# Allow binaries in current directory
|
|
182
|
+
return parts
|
|
183
|
+
|
|
184
|
+
# Check if it's a binary in PATH
|
|
185
|
+
if shutil.which(executable):
|
|
186
|
+
return parts
|
|
187
|
+
|
|
188
|
+
if executable not in ALLOWED_COMMANDS:
|
|
189
|
+
raise ValueError(f"MCP command needs to use one of the following executables: {ALLOWED_COMMANDS}")
|
|
190
|
+
|
|
191
|
+
first_part = parts[0]
|
|
192
|
+
executable = first_part.split("/")[-1]
|
|
193
|
+
|
|
194
|
+
# Allow known commands
|
|
195
|
+
if executable in ALLOWED_COMMANDS:
|
|
196
|
+
return parts
|
|
197
|
+
|
|
198
|
+
# Allow relative paths to custom binaries
|
|
199
|
+
if first_part.startswith(("./", "../")):
|
|
200
|
+
return parts
|
|
201
|
+
|
|
202
|
+
# Allow absolute paths to existing files
|
|
203
|
+
if first_part.startswith("/") and os.path.isfile(first_part):
|
|
204
|
+
return parts
|
|
205
|
+
|
|
206
|
+
# Allow binaries in current directory without ./
|
|
207
|
+
if "/" not in first_part and os.path.isfile(first_part):
|
|
208
|
+
return parts
|
|
209
|
+
|
|
210
|
+
# Allow binaries in PATH
|
|
211
|
+
if shutil.which(first_part):
|
|
212
|
+
return parts
|
|
213
|
+
|
|
214
|
+
raise ValueError(f"MCP command needs to use one of the following executables: {ALLOWED_COMMANDS}")
|
agno/utils/media.py
CHANGED
|
@@ -2,10 +2,11 @@ import base64
|
|
|
2
2
|
import time
|
|
3
3
|
from enum import Enum
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import List
|
|
5
|
+
from typing import List, Optional
|
|
6
6
|
|
|
7
7
|
import httpx
|
|
8
8
|
|
|
9
|
+
from agno.media import Audio, File, Image, Video
|
|
9
10
|
from agno.utils.log import log_info, log_warning
|
|
10
11
|
|
|
11
12
|
|
|
@@ -55,6 +56,17 @@ def download_image(url: str, output_path: str) -> bool:
|
|
|
55
56
|
return False
|
|
56
57
|
|
|
57
58
|
|
|
59
|
+
def download_audio(url: str, output_path: str) -> str:
|
|
60
|
+
"""Download audio from URL"""
|
|
61
|
+
response = httpx.get(url)
|
|
62
|
+
response.raise_for_status()
|
|
63
|
+
|
|
64
|
+
with open(output_path, "wb") as f:
|
|
65
|
+
for chunk in response.iter_bytes(chunk_size=8192):
|
|
66
|
+
f.write(chunk)
|
|
67
|
+
return output_path
|
|
68
|
+
|
|
69
|
+
|
|
58
70
|
def download_video(url: str, output_path: str) -> str:
|
|
59
71
|
"""Download video from URL"""
|
|
60
72
|
response = httpx.get(url)
|
|
@@ -185,3 +197,177 @@ def download_knowledge_filters_sample_data(
|
|
|
185
197
|
)
|
|
186
198
|
file_paths.append(str(download_path))
|
|
187
199
|
return file_paths
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def reconstruct_image_from_dict(img_data):
|
|
203
|
+
"""
|
|
204
|
+
Reconstruct an Image object from dictionary data.
|
|
205
|
+
|
|
206
|
+
Handles both base64-encoded content (from database) and regular image data (url/filepath).
|
|
207
|
+
"""
|
|
208
|
+
try:
|
|
209
|
+
if isinstance(img_data, dict):
|
|
210
|
+
# If content is base64 string, decode it back to bytes
|
|
211
|
+
if "content" in img_data and isinstance(img_data["content"], str):
|
|
212
|
+
return Image.from_base64(
|
|
213
|
+
img_data["content"],
|
|
214
|
+
id=img_data.get("id"),
|
|
215
|
+
mime_type=img_data.get("mime_type"),
|
|
216
|
+
format=img_data.get("format"),
|
|
217
|
+
detail=img_data.get("detail"),
|
|
218
|
+
original_prompt=img_data.get("original_prompt"),
|
|
219
|
+
revised_prompt=img_data.get("revised_prompt"),
|
|
220
|
+
alt_text=img_data.get("alt_text"),
|
|
221
|
+
)
|
|
222
|
+
else:
|
|
223
|
+
# Regular image (filepath/url)
|
|
224
|
+
return Image(**img_data)
|
|
225
|
+
return img_data
|
|
226
|
+
except Exception as e:
|
|
227
|
+
log_warning(f"Failed to reconstruct image from dict: {e}")
|
|
228
|
+
return None
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def reconstruct_video_from_dict(vid_data):
|
|
232
|
+
"""
|
|
233
|
+
Reconstruct a Video object from dictionary data.
|
|
234
|
+
|
|
235
|
+
Handles both base64-encoded content (from database) and regular video data (url/filepath).
|
|
236
|
+
"""
|
|
237
|
+
try:
|
|
238
|
+
if isinstance(vid_data, dict):
|
|
239
|
+
# If content is base64 string, decode it back to bytes
|
|
240
|
+
if "content" in vid_data and isinstance(vid_data["content"], str):
|
|
241
|
+
return Video.from_base64(
|
|
242
|
+
vid_data["content"],
|
|
243
|
+
id=vid_data.get("id"),
|
|
244
|
+
mime_type=vid_data.get("mime_type"),
|
|
245
|
+
format=vid_data.get("format"),
|
|
246
|
+
)
|
|
247
|
+
else:
|
|
248
|
+
# Regular video (filepath/url)
|
|
249
|
+
return Video(**vid_data)
|
|
250
|
+
return vid_data
|
|
251
|
+
except Exception as e:
|
|
252
|
+
log_warning(f"Failed to reconstruct video from dict: {e}")
|
|
253
|
+
return None
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def reconstruct_audio_from_dict(aud_data):
|
|
257
|
+
"""
|
|
258
|
+
Reconstruct an Audio object from dictionary data.
|
|
259
|
+
|
|
260
|
+
Handles both base64-encoded content (from database) and regular audio data (url/filepath).
|
|
261
|
+
"""
|
|
262
|
+
try:
|
|
263
|
+
if isinstance(aud_data, dict):
|
|
264
|
+
# If content is base64 string, decode it back to bytes
|
|
265
|
+
if "content" in aud_data and isinstance(aud_data["content"], str):
|
|
266
|
+
return Audio.from_base64(
|
|
267
|
+
aud_data["content"],
|
|
268
|
+
id=aud_data.get("id"),
|
|
269
|
+
mime_type=aud_data.get("mime_type"),
|
|
270
|
+
transcript=aud_data.get("transcript"),
|
|
271
|
+
expires_at=aud_data.get("expires_at"),
|
|
272
|
+
sample_rate=aud_data.get("sample_rate", 24000),
|
|
273
|
+
channels=aud_data.get("channels", 1),
|
|
274
|
+
)
|
|
275
|
+
else:
|
|
276
|
+
# Regular audio (filepath/url)
|
|
277
|
+
return Audio(**aud_data)
|
|
278
|
+
return aud_data
|
|
279
|
+
except Exception as e:
|
|
280
|
+
log_warning(f"Failed to reconstruct audio from dict: {e}")
|
|
281
|
+
return None
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def reconstruct_file_from_dict(file_data):
|
|
285
|
+
"""
|
|
286
|
+
Reconstruct a File object from dictionary data.
|
|
287
|
+
|
|
288
|
+
Handles both base64-encoded content (from database) and regular file data (url/filepath).
|
|
289
|
+
"""
|
|
290
|
+
try:
|
|
291
|
+
if isinstance(file_data, dict):
|
|
292
|
+
# If content is base64 string, decode it back to bytes
|
|
293
|
+
if "content" in file_data and isinstance(file_data["content"], str):
|
|
294
|
+
file_obj = File.from_base64(
|
|
295
|
+
file_data["content"],
|
|
296
|
+
id=file_data.get("id"),
|
|
297
|
+
mime_type=file_data.get("mime_type"),
|
|
298
|
+
filename=file_data.get("filename"),
|
|
299
|
+
name=file_data.get("name"),
|
|
300
|
+
format=file_data.get("format"),
|
|
301
|
+
)
|
|
302
|
+
# Preserve additional fields that from_base64 doesn't handle
|
|
303
|
+
if file_data.get("size") is not None:
|
|
304
|
+
file_obj.size = file_data.get("size")
|
|
305
|
+
if file_data.get("file_type") is not None:
|
|
306
|
+
file_obj.file_type = file_data.get("file_type")
|
|
307
|
+
if file_data.get("filepath") is not None:
|
|
308
|
+
file_obj.filepath = file_data.get("filepath")
|
|
309
|
+
if file_data.get("url") is not None:
|
|
310
|
+
file_obj.url = file_data.get("url")
|
|
311
|
+
return file_obj
|
|
312
|
+
else:
|
|
313
|
+
# Regular file (filepath/url)
|
|
314
|
+
return File(**file_data)
|
|
315
|
+
return file_data
|
|
316
|
+
except Exception as e:
|
|
317
|
+
log_warning(f"Failed to reconstruct file from dict: {e}")
|
|
318
|
+
return None
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def reconstruct_images(images: Optional[List[dict]]) -> Optional[List[Image]]:
|
|
322
|
+
"""Reconstruct a list of Image objects from list of dictionaries.
|
|
323
|
+
|
|
324
|
+
Failed reconstructions are skipped with a warning logged.
|
|
325
|
+
"""
|
|
326
|
+
if not images:
|
|
327
|
+
return None
|
|
328
|
+
reconstructed = [reconstruct_image_from_dict(img_data) for img_data in images]
|
|
329
|
+
valid_images = [img for img in reconstructed if img is not None]
|
|
330
|
+
return valid_images if valid_images else None
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def reconstruct_videos(videos: Optional[List[dict]]) -> Optional[List[Video]]:
|
|
334
|
+
"""Reconstruct a list of Video objects from list of dictionaries.
|
|
335
|
+
|
|
336
|
+
Failed reconstructions are skipped with a warning logged.
|
|
337
|
+
"""
|
|
338
|
+
if not videos:
|
|
339
|
+
return None
|
|
340
|
+
reconstructed = [reconstruct_video_from_dict(vid_data) for vid_data in videos]
|
|
341
|
+
valid_videos = [vid for vid in reconstructed if vid is not None]
|
|
342
|
+
return valid_videos if valid_videos else None
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def reconstruct_audio_list(audio: Optional[List[dict]]) -> Optional[List[Audio]]:
|
|
346
|
+
"""Reconstruct a list of Audio objects from list of dictionaries.
|
|
347
|
+
|
|
348
|
+
Failed reconstructions are skipped with a warning logged.
|
|
349
|
+
"""
|
|
350
|
+
if not audio:
|
|
351
|
+
return None
|
|
352
|
+
reconstructed = [reconstruct_audio_from_dict(aud_data) for aud_data in audio]
|
|
353
|
+
valid_audio = [aud for aud in reconstructed if aud is not None]
|
|
354
|
+
return valid_audio if valid_audio else None
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def reconstruct_files(files: Optional[List[dict]]) -> Optional[List[File]]:
|
|
358
|
+
"""Reconstruct a list of File objects from list of dictionaries.
|
|
359
|
+
|
|
360
|
+
Failed reconstructions are skipped with a warning logged.
|
|
361
|
+
"""
|
|
362
|
+
if not files:
|
|
363
|
+
return None
|
|
364
|
+
reconstructed = [reconstruct_file_from_dict(file_data) for file_data in files]
|
|
365
|
+
valid_files = [f for f in reconstructed if f is not None]
|
|
366
|
+
return valid_files if valid_files else None
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def reconstruct_response_audio(audio: Optional[dict]) -> Optional[Audio]:
|
|
370
|
+
"""Reconstruct a single Audio object for response audio."""
|
|
371
|
+
if not audio:
|
|
372
|
+
return None
|
|
373
|
+
return reconstruct_audio_from_dict(audio)
|
agno/utils/merge_dict.py
CHANGED
|
@@ -27,7 +27,7 @@ def merge_parallel_session_states(original_state: Dict[str, Any], modified_state
|
|
|
27
27
|
"""
|
|
28
28
|
if not original_state or not modified_states:
|
|
29
29
|
return
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
# Collect all actual changes (keys where value differs from original)
|
|
32
32
|
all_changes = {}
|
|
33
33
|
for modified_state in modified_states:
|
|
@@ -35,7 +35,7 @@ def merge_parallel_session_states(original_state: Dict[str, Any], modified_state
|
|
|
35
35
|
for key, value in modified_state.items():
|
|
36
36
|
if key not in original_state or original_state[key] != value:
|
|
37
37
|
all_changes[key] = value
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
# Apply all collected changes to the original state
|
|
40
40
|
for key, value in all_changes.items():
|
|
41
|
-
original_state[key] = value
|
|
41
|
+
original_state[key] = value
|
agno/utils/message.py
CHANGED
|
@@ -1,8 +1,68 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
1
2
|
from typing import Dict, List, Union
|
|
2
3
|
|
|
3
4
|
from pydantic import BaseModel
|
|
4
5
|
|
|
5
6
|
from agno.models.message import Message
|
|
7
|
+
from agno.utils.log import log_debug
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def filter_tool_calls(messages: List[Message], max_tool_calls: int) -> None:
|
|
11
|
+
"""
|
|
12
|
+
Filter messages (in-place) to keep only the most recent N tool calls.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
messages: List of messages to filter (modified in-place)
|
|
16
|
+
max_tool_calls: Number of recent tool calls to keep
|
|
17
|
+
"""
|
|
18
|
+
# Count total tool calls
|
|
19
|
+
tool_call_count = sum(1 for m in messages if m.role == "tool")
|
|
20
|
+
|
|
21
|
+
# No filtering needed
|
|
22
|
+
if tool_call_count <= max_tool_calls:
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
# Collect tool_call_ids to keep (most recent N)
|
|
26
|
+
tool_call_ids_list: List[str] = []
|
|
27
|
+
for msg in reversed(messages):
|
|
28
|
+
if msg.role == "tool" and len(tool_call_ids_list) < max_tool_calls:
|
|
29
|
+
if msg.tool_call_id:
|
|
30
|
+
tool_call_ids_list.append(msg.tool_call_id)
|
|
31
|
+
|
|
32
|
+
tool_call_ids_to_keep: set[str] = set(tool_call_ids_list)
|
|
33
|
+
|
|
34
|
+
# Filter messages in-place
|
|
35
|
+
filtered_messages = []
|
|
36
|
+
for msg in messages:
|
|
37
|
+
if msg.role == "tool":
|
|
38
|
+
# Keep only tool results in our window
|
|
39
|
+
if msg.tool_call_id in tool_call_ids_to_keep:
|
|
40
|
+
filtered_messages.append(msg)
|
|
41
|
+
elif msg.role == "assistant" and msg.tool_calls:
|
|
42
|
+
# Filter tool_calls within the assistant message
|
|
43
|
+
# Use deepcopy to ensure complete isolation of the filtered message
|
|
44
|
+
filtered_msg = deepcopy(msg)
|
|
45
|
+
# Filter tool_calls
|
|
46
|
+
if filtered_msg.tool_calls is not None:
|
|
47
|
+
filtered_msg.tool_calls = [
|
|
48
|
+
tc for tc in filtered_msg.tool_calls if tc.get("id") in tool_call_ids_to_keep
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
if filtered_msg.tool_calls:
|
|
52
|
+
# Has tool_calls remaining, keep it
|
|
53
|
+
filtered_messages.append(filtered_msg)
|
|
54
|
+
# skip empty messages
|
|
55
|
+
elif filtered_msg.content:
|
|
56
|
+
filtered_msg.tool_calls = None
|
|
57
|
+
filtered_messages.append(filtered_msg)
|
|
58
|
+
else:
|
|
59
|
+
filtered_messages.append(msg)
|
|
60
|
+
|
|
61
|
+
messages[:] = filtered_messages
|
|
62
|
+
|
|
63
|
+
# Log filtering information
|
|
64
|
+
num_filtered = tool_call_count - len(tool_call_ids_to_keep)
|
|
65
|
+
log_debug(f"Filtered {num_filtered} tool calls, kept {len(tool_call_ids_to_keep)}")
|
|
6
66
|
|
|
7
67
|
|
|
8
68
|
def get_text_from_message(message: Union[List, Dict, str, Message, BaseModel]) -> str:
|
agno/utils/models/ai_foundry.py
CHANGED
|
@@ -5,19 +5,26 @@ from agno.utils.log import log_warning
|
|
|
5
5
|
from agno.utils.openai import images_to_message
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def format_message(message: Message) -> Dict[str, Any]:
|
|
8
|
+
def format_message(message: Message, compress_tool_results: bool = False) -> Dict[str, Any]:
|
|
9
9
|
"""
|
|
10
10
|
Format a message into the format expected by OpenAI.
|
|
11
11
|
|
|
12
12
|
Args:
|
|
13
13
|
message (Message): The message to format.
|
|
14
|
+
compress_tool_results: Whether to compress tool results.
|
|
14
15
|
|
|
15
16
|
Returns:
|
|
16
17
|
Dict[str, Any]: The formatted message.
|
|
17
18
|
"""
|
|
19
|
+
# Use compressed content for tool messages if compression is active
|
|
20
|
+
content = message.content
|
|
21
|
+
|
|
22
|
+
if message.role == "tool":
|
|
23
|
+
content = message.get_content(use_compressed_content=compress_tool_results)
|
|
24
|
+
|
|
18
25
|
message_dict: Dict[str, Any] = {
|
|
19
26
|
"role": message.role,
|
|
20
|
-
"content":
|
|
27
|
+
"content": content,
|
|
21
28
|
"name": message.name,
|
|
22
29
|
"tool_call_id": message.tool_call_id,
|
|
23
30
|
"tool_calls": message.tool_calls,
|
agno/utils/models/claude.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from dataclasses import dataclass, field
|
|
3
|
-
from typing import Any, Dict, List, Optional, Tuple
|
|
3
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
4
4
|
|
|
5
5
|
from agno.media import File, Image
|
|
6
6
|
from agno.models.message import Message
|
|
@@ -32,6 +32,7 @@ class MCPServerConfiguration:
|
|
|
32
32
|
|
|
33
33
|
ROLE_MAP = {
|
|
34
34
|
"system": "system",
|
|
35
|
+
"developer": "system",
|
|
35
36
|
"user": "user",
|
|
36
37
|
"assistant": "assistant",
|
|
37
38
|
"tool": "user",
|
|
@@ -67,6 +68,8 @@ def _format_image_for_message(image: Image) -> Optional[Dict[str, Any]]:
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
try:
|
|
71
|
+
img_type = None
|
|
72
|
+
|
|
70
73
|
# Case 0: Image is an Anthropic uploaded file
|
|
71
74
|
if image.content is not None and hasattr(image.content, "id"):
|
|
72
75
|
content_bytes = image.content
|
|
@@ -75,6 +78,16 @@ def _format_image_for_message(image: Image) -> Optional[Dict[str, Any]]:
|
|
|
75
78
|
if image.url is not None:
|
|
76
79
|
content_bytes = image.get_content_bytes() # type: ignore
|
|
77
80
|
|
|
81
|
+
# If image URL has a suffix, use it as the type (without dot)
|
|
82
|
+
import os
|
|
83
|
+
from urllib.parse import urlparse
|
|
84
|
+
|
|
85
|
+
if image.url:
|
|
86
|
+
parsed_url = urlparse(image.url)
|
|
87
|
+
_, ext = os.path.splitext(parsed_url.path)
|
|
88
|
+
if ext:
|
|
89
|
+
img_type = ext.lstrip(".").lower()
|
|
90
|
+
|
|
78
91
|
# Case 2: Image is a local file path
|
|
79
92
|
elif image.filepath is not None:
|
|
80
93
|
from pathlib import Path
|
|
@@ -83,6 +96,11 @@ def _format_image_for_message(image: Image) -> Optional[Dict[str, Any]]:
|
|
|
83
96
|
if path.exists() and path.is_file():
|
|
84
97
|
with open(image.filepath, "rb") as f:
|
|
85
98
|
content_bytes = f.read()
|
|
99
|
+
|
|
100
|
+
# If image file path has a suffix, use it as the type (without dot)
|
|
101
|
+
path_ext = path.suffix.lstrip(".")
|
|
102
|
+
if path_ext:
|
|
103
|
+
img_type = path_ext.lower()
|
|
86
104
|
else:
|
|
87
105
|
log_error(f"Image file not found: {image}")
|
|
88
106
|
return None
|
|
@@ -95,15 +113,16 @@ def _format_image_for_message(image: Image) -> Optional[Dict[str, Any]]:
|
|
|
95
113
|
log_error(f"Unsupported image type: {type(image)}")
|
|
96
114
|
return None
|
|
97
115
|
|
|
98
|
-
if
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
116
|
+
if not img_type:
|
|
117
|
+
if using_filetype:
|
|
118
|
+
kind = filetype.guess(content_bytes)
|
|
119
|
+
if not kind:
|
|
120
|
+
log_error("Unable to determine image type")
|
|
121
|
+
return None
|
|
103
122
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
123
|
+
img_type = kind.extension
|
|
124
|
+
else:
|
|
125
|
+
img_type = imghdr.what(None, h=content_bytes) # type: ignore
|
|
107
126
|
|
|
108
127
|
if not img_type:
|
|
109
128
|
log_error("Unable to determine image type")
|
|
@@ -202,22 +221,26 @@ def _format_file_for_message(file: File) -> Optional[Dict[str, Any]]:
|
|
|
202
221
|
return None
|
|
203
222
|
|
|
204
223
|
|
|
205
|
-
def format_messages(
|
|
224
|
+
def format_messages(
|
|
225
|
+
messages: List[Message], compress_tool_results: bool = False
|
|
226
|
+
) -> Tuple[List[Dict[str, Union[str, list]]], str]:
|
|
206
227
|
"""
|
|
207
228
|
Process the list of messages and separate them into API messages and system messages.
|
|
208
229
|
|
|
209
230
|
Args:
|
|
210
231
|
messages (List[Message]): The list of messages to process.
|
|
232
|
+
compress_tool_results: Whether to compress tool results.
|
|
211
233
|
|
|
212
234
|
Returns:
|
|
213
|
-
Tuple[List[Dict[str, str]], str]: A tuple containing the list of API messages and the concatenated system messages.
|
|
235
|
+
Tuple[List[Dict[str, Union[str, list]]], str]: A tuple containing the list of API messages and the concatenated system messages.
|
|
214
236
|
"""
|
|
215
|
-
chat_messages: List[Dict[str, str]] = []
|
|
237
|
+
chat_messages: List[Dict[str, Union[str, list]]] = []
|
|
216
238
|
system_messages: List[str] = []
|
|
217
239
|
|
|
218
240
|
for message in messages:
|
|
219
241
|
content = message.content or ""
|
|
220
|
-
|
|
242
|
+
# Both "system" and "developer" roles should be extracted as system messages
|
|
243
|
+
if message.role in ("system", "developer"):
|
|
221
244
|
if content is not None:
|
|
222
245
|
system_messages.append(content) # type: ignore
|
|
223
246
|
continue
|
|
@@ -281,11 +304,15 @@ def format_messages(messages: List[Message]) -> Tuple[List[Dict[str, str]], str]
|
|
|
281
304
|
)
|
|
282
305
|
elif message.role == "tool":
|
|
283
306
|
content = []
|
|
307
|
+
|
|
308
|
+
# Use compressed content for tool messages if compression is active
|
|
309
|
+
tool_result = message.get_content(use_compressed_content=compress_tool_results)
|
|
310
|
+
|
|
284
311
|
content.append(
|
|
285
312
|
{
|
|
286
313
|
"type": "tool_result",
|
|
287
314
|
"tool_use_id": message.tool_call_id,
|
|
288
|
-
"content": str(
|
|
315
|
+
"content": str(tool_result),
|
|
289
316
|
}
|
|
290
317
|
)
|
|
291
318
|
|
|
@@ -300,6 +327,7 @@ def format_messages(messages: List[Message]) -> Tuple[List[Dict[str, str]], str]
|
|
|
300
327
|
def format_tools_for_model(tools: Optional[List[Dict[str, Any]]] = None) -> Optional[List[Dict[str, Any]]]:
|
|
301
328
|
"""
|
|
302
329
|
Transforms function definitions into a format accepted by the Anthropic API.
|
|
330
|
+
Now supports strict mode for structured outputs.
|
|
303
331
|
"""
|
|
304
332
|
if not tools:
|
|
305
333
|
return None
|
|
@@ -332,7 +360,14 @@ def format_tools_for_model(tools: Optional[List[Dict[str, Any]]] = None) -> Opti
|
|
|
332
360
|
"type": parameters.get("type", "object"),
|
|
333
361
|
"properties": input_properties,
|
|
334
362
|
"required": required_params,
|
|
363
|
+
"additionalProperties": False,
|
|
335
364
|
},
|
|
336
365
|
}
|
|
366
|
+
|
|
367
|
+
# Add strict mode if specified (check both function dict and tool_def top level)
|
|
368
|
+
strict_mode = func_def.get("strict") or tool_def.get("strict")
|
|
369
|
+
if strict_mode is True:
|
|
370
|
+
tool["strict"] = True
|
|
371
|
+
|
|
337
372
|
parsed_tools.append(tool)
|
|
338
373
|
return parsed_tools
|
agno/utils/models/cohere.py
CHANGED
|
@@ -46,21 +46,28 @@ def _format_images_for_message(message: Message, images: Sequence[Image]) -> Lis
|
|
|
46
46
|
return message_content_with_image
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
def format_messages(messages: List[Message]) -> List[Dict[str, Any]]:
|
|
49
|
+
def format_messages(messages: List[Message], compress_tool_results: bool = False) -> List[Dict[str, Any]]:
|
|
50
50
|
"""
|
|
51
51
|
Format messages for the Cohere API.
|
|
52
52
|
|
|
53
53
|
Args:
|
|
54
54
|
messages (List[Message]): The list of messages.
|
|
55
|
+
compress_tool_results: Whether to compress tool results.
|
|
55
56
|
|
|
56
57
|
Returns:
|
|
57
58
|
List[Dict[str, Any]]: The formatted messages.
|
|
58
59
|
"""
|
|
59
60
|
formatted_messages = []
|
|
60
61
|
for message in messages:
|
|
62
|
+
# Use compressed content for tool messages if compression is active
|
|
63
|
+
content = message.content
|
|
64
|
+
|
|
65
|
+
if message.role == "tool":
|
|
66
|
+
content = message.get_content(use_compressed_content=compress_tool_results)
|
|
67
|
+
|
|
61
68
|
message_dict = {
|
|
62
69
|
"role": message.role,
|
|
63
|
-
"content":
|
|
70
|
+
"content": content,
|
|
64
71
|
"name": message.name,
|
|
65
72
|
"tool_call_id": message.tool_call_id,
|
|
66
73
|
"tool_calls": message.tool_calls,
|