agno 2.3.1__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.
Files changed (75) hide show
  1. agno/agent/agent.py +514 -186
  2. agno/compression/__init__.py +3 -0
  3. agno/compression/manager.py +176 -0
  4. agno/db/dynamo/dynamo.py +11 -0
  5. agno/db/firestore/firestore.py +5 -1
  6. agno/db/gcs_json/gcs_json_db.py +5 -2
  7. agno/db/in_memory/in_memory_db.py +5 -2
  8. agno/db/json/json_db.py +5 -1
  9. agno/db/migrations/manager.py +4 -4
  10. agno/db/mongo/async_mongo.py +158 -34
  11. agno/db/mongo/mongo.py +6 -2
  12. agno/db/mysql/mysql.py +48 -54
  13. agno/db/postgres/async_postgres.py +61 -51
  14. agno/db/postgres/postgres.py +42 -50
  15. agno/db/redis/redis.py +5 -0
  16. agno/db/redis/utils.py +5 -5
  17. agno/db/schemas/memory.py +7 -5
  18. agno/db/singlestore/singlestore.py +99 -108
  19. agno/db/sqlite/async_sqlite.py +32 -30
  20. agno/db/sqlite/sqlite.py +34 -30
  21. agno/knowledge/reader/pdf_reader.py +2 -2
  22. agno/knowledge/reader/tavily_reader.py +0 -1
  23. agno/memory/__init__.py +14 -1
  24. agno/memory/manager.py +223 -8
  25. agno/memory/strategies/__init__.py +15 -0
  26. agno/memory/strategies/base.py +67 -0
  27. agno/memory/strategies/summarize.py +196 -0
  28. agno/memory/strategies/types.py +37 -0
  29. agno/models/anthropic/claude.py +84 -80
  30. agno/models/aws/bedrock.py +38 -16
  31. agno/models/aws/claude.py +97 -277
  32. agno/models/azure/ai_foundry.py +8 -4
  33. agno/models/base.py +101 -14
  34. agno/models/cerebras/cerebras.py +18 -7
  35. agno/models/cerebras/cerebras_openai.py +4 -2
  36. agno/models/cohere/chat.py +8 -4
  37. agno/models/google/gemini.py +578 -20
  38. agno/models/groq/groq.py +18 -5
  39. agno/models/huggingface/huggingface.py +17 -6
  40. agno/models/ibm/watsonx.py +16 -6
  41. agno/models/litellm/chat.py +17 -7
  42. agno/models/message.py +19 -5
  43. agno/models/meta/llama.py +20 -4
  44. agno/models/mistral/mistral.py +8 -4
  45. agno/models/ollama/chat.py +17 -6
  46. agno/models/openai/chat.py +17 -6
  47. agno/models/openai/responses.py +23 -9
  48. agno/models/vertexai/claude.py +99 -5
  49. agno/os/interfaces/agui/router.py +1 -0
  50. agno/os/interfaces/agui/utils.py +97 -57
  51. agno/os/router.py +16 -1
  52. agno/os/routers/memory/memory.py +146 -0
  53. agno/os/routers/memory/schemas.py +26 -0
  54. agno/os/schema.py +21 -6
  55. agno/os/utils.py +134 -10
  56. agno/run/base.py +2 -1
  57. agno/run/workflow.py +1 -1
  58. agno/team/team.py +571 -225
  59. agno/tools/mcp/mcp.py +1 -1
  60. agno/utils/agent.py +119 -1
  61. agno/utils/dttm.py +33 -0
  62. agno/utils/models/ai_foundry.py +9 -2
  63. agno/utils/models/claude.py +12 -5
  64. agno/utils/models/cohere.py +9 -2
  65. agno/utils/models/llama.py +9 -2
  66. agno/utils/models/mistral.py +4 -2
  67. agno/utils/print_response/agent.py +37 -2
  68. agno/utils/print_response/team.py +52 -0
  69. agno/utils/tokens.py +41 -0
  70. agno/workflow/types.py +2 -2
  71. {agno-2.3.1.dist-info → agno-2.3.3.dist-info}/METADATA +45 -40
  72. {agno-2.3.1.dist-info → agno-2.3.3.dist-info}/RECORD +75 -68
  73. {agno-2.3.1.dist-info → agno-2.3.3.dist-info}/WHEEL +0 -0
  74. {agno-2.3.1.dist-info → agno-2.3.3.dist-info}/licenses/LICENSE +0 -0
  75. {agno-2.3.1.dist-info → agno-2.3.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,196 @@
1
+ """Summarize strategy: Combine all memories into single comprehensive summary."""
2
+
3
+ from textwrap import dedent
4
+ from typing import List
5
+ from uuid import uuid4
6
+
7
+ from agno.db.schemas import UserMemory
8
+ from agno.memory.strategies import MemoryOptimizationStrategy
9
+ from agno.models.base import Model
10
+ from agno.models.message import Message
11
+ from agno.utils.dttm import now_epoch_s
12
+ from agno.utils.log import log_debug
13
+
14
+
15
+ class SummarizeStrategy(MemoryOptimizationStrategy):
16
+ """Combine all memories into single comprehensive summary.
17
+
18
+ This strategy summarizes all memories into one coherent narrative,
19
+ achieving maximum compression by eliminating redundancy. All
20
+ metadata (topics, user_id) is preserved in the summarized memory.
21
+ """
22
+
23
+ def _get_system_prompt(self) -> str:
24
+ """Get system prompt for memory summarization.
25
+
26
+ Returns:
27
+ System prompt string for LLM
28
+ """
29
+ return dedent("""\
30
+ You are a memory compression assistant. Your task is to summarize multiple memories about a user
31
+ into a single comprehensive summary while preserving all key facts.
32
+
33
+ Requirements:
34
+ - Combine related information from all memories
35
+ - Preserve all factual information
36
+ - Remove redundancy and consolidate repeated facts
37
+ - Create a coherent narrative about the user
38
+ - Maintain third-person perspective
39
+ - Do not add information not present in the original memories
40
+
41
+ Return only the summarized memory text, nothing else.\
42
+ """)
43
+
44
+ def optimize(
45
+ self,
46
+ memories: List[UserMemory],
47
+ model: Model,
48
+ ) -> List[UserMemory]:
49
+ """Summarize multiple memories into single comprehensive summary.
50
+
51
+ Args:
52
+ memories: List of UserMemory objects to summarize
53
+ model: Model to use for summarization
54
+
55
+ Returns:
56
+ List containing single summarized UserMemory object
57
+
58
+ Raises:
59
+ ValueError: If memories list is empty or if user_id cannot be determined
60
+ """
61
+ # Validate memories list
62
+ if not memories:
63
+ raise ValueError("No Memories found")
64
+
65
+ # Extract user_id from first memory
66
+ user_id = memories[0].user_id
67
+ if user_id is None:
68
+ raise ValueError("Cannot determine user_id: first memory does not have a valid user_id or is None")
69
+
70
+ # Collect all memory contents
71
+ memory_contents = [mem.memory for mem in memories if mem.memory]
72
+
73
+ # Combine topics - get unique topics from all memories
74
+ all_topics: List[str] = []
75
+ for mem in memories:
76
+ if mem.topics:
77
+ all_topics.extend(mem.topics)
78
+ summarized_topics = list(set(all_topics)) if all_topics else None
79
+
80
+ # Check if agent_id and team_id are consistent
81
+ agent_ids = {mem.agent_id for mem in memories if mem.agent_id}
82
+ summarized_agent_id = list(agent_ids)[0] if len(agent_ids) == 1 else None
83
+
84
+ team_ids = {mem.team_id for mem in memories if mem.team_id}
85
+ summarized_team_id = list(team_ids)[0] if len(team_ids) == 1 else None
86
+
87
+ # Create comprehensive prompt for summarization
88
+ combined_content = "\n\n".join([f"Memory {i + 1}: {content}" for i, content in enumerate(memory_contents)])
89
+
90
+ system_prompt = self._get_system_prompt()
91
+
92
+ messages_for_model = [
93
+ Message(role="system", content=system_prompt),
94
+ Message(role="user", content=f"Summarize these memories into a single summary:\n\n{combined_content}"),
95
+ ]
96
+
97
+ # Generate summarized content
98
+ response = model.response(messages=messages_for_model)
99
+ summarized_content = response.content or " ".join(memory_contents)
100
+
101
+ # Generate new memory_id
102
+ new_memory_id = str(uuid4())
103
+
104
+ # Create summarized memory
105
+ summarized_memory = UserMemory(
106
+ memory_id=new_memory_id,
107
+ memory=summarized_content.strip(),
108
+ topics=summarized_topics,
109
+ user_id=user_id,
110
+ agent_id=summarized_agent_id,
111
+ team_id=summarized_team_id,
112
+ updated_at=now_epoch_s(),
113
+ )
114
+
115
+ log_debug(
116
+ f"Summarized {len(memories)} memories into 1: {self.count_tokens(memories)} -> {self.count_tokens([summarized_memory])} tokens"
117
+ )
118
+
119
+ return [summarized_memory]
120
+
121
+ async def aoptimize(
122
+ self,
123
+ memories: List[UserMemory],
124
+ model: Model,
125
+ ) -> List[UserMemory]:
126
+ """Async version: Summarize multiple memories into single comprehensive summary.
127
+
128
+ Args:
129
+ memories: List of UserMemory objects to summarize
130
+ model: Model to use for summarization
131
+
132
+ Returns:
133
+ List containing single summarized UserMemory object
134
+
135
+ Raises:
136
+ ValueError: If memories list is empty or if user_id cannot be determined
137
+ """
138
+ # Validate memories list
139
+ if not memories:
140
+ raise ValueError("No Memories found")
141
+
142
+ # Extract user_id from first memory
143
+ user_id = memories[0].user_id
144
+ if user_id is None:
145
+ raise ValueError("Cannot determine user_id: first memory does not have a valid user_id or is None")
146
+
147
+ # Collect all memory contents
148
+ memory_contents = [mem.memory for mem in memories if mem.memory]
149
+
150
+ # Combine topics - get unique topics from all memories
151
+ all_topics: List[str] = []
152
+ for mem in memories:
153
+ if mem.topics:
154
+ all_topics.extend(mem.topics)
155
+ summarized_topics = list(set(all_topics)) if all_topics else None
156
+
157
+ # Check if agent_id and team_id are consistent
158
+ agent_ids = {mem.agent_id for mem in memories if mem.agent_id}
159
+ summarized_agent_id = list(agent_ids)[0] if len(agent_ids) == 1 else None
160
+
161
+ team_ids = {mem.team_id for mem in memories if mem.team_id}
162
+ summarized_team_id = list(team_ids)[0] if len(team_ids) == 1 else None
163
+
164
+ # Create comprehensive prompt for summarization
165
+ combined_content = "\n\n".join([f"Memory {i + 1}: {content}" for i, content in enumerate(memory_contents)])
166
+
167
+ system_prompt = self._get_system_prompt()
168
+
169
+ messages_for_model = [
170
+ Message(role="system", content=system_prompt),
171
+ Message(role="user", content=f"Summarize these memories into a single summary:\n\n{combined_content}"),
172
+ ]
173
+
174
+ # Generate summarized content (async)
175
+ response = await model.aresponse(messages=messages_for_model)
176
+ summarized_content = response.content or " ".join(memory_contents)
177
+
178
+ # Generate new memory_id
179
+ new_memory_id = str(uuid4())
180
+
181
+ # Create summarized memory
182
+ summarized_memory = UserMemory(
183
+ memory_id=new_memory_id,
184
+ memory=summarized_content.strip(),
185
+ topics=summarized_topics,
186
+ user_id=user_id,
187
+ agent_id=summarized_agent_id,
188
+ team_id=summarized_team_id,
189
+ updated_at=now_epoch_s(),
190
+ )
191
+
192
+ log_debug(
193
+ f"Summarized {len(memories)} memories into 1: {self.count_tokens(memories)} -> {self.count_tokens([summarized_memory])} tokens"
194
+ )
195
+
196
+ return [summarized_memory]
@@ -0,0 +1,37 @@
1
+ """Memory optimization strategy types and factory."""
2
+
3
+ from enum import Enum
4
+
5
+ from agno.memory.strategies import MemoryOptimizationStrategy
6
+
7
+
8
+ class MemoryOptimizationStrategyType(str, Enum):
9
+ """Enumeration of available memory optimization strategies."""
10
+
11
+ SUMMARIZE = "summarize"
12
+
13
+
14
+ class MemoryOptimizationStrategyFactory:
15
+ """Factory for creating memory optimization strategy instances."""
16
+
17
+ @classmethod
18
+ def create_strategy(cls, strategy_type: MemoryOptimizationStrategyType, **kwargs) -> MemoryOptimizationStrategy:
19
+ """Create an instance of the optimization strategy with given parameters.
20
+
21
+ Args:
22
+ strategy_type: Type of strategy to create
23
+ **kwargs: Additional parameters for strategy initialization
24
+
25
+ Returns:
26
+ MemoryOptimizationStrategy instance
27
+ """
28
+ strategy_map = {
29
+ MemoryOptimizationStrategyType.SUMMARIZE: cls._create_summarize_strategy,
30
+ }
31
+ return strategy_map[strategy_type](**kwargs)
32
+
33
+ @classmethod
34
+ def _create_summarize_strategy(cls, **kwargs) -> MemoryOptimizationStrategy:
35
+ from agno.memory.strategies.summarize import SummarizeStrategy
36
+
37
+ return SummarizeStrategy(**kwargs)
@@ -183,7 +183,7 @@ class Claude(Model):
183
183
  )
