agno 2.3.2__py3-none-any.whl → 2.3.3__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 +513 -185
- agno/compression/__init__.py +3 -0
- agno/compression/manager.py +176 -0
- agno/db/dynamo/dynamo.py +11 -0
- agno/db/firestore/firestore.py +5 -1
- agno/db/gcs_json/gcs_json_db.py +5 -2
- agno/db/in_memory/in_memory_db.py +5 -2
- agno/db/json/json_db.py +5 -1
- agno/db/migrations/manager.py +4 -4
- agno/db/mongo/async_mongo.py +158 -34
- agno/db/mongo/mongo.py +6 -2
- agno/db/mysql/mysql.py +48 -54
- agno/db/postgres/async_postgres.py +61 -51
- agno/db/postgres/postgres.py +42 -50
- agno/db/redis/redis.py +5 -0
- agno/db/redis/utils.py +5 -5
- agno/db/singlestore/singlestore.py +99 -108
- agno/db/sqlite/async_sqlite.py +29 -27
- agno/db/sqlite/sqlite.py +30 -26
- agno/knowledge/reader/pdf_reader.py +2 -2
- agno/knowledge/reader/tavily_reader.py +0 -1
- agno/memory/__init__.py +14 -1
- agno/memory/manager.py +217 -4
- agno/memory/strategies/__init__.py +15 -0
- agno/memory/strategies/base.py +67 -0
- agno/memory/strategies/summarize.py +196 -0
- agno/memory/strategies/types.py +37 -0
- agno/models/anthropic/claude.py +84 -80
- agno/models/aws/bedrock.py +38 -16
- agno/models/aws/claude.py +97 -277
- agno/models/azure/ai_foundry.py +8 -4
- agno/models/base.py +101 -14
- agno/models/cerebras/cerebras.py +18 -7
- agno/models/cerebras/cerebras_openai.py +4 -2
- agno/models/cohere/chat.py +8 -4
- agno/models/google/gemini.py +578 -20
- agno/models/groq/groq.py +18 -5
- agno/models/huggingface/huggingface.py +17 -6
- agno/models/ibm/watsonx.py +16 -6
- agno/models/litellm/chat.py +17 -7
- agno/models/message.py +19 -5
- agno/models/meta/llama.py +20 -4
- agno/models/mistral/mistral.py +8 -4
- agno/models/ollama/chat.py +17 -6
- agno/models/openai/chat.py +17 -6
- agno/models/openai/responses.py +23 -9
- agno/models/vertexai/claude.py +99 -5
- agno/os/interfaces/agui/router.py +1 -0
- agno/os/interfaces/agui/utils.py +97 -57
- agno/os/router.py +16 -0
- agno/os/routers/memory/memory.py +143 -0
- agno/os/routers/memory/schemas.py +26 -0
- agno/os/schema.py +21 -6
- agno/os/utils.py +134 -10
- agno/run/base.py +2 -1
- agno/run/workflow.py +1 -1
- agno/team/team.py +565 -219
- agno/tools/mcp/mcp.py +1 -1
- agno/utils/agent.py +119 -1
- agno/utils/models/ai_foundry.py +9 -2
- agno/utils/models/claude.py +12 -5
- 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 +37 -2
- agno/utils/print_response/team.py +52 -0
- agno/utils/tokens.py +41 -0
- agno/workflow/types.py +2 -2
- {agno-2.3.2.dist-info → agno-2.3.3.dist-info}/METADATA +45 -40
- {agno-2.3.2.dist-info → agno-2.3.3.dist-info}/RECORD +73 -66
- {agno-2.3.2.dist-info → agno-2.3.3.dist-info}/WHEEL +0 -0
- {agno-2.3.2.dist-info → agno-2.3.3.dist-info}/licenses/LICENSE +0 -0
- {agno-2.3.2.dist-info → agno-2.3.3.dist-info}/top_level.txt +0 -0
agno/tools/mcp/mcp.py
CHANGED
|
@@ -43,7 +43,7 @@ class MCPTools(Toolkit):
|
|
|
43
43
|
include_tools: Optional[list[str]] = None,
|
|
44
44
|
exclude_tools: Optional[list[str]] = None,
|
|
45
45
|
refresh_connection: bool = False,
|
|
46
|
-
tool_name_prefix: Optional[str] =
|
|
46
|
+
tool_name_prefix: Optional[str] = None,
|
|
47
47
|
**kwargs,
|
|
48
48
|
):
|
|
49
49
|
"""
|
agno/utils/agent.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
from asyncio import Future, Task
|
|
2
|
-
from typing import TYPE_CHECKING, Any, AsyncIterator, Dict, Iterator, List, Optional, Sequence, Union
|
|
2
|
+
from typing import TYPE_CHECKING, Any, AsyncIterator, Callable, Dict, Iterator, List, Optional, Sequence, Union
|
|
3
3
|
|
|
4
4
|
from agno.media import Audio, File, Image, Video
|
|
5
5
|
from agno.models.message import Message
|
|
6
6
|
from agno.models.metrics import Metrics
|
|
7
7
|
from agno.models.response import ModelResponse
|
|
8
|
+
from agno.run import RunContext
|
|
8
9
|
from agno.run.agent import RunEvent, RunInput, RunOutput, RunOutputEvent
|
|
9
10
|
from agno.run.team import RunOutputEvent as TeamRunOutputEvent
|
|
10
11
|
from agno.run.team import TeamRunOutput
|
|
@@ -818,3 +819,120 @@ async def aget_chat_history_util(entity: Union["Agent", "Team"], session_id: str
|
|
|
818
819
|
raise Exception("Session not found")
|
|
819
820
|
|
|
820
821
|
return session.get_chat_history() # type: ignore
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def execute_instructions(
|
|
825
|
+
instructions: Callable,
|
|
826
|
+
agent: Optional[Union["Agent", "Team"]] = None,
|
|
827
|
+
team: Optional["Team"] = None,
|
|
828
|
+
session_state: Optional[Dict[str, Any]] = None,
|
|
829
|
+
run_context: Optional[RunContext] = None,
|
|
830
|
+
) -> Union[str, List[str]]:
|
|
831
|
+
"""Execute the instructions function."""
|
|
832
|
+
import inspect
|
|
833
|
+
|
|
834
|
+
signature = inspect.signature(instructions)
|
|
835
|
+
instruction_args: Dict[str, Any] = {}
|
|
836
|
+
|
|
837
|
+
# Check for agent parameter
|
|
838
|
+
if "agent" in signature.parameters:
|
|
839
|
+
instruction_args["agent"] = agent
|
|
840
|
+
|
|
841
|
+
if "team" in signature.parameters:
|
|
842
|
+
instruction_args["team"] = team
|
|
843
|
+
|
|
844
|
+
# Check for session_state parameter
|
|
845
|
+
if "session_state" in signature.parameters:
|
|
846
|
+
instruction_args["session_state"] = session_state or {}
|
|
847
|
+
|
|
848
|
+
# Check for run_context parameter
|
|
849
|
+
if "run_context" in signature.parameters:
|
|
850
|
+
instruction_args["run_context"] = run_context or None
|
|
851
|
+
|
|
852
|
+
# Run the instructions function, await if it's awaitable, otherwise run directly (in thread)
|
|
853
|
+
if inspect.iscoroutinefunction(instructions):
|
|
854
|
+
raise Exception("Instructions function is async, use `agent.arun()` instead")
|
|
855
|
+
|
|
856
|
+
# Run the instructions function
|
|
857
|
+
return instructions(**instruction_args)
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
def execute_system_message(
|
|
861
|
+
system_message: Callable,
|
|
862
|
+
agent: Optional[Union["Agent", "Team"]] = None,
|
|
863
|
+
team: Optional["Team"] = None,
|
|
864
|
+
session_state: Optional[Dict[str, Any]] = None,
|
|
865
|
+
run_context: Optional[RunContext] = None,
|
|
866
|
+
) -> str:
|
|
867
|
+
"""Execute the system message function."""
|
|
868
|
+
import inspect
|
|
869
|
+
|
|
870
|
+
signature = inspect.signature(system_message)
|
|
871
|
+
system_message_args: Dict[str, Any] = {}
|
|
872
|
+
|
|
873
|
+
# Check for agent parameter
|
|
874
|
+
if "agent" in signature.parameters:
|
|
875
|
+
system_message_args["agent"] = agent
|
|
876
|
+
if "team" in signature.parameters:
|
|
877
|
+
system_message_args["team"] = team
|
|
878
|
+
if inspect.iscoroutinefunction(system_message):
|
|
879
|
+
raise ValueError("System message function is async, use `agent.arun()` instead")
|
|
880
|
+
|
|
881
|
+
return system_message(**system_message_args)
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
async def aexecute_instructions(
|
|
885
|
+
instructions: Callable,
|
|
886
|
+
agent: Optional[Union["Agent", "Team"]] = None,
|
|
887
|
+
team: Optional["Team"] = None,
|
|
888
|
+
session_state: Optional[Dict[str, Any]] = None,
|
|
889
|
+
run_context: Optional[RunContext] = None,
|
|
890
|
+
) -> Union[str, List[str]]:
|
|
891
|
+
"""Execute the instructions function."""
|
|
892
|
+
import inspect
|
|
893
|
+
|
|
894
|
+
signature = inspect.signature(instructions)
|
|
895
|
+
instruction_args: Dict[str, Any] = {}
|
|
896
|
+
|
|
897
|
+
# Check for agent parameter
|
|
898
|
+
if "agent" in signature.parameters:
|
|
899
|
+
instruction_args["agent"] = agent
|
|
900
|
+
if "team" in signature.parameters:
|
|
901
|
+
instruction_args["team"] = team
|
|
902
|
+
|
|
903
|
+
# Check for session_state parameter
|
|
904
|
+
if "session_state" in signature.parameters:
|
|
905
|
+
instruction_args["session_state"] = session_state or {}
|
|
906
|
+
|
|
907
|
+
# Check for run_context parameter
|
|
908
|
+
if "run_context" in signature.parameters:
|
|
909
|
+
instruction_args["run_context"] = run_context or None
|
|
910
|
+
|
|
911
|
+
if inspect.iscoroutinefunction(instructions):
|
|
912
|
+
return await instructions(**instruction_args)
|
|
913
|
+
else:
|
|
914
|
+
return instructions(**instruction_args)
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
async def aexecute_system_message(
|
|
918
|
+
system_message: Callable,
|
|
919
|
+
agent: Optional[Union["Agent", "Team"]] = None,
|
|
920
|
+
team: Optional["Team"] = None,
|
|
921
|
+
session_state: Optional[Dict[str, Any]] = None,
|
|
922
|
+
run_context: Optional[RunContext] = None,
|
|
923
|
+
) -> str:
|
|
924
|
+
import inspect
|
|
925
|
+
|
|
926
|
+
signature = inspect.signature(system_message)
|
|
927
|
+
system_message_args: Dict[str, Any] = {}
|
|
928
|
+
|
|
929
|
+
# Check for agent parameter
|
|
930
|
+
if "agent" in signature.parameters:
|
|
931
|
+
system_message_args["agent"] = agent
|
|
932
|
+
if "team" in signature.parameters:
|
|
933
|
+
system_message_args["team"] = team
|
|
934
|
+
|
|
935
|
+
if inspect.iscoroutinefunction(system_message):
|
|
936
|
+
return await system_message(**system_message_args)
|
|
937
|
+
else:
|
|
938
|
+
return system_message(**system_message_args)
|
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
|
|
@@ -221,17 +221,20 @@ def _format_file_for_message(file: File) -> Optional[Dict[str, Any]]:
|
|
|
221
221
|
return None
|
|
222
222
|
|
|
223
223
|
|
|
224
|
-
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]:
|
|
225
227
|
"""
|
|
226
228
|
Process the list of messages and separate them into API messages and system messages.
|
|
227
229
|
|
|
228
230
|
Args:
|
|
229
231
|
messages (List[Message]): The list of messages to process.
|
|
232
|
+
compress_tool_results: Whether to compress tool results.
|
|
230
233
|
|
|
231
234
|
Returns:
|
|
232
|
-
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.
|
|
233
236
|
"""
|
|
234
|
-
chat_messages: List[Dict[str, str]] = []
|
|
237
|
+
chat_messages: List[Dict[str, Union[str, list]]] = []
|
|
235
238
|
system_messages: List[str] = []
|
|
236
239
|
|
|
237
240
|
for message in messages:
|
|
@@ -301,11 +304,15 @@ def format_messages(messages: List[Message]) -> Tuple[List[Dict[str, str]], str]
|
|
|
301
304
|
)
|
|
302
305
|
elif message.role == "tool":
|
|
303
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
|
+
|
|
304
311
|
content.append(
|
|
305
312
|
{
|
|
306
313
|
"type": "tool_result",
|
|
307
314
|
"tool_use_id": message.tool_call_id,
|
|
308
|
-
"content": str(
|
|
315
|
+
"content": str(tool_result),
|
|
309
316
|
}
|
|
310
317
|
)
|
|
311
318
|
|
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,
|
agno/utils/models/llama.py
CHANGED
|
@@ -19,13 +19,17 @@ TOOL_CALL_ROLE_MAP = {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def format_message(
|
|
22
|
+
def format_message(
|
|
23
|
+
message: Message, openai_like: bool = False, tool_calls: bool = False, compress_tool_results: bool = False
|
|
24
|
+
) -> Dict[str, Any]:
|
|
23
25
|
"""
|
|
24
26
|
Format a message into the format expected by Llama API.
|
|
25
27
|
|
|
26
28
|
Args:
|
|
27
29
|
message (Message): The message to format.
|
|
28
30
|
openai_like (bool): Whether to format the message as an OpenAI-like message.
|
|
31
|
+
tool_calls (bool): Whether tool calls are present.
|
|
32
|
+
compress_tool_results: Whether to compress tool results.
|
|
29
33
|
|
|
30
34
|
Returns:
|
|
31
35
|
Dict[str, Any]: The formatted message.
|
|
@@ -52,10 +56,13 @@ def format_message(message: Message, openai_like: bool = False, tool_calls: bool
|
|
|
52
56
|
log_warning("Audio input is currently unsupported.")
|
|
53
57
|
|
|
54
58
|
if message.role == "tool":
|
|
59
|
+
# Use compressed content if compression is active
|
|
60
|
+
content = message.get_content(use_compressed_content=compress_tool_results)
|
|
61
|
+
|
|
55
62
|
message_dict = {
|
|
56
63
|
"role": "tool",
|
|
57
64
|
"tool_call_id": message.tool_call_id,
|
|
58
|
-
"content":
|
|
65
|
+
"content": content,
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
if message.role == "assistant":
|
agno/utils/models/mistral.py
CHANGED
|
@@ -48,7 +48,7 @@ def _format_image_for_message(image: Image) -> Optional[ImageURLChunk]:
|
|
|
48
48
|
return None
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
def format_messages(messages: List[Message]) -> List[MistralMessage]:
|
|
51
|
+
def format_messages(messages: List[Message], compress_tool_results: bool = False) -> List[MistralMessage]:
|
|
52
52
|
mistral_messages: List[MistralMessage] = []
|
|
53
53
|
|
|
54
54
|
for message in messages:
|
|
@@ -84,7 +84,9 @@ def format_messages(messages: List[Message]) -> List[MistralMessage]:
|
|
|
84
84
|
elif message.role == "system":
|
|
85
85
|
mistral_message = SystemMessage(role="system", content=message.content)
|
|
86
86
|
elif message.role == "tool":
|
|
87
|
-
|
|
87
|
+
# Get compressed content if compression is active
|
|
88
|
+
tool_content = message.get_content(use_compressed_content=compress_tool_results)
|
|
89
|
+
mistral_message = ToolMessage(name="tool", content=tool_content, tool_call_id=message.tool_call_id)
|
|
88
90
|
else:
|
|
89
91
|
raise ValueError(f"Unknown role: {message.role}")
|
|
90
92
|
|
|
@@ -179,6 +179,7 @@ def print_response_stream(
|
|
|
179
179
|
show_reasoning=show_reasoning,
|
|
180
180
|
show_full_reasoning=show_full_reasoning,
|
|
181
181
|
accumulated_tool_calls=accumulated_tool_calls,
|
|
182
|
+
compression_manager=agent.compression_manager,
|
|
182
183
|
)
|
|
183
184
|
panels.extend(additional_panels)
|
|
184
185
|
if panels:
|
|
@@ -204,6 +205,10 @@ def print_response_stream(
|
|
|
204
205
|
live_log.update(Group(*panels))
|
|
205
206
|
agent.session_summary_manager.summaries_updated = False
|
|
206
207
|
|
|
208
|
+
# Clear compression stats after final display
|
|
209
|
+
if agent.compression_manager is not None:
|
|
210
|
+
agent.compression_manager.stats.clear()
|
|
211
|
+
|
|
207
212
|
response_timer.stop()
|
|
208
213
|
|
|
209
214
|
# Final update to remove the "Thinking..." status
|
|
@@ -366,6 +371,7 @@ async def aprint_response_stream(
|
|
|
366
371
|
show_reasoning=show_reasoning,
|
|
367
372
|
show_full_reasoning=show_full_reasoning,
|
|
368
373
|
accumulated_tool_calls=accumulated_tool_calls,
|
|
374
|
+
compression_manager=agent.compression_manager,
|
|
369
375
|
)
|
|
370
376
|
panels.extend(additional_panels)
|
|
371
377
|
if panels:
|
|
@@ -391,6 +397,10 @@ async def aprint_response_stream(
|
|
|
391
397
|
live_log.update(Group(*panels))
|
|
392
398
|
agent.session_summary_manager.summaries_updated = False
|
|
393
399
|
|
|
400
|
+
# Clear compression stats after final display
|
|
401
|
+
if agent.compression_manager is not None:
|
|
402
|
+
agent.compression_manager.stats.clear()
|
|
403
|
+
|
|
394
404
|
response_timer.stop()
|
|
395
405
|
|
|
396
406
|
# Final update to remove the "Thinking..." status
|
|
@@ -407,6 +417,7 @@ def build_panels_stream(
|
|
|
407
417
|
show_reasoning: bool = True,
|
|
408
418
|
show_full_reasoning: bool = False,
|
|
409
419
|
accumulated_tool_calls: Optional[List] = None,
|
|
420
|
+
compression_manager: Optional[Any] = None,
|
|
410
421
|
):
|
|
411
422
|
panels = []
|
|
412
423
|
|
|
@@ -447,8 +458,18 @@ def build_panels_stream(
|
|
|
447
458
|
for formatted_tool_call in formatted_tool_calls:
|
|
448
459
|
tool_calls_content.append(f"• {formatted_tool_call}\n")
|
|
449
460
|
|
|
461
|
+
tool_calls_text = tool_calls_content.plain.rstrip()
|
|
462
|
+
|
|
463
|
+
# Add compression stats if available (don't clear - caller will clear after final display)
|
|
464
|
+
if compression_manager is not None and compression_manager.stats:
|
|
465
|
+
stats = compression_manager.stats
|
|
466
|
+
saved = stats.get("original_size", 0) - stats.get("compressed_size", 0)
|
|
467
|
+
orig = stats.get("original_size", 1)
|
|
468
|
+
if stats.get("messages_compressed", 0) > 0:
|
|
469
|
+
tool_calls_text += f"\n\nTool results compressed: {stats.get('messages_compressed', 0)} | Saved: {saved:,} chars ({saved / orig * 100:.0f}%)"
|
|
470
|
+
|
|
450
471
|
tool_calls_panel = create_panel(
|
|
451
|
-
content=
|
|
472
|
+
content=tool_calls_text,
|
|
452
473
|
title="Tool Calls",
|
|
453
474
|
border_style="yellow",
|
|
454
475
|
)
|
|
@@ -589,6 +610,7 @@ def print_response(
|
|
|
589
610
|
show_full_reasoning=show_full_reasoning,
|
|
590
611
|
tags_to_include_in_markdown=tags_to_include_in_markdown,
|
|
591
612
|
markdown=markdown,
|
|
613
|
+
compression_manager=agent.compression_manager,
|
|
592
614
|
)
|
|
593
615
|
panels.extend(additional_panels)
|
|
594
616
|
|
|
@@ -721,6 +743,7 @@ async def aprint_response(
|
|
|
721
743
|
show_full_reasoning=show_full_reasoning,
|
|
722
744
|
tags_to_include_in_markdown=tags_to_include_in_markdown,
|
|
723
745
|
markdown=markdown,
|
|
746
|
+
compression_manager=agent.compression_manager,
|
|
724
747
|
)
|
|
725
748
|
panels.extend(additional_panels)
|
|
726
749
|
|
|
@@ -757,6 +780,7 @@ def build_panels(
|
|
|
757
780
|
show_full_reasoning: bool = False,
|
|
758
781
|
tags_to_include_in_markdown: Optional[Set[str]] = None,
|
|
759
782
|
markdown: bool = False,
|
|
783
|
+
compression_manager: Optional[Any] = None,
|
|
760
784
|
):
|
|
761
785
|
panels = []
|
|
762
786
|
|
|
@@ -808,8 +832,19 @@ def build_panels(
|
|
|
808
832
|
for formatted_tool_call in formatted_tool_calls:
|
|
809
833
|
tool_calls_content.append(f"• {formatted_tool_call}\n")
|
|
810
834
|
|
|
835
|
+
tool_calls_text = tool_calls_content.plain.rstrip()
|
|
836
|
+
|
|
837
|
+
# Add compression stats if available
|
|
838
|
+
if compression_manager is not None and compression_manager.stats:
|
|
839
|
+
stats = compression_manager.stats
|
|
840
|
+
saved = stats.get("original_size", 0) - stats.get("compressed_size", 0)
|
|
841
|
+
orig = stats.get("original_size", 1)
|
|
842
|
+
if stats.get("messages_compressed", 0) > 0:
|
|
843
|
+
tool_calls_text += f"\n\nTool results compressed: {stats.get('messages_compressed', 0)} | Saved: {saved:,} chars ({saved / orig * 100:.0f}%)"
|
|
844
|
+
compression_manager.stats.clear()
|
|
845
|
+
|
|
811
846
|
tool_calls_panel = create_panel(
|
|
812
|
-
content=
|
|
847
|
+
content=tool_calls_text,
|
|
813
848
|
title="Tool Calls",
|
|
814
849
|
border_style="yellow",
|
|
815
850
|
)
|
|
@@ -260,6 +260,15 @@ def print_response(
|
|
|
260
260
|
# Join with blank lines between items
|
|
261
261
|
tool_calls_text = "\n\n".join(lines)
|
|
262
262
|
|
|
263
|
+
# Add compression stats at end of tool calls
|
|
264
|
+
if team.compression_manager is not None and team.compression_manager.stats:
|
|
265
|
+
stats = team.compression_manager.stats
|
|
266
|
+
saved = stats.get("original_size", 0) - stats.get("compressed_size", 0)
|
|
267
|
+
orig = stats.get("original_size", 1)
|
|
268
|
+
if stats.get("messages_compressed", 0) > 0:
|
|
269
|
+
tool_calls_text += f"\n\nTool results compressed: {stats.get('messages_compressed', 0)} | Saved: {saved:,} chars ({saved / orig * 100:.0f}%)"
|
|
270
|
+
team.compression_manager.stats.clear()
|
|
271
|
+
|
|
263
272
|
team_tool_calls_panel = create_panel(
|
|
264
273
|
content=tool_calls_text,
|
|
265
274
|
title="Team Tool Calls",
|
|
@@ -613,6 +622,14 @@ def print_response_stream(
|
|
|
613
622
|
# Join with blank lines between items
|
|
614
623
|
tool_calls_text = "\n\n".join(lines)
|
|
615
624
|
|
|
625
|
+
# Add compression stats if available (don't clear - will be cleared in final_panels)
|
|
626
|
+
if team.compression_manager is not None and team.compression_manager.stats:
|
|
627
|
+
stats = team.compression_manager.stats
|
|
628
|
+
saved = stats.get("original_size", 0) - stats.get("compressed_size", 0)
|
|
629
|
+
orig = stats.get("original_size", 1)
|
|
630
|
+
if stats.get("messages_compressed", 0) > 0:
|
|
631
|
+
tool_calls_text += f"\n\nTool results compressed: {stats.get('messages_compressed', 0)} | Saved: {saved:,} chars ({saved / orig * 100:.0f}%)"
|
|
632
|
+
|
|
616
633
|
team_tool_calls_panel = create_panel(
|
|
617
634
|
content=tool_calls_text,
|
|
618
635
|
title="Team Tool Calls",
|
|
@@ -815,6 +832,15 @@ def print_response_stream(
|
|
|
815
832
|
|
|
816
833
|
tool_calls_text = "\n\n".join(lines)
|
|
817
834
|
|
|
835
|
+
# Add compression stats at end of tool calls
|
|
836
|
+
if team.compression_manager is not None and team.compression_manager.stats:
|
|
837
|
+
stats = team.compression_manager.stats
|
|
838
|
+
saved = stats.get("original_size", 0) - stats.get("compressed_size", 0)
|
|
839
|
+
orig = stats.get("original_size", 1)
|
|
840
|
+
if stats.get("messages_compressed", 0) > 0:
|
|
841
|
+
tool_calls_text += f"\n\nTool results compressed: {stats.get('messages_compressed', 0)} | Saved: {saved:,} chars ({saved / orig * 100:.0f}%)"
|
|
842
|
+
team.compression_manager.stats.clear()
|
|
843
|
+
|
|
818
844
|
team_tool_calls_panel = create_panel(
|
|
819
845
|
content=tool_calls_text,
|
|
820
846
|
title="Team Tool Calls",
|
|
@@ -1095,6 +1121,15 @@ async def aprint_response(
|
|
|
1095
1121
|
|
|
1096
1122
|
tool_calls_text = "\n\n".join(lines)
|
|
1097
1123
|
|
|
1124
|
+
# Add compression stats at end of tool calls
|
|
1125
|
+
if team.compression_manager is not None and team.compression_manager.stats:
|
|
1126
|
+
stats = team.compression_manager.stats
|
|
1127
|
+
saved = stats.get("original_size", 0) - stats.get("compressed_size", 0)
|
|
1128
|
+
orig = stats.get("original_size", 1)
|
|
1129
|
+
if stats.get("messages_compressed", 0) > 0:
|
|
1130
|
+
tool_calls_text += f"\n\nTool results compressed: {stats.get('messages_compressed', 0)} | Saved: {saved:,} chars ({saved / orig * 100:.0f}%)"
|
|
1131
|
+
team.compression_manager.stats.clear()
|
|
1132
|
+
|
|
1098
1133
|
team_tool_calls_panel = create_panel(
|
|
1099
1134
|
content=tool_calls_text,
|
|
1100
1135
|
title="Team Tool Calls",
|
|
@@ -1446,6 +1481,14 @@ async def aprint_response_stream(
|
|
|
1446
1481
|
# Join with blank lines between items
|
|
1447
1482
|
tool_calls_text = "\n\n".join(lines)
|
|
1448
1483
|
|
|
1484
|
+
# Add compression stats if available (don't clear - will be cleared in final_panels)
|
|
1485
|
+
if team.compression_manager is not None and team.compression_manager.stats:
|
|
1486
|
+
stats = team.compression_manager.stats
|
|
1487
|
+
saved = stats.get("original_size", 0) - stats.get("compressed_size", 0)
|
|
1488
|
+
orig = stats.get("original_size", 1)
|
|
1489
|
+
if stats.get("messages_compressed", 0) > 0:
|
|
1490
|
+
tool_calls_text += f"\n\nTool results compressed: {stats.get('messages_compressed', 0)} | Saved: {saved:,} chars ({saved / orig * 100:.0f}%)"
|
|
1491
|
+
|
|
1449
1492
|
team_tool_calls_panel = create_panel(
|
|
1450
1493
|
content=tool_calls_text,
|
|
1451
1494
|
title="Team Tool Calls",
|
|
@@ -1666,6 +1709,15 @@ async def aprint_response_stream(
|
|
|
1666
1709
|
|
|
1667
1710
|
tool_calls_text = "\n\n".join(lines)
|
|
1668
1711
|
|
|
1712
|
+
# Add compression stats at end of tool calls
|
|
1713
|
+
if team.compression_manager is not None and team.compression_manager.stats:
|
|
1714
|
+
stats = team.compression_manager.stats
|
|
1715
|
+
saved = stats.get("original_size", 0) - stats.get("compressed_size", 0)
|
|
1716
|
+
orig = stats.get("original_size", 1)
|
|
1717
|
+
if stats.get("messages_compressed", 0) > 0:
|
|
1718
|
+
tool_calls_text += f"\n\nTool results compressed: {stats.get('messages_compressed', 0)} | Saved: {saved:,} chars ({saved / orig * 100:.0f}%)"
|
|
1719
|
+
team.compression_manager.stats.clear()
|
|
1720
|
+
|
|
1669
1721
|
team_tool_calls_panel = create_panel(
|
|
1670
1722
|
content=tool_calls_text,
|
|
1671
1723
|
title="Team Tool Calls",
|
agno/utils/tokens.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Token counting utilities for text processing."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def count_tokens(text: str) -> int:
|
|
5
|
+
"""Count tokens in text using tiktoken.
|
|
6
|
+
|
|
7
|
+
Uses cl100k_base encoding (compatible with GPT-4, GPT-4o, GPT-3.5-turbo).
|
|
8
|
+
Falls back to character-based estimation if tiktoken is not available.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
text: The text string to count tokens for.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
Total token count for the text.
|
|
15
|
+
|
|
16
|
+
Examples:
|
|
17
|
+
>>> count_tokens("Hello world")
|
|
18
|
+
2
|
|
19
|
+
>>> count_tokens("")
|
|
20
|
+
0
|
|
21
|
+
"""
|
|
22
|
+
try:
|
|
23
|
+
import tiktoken
|
|
24
|
+
|
|
25
|
+
# Use cl100k_base encoding (GPT-4, GPT-4o, GPT-3.5-turbo)
|
|
26
|
+
encoding = tiktoken.get_encoding("cl100k_base")
|
|
27
|
+
tokens = encoding.encode(text)
|
|
28
|
+
return len(tokens)
|
|
29
|
+
except ImportError:
|
|
30
|
+
from agno.utils.log import log_warning
|
|
31
|
+
|
|
32
|
+
log_warning(
|
|
33
|
+
"tiktoken not installed. You can install with `pip install -U tiktoken`. Using character-based estimation."
|
|
34
|
+
)
|
|
35
|
+
# Fallback: rough estimation (1 token H 4 characters)
|
|
36
|
+
return len(text) // 4
|
|
37
|
+
except Exception as e:
|
|
38
|
+
from agno.utils.log import log_warning
|
|
39
|
+
|
|
40
|
+
log_warning(f"Error counting tokens: {e}. Using character-based estimation.")
|
|
41
|
+
return len(text) // 4
|
agno/workflow/types.py
CHANGED
|
@@ -209,7 +209,7 @@ class StepInput:
|
|
|
209
209
|
input_dict: Optional[Union[str, Dict[str, Any], List[Any]]] = None
|
|
210
210
|
if self.input is not None:
|
|
211
211
|
if isinstance(self.input, BaseModel):
|
|
212
|
-
input_dict = self.input.model_dump(exclude_none=True)
|
|
212
|
+
input_dict = self.input.model_dump(exclude_none=True, mode="json")
|
|
213
213
|
elif isinstance(self.input, (dict, list)):
|
|
214
214
|
input_dict = self.input
|
|
215
215
|
else:
|
|
@@ -281,7 +281,7 @@ class StepOutput:
|
|
|
281
281
|
content_dict: Optional[Union[str, Dict[str, Any], List[Any]]] = None
|
|
282
282
|
if self.content is not None:
|
|
283
283
|
if isinstance(self.content, BaseModel):
|
|
284
|
-
content_dict = self.content.model_dump(exclude_none=True)
|
|
284
|
+
content_dict = self.content.model_dump(exclude_none=True, mode="json")
|
|
285
285
|
elif isinstance(self.content, (dict, list)):
|
|
286
286
|
content_dict = self.content
|
|
287
287
|
else:
|