agno 2.3.2__py3-none-any.whl → 2.3.4__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 (90) hide show
  1. agno/agent/agent.py +513 -185
  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 +66 -52
  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/singlestore/singlestore.py +99 -108
  18. agno/db/sqlite/async_sqlite.py +29 -27
  19. agno/db/sqlite/sqlite.py +30 -26
  20. agno/knowledge/reader/pdf_reader.py +2 -2
  21. agno/knowledge/reader/tavily_reader.py +0 -1
  22. agno/memory/__init__.py +14 -1
  23. agno/memory/manager.py +217 -4
  24. agno/memory/strategies/__init__.py +15 -0
  25. agno/memory/strategies/base.py +67 -0
  26. agno/memory/strategies/summarize.py +196 -0
  27. agno/memory/strategies/types.py +37 -0
  28. agno/models/aimlapi/aimlapi.py +18 -0
  29. agno/models/anthropic/claude.py +87 -81
  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 +25 -9
  35. agno/models/cerebras/cerebras_openai.py +22 -2
  36. agno/models/cohere/chat.py +18 -6
  37. agno/models/cometapi/cometapi.py +19 -1
  38. agno/models/deepinfra/deepinfra.py +19 -1
  39. agno/models/fireworks/fireworks.py +19 -1
  40. agno/models/google/gemini.py +583 -21
  41. agno/models/groq/groq.py +23 -6
  42. agno/models/huggingface/huggingface.py +22 -7
  43. agno/models/ibm/watsonx.py +21 -7
  44. agno/models/internlm/internlm.py +19 -1
  45. agno/models/langdb/langdb.py +10 -0
  46. agno/models/litellm/chat.py +17 -7
  47. agno/models/litellm/litellm_openai.py +19 -1
  48. agno/models/message.py +19 -5
  49. agno/models/meta/llama.py +25 -5
  50. agno/models/meta/llama_openai.py +18 -0
  51. agno/models/mistral/mistral.py +13 -5
  52. agno/models/nvidia/nvidia.py +19 -1
  53. agno/models/ollama/chat.py +17 -6
  54. agno/models/openai/chat.py +22 -7
  55. agno/models/openai/responses.py +28 -10
  56. agno/models/openrouter/openrouter.py +20 -0
  57. agno/models/perplexity/perplexity.py +17 -0
  58. agno/models/requesty/requesty.py +18 -0
  59. agno/models/sambanova/sambanova.py +19 -1
  60. agno/models/siliconflow/siliconflow.py +19 -1
  61. agno/models/together/together.py +19 -1
  62. agno/models/vercel/v0.py +19 -1
  63. agno/models/vertexai/claude.py +99 -5
  64. agno/models/xai/xai.py +18 -0
  65. agno/os/interfaces/agui/router.py +1 -0
  66. agno/os/interfaces/agui/utils.py +97 -57
  67. agno/os/router.py +16 -0
  68. agno/os/routers/memory/memory.py +143 -0
  69. agno/os/routers/memory/schemas.py +26 -0
  70. agno/os/schema.py +33 -6
  71. agno/os/utils.py +134 -10
  72. agno/run/base.py +2 -1
  73. agno/run/workflow.py +1 -1
  74. agno/team/team.py +566 -219
  75. agno/tools/mcp/mcp.py +1 -1
  76. agno/utils/agent.py +119 -1
  77. agno/utils/models/ai_foundry.py +9 -2
  78. agno/utils/models/claude.py +12 -5
  79. agno/utils/models/cohere.py +9 -2
  80. agno/utils/models/llama.py +9 -2
  81. agno/utils/models/mistral.py +4 -2
  82. agno/utils/print_response/agent.py +37 -2
  83. agno/utils/print_response/team.py +52 -0
  84. agno/utils/tokens.py +41 -0
  85. agno/workflow/types.py +2 -2
  86. {agno-2.3.2.dist-info → agno-2.3.4.dist-info}/METADATA +45 -40
  87. {agno-2.3.2.dist-info → agno-2.3.4.dist-info}/RECORD +90 -83
  88. {agno-2.3.2.dist-info → agno-2.3.4.dist-info}/WHEEL +0 -0
  89. {agno-2.3.2.dist-info → agno-2.3.4.dist-info}/licenses/LICENSE +0 -0
  90. {agno-2.3.2.dist-info → agno-2.3.4.dist-info}/top_level.txt +0 -0