184
184
  return False
185
185
 
186
- # Check for legacy model patterns that don't support structured outputs
186
+ # Check for legacy model patterns which don't support structured outputs
187
187
  if self.id.startswith("claude-3-"):
188
188
  return False
189
189
  if self.id.startswith("claude-sonnet-4-") and not self.id.startswith("claude-sonnet-4-5"):
@@ -222,64 +222,6 @@ class Claude(Model):
222
222
 
223
223
  return False
224
224
 
225
- def _has_beta_features(
226
- self,
227
- response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
228
- tools: Optional[List[Dict[str, Any]]] = None,
229
- ) -> bool:
230
- """Check if the model has any Anthropic beta features enabled."""
231
- return (
232
- self.mcp_servers is not None
233
- or self.context_management is not None
234
- or self.skills is not None
235
- or self.betas is not None
236
- or self._using_structured_outputs(response_format, tools)
237
- )
238
-
239
- def get_client(self) -> AnthropicClient:
240
- """
241
- Returns an instance of the Anthropic client.
242
- """
243
- if self.client and not self.client.is_closed():
244
- return self.client
245
-
246
- _client_params = self._get_client_params()
247
- if self.http_client:
248
- if isinstance(self.http_client, httpx.Client):
249
- _client_params["http_client"] = self.http_client
250
- else:
251
- log_warning("http_client is not an instance of httpx.Client. Using default global httpx.Client.")
252
- # Use global sync client when user http_client is invalid
253
- _client_params["http_client"] = get_default_sync_client()
254
- else:
255
- # Use global sync client when no custom http_client is provided
256
- _client_params["http_client"] = get_default_sync_client()
257
- self.client = AnthropicClient(**_client_params)
258
- return self.client
259
-
260
- def get_async_client(self) -> AsyncAnthropicClient:
261
- """
262
- Returns an instance of the async Anthropic client.
263
- """
264
- if self.async_client and not self.async_client.is_closed():
265
- return self.async_client
266
-
267
- _client_params = self._get_client_params()
268
- if self.http_client:
269
- if isinstance(self.http_client, httpx.AsyncClient):
270
- _client_params["http_client"] = self.http_client
271
- else:
272
- log_warning(
273
- "http_client is not an instance of httpx.AsyncClient. Using default global httpx.AsyncClient."
274
- )
275
- # Use global async client when user http_client is invalid
276
- _client_params["http_client"] = get_default_async_client()
277
- else:
278
- # Use global async client when no custom http_client is provided
279
- _client_params["http_client"] = get_default_async_client()
280
- self.async_client = AsyncAnthropicClient(**_client_params)
281
- return self.async_client
282
-
283
225
  def _validate_thinking_support(self) -> None:
