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
@@ -406,7 +406,7 @@ class PDFImageReader(BasePDFReader):
406
406
  return []
407
407
 
408
408
  # Read and chunk.
409
- return self._pdf_reader_to_documents(pdf_reader, doc_name, read_images=True, use_uuid_for_id=False)
409
+ return self._pdf_reader_to_documents(pdf_reader, doc_name, read_images=True, use_uuid_for_id=True)
410
410
 
411
411
  async def async_read(
412
412
  self, pdf: Union[str, Path, IO[Any]], name: Optional[str] = None, password: Optional[str] = None
@@ -428,4 +428,4 @@ class PDFImageReader(BasePDFReader):
428
428
  return []
429
429
 
430
430
  # Read and chunk.
431
- return await self._async_pdf_reader_to_documents(pdf_reader, doc_name, read_images=True, use_uuid_for_id=False)
431
+ return await self._async_pdf_reader_to_documents(pdf_reader, doc_name, read_images=True, use_uuid_for_id=True)
@@ -140,7 +140,6 @@ class TavilyReader(Reader):
140
140
  documents.extend(self.chunk_document(Document(name=name or url, id=url, content=content)))
141
141
  else:
142
142
  documents.append(Document(name=name or url, id=url, content=content))
143
-
144
143
  return documents
145
144
 
146
145
  except Exception as e:
agno/memory/__init__.py CHANGED
@@ -1,3 +1,16 @@
1
1
  from agno.memory.manager import MemoryManager, UserMemory
2
+ from agno.memory.strategies import (
3
+ MemoryOptimizationStrategy,
4
+ MemoryOptimizationStrategyFactory,
5
+ MemoryOptimizationStrategyType,
6
+ SummarizeStrategy,
7
+ )
2
8
 
3
- __all__ = ["MemoryManager", "UserMemory"]
9
+ __all__ = [
10
+ "MemoryManager",
11
+ "UserMemory",
12
+ "MemoryOptimizationStrategy",
13
+ "MemoryOptimizationStrategyType",
14
+ "MemoryOptimizationStrategyFactory",
15
+ "SummarizeStrategy",
16
+ ]
agno/memory/manager.py CHANGED
@@ -1,6 +1,5 @@
1
1
  from copy import deepcopy
2
2
  from dataclasses import dataclass
3
- from datetime import datetime
4
3
  from os import getenv
5
4
  from textwrap import dedent
6
5
  from typing import Any, Callable, Dict, List, Literal, Optional, Type, Union
@@ -9,6 +8,11 @@ from pydantic import BaseModel, Field
9
8
 
10
9
  from agno.db.base import AsyncBaseDb, BaseDb
11
10
  from agno.db.schemas import UserMemory
11
+ from agno.memory.strategies import MemoryOptimizationStrategy
12
+ from agno.memory.strategies.types import (
13
+ MemoryOptimizationStrategyFactory,
14
+ MemoryOptimizationStrategyType,
15
+ )
12
16
  from agno.models.base import Model
13
17
  from agno.models.message import Message
14
18
  from agno.models.utils import get_model
@@ -90,9 +94,6 @@ class MemoryManager:
90
94
  self.clear_memories = clear_memories
91
95
  self.debug_mode = debug_mode
92
96
 
93
- self._get_models()
94
-
95
- def _get_models(self) -> None:
96
97
  if self.model is not None:
97
98
  self.model = get_model(self.model)
98
99
 
@@ -292,6 +293,74 @@ class MemoryManager:
292
293
  log_warning("Memory DB not provided.")
293
294
  return None
294
295
 
296
+ def clear_user_memories(self, user_id: Optional[str] = None) -> None:
297
+ """Clear all memories for a specific user.
298
+
299
+ Args:
300
+ user_id (Optional[str]): The user id to clear memories for. If not provided, clears memories for the "default" user.
301
+ """
302
+ if user_id is None:
303
+ log_warning("Using default user id.")
304
+ user_id = "default"
305
+
306
+ if not self.db:
307
+ log_warning("Memory DB not provided.")
308
+ return
309
+
310
+ if isinstance(self.db, AsyncBaseDb):
311
+ raise ValueError(
312
+ "clear_user_memories() is not supported with an async DB. Please use aclear_user_memories() instead."
313
+ )
314
+
315
+ # TODO: This is inefficient - we fetch all memories just to get their IDs.
316
+ # Extend delete_user_memories() to accept just user_id and delete all memories
317
+ # for that user directly without requiring a list of memory_ids.
318
+ memories = self.get_user_memories(user_id=user_id)
319
+ if not memories:
320
+ log_debug(f"No memories found for user {user_id}")
321
+ return
322
+
323
+ # Extract memory IDs
324
+ memory_ids = [mem.memory_id for mem in memories if mem.memory_id]
325
+
326
+ if memory_ids:
327
+ # Delete all memories in a single batch operation
328
+ self.db.delete_user_memories(memory_ids=memory_ids, user_id=user_id)
329
+ log_debug(f"Cleared {len(memory_ids)} memories for user {user_id}")
330
+
331
+ async def aclear_user_memories(self, user_id: Optional[str] = None) -> None:
332
+ """Clear all memories for a specific user (async).
333
+
334
+ Args:
335
+ user_id (Optional[str]): The user id to clear memories for. If not provided, clears memories for the "default" user.
336
+ """
337
+ if user_id is None:
338
+ user_id = "default"
339
+
340
+ if not self.db:
341
+ log_warning("Memory DB not provided.")
342
+ return
343
+
344
+ if isinstance(self.db, AsyncBaseDb):
345
+ memories = await self.aget_user_memories(user_id=user_id)
346
+ else:
347
+ memories = self.get_user_memories(user_id=user_id)
348
+
349
+ if not memories:
350
+ log_debug(f"No memories found for user {user_id}")
351
+ return
352
+
353
+ # Extract memory IDs
354
+ memory_ids = [mem.memory_id for mem in memories if mem.memory_id]
355
+
356
+ if memory_ids:
357
+ # Delete all memories in a single batch operation
358
+ if isinstance(self.db, AsyncBaseDb):
359
+ await self.db.delete_user_memories(memory_ids=memory_ids, user_id=user_id)
360
+ else:
361
+ self.db.delete_user_memories(memory_ids=memory_ids, user_id=user_id)
362
+ log_debug(f"Cleared {len(memory_ids)} memories for user {user_id}")
363
+
295
364
  # -*- Agent Functions
296
365
  def create_user_memories(
297
366
  self,
@@ -714,6 +783,150 @@ class MemoryManager:
714
783
 
715
784
  return sorted_memories_list
716
785
 
786
+ def optimize_memories(
787
+ self,
788
+ user_id: Optional[str] = None,
789
+ strategy: Union[
790
+ MemoryOptimizationStrategyType, MemoryOptimizationStrategy
791
+ ] = MemoryOptimizationStrategyType.SUMMARIZE,
792
+ apply: bool = True,
793
+ ) -> List[UserMemory]:
794
+ """Optimize user memories using the specified strategy.
795
+
796
+ Args:
797
+ user_id: User ID to optimize memories for. Defaults to "default".
798
+ strategy: Optimization strategy. Can be:
799
+ - Enum: MemoryOptimizationStrategyType.SUMMARIZE
800
+ - Instance: Custom MemoryOptimizationStrategy instance
801
+ apply: If True, automatically replace memories in database.
802
+
803
+ Returns:
804
+ List of optimized UserMemory objects.
805
+ """
806
+ if user_id is None:
807
+ user_id = "default"
808
+
809
+ if isinstance(self.db, AsyncBaseDb):
810
+ raise ValueError(
811
+ "optimize_memories() is not supported with an async DB. Please use aoptimize_memories() instead."
812
+ )
813
+
814
+ # Get user memories
815
+ memories = self.get_user_memories(user_id=user_id)
816
+ if not memories:
817
+ log_debug("No memories to optimize")
818
+ return []
819
+
820
+ # Get strategy instance
821
+ if isinstance(strategy, MemoryOptimizationStrategyType):
822
+ strategy_instance = MemoryOptimizationStrategyFactory.create_strategy(strategy)
823
+ else:
824
+ # Already a strategy instance
825
+ strategy_instance = strategy
826
+
827
+ # Optimize memories using strategy
828
+ optimization_model = self.get_model()
829
+ optimized_memories = strategy_instance.optimize(memories=memories, model=optimization_model)
830
+
831
+ # Apply to database if requested
832
+ if apply:
833
+ log_debug(f"Applying optimized memories to database for user {user_id}")
834
+
835
+ if not self.db:
836
+ log_warning("Memory DB not provided. Cannot apply optimized memories.")
837
+ return optimized_memories
838
+
839
+ # Clear all existing memories for the user
840
+ self.clear_user_memories(user_id=user_id)
841
+
842
+ # Add all optimized memories
843
+ for opt_mem in optimized_memories:
844
+ # Ensure memory has an ID (generate if needed for new memories)
845
+ if not opt_mem.memory_id:
846
+ from uuid import uuid4
847
+
848
+ opt_mem.memory_id = str(uuid4())
849
+
850
+ self.db.upsert_user_memory(memory=opt_mem)
851
+
852
+ optimized_tokens = strategy_instance.count_tokens(optimized_memories)
853
+ log_debug(f"Optimization complete. New token count: {optimized_tokens}")
854
+
855
+ return optimized_memories
856
+
857
+ async def aoptimize_memories(
858
+ self,
859
+ user_id: Optional[str] = None,
860
+ strategy: Union[
861
+ MemoryOptimizationStrategyType, MemoryOptimizationStrategy
862
+ ] = MemoryOptimizationStrategyType.SUMMARIZE,
863
+ apply: bool = True,
864
+ ) -> List[UserMemory]:
865
+ """Async version of optimize_memories.
866
+
867
+ Args:
868
+ user_id: User ID to optimize memories for. Defaults to "default".
869
+ strategy: Optimization strategy. Can be:
870
+ - Enum: MemoryOptimizationStrategyType.SUMMARIZE
871
+ - Instance: Custom MemoryOptimizationStrategy instance
872
+ apply: If True, automatically replace memories in database.
873
+
874
+ Returns:
875
+ List of optimized UserMemory objects.
876
+ """
877
+ if user_id is None:
878
+ user_id = "default"
879
+
880
+ # Get user memories - handle both sync and async DBs
881
+ if isinstance(self.db, AsyncBaseDb):
882
+ memories = await self.aget_user_memories(user_id=user_id)
883
+ else:
884
+ memories = self.get_user_memories(user_id=user_id)
885
+
886
+ if not memories:
887
+ log_debug("No memories to optimize")
888
+ return []
889
+
890
+ # Get strategy instance
891
+ if isinstance(strategy, MemoryOptimizationStrategyType):
892
+ strategy_instance = MemoryOptimizationStrategyFactory.create_strategy(strategy)
893
+ else:
894
+ # Already a strategy instance
895
+ strategy_instance = strategy
896
+
897
+ # Optimize memories using strategy (async)
898
+ optimization_model = self.get_model()
899
+ optimized_memories = await strategy_instance.aoptimize(memories=memories, model=optimization_model)
900
+
901
+ # Apply to database if requested
902
+ if apply:
903
+ log_debug(f"Optimizing memories for user {user_id}")
904
+
905
+ if not self.db:
906
+ log_warning("Memory DB not provided. Cannot apply optimized memories.")
907
+ return optimized_memories
908
+
909
+ # Clear all existing memories for the user
910
+ await self.aclear_user_memories(user_id=user_id)
911
+
912
+ # Add all optimized memories
913
+ for opt_mem in optimized_memories:
914
+ # Ensure memory has an ID (generate if needed for new memories)
915
+ if not opt_mem.memory_id:
916
+ from uuid import uuid4
917
+
918
+ opt_mem.memory_id = str(uuid4())
919
+
920
+ if isinstance(self.db, AsyncBaseDb):
921
+ await self.db.upsert_user_memory(memory=opt_mem)
922
+ elif isinstance(self.db, BaseDb):
923
+ self.db.upsert_user_memory(memory=opt_mem)
924
+
925
+ optimized_tokens = strategy_instance.count_tokens(optimized_memories)
926
+ log_debug(f"Memory optimization complete. New token count: {optimized_tokens}")
927
+
928
+ return optimized_memories
929
+
717
930
  # --Memory Manager Functions--
718
931
  def determine_tools_for_model(self, tools: List[Callable]) -> List[Union[Function, dict]]:
719
932
  # Have to reset each time, because of different user IDs
@@ -0,0 +1,15 @@
1
+ """Memory optimization strategy implementations."""
2
+
3
+ from agno.memory.strategies.base import MemoryOptimizationStrategy
4
+ from agno.memory.strategies.summarize import SummarizeStrategy
5
+ from agno.memory.strategies.types import (
6
+ MemoryOptimizationStrategyFactory,
7
+ MemoryOptimizationStrategyType,
8
+ )
9
+
10
+ __all__ = [
11
+ "MemoryOptimizationStrategy",
12
+ "MemoryOptimizationStrategyFactory",
13
+ "MemoryOptimizationStrategyType",
14
+ "SummarizeStrategy",
15
+ ]
@@ -0,0 +1,67 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import List
3
+
4
+ from agno.db.schemas import UserMemory
5
+ from agno.models.base import Model
6
+ from agno.utils.tokens import count_tokens as count_text_tokens
7
+
8
+
9
+ class MemoryOptimizationStrategy(ABC):
10
+ """Abstract base class for memory optimization strategies.
11
+
12
+ Subclasses must implement optimize() and aoptimize().
13
+ get_system_prompt() is optional and only needed for LLM-based strategies.
14
+ """
15
+
16
+ def get_system_prompt(self) -> str:
17
+ """Get system prompt for this optimization strategy.
18
+
19
+ Returns:
20
+ System prompt string for LLM-based strategies.
21
+ """
22
+ raise NotImplementedError
23
+
24
+ @abstractmethod
25
+ def optimize(
26
+ self,
27
+ memories: List[UserMemory],
28
+ model: Model,
29
+ ) -> List[UserMemory]:
30
+ """Optimize memories synchronously.
31
+
32
+ Args:
33
+ memories: List of UserMemory objects to optimize
34
+ model: Model to use for optimization (if needed)
35
+
36
+ Returns:
37
+ List of optimized UserMemory objects
38
+ """
39
+ raise NotImplementedError
40
+
41
+ @abstractmethod
42
+ async def aoptimize(
43
+ self,
44
+ memories: List[UserMemory],
45
+ model: Model,
46
+ ) -> List[UserMemory]:
47
+ """Optimize memories asynchronously.
48
+
49
+ Args:
50
+ memories: List of UserMemory objects to optimize
51
+ model: Model to use for optimization (if needed)
52
+
53
+ Returns:
54
+ List of optimized UserMemory objects
55
+ """
56
+ raise NotImplementedError
57
+
58
+ def count_tokens(self, memories: List[UserMemory]) -> int:
59
+ """Count total tokens across all memories.
60
+
61
+ Args:
62
+ memories: List of UserMemory objects
63
+
64
+ Returns:
65
+ Total token count using tiktoken (or fallback estimation)
66
+ """
67
+ return sum(count_text_tokens(mem.memory or "") for mem in memories)
@@ -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)
@@ -2,6 +2,7 @@ from dataclasses import dataclass, field
2
2
  from os import getenv
3
3
  from typing import Any, Dict, Optional
4
4
 
5
+ from agno.exceptions import ModelProviderError
5
6
  from agno.models.message import Message
6
7
  from agno.models.openai.like import OpenAILike
7
8
 
@@ -28,6 +29,23 @@ class AIMLAPI(OpenAILike):
28
29
  base_url: str = "https://api.aimlapi.com/v1"
29
30
  max_tokens: int = 4096
30
31
 
32
+ def _get_client_params(self) -> Dict[str, Any]:
33
+ """
34
+ Returns client parameters for API requests, checking for AIMLAPI_API_KEY.
35
+
36
+ Returns:
37
+ Dict[str, Any]: A dictionary of client parameters for API requests.
38
+ """
39
+ if not self.api_key:
40
+ self.api_key = getenv("AIMLAPI_API_KEY")
41
+ if not self.api_key:
42
+ raise ModelProviderError(
43
+ message="AIMLAPI_API_KEY not set. Please set the AIMLAPI_API_KEY environment variable.",
44
+ model_name=self.name,
45
+ model_id=self.id,
46
+ )
47
+ return super()._get_client_params()
48
+
31
49
  def _format_message(self, message: Message) -> Dict[str, Any]:
32
50
  """
33
51
  Minimal additional formatter that only replaces None with empty string.