@@ -154,7 +154,9 @@ class Claude(Model):
154
154
 
155
155
  self.api_key = self.api_key or getenv("ANTHROPIC_API_KEY")
156
156
  if not self.api_key:
157
- log_error("ANTHROPIC_API_KEY not set. Please set the ANTHROPIC_API_KEY environment variable.")
157
+ raise ModelProviderError(
158
+ "ANTHROPIC_API_KEY not set. Please set the ANTHROPIC_API_KEY environment variable."
159
+ )
158
160
 
159
161
  # Add API key to client parameters
160
162
  client_params["api_key"] = self.api_key
@@ -183,7 +185,7 @@ class Claude(Model):
183
185
  )
184
186
  return False
185
187
 
186
- # Check for legacy model patterns that don't support structured outputs
188
+ # Check for legacy model patterns which don't support structured outputs
187
189
  if self.id.startswith("claude-3-"):
188
190
  return False
189
191
  if self.id.startswith("claude-sonnet-4-") and not self.id.startswith("claude-sonnet-4-5"):
@@ -222,64 +224,6 @@ class Claude(Model):
222
224
 
223
225
  return False
224
226
 
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
227
  def _validate_thinking_support(self) -> None:
284
228
  """
285
229
  Validate that the current model supports extended thinking.
@@ -375,6 +319,81 @@ class Claude(Model):
375
319
 
376
320
  return None
377
321
 
322
+ def _validate_structured_outputs_usage(
323
+ self,
324
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
325
+ tools: Optional[List[Dict[str, Any]]] = None,
326
+ ) -> None:
327
+ """
328
+ Validate that structured outputs are only used with supported models.
329
+
330
+ Raises:
331
+ ValueError: If structured outputs are used with unsupported model
332
+ """
333
+ if not self._using_structured_outputs(response_format, tools):
334
+ return
335
+
336
+ if not self._supports_structured_outputs():
337
+ raise ValueError(f"Model '{self.id}' does not support structured outputs.\n\n")
338
+
339
+ def _has_beta_features(
340
+ self,
341
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
342
+ tools: Optional[List[Dict[str, Any]]] = None,
343
+ ) -> bool:
344
+ """Check if the model has any Anthropic beta features enabled."""
345
+ return (
346
+ self.mcp_servers is not None
347
+ or self.context_management is not None
348
+ or self.skills is not None
349
+ or self.betas is not None
350
+ or self._using_structured_outputs(response_format, tools)
351
+ )
352
+
353
+ def get_client(self) -> AnthropicClient:
354
+ """
355
+ Returns an instance of the Anthropic client.
356
+ """
357
+ if self.client and not self.client.is_closed():
358
+ return self.client
359
+
360
+ _client_params = self._get_client_params()
361
+ if self.http_client:
362
+ if isinstance(self.http_client, httpx.Client):
363
+ _client_params["http_client"] = self.http_client
364
+ else:
365
+ log_warning("http_client is not an instance of httpx.Client. Using default global httpx.Client.")
366
+ # Use global sync client when user http_client is invalid
367
+ _client_params["http_client"] = get_default_sync_client()
368
+ else:
369
+ # Use global sync client when no custom http_client is provided
370
+ _client_params["http_client"] = get_default_sync_client()
371
+ self.client = AnthropicClient(**_client_params)
372
+ return self.client
373
+
374
+ def get_async_client(self) -> AsyncAnthropicClient:
375
+ """
376
+ Returns an instance of the async Anthropic client.
377
+ """
378
+ if self.async_client and not self.async_client.is_closed():
379
+ return self.async_client
380
+
381
+ _client_params = self._get_client_params()
382
+ if self.http_client:
383
+ if isinstance(self.http_client, httpx.AsyncClient):
384
+ _client_params["http_client"] = self.http_client
385
+ else:
386
+ log_warning(
387
+ "http_client is not an instance of httpx.AsyncClient. Using default global httpx.AsyncClient."
388
+ )
389
+ # Use global async client when user http_client is invalid
390
+ _client_params["http_client"] = get_default_async_client()
391
+ else:
392
+ # Use global async client when no custom http_client is provided
393
+ _client_params["http_client"] = get_default_async_client()
394
+ self.async_client = AsyncAnthropicClient(**_client_params)
395
+ return self.async_client
396
+
378
397
  def get_request_params(
379
398
  self,
380
399
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
@@ -427,23 +446,6 @@ class Claude(Model):
427
446
 
428
447
  return _request_params
429
448
 
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
449
  def _prepare_request_kwargs(
448
450
  self,
449
451
  system_message: str,
@@ -507,6 +509,7 @@ class Claude(Model):
507
509
  tools: Optional[List[Dict[str, Any]]] = None,
508
510
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
509
511
  run_response: Optional[RunOutput] = None,
512
+ compress_tool_results: bool = False,
510
513
  ) -> ModelResponse:
511
514
  """