284
226
  """
285
227
  Validate that the current model supports extended thinking.
@@ -375,6 +317,81 @@ class Claude(Model):
375
317
 
376
318
  return None
377
319
 
320
+ def _validate_structured_outputs_usage(
321
+ self,
322
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
323
+ tools: Optional[List[Dict[str, Any]]] = None,
324
+ ) -> None:
325
+ """
326
+ Validate that structured outputs are only used with supported models.
327
+
328
+ Raises:
329
+ ValueError: If structured outputs are used with unsupported model
330
+ """
331
+ if not self._using_structured_outputs(response_format, tools):
332
+ return
333
+
334
+ if not self._supports_structured_outputs():
335
+ raise ValueError(f"Model '{self.id}' does not support structured outputs.\n\n")
336
+
337
+ def _has_beta_features(
338
+ self,
339
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
340
+ tools: Optional[List[Dict[str, Any]]] = None,
341
+ ) -> bool:
342
+ """Check if the model has any Anthropic beta features enabled."""
343
+ return (
344
+ self.mcp_servers is not None
345
+ or self.context_management is not None
346
+ or self.skills is not None
347
+ or self.betas is not None
348
+ or self._using_structured_outputs(response_format, tools)
349
+ )
350
+
351
+ def get_client(self) -> AnthropicClient:
352
+ """
353
+ Returns an instance of the Anthropic client.
354
+ """
355
+ if self.client and not self.client.is_closed():
356
+ return self.client
357
+
358
+ _client_params = self._get_client_params()
359
+ if self.http_client:
360
+ if isinstance(self.http_client, httpx.Client):
361
+ _client_params["http_client"] = self.http_client
362
+ else:
363
+ log_warning("http_client is not an instance of httpx.Client. Using default global httpx.Client.")
364
+ # Use global sync client when user http_client is invalid
365
+ _client_params["http_client"] = get_default_sync_client()
366
+ else:
367
+ # Use global sync client when no custom http_client is provided
368
+ _client_params["http_client"] = get_default_sync_client()
369
+ self.client = AnthropicClient(**_client_params)
370
+ return self.client
371
+
372
+ def get_async_client(self) -> AsyncAnthropicClient:
373
+ """
374
+ Returns an instance of the async Anthropic client.
375
+ """
376
+ if self.async_client and not self.async_client.is_closed():
377
+ return self.async_client
378
+
379
+ _client_params = self._get_client_params()
380
+ if self.http_client:
381
+ if isinstance(self.http_client, httpx.AsyncClient):
382
+ _client_params["http_client"] = self.http_client
383
+ else:
384
+ log_warning(
385
+ "http_client is not an instance of httpx.AsyncClient. Using default global httpx.AsyncClient."
386
+ )
387
+ # Use global async client when user http_client is invalid
388
+ _client_params["http_client"] = get_default_async_client()
389
+ else:
390
+ # Use global async client when no custom http_client is provided
391
+ _client_params["http_client"] = get_default_async_client()
392
+ self.async_client = AsyncAnthropicClient(**_client_params)
393
+ return self.async_client
394
+
378
395
  def get_request_params(
379
396
  self,
380
397
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
@@ -427,23 +444,6 @@ class Claude(Model):
427
444
 
428
445
  return _request_params
429
446
 
430
- def _validate_structured_outputs_usage(
431
- self,
432
- response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
433
- tools: Optional[List[Dict[str, Any]]] = None,
434
- ) -> None:
435
- """
436
- Validate that structured outputs are only used with supported models.
437
-
438
- Raises:
439
- ValueError: If structured outputs are used with unsupported model
440
- """
441
- if not self._using_structured_outputs(response_format, tools):
442
- return
443
-
444
- if not self._supports_structured_outputs():
445
- raise ValueError(f"Model '{self.id}' does not support structured outputs.\n\n")
446
-
447
447
  def _prepare_request_kwargs(
448
448
  self,
449
449
  system_message: str,
@@ -507,6 +507,7 @@ class Claude(Model):
507
507
  tools: Optional[List[Dict[str, Any]]] = None,
508
508
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
509
509
  run_response: Optional[RunOutput] = None,
510
+ compress_tool_results: bool = False,
510
511
  ) -> ModelResponse:
511
512
  """
