agno 2.2.13__py3-none-any.whl → 2.4.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/__init__.py +6 -0
- agno/agent/agent.py +5252 -3145
- agno/agent/remote.py +525 -0
- agno/api/api.py +2 -0
- agno/client/__init__.py +3 -0
- agno/client/a2a/__init__.py +10 -0
- agno/client/a2a/client.py +554 -0
- agno/client/a2a/schemas.py +112 -0
- agno/client/a2a/utils.py +369 -0
- agno/client/os.py +2669 -0
- agno/compression/__init__.py +3 -0
- agno/compression/manager.py +247 -0
- agno/culture/manager.py +2 -2
- agno/db/base.py +927 -6
- agno/db/dynamo/dynamo.py +788 -2
- agno/db/dynamo/schemas.py +128 -0
- agno/db/dynamo/utils.py +26 -3
- agno/db/firestore/firestore.py +674 -50
- agno/db/firestore/schemas.py +41 -0
- agno/db/firestore/utils.py +25 -10
- agno/db/gcs_json/gcs_json_db.py +506 -3
- agno/db/gcs_json/utils.py +14 -2
- agno/db/in_memory/in_memory_db.py +203 -4
- agno/db/in_memory/utils.py +14 -2
- agno/db/json/json_db.py +498 -2
- agno/db/json/utils.py +14 -2
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/utils.py +19 -0
- agno/db/migrations/v1_to_v2.py +54 -16
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +977 -0
- agno/db/mongo/async_mongo.py +1013 -39
- agno/db/mongo/mongo.py +684 -4
- agno/db/mongo/schemas.py +48 -0
- agno/db/mongo/utils.py +17 -0
- agno/db/mysql/__init__.py +2 -1
- agno/db/mysql/async_mysql.py +2958 -0
- agno/db/mysql/mysql.py +722 -53
- agno/db/mysql/schemas.py +77 -11
- agno/db/mysql/utils.py +151 -8
- agno/db/postgres/async_postgres.py +1254 -137
- agno/db/postgres/postgres.py +2316 -93
- agno/db/postgres/schemas.py +153 -21
- agno/db/postgres/utils.py +22 -7
- agno/db/redis/redis.py +531 -3
- agno/db/redis/schemas.py +36 -0
- agno/db/redis/utils.py +31 -15
- agno/db/schemas/evals.py +1 -0
- agno/db/schemas/memory.py +20 -9
- agno/db/singlestore/schemas.py +70 -1
- agno/db/singlestore/singlestore.py +737 -74
- agno/db/singlestore/utils.py +13 -3
- agno/db/sqlite/async_sqlite.py +1069 -89
- agno/db/sqlite/schemas.py +133 -1
- agno/db/sqlite/sqlite.py +2203 -165
- agno/db/sqlite/utils.py +21 -11
- agno/db/surrealdb/models.py +25 -0
- agno/db/surrealdb/surrealdb.py +603 -1
- agno/db/utils.py +60 -0
- agno/eval/__init__.py +26 -3
- agno/eval/accuracy.py +25 -12
- agno/eval/agent_as_judge.py +871 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +10 -4
- agno/eval/reliability.py +22 -13
- agno/eval/utils.py +2 -1
- agno/exceptions.py +42 -0
- agno/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/client.py +13 -2
- agno/knowledge/__init__.py +4 -0
- agno/knowledge/chunking/code.py +90 -0
- agno/knowledge/chunking/document.py +65 -4
- agno/knowledge/chunking/fixed.py +4 -1
- agno/knowledge/chunking/markdown.py +102 -11
- agno/knowledge/chunking/recursive.py +2 -2
- agno/knowledge/chunking/semantic.py +130 -48
- agno/knowledge/chunking/strategy.py +18 -0
- agno/knowledge/embedder/azure_openai.py +0 -1
- agno/knowledge/embedder/google.py +1 -1
- agno/knowledge/embedder/mistral.py +1 -1
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/openai.py +16 -12
- agno/knowledge/filesystem.py +412 -0
- agno/knowledge/knowledge.py +4261 -1199
- agno/knowledge/protocol.py +134 -0
- agno/knowledge/reader/arxiv_reader.py +3 -2
- agno/knowledge/reader/base.py +9 -7
- agno/knowledge/reader/csv_reader.py +91 -42
- agno/knowledge/reader/docx_reader.py +9 -10
- agno/knowledge/reader/excel_reader.py +225 -0
- agno/knowledge/reader/field_labeled_csv_reader.py +38 -48
- agno/knowledge/reader/firecrawl_reader.py +3 -2
- agno/knowledge/reader/json_reader.py +16 -22
- agno/knowledge/reader/markdown_reader.py +15 -14
- agno/knowledge/reader/pdf_reader.py +33 -28
- agno/knowledge/reader/pptx_reader.py +9 -10
- agno/knowledge/reader/reader_factory.py +135 -1
- agno/knowledge/reader/s3_reader.py +8 -16
- agno/knowledge/reader/tavily_reader.py +3 -3
- agno/knowledge/reader/text_reader.py +15 -14
- agno/knowledge/reader/utils/__init__.py +17 -0
- agno/knowledge/reader/utils/spreadsheet.py +114 -0
- agno/knowledge/reader/web_search_reader.py +8 -65
- agno/knowledge/reader/website_reader.py +16 -13
- agno/knowledge/reader/wikipedia_reader.py +36 -3
- agno/knowledge/reader/youtube_reader.py +3 -2
- agno/knowledge/remote_content/__init__.py +33 -0
- agno/knowledge/remote_content/config.py +266 -0
- agno/knowledge/remote_content/remote_content.py +105 -17
- agno/knowledge/utils.py +76 -22
- agno/learn/__init__.py +71 -0
- agno/learn/config.py +463 -0
- agno/learn/curate.py +185 -0
- agno/learn/machine.py +725 -0
- agno/learn/schemas.py +1114 -0
- agno/learn/stores/__init__.py +38 -0
- agno/learn/stores/decision_log.py +1156 -0
- agno/learn/stores/entity_memory.py +3275 -0
- agno/learn/stores/learned_knowledge.py +1583 -0
- agno/learn/stores/protocol.py +117 -0
- agno/learn/stores/session_context.py +1217 -0
- agno/learn/stores/user_memory.py +1495 -0
- agno/learn/stores/user_profile.py +1220 -0
- agno/learn/utils.py +209 -0
- agno/media.py +22 -6
- agno/memory/__init__.py +14 -1
- agno/memory/manager.py +223 -8
- agno/memory/strategies/__init__.py +15 -0
- agno/memory/strategies/base.py +66 -0
- agno/memory/strategies/summarize.py +196 -0
- agno/memory/strategies/types.py +37 -0
- agno/models/aimlapi/aimlapi.py +17 -0
- agno/models/anthropic/claude.py +434 -59
- agno/models/aws/bedrock.py +121 -20
- agno/models/aws/claude.py +131 -274
- agno/models/azure/ai_foundry.py +10 -6
- agno/models/azure/openai_chat.py +33 -10
- agno/models/base.py +1162 -561
- agno/models/cerebras/cerebras.py +120 -24
- agno/models/cerebras/cerebras_openai.py +21 -2
- agno/models/cohere/chat.py +65 -6
- agno/models/cometapi/cometapi.py +18 -1
- agno/models/dashscope/dashscope.py +2 -3
- agno/models/deepinfra/deepinfra.py +18 -1
- agno/models/deepseek/deepseek.py +69 -3
- agno/models/fireworks/fireworks.py +18 -1
- agno/models/google/gemini.py +959 -89
- agno/models/google/utils.py +22 -0
- agno/models/groq/groq.py +48 -18
- agno/models/huggingface/huggingface.py +17 -6
- agno/models/ibm/watsonx.py +16 -6
- agno/models/internlm/internlm.py +18 -1
- agno/models/langdb/langdb.py +13 -1
- agno/models/litellm/chat.py +88 -9
- agno/models/litellm/litellm_openai.py +18 -1
- agno/models/message.py +24 -5
- agno/models/meta/llama.py +40 -13
- agno/models/meta/llama_openai.py +22 -21
- agno/models/metrics.py +12 -0
- agno/models/mistral/mistral.py +8 -4
- agno/models/n1n/__init__.py +3 -0
- agno/models/n1n/n1n.py +57 -0
- agno/models/nebius/nebius.py +6 -7
- agno/models/nvidia/nvidia.py +20 -3
- agno/models/ollama/__init__.py +2 -0
- agno/models/ollama/chat.py +17 -6
- agno/models/ollama/responses.py +100 -0
- agno/models/openai/__init__.py +2 -0
- agno/models/openai/chat.py +117 -26
- agno/models/openai/open_responses.py +46 -0
- agno/models/openai/responses.py +110 -32
- agno/models/openrouter/__init__.py +2 -0
- agno/models/openrouter/openrouter.py +67 -2
- agno/models/openrouter/responses.py +146 -0
- agno/models/perplexity/perplexity.py +19 -1
- agno/models/portkey/portkey.py +7 -6
- agno/models/requesty/requesty.py +19 -2
- agno/models/response.py +20 -2
- agno/models/sambanova/sambanova.py +20 -3
- agno/models/siliconflow/siliconflow.py +19 -2
- agno/models/together/together.py +20 -3
- agno/models/vercel/v0.py +20 -3
- agno/models/vertexai/claude.py +124 -4
- agno/models/vllm/vllm.py +19 -14
- agno/models/xai/xai.py +19 -2
- agno/os/app.py +467 -137
- agno/os/auth.py +253 -5
- agno/os/config.py +22 -0
- agno/os/interfaces/a2a/a2a.py +7 -6
- agno/os/interfaces/a2a/router.py +635 -26
- agno/os/interfaces/a2a/utils.py +32 -33
- agno/os/interfaces/agui/agui.py +5 -3
- agno/os/interfaces/agui/router.py +26 -16
- agno/os/interfaces/agui/utils.py +97 -57
- agno/os/interfaces/base.py +7 -7
- agno/os/interfaces/slack/router.py +16 -7
- agno/os/interfaces/slack/slack.py +7 -7
- agno/os/interfaces/whatsapp/router.py +35 -7
- agno/os/interfaces/whatsapp/security.py +3 -1
- agno/os/interfaces/whatsapp/whatsapp.py +11 -8
- agno/os/managers.py +326 -0
- agno/os/mcp.py +652 -79
- agno/os/middleware/__init__.py +4 -0
- agno/os/middleware/jwt.py +718 -115
- agno/os/middleware/trailing_slash.py +27 -0
- agno/os/router.py +105 -1558
- agno/os/routers/agents/__init__.py +3 -0
- agno/os/routers/agents/router.py +655 -0
- agno/os/routers/agents/schema.py +288 -0
- agno/os/routers/components/__init__.py +3 -0
- agno/os/routers/components/components.py +475 -0
- agno/os/routers/database.py +155 -0
- agno/os/routers/evals/evals.py +111 -18
- agno/os/routers/evals/schemas.py +38 -5
- agno/os/routers/evals/utils.py +80 -11
- agno/os/routers/health.py +3 -3
- agno/os/routers/knowledge/knowledge.py +284 -35
- agno/os/routers/knowledge/schemas.py +14 -2
- agno/os/routers/memory/memory.py +274 -11
- agno/os/routers/memory/schemas.py +44 -3
- agno/os/routers/metrics/metrics.py +30 -15
- agno/os/routers/metrics/schemas.py +10 -6
- agno/os/routers/registry/__init__.py +3 -0
- agno/os/routers/registry/registry.py +337 -0
- agno/os/routers/session/session.py +143 -14
- agno/os/routers/teams/__init__.py +3 -0
- agno/os/routers/teams/router.py +550 -0
- agno/os/routers/teams/schema.py +280 -0
- agno/os/routers/traces/__init__.py +3 -0
- agno/os/routers/traces/schemas.py +414 -0
- agno/os/routers/traces/traces.py +549 -0
- agno/os/routers/workflows/__init__.py +3 -0
- agno/os/routers/workflows/router.py +757 -0
- agno/os/routers/workflows/schema.py +139 -0
- agno/os/schema.py +157 -584
- agno/os/scopes.py +469 -0
- agno/os/settings.py +3 -0
- agno/os/utils.py +574 -185
- agno/reasoning/anthropic.py +85 -1
- agno/reasoning/azure_ai_foundry.py +93 -1
- agno/reasoning/deepseek.py +102 -2
- agno/reasoning/default.py +6 -7
- agno/reasoning/gemini.py +87 -3
- agno/reasoning/groq.py +109 -2
- agno/reasoning/helpers.py +6 -7
- agno/reasoning/manager.py +1238 -0
- agno/reasoning/ollama.py +93 -1
- agno/reasoning/openai.py +115 -1
- agno/reasoning/vertexai.py +85 -1
- agno/registry/__init__.py +3 -0
- agno/registry/registry.py +68 -0
- agno/remote/__init__.py +3 -0
- agno/remote/base.py +581 -0
- agno/run/__init__.py +2 -4
- agno/run/agent.py +134 -19
- agno/run/base.py +49 -1
- agno/run/cancel.py +65 -52
- agno/run/cancellation_management/__init__.py +9 -0
- agno/run/cancellation_management/base.py +78 -0
- agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
- agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
- agno/run/requirement.py +181 -0
- agno/run/team.py +111 -19
- agno/run/workflow.py +2 -1
- agno/session/agent.py +57 -92
- agno/session/summary.py +1 -1
- agno/session/team.py +62 -115
- agno/session/workflow.py +353 -57
- agno/skills/__init__.py +17 -0
- agno/skills/agent_skills.py +377 -0
- agno/skills/errors.py +32 -0
- agno/skills/loaders/__init__.py +4 -0
- agno/skills/loaders/base.py +27 -0
- agno/skills/loaders/local.py +216 -0
- agno/skills/skill.py +65 -0
- agno/skills/utils.py +107 -0
- agno/skills/validator.py +277 -0
- agno/table.py +10 -0
- agno/team/__init__.py +5 -1
- agno/team/remote.py +447 -0
- agno/team/team.py +3769 -2202
- agno/tools/brandfetch.py +27 -18
- agno/tools/browserbase.py +225 -16
- agno/tools/crawl4ai.py +3 -0
- agno/tools/duckduckgo.py +25 -71
- agno/tools/exa.py +0 -21
- agno/tools/file.py +14 -13
- agno/tools/file_generation.py +12 -6
- agno/tools/firecrawl.py +15 -7
- agno/tools/function.py +94 -113
- agno/tools/google_bigquery.py +11 -2
- agno/tools/google_drive.py +4 -3
- agno/tools/knowledge.py +9 -4
- agno/tools/mcp/mcp.py +301 -18
- agno/tools/mcp/multi_mcp.py +269 -14
- agno/tools/mem0.py +11 -10
- agno/tools/memory.py +47 -46
- agno/tools/mlx_transcribe.py +10 -7
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/nano_banana.py +151 -0
- agno/tools/parallel.py +0 -7
- agno/tools/postgres.py +76 -36
- agno/tools/python.py +14 -6
- agno/tools/reasoning.py +30 -23
- agno/tools/redshift.py +406 -0
- agno/tools/shopify.py +1519 -0
- agno/tools/spotify.py +919 -0
- agno/tools/tavily.py +4 -1
- agno/tools/toolkit.py +253 -18
- agno/tools/websearch.py +93 -0
- agno/tools/website.py +1 -1
- agno/tools/wikipedia.py +1 -1
- agno/tools/workflow.py +56 -48
- agno/tools/yfinance.py +12 -11
- agno/tracing/__init__.py +12 -0
- agno/tracing/exporter.py +161 -0
- agno/tracing/schemas.py +276 -0
- agno/tracing/setup.py +112 -0
- agno/utils/agent.py +251 -10
- agno/utils/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +264 -7
- agno/utils/hooks.py +111 -3
- agno/utils/http.py +161 -2
- agno/utils/mcp.py +49 -8
- agno/utils/media.py +22 -1
- agno/utils/models/ai_foundry.py +9 -2
- agno/utils/models/claude.py +20 -5
- agno/utils/models/cohere.py +9 -2
- agno/utils/models/llama.py +9 -2
- agno/utils/models/mistral.py +4 -2
- agno/utils/os.py +0 -0
- agno/utils/print_response/agent.py +99 -16
- agno/utils/print_response/team.py +223 -24
- agno/utils/print_response/workflow.py +0 -2
- agno/utils/prompts.py +8 -6
- agno/utils/remote.py +23 -0
- agno/utils/response.py +1 -13
- agno/utils/string.py +91 -2
- agno/utils/team.py +62 -12
- agno/utils/tokens.py +657 -0
- agno/vectordb/base.py +15 -2
- agno/vectordb/cassandra/cassandra.py +1 -1
- agno/vectordb/chroma/__init__.py +2 -1
- agno/vectordb/chroma/chromadb.py +468 -23
- agno/vectordb/clickhouse/clickhousedb.py +1 -1
- agno/vectordb/couchbase/couchbase.py +6 -2
- agno/vectordb/lancedb/lance_db.py +7 -38
- agno/vectordb/lightrag/lightrag.py +7 -6
- agno/vectordb/milvus/milvus.py +118 -84
- agno/vectordb/mongodb/__init__.py +2 -1
- agno/vectordb/mongodb/mongodb.py +14 -31
- agno/vectordb/pgvector/pgvector.py +120 -66
- agno/vectordb/pineconedb/pineconedb.py +2 -19
- agno/vectordb/qdrant/__init__.py +2 -1
- agno/vectordb/qdrant/qdrant.py +33 -56
- agno/vectordb/redis/__init__.py +2 -1
- agno/vectordb/redis/redisdb.py +19 -31
- agno/vectordb/singlestore/singlestore.py +17 -9
- agno/vectordb/surrealdb/surrealdb.py +2 -38
- agno/vectordb/weaviate/__init__.py +2 -1
- agno/vectordb/weaviate/weaviate.py +7 -3
- agno/workflow/__init__.py +5 -1
- agno/workflow/agent.py +2 -2
- agno/workflow/condition.py +12 -10
- agno/workflow/loop.py +28 -9
- agno/workflow/parallel.py +21 -13
- agno/workflow/remote.py +362 -0
- agno/workflow/router.py +12 -9
- agno/workflow/step.py +261 -36
- agno/workflow/steps.py +12 -8
- agno/workflow/types.py +40 -77
- agno/workflow/workflow.py +939 -213
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/METADATA +134 -181
- agno-2.4.3.dist-info/RECORD +677 -0
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/WHEEL +1 -1
- agno/tools/googlesearch.py +0 -98
- agno/tools/memori.py +0 -339
- agno-2.2.13.dist-info/RECORD +0 -575
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/licenses/LICENSE +0 -0
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/top_level.txt +0 -0
agno/learn/utils.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Learning Machine Utilities
|
|
3
|
+
==========================
|
|
4
|
+
Helper functions for safe data handling.
|
|
5
|
+
|
|
6
|
+
All functions are designed to never raise exceptions -
|
|
7
|
+
they return None on any failure. This prevents learning
|
|
8
|
+
extraction errors from crashing the main agent.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from dataclasses import asdict, fields
|
|
12
|
+
from typing import Any, Dict, List, Optional, Type, TypeVar
|
|
13
|
+
|
|
14
|
+
T = TypeVar("T")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _safe_get(data: Any, key: str, default: Any = None) -> Any:
|
|
18
|
+
"""Safely get a key from dict-like data.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
data: Dict or object with attributes.
|
|
22
|
+
key: Key or attribute name to get.
|
|
23
|
+
default: Value to return if not found.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
The value, or default if not found.
|
|
27
|
+
"""
|
|
28
|
+
if isinstance(data, dict):
|
|
29
|
+
return data.get(key, default)
|
|
30
|
+
return getattr(data, key, default)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _parse_json(data: Any) -> Optional[Dict]:
|
|
34
|
+
"""Parse JSON string to dict, or return dict as-is.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
data: JSON string, dict, or None.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Parsed dict, or None if parsing fails.
|
|
41
|
+
"""
|
|
42
|
+
if data is None:
|
|
43
|
+
return None
|
|
44
|
+
if isinstance(data, dict):
|
|
45
|
+
return data
|
|
46
|
+
if isinstance(data, str):
|
|
47
|
+
import json
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
return json.loads(data)
|
|
51
|
+
except Exception:
|
|
52
|
+
return None
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def from_dict_safe(cls: Type[T], data: Any) -> Optional[T]:
|
|
57
|
+
"""Safely create a dataclass instance from dict-like data.
|
|
58
|
+
|
|
59
|
+
Works with any dataclass - automatically handles subclass fields.
|
|
60
|
+
Never raises - returns None on any failure.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
cls: The dataclass type to instantiate.
|
|
64
|
+
data: Dict, JSON string, or existing instance.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Instance of cls, or None if parsing fails.
|
|
68
|
+
|
|
69
|
+
Example:
|
|
70
|
+
>>> profile = from_dict_safe(UserProfile, {"user_id": "123"})
|
|
71
|
+
>>> profile.user_id
|
|
72
|
+
'123'
|
|
73
|
+
"""
|
|
74
|
+
if data is None:
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
# Already the right type
|
|
78
|
+
if isinstance(data, cls):
|
|
79
|
+
return data
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
# Parse JSON string if needed
|
|
83
|
+
parsed = _parse_json(data)
|
|
84
|
+
if parsed is None:
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
# Get valid field names for this class
|
|
88
|
+
field_names = {f.name for f in fields(cls)} # type: ignore
|
|
89
|
+
|
|
90
|
+
# Filter to only valid fields
|
|
91
|
+
kwargs = {k: v for k, v in parsed.items() if k in field_names}
|
|
92
|
+
|
|
93
|
+
return cls(**kwargs)
|
|
94
|
+
except Exception:
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def print_panel(
|
|
99
|
+
title: str,
|
|
100
|
+
subtitle: str,
|
|
101
|
+
lines: List[str],
|
|
102
|
+
*,
|
|
103
|
+
empty_message: str = "No data",
|
|
104
|
+
raw_data: Any = None,
|
|
105
|
+
raw: bool = False,
|
|
106
|
+
) -> None:
|
|
107
|
+
"""Print formatted panel output for learning stores.
|
|
108
|
+
|
|
109
|
+
Uses rich library for formatted output with a bordered panel.
|
|
110
|
+
Falls back to pprint when raw=True or rich is unavailable.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
title: Panel title (e.g., "User Profile", "Session Context")
|
|
114
|
+
subtitle: Panel subtitle (e.g., user_id, session_id)
|
|
115
|
+
lines: Content lines to display inside the panel
|
|
116
|
+
empty_message: Message shown when lines is empty
|
|
117
|
+
raw_data: Object to pprint when raw=True
|
|
118
|
+
raw: If True, use pprint instead of formatted panel
|
|
119
|
+
|
|
120
|
+
Example:
|
|
121
|
+
>>> print_panel(
|
|
122
|
+
... title="User Profile",
|
|
123
|
+
... subtitle="alice@example.com",
|
|
124
|
+
... lines=["Name: Alice", "Memories:", " [abc123] Loves Python"],
|
|
125
|
+
... raw_data=profile,
|
|
126
|
+
... )
|
|
127
|
+
╭──────────────── User Profile ─────────────────╮
|
|
128
|
+
│ Name: Alice │
|
|
129
|
+
│ Memories: │
|
|
130
|
+
│ [abc123] Loves Python │
|
|
131
|
+
╰─────────────── alice@example.com ─────────────╯
|
|
132
|
+
"""
|
|
133
|
+
if raw and raw_data is not None:
|
|
134
|
+
from pprint import pprint
|
|
135
|
+
|
|
136
|
+
pprint(to_dict_safe(raw_data) or raw_data)
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
from rich.console import Console
|
|
141
|
+
from rich.panel import Panel
|
|
142
|
+
|
|
143
|
+
console = Console()
|
|
144
|
+
|
|
145
|
+
if not lines:
|
|
146
|
+
content = f"[dim]{empty_message}[/dim]"
|
|
147
|
+
else:
|
|
148
|
+
content = "\n".join(lines)
|
|
149
|
+
|
|
150
|
+
panel = Panel(
|
|
151
|
+
content,
|
|
152
|
+
title=f"[bold]{title}[/bold]",
|
|
153
|
+
subtitle=f"[dim]{subtitle}[/dim]",
|
|
154
|
+
border_style="blue",
|
|
155
|
+
)
|
|
156
|
+
console.print(panel)
|
|
157
|
+
|
|
158
|
+
except ImportError:
|
|
159
|
+
# Fallback if rich not installed
|
|
160
|
+
from pprint import pprint
|
|
161
|
+
|
|
162
|
+
print(f"=== {title} ({subtitle}) ===")
|
|
163
|
+
if not lines:
|
|
164
|
+
print(f" {empty_message}")
|
|
165
|
+
else:
|
|
166
|
+
for line in lines:
|
|
167
|
+
print(f" {line}")
|
|
168
|
+
print()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def to_dict_safe(obj: Any) -> Optional[Dict[str, Any]]:
|
|
172
|
+
"""Safely convert a dataclass to dict.
|
|
173
|
+
|
|
174
|
+
Works with any dataclass. Never raises - returns None on failure.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
obj: Dataclass instance to convert.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Dict representation, or None if conversion fails.
|
|
181
|
+
|
|
182
|
+
Example:
|
|
183
|
+
>>> profile = UserProfile(user_id="123")
|
|
184
|
+
>>> to_dict_safe(profile)
|
|
185
|
+
{'user_id': '123', 'name': None, ...}
|
|
186
|
+
"""
|
|
187
|
+
if obj is None:
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
# Already a dict
|
|
192
|
+
if isinstance(obj, dict):
|
|
193
|
+
return obj
|
|
194
|
+
|
|
195
|
+
# Has to_dict method
|
|
196
|
+
if hasattr(obj, "to_dict"):
|
|
197
|
+
return obj.to_dict()
|
|
198
|
+
|
|
199
|
+
# Is a dataclass
|
|
200
|
+
if hasattr(obj, "__dataclass_fields__"):
|
|
201
|
+
return asdict(obj)
|
|
202
|
+
|
|
203
|
+
# Has __dict__
|
|
204
|
+
if hasattr(obj, "__dict__"):
|
|
205
|
+
return dict(obj.__dict__)
|
|
206
|
+
|
|
207
|
+
return None
|
|
208
|
+
except Exception:
|
|
209
|
+
return None
|
agno/media.py
CHANGED
|
@@ -4,6 +4,8 @@ from uuid import uuid4
|
|
|
4
4
|
|
|
5
5
|
from pydantic import BaseModel, field_validator, model_validator
|
|
6
6
|
|
|
7
|
+
from agno.utils.log import log_error
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
class Image(BaseModel):
|
|
9
11
|
"""Unified Image class for all use cases (input, output, artifacts)"""
|
|
@@ -395,10 +397,20 @@ class File(BaseModel):
|
|
|
395
397
|
name: Optional[str] = None,
|
|
396
398
|
format: Optional[str] = None,
|
|
397
399
|
) -> "File":
|
|
398
|
-
"""Create File from base64 encoded content
|
|
400
|
+
"""Create File from base64 encoded content or plain text.
|
|
401
|
+
|
|
402
|
+
Handles both base64-encoded binary content and plain text content
|
|
403
|
+
(which is stored as UTF-8 strings for text/* MIME types).
|
|
404
|
+
"""
|
|
399
405
|
import base64
|
|
400
406
|
|
|
401
|
-
|
|
407
|
+
try:
|
|
408
|
+
content_bytes = base64.b64decode(base64_content)
|
|
409
|
+
except Exception:
|
|
410
|
+
# If not valid base64, it might be plain text content (text/csv, text/plain, etc.)
|
|
411
|
+
# which is stored as UTF-8 strings, not base64
|
|
412
|
+
content_bytes = base64_content.encode("utf-8")
|
|
413
|
+
|
|
402
414
|
return cls(
|
|
403
415
|
content=content_bytes,
|
|
404
416
|
id=id,
|
|
@@ -413,10 +425,14 @@ class File(BaseModel):
|
|
|
413
425
|
import httpx
|
|
414
426
|
|
|
415
427
|
if self.url:
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
428
|
+
try:
|
|
429
|
+
response = httpx.get(self.url)
|
|
430
|
+
content = response.content
|
|
431
|
+
mime_type = response.headers.get("Content-Type", "").split(";")[0]
|
|
432
|
+
return content, mime_type
|
|
433
|
+
except Exception:
|
|
434
|
+
log_error(f"Failed to download file from {self.url}")
|
|
435
|
+
return None
|
|
420
436
|
else:
|
|
421
437
|
return None
|
|
422
438
|
|
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__ = [
|
|
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,10 +8,16 @@ 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
|
|
15
19
|
from agno.tools.function import Function
|
|
20
|
+
from agno.utils.dttm import now_epoch_s
|
|
16
21
|
from agno.utils.log import (
|
|
17
22
|
log_debug,
|
|
18
23
|
log_error,
|
|
@@ -89,9 +94,6 @@ class MemoryManager:
|
|
|
89
94
|
self.clear_memories = clear_memories
|
|
90
95
|
self.debug_mode = debug_mode
|
|
91
96
|
|
|
92
|
-
self._get_models()
|
|
93
|
-
|
|
94
|
-
def _get_models(self) -> None:
|
|
95
97
|
if self.model is not None:
|
|
96
98
|
self.model = get_model(self.model)
|
|
97
99
|
|
|
@@ -227,7 +229,7 @@ class MemoryManager:
|
|
|
227
229
|
memory.user_id = user_id
|
|
228
230
|
|
|
229
231
|
if not memory.updated_at:
|
|
230
|
-
memory.updated_at =
|
|
232
|
+
memory.updated_at = now_epoch_s()
|
|
231
233
|
|
|
232
234
|
self._upsert_db_memory(memory=memory)
|
|
233
235
|
return memory.memory_id
|
|
@@ -255,7 +257,7 @@ class MemoryManager:
|
|
|
255
257
|
user_id = "default"
|
|
256
258
|
|
|
257
259
|
if not memory.updated_at:
|
|
258
|
-
memory.updated_at =
|
|
260
|
+
memory.updated_at = now_epoch_s()
|
|
259
261
|
|
|
260
262
|
memory.memory_id = memory_id
|
|
261
263
|
memory.user_id = user_id
|
|
@@ -291,6 +293,74 @@ class MemoryManager:
|
|
|
291
293
|
log_warning("Memory DB not provided.")
|
|
292
294
|
return None
|
|
293
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
|
+
|
|
294
364
|
# -*- Agent Functions
|
|
295
365
|
def create_user_memories(
|
|
296
366
|
self,
|
|
@@ -671,7 +741,7 @@ class MemoryManager:
|
|
|
671
741
|
# If updated_at is None, place at the beginning of the list
|
|
672
742
|
sorted_memories_list = sorted(
|
|
673
743
|
memories_list,
|
|
674
|
-
key=lambda
|
|
744
|
+
key=lambda m: m.updated_at if m.updated_at is not None else 0,
|
|
675
745
|
)
|
|
676
746
|
else:
|
|
677
747
|
sorted_memories_list = []
|
|
@@ -694,6 +764,7 @@ class MemoryManager:
|
|
|
694
764
|
if memories is None:
|
|
695
765
|
memories = {}
|
|
696
766
|
|
|
767
|
+
MAX_UNIX_TS = 2**63 - 1
|
|
697
768
|
memories_list = memories.get(user_id, [])
|
|
698
769
|
# Sort memories by updated_at timestamp if available
|
|
699
770
|
if memories_list:
|
|
@@ -701,7 +772,7 @@ class MemoryManager:
|
|
|
701
772
|
# If updated_at is None, place at the end of the list
|
|
702
773
|
sorted_memories_list = sorted(
|
|
703
774
|
memories_list,
|
|
704
|
-
key=lambda
|
|
775
|
+
key=lambda m: m.updated_at if m.updated_at is not None else MAX_UNIX_TS,
|
|
705
776
|
)
|
|
706
777
|
|
|
707
778
|
else:
|
|
@@ -712,6 +783,150 @@ class MemoryManager:
|
|
|
712
783
|
|
|
713
784
|
return sorted_memories_list
|
|
714
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
|
+
|
|
715
930
|
# --Memory Manager Functions--
|
|
716
931
|
def determine_tools_for_model(self, tools: List[Callable]) -> List[Union[Function, dict]]:
|
|
717
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,66 @@
|
|
|
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_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
|
+
Returns:
|
|
64
|
+
Total token count
|
|
65
|
+
"""
|
|
66
|
+
return sum(count_text_tokens(m.memory or "") for m in memories)
|