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/api/api.py
CHANGED
|
@@ -19,6 +19,7 @@ class Api:
|
|
|
19
19
|
base_url=agno_api_settings.api_url,
|
|
20
20
|
headers=self.headers,
|
|
21
21
|
timeout=60,
|
|
22
|
+
http2=True,
|
|
22
23
|
)
|
|
23
24
|
|
|
24
25
|
def AsyncClient(self) -> HttpxAsyncClient:
|
|
@@ -26,6 +27,7 @@ class Api:
|
|
|
26
27
|
base_url=agno_api_settings.api_url,
|
|
27
28
|
headers=self.headers,
|
|
28
29
|
timeout=60,
|
|
30
|
+
http2=True,
|
|
29
31
|
)
|
|
30
32
|
|
|
31
33
|
|
agno/api/os.py
CHANGED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from textwrap import dedent
|
|
4
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from agno.models.base import Model
|
|
9
|
+
from agno.models.message import Message
|
|
10
|
+
from agno.models.utils import get_model
|
|
11
|
+
from agno.utils.log import log_error, log_info, log_warning
|
|
12
|
+
|
|
13
|
+
DEFAULT_COMPRESSION_PROMPT = dedent("""\
|
|
14
|
+
You are compressing tool call results to save context space while preserving critical information.
|
|
15
|
+
|
|
16
|
+
Your goal: Extract only the essential information from the tool output.
|
|
17
|
+
|
|
18
|
+
ALWAYS PRESERVE:
|
|
19
|
+
• Specific facts: numbers, statistics, amounts, prices, quantities, metrics
|
|
20
|
+
• Temporal data: dates, times, timestamps (use short format: "Oct 21 2025")
|
|
21
|
+
• Entities: people, companies, products, locations, organizations
|
|
22
|
+
• Identifiers: URLs, IDs, codes, technical identifiers, versions
|
|
23
|
+
• Key quotes, citations, sources (if relevant to agent's task)
|
|
24
|
+
|
|
25
|
+
COMPRESS TO ESSENTIALS:
|
|
26
|
+
• Descriptions: keep only key attributes
|
|
27
|
+
• Explanations: distill to core insight
|
|
28
|
+
• Lists: focus on most relevant items based on agent context
|
|
29
|
+
• Background: minimal context only if critical
|
|
30
|
+
|
|
31
|
+
REMOVE ENTIRELY:
|
|
32
|
+
• Introductions, conclusions, transitions
|
|
33
|
+
• Hedging language ("might", "possibly", "appears to")
|
|
34
|
+
• Meta-commentary ("According to", "The results show")
|
|
35
|
+
• Formatting artifacts (markdown, HTML, JSON structure)
|
|
36
|
+
• Redundant or repetitive information
|
|
37
|
+
• Generic background not relevant to agent's task
|
|
38
|
+
• Promotional language, filler words
|
|
39
|
+
|
|
40
|
+
EXAMPLE:
|
|
41
|
+
Input: "According to recent market analysis and industry reports, OpenAI has made several significant announcements in the technology sector. The company revealed ChatGPT Atlas on October 21, 2025, which represents a new AI-powered browser application that has been specifically designed for macOS users. This browser is strategically positioned to compete with traditional search engines in the market. Additionally, on October 6, 2025, OpenAI launched Apps in ChatGPT, which includes a comprehensive software development kit (SDK) for developers. The company has also announced several initial strategic partners who will be integrating with this new feature, including well-known companies such as Spotify, the popular music streaming service, Zillow, which is a real estate marketplace platform, and Canva, a graphic design platform."
|
|
42
|
+
|
|
43
|
+
Output: "OpenAI - Oct 21 2025: ChatGPT Atlas (AI browser, macOS, search competitor); Oct 6 2025: Apps in ChatGPT + SDK; Partners: Spotify, Zillow, Canva"
|
|
44
|
+
|
|
45
|
+
Be concise while retaining all critical facts.
|
|
46
|
+
""")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class CompressionManager:
|
|
51
|
+
model: Optional[Model] = None # model used for compression
|
|
52
|
+
compress_tool_results: bool = True
|
|
53
|
+
compress_tool_results_limit: Optional[int] = None
|
|
54
|
+
compress_token_limit: Optional[int] = None
|
|
55
|
+
compress_tool_call_instructions: Optional[str] = None
|
|
56
|
+
|
|
57
|
+
stats: Dict[str, Any] = field(default_factory=dict)
|
|
58
|
+
|
|
59
|
+
def __post_init__(self):
|
|
60
|
+
if self.compress_tool_results_limit is None and self.compress_token_limit is None:
|
|
61
|
+
self.compress_tool_results_limit = 3
|
|
62
|
+
|
|
63
|
+
def _is_tool_result_message(self, msg: Message) -> bool:
|
|
64
|
+
return msg.role == "tool"
|
|
65
|
+
|
|
66
|
+
def should_compress(
|
|
67
|
+
self,
|
|
68
|
+
messages: List[Message],
|
|
69
|
+
tools: Optional[List] = None,
|
|
70
|
+
model: Optional[Model] = None,
|
|
71
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
72
|
+
) -> bool:
|
|
73
|
+
"""Check if tool results should be compressed.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
messages: List of messages to check.
|
|
77
|
+
tools: List of tools for token counting.
|
|
78
|
+
model: The Agent / Team model.
|
|
79
|
+
response_format: Output schema for accurate token counting.
|
|
80
|
+
"""
|
|
81
|
+
if not self.compress_tool_results:
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
# Token-based threshold check
|
|
85
|
+
if self.compress_token_limit is not None and model is not None:
|
|
86
|
+
tokens = model.count_tokens(messages, tools, response_format)
|
|
87
|
+
if tokens >= self.compress_token_limit:
|
|
88
|
+
log_info(f"Token limit hit: {tokens} >= {self.compress_token_limit}")
|
|
89
|
+
return True
|
|
90
|
+
|
|
91
|
+
# Count-based threshold check
|
|
92
|
+
if self.compress_tool_results_limit is not None:
|
|
93
|
+
uncompressed_tools_count = len(
|
|
94
|
+
[m for m in messages if self._is_tool_result_message(m) and m.compressed_content is None]
|
|
95
|
+
)
|
|
96
|
+
if uncompressed_tools_count >= self.compress_tool_results_limit:
|
|
97
|
+
log_info(f"Tool count limit hit: {uncompressed_tools_count} >= {self.compress_tool_results_limit}")
|
|
98
|
+
return True
|
|
99
|
+
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
def _compress_tool_result(self, tool_result: Message) -> Optional[str]:
|
|
103
|
+
if not tool_result:
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
tool_content = f"Tool: {tool_result.tool_name or 'unknown'}\n{tool_result.content}"
|
|
107
|
+
|
|
108
|
+
self.model = get_model(self.model)
|
|
109
|
+
if not self.model:
|
|
110
|
+
log_warning("No compression model available")
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
compression_prompt = self.compress_tool_call_instructions or DEFAULT_COMPRESSION_PROMPT
|
|
114
|
+
compression_message = "Tool Results to Compress: " + tool_content + "\n"
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
response = self.model.response(
|
|
118
|
+
messages=[
|
|
119
|
+
Message(role="system", content=compression_prompt),
|
|
120
|
+
Message(role="user", content=compression_message),
|
|
121
|
+
]
|
|
122
|
+
)
|
|
123
|
+
return response.content
|
|
124
|
+
except Exception as e:
|
|
125
|
+
log_error(f"Error compressing tool result: {e}")
|
|
126
|
+
return tool_content
|
|
127
|
+
|
|
128
|
+
def compress(self, messages: List[Message]) -> None:
|
|
129
|
+
"""Compress uncompressed tool results"""
|
|
130
|
+
if not self.compress_tool_results:
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
uncompressed_tools = [msg for msg in messages if msg.role == "tool" and msg.compressed_content is None]
|
|
134
|
+
|
|
135
|
+
if not uncompressed_tools:
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
# Compress uncompressed tool results
|
|
139
|
+
for tool_msg in uncompressed_tools:
|
|
140
|
+
original_len = len(str(tool_msg.content)) if tool_msg.content else 0
|
|
141
|
+
compressed = self._compress_tool_result(tool_msg)
|
|
142
|
+
if compressed:
|
|
143
|
+
tool_msg.compressed_content = compressed
|
|
144
|
+
# Count actual tool results (Gemini combines multiple in one message)
|
|
145
|
+
tool_results_count = len(tool_msg.tool_calls) if tool_msg.tool_calls else 1
|
|
146
|
+
self.stats["tool_results_compressed"] = (
|
|
147
|
+
self.stats.get("tool_results_compressed", 0) + tool_results_count
|
|
148
|
+
)
|
|
149
|
+
self.stats["original_size"] = self.stats.get("original_size", 0) + original_len
|
|
150
|
+
self.stats["compressed_size"] = self.stats.get("compressed_size", 0) + len(compressed)
|
|
151
|
+
else:
|
|
152
|
+
log_warning(f"Compression failed for {tool_msg.tool_name}")
|
|
153
|
+
|
|
154
|
+
# * Async methods *#
|
|
155
|
+
async def ashould_compress(
|
|
156
|
+
self,
|
|
157
|
+
messages: List[Message],
|
|
158
|
+
tools: Optional[List] = None,
|
|
159
|
+
model: Optional[Model] = None,
|
|
160
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
161
|
+
) -> bool:
|
|
162
|
+
"""Async check if tool results should be compressed.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
messages: List of messages to check.
|
|
166
|
+
tools: List of tools for token counting.
|
|
167
|
+
model: The Agent / Team model.
|
|
168
|
+
response_format: Output schema for accurate token counting.
|
|
169
|
+
"""
|
|
170
|
+
if not self.compress_tool_results:
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
# Token-based threshold check
|
|
174
|
+
if self.compress_token_limit is not None and model is not None:
|
|
175
|
+
tokens = await model.acount_tokens(messages, tools, response_format)
|
|
176
|
+
if tokens >= self.compress_token_limit:
|
|
177
|
+
log_info(f"Token limit hit: {tokens} >= {self.compress_token_limit}")
|
|
178
|
+
return True
|
|
179
|
+
|
|
180
|
+
# Count-based threshold check
|
|
181
|
+
if self.compress_tool_results_limit is not None:
|
|
182
|
+
uncompressed_tools_count = len(
|
|
183
|
+
[m for m in messages if self._is_tool_result_message(m) and m.compressed_content is None]
|
|
184
|
+
)
|
|
185
|
+
if uncompressed_tools_count >= self.compress_tool_results_limit:
|
|
186
|
+
log_info(f"Tool count limit hit: {uncompressed_tools_count} >= {self.compress_tool_results_limit}")
|
|
187
|
+
return True
|
|
188
|
+
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
async def _acompress_tool_result(self, tool_result: Message) -> Optional[str]:
|
|
192
|
+
"""Async compress a single tool result"""
|
|
193
|
+
if not tool_result:
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
tool_content = f"Tool: {tool_result.tool_name or 'unknown'}\n{tool_result.content}"
|
|
197
|
+
|
|
198
|
+
self.model = get_model(self.model)
|
|
199
|
+
if not self.model:
|
|
200
|
+
log_warning("No compression model available")
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
compression_prompt = self.compress_tool_call_instructions or DEFAULT_COMPRESSION_PROMPT
|
|
204
|
+
compression_message = "Tool Results to Compress: " + tool_content + "\n"
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
response = await self.model.aresponse(
|
|
208
|
+
messages=[
|
|
209
|
+
Message(role="system", content=compression_prompt),
|
|
210
|
+
Message(role="user", content=compression_message),
|
|
211
|
+
]
|
|
212
|
+
)
|
|
213
|
+
return response.content
|
|
214
|
+
except Exception as e:
|
|
215
|
+
log_error(f"Error compressing tool result: {e}")
|
|
216
|
+
return tool_content
|
|
217
|
+
|
|
218
|
+
async def acompress(self, messages: List[Message]) -> None:
|
|
219
|
+
"""Async compress uncompressed tool results"""
|
|
220
|
+
if not self.compress_tool_results:
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
uncompressed_tools = [msg for msg in messages if msg.role == "tool" and msg.compressed_content is None]
|
|
224
|
+
|
|
225
|
+
if not uncompressed_tools:
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
# Track original sizes before compression
|
|
229
|
+
original_sizes = [len(str(msg.content)) if msg.content else 0 for msg in uncompressed_tools]
|
|
230
|
+
|
|
231
|
+
# Parallel compression using asyncio.gather
|
|
232
|
+
tasks = [self._acompress_tool_result(msg) for msg in uncompressed_tools]
|
|
233
|
+
results = await asyncio.gather(*tasks)
|
|
234
|
+
|
|
235
|
+
# Apply results and track stats
|
|
236
|
+
for msg, compressed, original_len in zip(uncompressed_tools, results, original_sizes):
|
|
237
|
+
if compressed:
|
|
238
|
+
msg.compressed_content = compressed
|
|
239
|
+
# Count actual tool results (Gemini combines multiple in one message)
|
|
240
|
+
tool_results_count = len(msg.tool_calls) if msg.tool_calls else 1
|
|
241
|
+
self.stats["tool_results_compressed"] = (
|
|
242
|
+
self.stats.get("tool_results_compressed", 0) + tool_results_count
|
|
243
|
+
)
|
|
244
|
+
self.stats["original_size"] = self.stats.get("original_size", 0) + original_len
|
|
245
|
+
self.stats["compressed_size"] = self.stats.get("compressed_size", 0) + len(compressed)
|
|
246
|
+
else:
|
|
247
|
+
log_warning(f"Compression failed for {msg.tool_name}")
|
agno/culture/__init__.py
ADDED