512
513
  Send a request to the Anthropic API to generate a response.
@@ -515,7 +516,7 @@ class Claude(Model):
515
516
  if run_response and run_response.metrics:
516
517
  run_response.metrics.set_time_to_first_token()
517
518
 
518
- chat_messages, system_message = format_messages(messages)
519
+ chat_messages, system_message = format_messages(messages, compress_tool_results=compress_tool_results)
519
520
  request_kwargs = self._prepare_request_kwargs(system_message, tools=tools, response_format=response_format)
520
521
 
521
522
  if self._has_beta_features(response_format=response_format, tools=tools):
@@ -563,6 +564,7 @@ class Claude(Model):
563
564
  tools: Optional[List[Dict[str, Any]]] = None,
564
565
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
565
566
  run_response: Optional[RunOutput] = None,
567
+ compress_tool_results: bool = False,
566
568
  ) -> Any:
567
569
  """
568
570
  Stream a response from the Anthropic API.
@@ -578,7 +580,7 @@ class Claude(Model):
578
580
  RateLimitError: If the API rate limit is exceeded
579
581
  APIStatusError: For other API-related errors
580
582
  """
581
- chat_messages, system_message = format_messages(messages)
583
+ chat_messages, system_message = format_messages(messages, compress_tool_results=compress_tool_results)
582
584
  request_kwargs = self._prepare_request_kwargs(system_message, tools=tools, response_format=response_format)