512
515
  Send a request to the Anthropic API to generate a response.
@@ -515,7 +518,7 @@ class Claude(Model):
515
518
  if run_response and run_response.metrics:
516
519
  run_response.metrics.set_time_to_first_token()
517
520
 
518
- chat_messages, system_message = format_messages(messages)
521
+ chat_messages, system_message = format_messages(messages, compress_tool_results=compress_tool_results)
519
522
  request_kwargs = self._prepare_request_kwargs(system_message, tools=tools, response_format=response_format)
520
523
 
521
524
  if self._has_beta_features(response_format=response_format, tools=tools):
@@ -563,6 +566,7 @@ class Claude(Model):
563
566
  tools: Optional[List[Dict[str, Any]]] = None,
564
567
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
565
568
  run_response: Optional[RunOutput] = None,
569
+ compress_tool_results: bool = False,
566
570
  ) -> Any:
567
571
  """
568
572
  Stream a response from the Anthropic API.
@@ -578,7 +582,7 @@ class Claude(Model):
578
582
  RateLimitError: If the API rate limit is exceeded
579
583
  APIStatusError: For other API-related errors
580
584
  """
581
- chat_messages, system_message = format_messages(messages)
585
+ chat_messages, system_message = format_messages(messages, compress_tool_results=compress_tool_results)
582
586
  request_kwargs = self._prepare_request_kwargs(system_message, tools=tools, response_format=response_format)
583
587
 
584
588
  try:
@@ -630,6 +634,7 @@ class Claude(Model):
630
634
  tools: Optional[List[Dict[str, Any]]] = None,
631
635
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
632
636
  run_response: Optional[RunOutput] = None,
637
+ compress_tool_results: bool = False,
633
638
  ) -> ModelResponse:
634
639
  """
635
640
  Send an asynchronous request to the Anthropic API to generate a response.
@@ -638,7 +643,7 @@ class Claude(Model):
638
643
  if run_response and run_response.metrics:
639
644
  run_response.metrics.set_time_to_first_token()
640
645
 
641
- chat_messages, system_message = format_messages(messages)
646
+ chat_messages, system_message = format_messages(messages, compress_tool_results=compress_tool_results)
642
647
  request_kwargs = self._prepare_request_kwargs(system_message, tools=tools, response_format=response_format)
643
648
 
644
649
  # Beta features
@@ -687,6 +692,7 @@ class Claude(Model):
687
692
  tools: Optional[List[Dict[str, Any]]] = None,
688
693
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
689
694
  run_response: Optional[RunOutput] = None,
695
+ compress_tool_results: bool = False,
690
696
  ) -> AsyncIterator[ModelResponse]:
691
697
  """
692
698
  Stream an asynchronous response from the Anthropic API.
@@ -703,7 +709,7 @@ class Claude(Model):
703
709
  if run_response and run_response.metrics:
704
710
  run_response.metrics.set_time_to_first_token()
705
711
 
706
- chat_messages, system_message = format_messages(messages)
712
+ chat_messages, system_message = format_messages(messages, compress_tool_results=compress_tool_results)
707
713
  request_kwargs = self._prepare_request_kwargs(system_message, tools=tools, response_format=response_format)
708
714
 
709
715
  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
  """