583
585
 
584
586
  try:
@@ -630,6 +632,7 @@ class Claude(Model):
630
632
  tools: Optional[List[Dict[str, Any]]] = None,
631
633
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
632
634
  run_response: Optional[RunOutput] = None,
635
+ compress_tool_results: bool = False,
633
636
  ) -> ModelResponse:
634
637
  """
635
638
  Send an asynchronous request to the Anthropic API to generate a response.
@@ -638,7 +641,7 @@ class Claude(Model):
638
641
  if run_response and run_response.metrics:
639
642
  run_response.metrics.set_time_to_first_token()
640
643
 
641
- chat_messages, system_message = format_messages(messages)
644
+ chat_messages, system_message = format_messages(messages, compress_tool_results=compress_tool_results)
642
645
  request_kwargs = self._prepare_request_kwargs(system_message, tools=tools, response_format=response_format)
643
646
 
644
647
  # Beta features
@@ -687,6 +690,7 @@ class Claude(Model):
687
690
  tools: Optional[List[Dict[str, Any]]] = None,
688
691
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
689
692
  run_response: Optional[RunOutput] = None,
693
+ compress_tool_results: bool = False,
690
694
  ) -> AsyncIterator[ModelResponse]:
691
695
  """
692
696
  Stream an asynchronous response from the Anthropic API.
@@ -703,7 +707,7 @@ class Claude(Model):
703
707
  if run_response and run_response.metrics:
704
708
  run_response.metrics.set_time_to_first_token()
705
709
 
706
- chat_messages, system_message = format_messages(messages)
710
+ chat_messages, system_message = format_messages(messages, compress_tool_results=compress_tool_results)
707
711
  request_kwargs = self._prepare_request_kwargs(system_message, tools=tools, response_format=response_format)
708
712
 
709
713
  if self._has_beta_features(response_format=response_format, tools=tools):
@@ -219,21 +219,35 @@ class AwsBedrock(Model):
219
219
 
220
220
  return {k: v for k, v in request_kwargs.items() if v is not None}
221
221
 
222
- def _format_messages(self, messages: List[Message]) -> Tuple[List[Dict[str, Any]], Optional[List[Dict[str, Any]]]]:
222
+ def _format_messages(
223
+ self, messages: List[Message], compress_tool_results: bool = False
224
+ ) -> Tuple[List[Dict[str, Any]], Optional[List[Dict[str, Any]]]]:
223
225
  """
224
226
  Format the messages for the request.
225
227
 
228
+ Args:
229
+ messages: List of messages to format
230
+ compress_tool_results: Whether to compress tool results
231
+
226
232
  Returns:
227
233
  Tuple[List[Dict[str, Any]], Optional[List[Dict[str, Any]]]]: The formatted messages.
228
234
  """
235
+
229
236
  formatted_messages: List[Dict[str, Any]] = []
230
237
  system_message = None
231
238
  for message in messages:
232
239
  if message.role == "system":
233
240
  system_message = [{"text": message.content}]
241
+ elif message.role == "tool":
242
+ content = message.get_content(use_compressed_content=compress_tool_results)
243
+ tool_result = {
244
+ "toolUseId": message.tool_call_id,
245
+ "content": [{"json": {"result": content}}],
246
+ }
247
+ formatted_message: Dict[str, Any] = {"role": "user", "content": [{"toolResult": tool_result}]}
248
+ formatted_messages.append(formatted_message)
234
249
  else:
235
- formatted_message: Dict[str, Any] = {"role": message.role, "content": []}
236
- # Handle tool results
250
+ formatted_message = {"role": message.role, "content": []}
237
251
  if isinstance(message.content, list):
238
252
  formatted_message["content"].extend(message.content)
239
253
  elif message.tool_calls:
@@ -352,12 +366,13 @@ class AwsBedrock(Model):
352
366
  tools: Optional[List[Dict[str, Any]]] = None,
353
367
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
354
368
  run_response: Optional[RunOutput] = None,
369
+ compress_tool_results: bool = False,
355
370
  ) -> ModelResponse:
356
371
  """
357
372
  Invoke the Bedrock API.
358
373
  """
359
374
  try:
360
- formatted_messages, system_message = self._format_messages(messages)
375
+ formatted_messages, system_message = self._format_messages(messages, compress_tool_results)
361
376
 
362
377
  tool_config = None
363
378
  if tools:
@@ -400,12 +415,13 @@ class AwsBedrock(Model):
400
415
  tools: Optional[List[Dict[str, Any]]] = None,
401
416
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
402
417
  run_response: Optional[RunOutput] = None,
418
+ compress_tool_results: bool = False,
403
419
  ) -> Iterator[ModelResponse]:
404
420
  """
405
421
  Invoke the Bedrock API with streaming.
406
422
  """
407
423
  try:
408
- formatted_messages, system_message = self._format_messages(messages)
424
+ formatted_messages, system_message = self._format_messages(messages, compress_tool_results)
409
425
 
410
426
  tool_config = None
411
427
  if tools:
@@ -452,12 +468,13 @@ class AwsBedrock(Model):
452
468
  tools: Optional[List[Dict[str, Any]]] = None,
453
469
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
454
470
  run_response: Optional[RunOutput] = None,
471
+ compress_tool_results: bool = False,
455
472
  ) -> ModelResponse:
456
473
  """
457
474
  Async invoke the Bedrock API.
458
475
  """
459
476
  try:
460
- formatted_messages, system_message = self._format_messages(messages)
477
+ formatted_messages, system_message = self._format_messages(messages, compress_tool_results)
461
478
 
462
479
  tool_config = None
463
480
  if tools:
@@ -503,12 +520,13 @@ class AwsBedrock(Model):
503
520
  tools: Optional[List[Dict[str, Any]]] = None,
504
521
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
505
522
  run_response: Optional[RunOutput] = None,
523
+ compress_tool_results: bool = False,
506
524
  ) -> AsyncIterator[ModelResponse]:
507
525
  """
508
526
  Async invoke the Bedrock API with streaming.
509
527
  """
510
528
  try:
511
- formatted_messages, system_message = self._format_messages(messages)
529
+ formatted_messages, system_message = self._format_messages(messages, compress_tool_results)
512
530
 
513
531
  tool_config = None
514
532
  if tools:
@@ -549,30 +567,34 @@ class AwsBedrock(Model):
549
567
 
550
568
  # Overwrite the default from the base model
551
569
  def format_function_call_results(
552
- self, messages: List[Message], function_call_results: List[Message], **kwargs
570
+ self,
571
+ messages: List[Message],
572
+ function_call_results: List[Message],
573
+ compress_tool_results: bool = False,
574
+ **kwargs,
553
575
  ) -> None:
554
576
  """
555
- Handle the results of function calls.
577
+ Handle the results of function calls for Bedrock.
578
+ Uses compressed_content if compress_tool_results is True.
556
579
 
557
580
  Args:
558
581
  messages (List[Message]): The list of conversation messages.
559
582
  function_call_results (List[Message]): The results of the function calls.
583
+ compress_tool_results: Whether to compress tool results.
560
584
  **kwargs: Additional arguments including tool_ids.
561
585
  """
586
+
562
587
  if function_call_results:
563
588
  tool_ids = kwargs.get("tool_ids", [])
564
- tool_result_content: List = []
565
589
 
566
590
  for _fc_message_index, _fc_message in enumerate(function_call_results):
567
591
  # Use tool_call_id from message if tool_ids list is insufficient
568
592
  tool_id = tool_ids[_fc_message_index] if _fc_message_index < len(tool_ids) else _fc_message.tool_call_id
569
- tool_result = {
570
- "toolUseId": tool_id,
571
- "content": [{"json": {"result": _fc_message.content}}],
572
- }
573
- tool_result_content.append({"toolResult": tool_result})
593
+ if not _fc_message.tool_call_id:
594
+ _fc_message.tool_call_id = tool_id
574
595
 
575
- messages.append(Message(role="user", content=tool_result_content))
596
+ # Append as standard role="tool" message
597
+ messages.append(_fc_message)
576
598
 
577
599
  def _parse_provider_response(self, response: Dict[str, Any], **kwargs) -> ModelResponse:
578
600
  """