agno 0.1.2__py3-none-any.whl → 2.3.13__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/__init__.py +8 -0
- agno/agent/__init__.py +44 -5
- agno/agent/agent.py +10531 -2975
- agno/api/agent.py +14 -53
- agno/api/api.py +7 -46
- agno/api/evals.py +22 -0
- agno/api/os.py +17 -0
- agno/api/routes.py +6 -25
- agno/api/schemas/__init__.py +9 -0
- agno/api/schemas/agent.py +6 -9
- agno/api/schemas/evals.py +16 -0
- agno/api/schemas/os.py +14 -0
- agno/api/schemas/team.py +10 -10
- agno/api/schemas/utils.py +21 -0
- agno/api/schemas/workflows.py +16 -0
- agno/api/settings.py +53 -0
- agno/api/team.py +22 -26
- agno/api/workflow.py +28 -0
- agno/cloud/aws/base.py +214 -0
- agno/cloud/aws/s3/__init__.py +2 -0
- agno/cloud/aws/s3/api_client.py +43 -0
- agno/cloud/aws/s3/bucket.py +195 -0
- agno/cloud/aws/s3/object.py +57 -0
- agno/compression/__init__.py +3 -0
- agno/compression/manager.py +247 -0
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/__init__.py +24 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +946 -0
- agno/db/dynamo/__init__.py +3 -0
- agno/db/dynamo/dynamo.py +2781 -0
- agno/db/dynamo/schemas.py +442 -0
- agno/db/dynamo/utils.py +743 -0
- agno/db/firestore/__init__.py +3 -0
- agno/db/firestore/firestore.py +2379 -0
- agno/db/firestore/schemas.py +181 -0
- agno/db/firestore/utils.py +376 -0
- agno/db/gcs_json/__init__.py +3 -0
- agno/db/gcs_json/gcs_json_db.py +1791 -0
- agno/db/gcs_json/utils.py +228 -0
- agno/db/in_memory/__init__.py +3 -0
- agno/db/in_memory/in_memory_db.py +1312 -0
- agno/db/in_memory/utils.py +230 -0
- agno/db/json/__init__.py +3 -0
- agno/db/json/json_db.py +1777 -0
- agno/db/json/utils.py +230 -0
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +635 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/__init__.py +17 -0
- agno/db/mongo/async_mongo.py +2760 -0
- agno/db/mongo/mongo.py +2597 -0
- agno/db/mongo/schemas.py +119 -0
- agno/db/mongo/utils.py +276 -0
- agno/db/mysql/__init__.py +4 -0
- agno/db/mysql/async_mysql.py +2912 -0
- agno/db/mysql/mysql.py +2923 -0
- agno/db/mysql/schemas.py +186 -0
- agno/db/mysql/utils.py +488 -0
- agno/db/postgres/__init__.py +4 -0
- agno/db/postgres/async_postgres.py +2579 -0
- agno/db/postgres/postgres.py +2870 -0
- agno/db/postgres/schemas.py +187 -0
- agno/db/postgres/utils.py +442 -0
- agno/db/redis/__init__.py +3 -0
- agno/db/redis/redis.py +2141 -0
- agno/db/redis/schemas.py +159 -0
- agno/db/redis/utils.py +346 -0
- agno/db/schemas/__init__.py +4 -0
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/evals.py +34 -0
- agno/db/schemas/knowledge.py +40 -0
- agno/db/schemas/memory.py +61 -0
- agno/db/singlestore/__init__.py +3 -0
- agno/db/singlestore/schemas.py +179 -0
- agno/db/singlestore/singlestore.py +2877 -0
- agno/db/singlestore/utils.py +384 -0
- agno/db/sqlite/__init__.py +4 -0
- agno/db/sqlite/async_sqlite.py +2911 -0
- agno/db/sqlite/schemas.py +181 -0
- agno/db/sqlite/sqlite.py +2908 -0
- agno/db/sqlite/utils.py +429 -0
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +334 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1908 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +118 -0
- agno/eval/__init__.py +24 -0
- agno/eval/accuracy.py +666 -276
- agno/eval/agent_as_judge.py +861 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +779 -0
- agno/eval/reliability.py +241 -62
- agno/eval/utils.py +120 -0
- agno/exceptions.py +143 -1
- agno/filters.py +354 -0
- agno/guardrails/__init__.py +6 -0
- agno/guardrails/base.py +19 -0
- agno/guardrails/openai.py +144 -0
- agno/guardrails/pii.py +94 -0
- agno/guardrails/prompt_injection.py +52 -0
- agno/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/__init__.py +3 -0
- agno/integrations/discord/client.py +203 -0
- agno/knowledge/__init__.py +5 -1
- agno/{document → knowledge}/chunking/agentic.py +22 -14
- agno/{document → knowledge}/chunking/document.py +2 -2
- agno/{document → knowledge}/chunking/fixed.py +7 -6
- agno/knowledge/chunking/markdown.py +151 -0
- agno/{document → knowledge}/chunking/recursive.py +15 -3
- agno/knowledge/chunking/row.py +39 -0
- agno/knowledge/chunking/semantic.py +91 -0
- agno/knowledge/chunking/strategy.py +165 -0
- agno/knowledge/content.py +74 -0
- agno/knowledge/document/__init__.py +5 -0
- agno/{document → knowledge/document}/base.py +12 -2
- agno/knowledge/embedder/__init__.py +5 -0
- agno/knowledge/embedder/aws_bedrock.py +343 -0
- agno/knowledge/embedder/azure_openai.py +210 -0
- agno/{embedder → knowledge/embedder}/base.py +8 -0
- agno/knowledge/embedder/cohere.py +323 -0
- agno/knowledge/embedder/fastembed.py +62 -0
- agno/{embedder → knowledge/embedder}/fireworks.py +1 -1
- agno/knowledge/embedder/google.py +258 -0
- agno/knowledge/embedder/huggingface.py +94 -0
- agno/knowledge/embedder/jina.py +182 -0
- agno/knowledge/embedder/langdb.py +22 -0
- agno/knowledge/embedder/mistral.py +206 -0
- agno/knowledge/embedder/nebius.py +13 -0
- agno/knowledge/embedder/ollama.py +154 -0
- agno/knowledge/embedder/openai.py +195 -0
- agno/knowledge/embedder/sentence_transformer.py +63 -0
- agno/{embedder → knowledge/embedder}/together.py +1 -1
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/embedder/voyageai.py +165 -0
- agno/knowledge/knowledge.py +3006 -0
- agno/knowledge/reader/__init__.py +7 -0
- agno/knowledge/reader/arxiv_reader.py +81 -0
- agno/knowledge/reader/base.py +95 -0
- agno/knowledge/reader/csv_reader.py +164 -0
- agno/knowledge/reader/docx_reader.py +82 -0
- agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
- agno/knowledge/reader/firecrawl_reader.py +201 -0
- agno/knowledge/reader/json_reader.py +88 -0
- agno/knowledge/reader/markdown_reader.py +137 -0
- agno/knowledge/reader/pdf_reader.py +431 -0
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +313 -0
- agno/knowledge/reader/s3_reader.py +89 -0
- agno/knowledge/reader/tavily_reader.py +193 -0
- agno/knowledge/reader/text_reader.py +127 -0
- agno/knowledge/reader/web_search_reader.py +325 -0
- agno/knowledge/reader/website_reader.py +455 -0
- agno/knowledge/reader/wikipedia_reader.py +91 -0
- agno/knowledge/reader/youtube_reader.py +78 -0
- agno/knowledge/remote_content/remote_content.py +88 -0
- agno/knowledge/reranker/__init__.py +3 -0
- agno/{reranker → knowledge/reranker}/base.py +1 -1
- agno/{reranker → knowledge/reranker}/cohere.py +2 -2
- agno/knowledge/reranker/infinity.py +195 -0
- agno/knowledge/reranker/sentence_transformer.py +54 -0
- agno/knowledge/types.py +39 -0
- agno/knowledge/utils.py +234 -0
- agno/media.py +439 -95
- agno/memory/__init__.py +16 -3
- agno/memory/manager.py +1474 -123
- 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/__init__.py +5 -0
- agno/models/aimlapi/aimlapi.py +62 -0
- agno/models/anthropic/__init__.py +4 -0
- agno/models/anthropic/claude.py +960 -496
- agno/models/aws/__init__.py +15 -0
- agno/models/aws/bedrock.py +686 -451
- agno/models/aws/claude.py +190 -183
- agno/models/azure/__init__.py +18 -1
- agno/models/azure/ai_foundry.py +489 -0
- agno/models/azure/openai_chat.py +89 -40
- agno/models/base.py +2477 -550
- agno/models/cerebras/__init__.py +12 -0
- agno/models/cerebras/cerebras.py +565 -0
- agno/models/cerebras/cerebras_openai.py +131 -0
- agno/models/cohere/__init__.py +4 -0
- agno/models/cohere/chat.py +306 -492
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +74 -0
- agno/models/dashscope/__init__.py +5 -0
- agno/models/dashscope/dashscope.py +90 -0
- agno/models/deepinfra/__init__.py +5 -0
- agno/models/deepinfra/deepinfra.py +45 -0
- agno/models/deepseek/__init__.py +4 -0
- agno/models/deepseek/deepseek.py +110 -9
- agno/models/fireworks/__init__.py +4 -0
- agno/models/fireworks/fireworks.py +19 -22
- agno/models/google/__init__.py +3 -7
- agno/models/google/gemini.py +1717 -662
- agno/models/google/utils.py +22 -0
- agno/models/groq/__init__.py +4 -0
- agno/models/groq/groq.py +391 -666
- agno/models/huggingface/__init__.py +4 -0
- agno/models/huggingface/huggingface.py +266 -538
- agno/models/ibm/__init__.py +5 -0
- agno/models/ibm/watsonx.py +432 -0
- agno/models/internlm/__init__.py +3 -0
- agno/models/internlm/internlm.py +20 -3
- agno/models/langdb/__init__.py +1 -0
- agno/models/langdb/langdb.py +60 -0
- agno/models/litellm/__init__.py +14 -0
- agno/models/litellm/chat.py +503 -0
- agno/models/litellm/litellm_openai.py +42 -0
- agno/models/llama_cpp/__init__.py +5 -0
- agno/models/llama_cpp/llama_cpp.py +22 -0
- agno/models/lmstudio/__init__.py +5 -0
- agno/models/lmstudio/lmstudio.py +25 -0
- agno/models/message.py +361 -39
- agno/models/meta/__init__.py +12 -0
- agno/models/meta/llama.py +502 -0
- agno/models/meta/llama_openai.py +79 -0
- agno/models/metrics.py +120 -0
- agno/models/mistral/__init__.py +4 -0
- agno/models/mistral/mistral.py +293 -393
- agno/models/nebius/__init__.py +3 -0
- agno/models/nebius/nebius.py +53 -0
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +22 -0
- agno/models/nvidia/__init__.py +4 -0
- agno/models/nvidia/nvidia.py +22 -3
- agno/models/ollama/__init__.py +4 -2
- agno/models/ollama/chat.py +257 -492
- agno/models/openai/__init__.py +7 -0
- agno/models/openai/chat.py +725 -770
- agno/models/openai/like.py +16 -2
- agno/models/openai/responses.py +1121 -0
- agno/models/openrouter/__init__.py +4 -0
- agno/models/openrouter/openrouter.py +62 -5
- agno/models/perplexity/__init__.py +5 -0
- agno/models/perplexity/perplexity.py +203 -0
- agno/models/portkey/__init__.py +3 -0
- agno/models/portkey/portkey.py +82 -0
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +69 -0
- agno/models/response.py +177 -7
- agno/models/sambanova/__init__.py +4 -0
- agno/models/sambanova/sambanova.py +23 -4
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +42 -0
- agno/models/together/__init__.py +4 -0
- agno/models/together/together.py +21 -164
- agno/models/utils.py +266 -0
- agno/models/vercel/__init__.py +3 -0
- agno/models/vercel/v0.py +43 -0
- agno/models/vertexai/__init__.py +0 -1
- agno/models/vertexai/claude.py +190 -0
- agno/models/vllm/__init__.py +3 -0
- agno/models/vllm/vllm.py +83 -0
- agno/models/xai/__init__.py +2 -0
- agno/models/xai/xai.py +111 -7
- agno/os/__init__.py +3 -0
- agno/os/app.py +1027 -0
- agno/os/auth.py +244 -0
- agno/os/config.py +126 -0
- agno/os/interfaces/__init__.py +1 -0
- agno/os/interfaces/a2a/__init__.py +3 -0
- agno/os/interfaces/a2a/a2a.py +42 -0
- agno/os/interfaces/a2a/router.py +249 -0
- agno/os/interfaces/a2a/utils.py +924 -0
- agno/os/interfaces/agui/__init__.py +3 -0
- agno/os/interfaces/agui/agui.py +47 -0
- agno/os/interfaces/agui/router.py +147 -0
- agno/os/interfaces/agui/utils.py +574 -0
- agno/os/interfaces/base.py +25 -0
- agno/os/interfaces/slack/__init__.py +3 -0
- agno/os/interfaces/slack/router.py +148 -0
- agno/os/interfaces/slack/security.py +30 -0
- agno/os/interfaces/slack/slack.py +47 -0
- agno/os/interfaces/whatsapp/__init__.py +3 -0
- agno/os/interfaces/whatsapp/router.py +210 -0
- agno/os/interfaces/whatsapp/security.py +55 -0
- agno/os/interfaces/whatsapp/whatsapp.py +36 -0
- agno/os/mcp.py +293 -0
- agno/os/middleware/__init__.py +9 -0
- agno/os/middleware/jwt.py +797 -0
- agno/os/router.py +258 -0
- agno/os/routers/__init__.py +3 -0
- agno/os/routers/agents/__init__.py +3 -0
- agno/os/routers/agents/router.py +599 -0
- agno/os/routers/agents/schema.py +261 -0
- agno/os/routers/evals/__init__.py +3 -0
- agno/os/routers/evals/evals.py +450 -0
- agno/os/routers/evals/schemas.py +174 -0
- agno/os/routers/evals/utils.py +231 -0
- agno/os/routers/health.py +31 -0
- agno/os/routers/home.py +52 -0
- agno/os/routers/knowledge/__init__.py +3 -0
- agno/os/routers/knowledge/knowledge.py +1008 -0
- agno/os/routers/knowledge/schemas.py +178 -0
- agno/os/routers/memory/__init__.py +3 -0
- agno/os/routers/memory/memory.py +661 -0
- agno/os/routers/memory/schemas.py +88 -0
- agno/os/routers/metrics/__init__.py +3 -0
- agno/os/routers/metrics/metrics.py +190 -0
- agno/os/routers/metrics/schemas.py +47 -0
- agno/os/routers/session/__init__.py +3 -0
- agno/os/routers/session/session.py +997 -0
- agno/os/routers/teams/__init__.py +3 -0
- agno/os/routers/teams/router.py +512 -0
- agno/os/routers/teams/schema.py +257 -0
- agno/os/routers/traces/__init__.py +3 -0
- agno/os/routers/traces/schemas.py +414 -0
- agno/os/routers/traces/traces.py +499 -0
- agno/os/routers/workflows/__init__.py +3 -0
- agno/os/routers/workflows/router.py +624 -0
- agno/os/routers/workflows/schema.py +75 -0
- agno/os/schema.py +534 -0
- agno/os/scopes.py +469 -0
- agno/{playground → os}/settings.py +7 -15
- agno/os/utils.py +973 -0
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/azure_ai_foundry.py +67 -0
- agno/reasoning/deepseek.py +63 -0
- agno/reasoning/default.py +97 -0
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/groq.py +71 -0
- agno/reasoning/helpers.py +24 -1
- agno/reasoning/ollama.py +67 -0
- agno/reasoning/openai.py +86 -0
- agno/reasoning/step.py +2 -1
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +822 -0
- agno/run/base.py +247 -0
- agno/run/cancel.py +81 -0
- agno/run/requirement.py +181 -0
- agno/run/team.py +767 -0
- agno/run/workflow.py +708 -0
- agno/session/__init__.py +10 -0
- agno/session/agent.py +260 -0
- agno/session/summary.py +265 -0
- agno/session/team.py +342 -0
- agno/session/workflow.py +501 -0
- agno/table.py +10 -0
- agno/team/__init__.py +37 -0
- agno/team/team.py +9536 -0
- agno/tools/__init__.py +7 -0
- agno/tools/agentql.py +120 -0
- agno/tools/airflow.py +22 -12
- agno/tools/api.py +122 -0
- agno/tools/apify.py +276 -83
- agno/tools/{arxiv_toolkit.py → arxiv.py} +20 -12
- agno/tools/aws_lambda.py +28 -7
- agno/tools/aws_ses.py +66 -0
- agno/tools/baidusearch.py +11 -4
- agno/tools/bitbucket.py +292 -0
- agno/tools/brandfetch.py +213 -0
- agno/tools/bravesearch.py +106 -0
- agno/tools/brightdata.py +367 -0
- agno/tools/browserbase.py +209 -0
- agno/tools/calcom.py +32 -23
- agno/tools/calculator.py +24 -37
- agno/tools/cartesia.py +187 -0
- agno/tools/{clickup_tool.py → clickup.py} +17 -28
- agno/tools/confluence.py +91 -26
- agno/tools/crawl4ai.py +139 -43
- agno/tools/csv_toolkit.py +28 -22
- agno/tools/dalle.py +36 -22
- agno/tools/daytona.py +475 -0
- agno/tools/decorator.py +169 -14
- agno/tools/desi_vocal.py +23 -11
- agno/tools/discord.py +32 -29
- agno/tools/docker.py +716 -0
- agno/tools/duckdb.py +76 -81
- agno/tools/duckduckgo.py +43 -40
- agno/tools/e2b.py +703 -0
- agno/tools/eleven_labs.py +65 -54
- agno/tools/email.py +13 -5
- agno/tools/evm.py +129 -0
- agno/tools/exa.py +324 -42
- agno/tools/fal.py +39 -35
- agno/tools/file.py +196 -30
- agno/tools/file_generation.py +356 -0
- agno/tools/financial_datasets.py +288 -0
- agno/tools/firecrawl.py +108 -33
- agno/tools/function.py +960 -122
- agno/tools/giphy.py +34 -12
- agno/tools/github.py +1294 -97
- agno/tools/gmail.py +922 -0
- agno/tools/google_bigquery.py +117 -0
- agno/tools/google_drive.py +271 -0
- agno/tools/google_maps.py +253 -0
- agno/tools/googlecalendar.py +607 -107
- agno/tools/googlesheets.py +377 -0
- agno/tools/hackernews.py +20 -12
- agno/tools/jina.py +24 -14
- agno/tools/jira.py +48 -19
- agno/tools/knowledge.py +218 -0
- agno/tools/linear.py +82 -43
- agno/tools/linkup.py +58 -0
- agno/tools/local_file_system.py +15 -7
- agno/tools/lumalab.py +41 -26
- agno/tools/mcp/__init__.py +10 -0
- agno/tools/mcp/mcp.py +331 -0
- agno/tools/mcp/multi_mcp.py +347 -0
- agno/tools/mcp/params.py +24 -0
- agno/tools/mcp_toolbox.py +284 -0
- agno/tools/mem0.py +193 -0
- agno/tools/memory.py +419 -0
- agno/tools/mlx_transcribe.py +11 -9
- agno/tools/models/azure_openai.py +190 -0
- agno/tools/models/gemini.py +203 -0
- agno/tools/models/groq.py +158 -0
- agno/tools/models/morph.py +186 -0
- agno/tools/models/nebius.py +124 -0
- agno/tools/models_labs.py +163 -82
- agno/tools/moviepy_video.py +18 -13
- agno/tools/nano_banana.py +151 -0
- agno/tools/neo4j.py +134 -0
- agno/tools/newspaper.py +15 -4
- agno/tools/newspaper4k.py +19 -6
- agno/tools/notion.py +204 -0
- agno/tools/openai.py +181 -17
- agno/tools/openbb.py +27 -20
- agno/tools/opencv.py +321 -0
- agno/tools/openweather.py +233 -0
- agno/tools/oxylabs.py +385 -0
- agno/tools/pandas.py +25 -15
- agno/tools/parallel.py +314 -0
- agno/tools/postgres.py +238 -185
- agno/tools/pubmed.py +125 -13
- agno/tools/python.py +48 -35
- agno/tools/reasoning.py +283 -0
- agno/tools/reddit.py +207 -29
- agno/tools/redshift.py +406 -0
- agno/tools/replicate.py +69 -26
- agno/tools/resend.py +11 -6
- agno/tools/scrapegraph.py +179 -19
- agno/tools/searxng.py +23 -31
- agno/tools/serpapi.py +15 -10
- agno/tools/serper.py +255 -0
- agno/tools/shell.py +23 -12
- agno/tools/shopify.py +1519 -0
- agno/tools/slack.py +56 -14
- agno/tools/sleep.py +8 -6
- agno/tools/spider.py +35 -11
- agno/tools/spotify.py +919 -0
- agno/tools/sql.py +34 -19
- agno/tools/tavily.py +158 -8
- agno/tools/telegram.py +18 -8
- agno/tools/todoist.py +218 -0
- agno/tools/toolkit.py +134 -9
- agno/tools/trafilatura.py +388 -0
- agno/tools/trello.py +25 -28
- agno/tools/twilio.py +18 -9
- agno/tools/user_control_flow.py +78 -0
- agno/tools/valyu.py +228 -0
- agno/tools/visualization.py +467 -0
- agno/tools/webbrowser.py +28 -0
- agno/tools/webex.py +76 -0
- agno/tools/website.py +23 -19
- agno/tools/webtools.py +45 -0
- agno/tools/whatsapp.py +286 -0
- agno/tools/wikipedia.py +28 -19
- agno/tools/workflow.py +285 -0
- agno/tools/{twitter.py → x.py} +142 -46
- agno/tools/yfinance.py +41 -39
- agno/tools/youtube.py +34 -17
- agno/tools/zendesk.py +15 -5
- agno/tools/zep.py +454 -0
- agno/tools/zoom.py +86 -37
- agno/tracing/__init__.py +12 -0
- agno/tracing/exporter.py +157 -0
- agno/tracing/schemas.py +276 -0
- agno/tracing/setup.py +111 -0
- agno/utils/agent.py +938 -0
- agno/utils/audio.py +37 -1
- agno/utils/certs.py +27 -0
- agno/utils/code_execution.py +11 -0
- agno/utils/common.py +103 -20
- agno/utils/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +700 -0
- agno/utils/functions.py +107 -37
- agno/utils/gemini.py +426 -0
- agno/utils/hooks.py +171 -0
- agno/utils/http.py +185 -0
- agno/utils/json_schema.py +159 -37
- agno/utils/knowledge.py +36 -0
- agno/utils/location.py +19 -0
- agno/utils/log.py +221 -8
- agno/utils/mcp.py +214 -0
- agno/utils/media.py +335 -14
- agno/utils/merge_dict.py +22 -1
- agno/utils/message.py +77 -2
- agno/utils/models/ai_foundry.py +50 -0
- agno/utils/models/claude.py +373 -0
- agno/utils/models/cohere.py +94 -0
- agno/utils/models/llama.py +85 -0
- agno/utils/models/mistral.py +100 -0
- agno/utils/models/openai_responses.py +140 -0
- agno/utils/models/schema_utils.py +153 -0
- agno/utils/models/watsonx.py +41 -0
- agno/utils/openai.py +257 -0
- agno/utils/pickle.py +1 -1
- agno/utils/pprint.py +124 -8
- agno/utils/print_response/agent.py +930 -0
- agno/utils/print_response/team.py +1914 -0
- agno/utils/print_response/workflow.py +1668 -0
- agno/utils/prompts.py +111 -0
- agno/utils/reasoning.py +108 -0
- agno/utils/response.py +163 -0
- agno/utils/serialize.py +32 -0
- agno/utils/shell.py +4 -4
- agno/utils/streamlit.py +487 -0
- agno/utils/string.py +204 -51
- agno/utils/team.py +139 -0
- agno/utils/timer.py +9 -2
- agno/utils/tokens.py +657 -0
- agno/utils/tools.py +19 -1
- agno/utils/whatsapp.py +305 -0
- agno/utils/yaml_io.py +3 -3
- agno/vectordb/__init__.py +2 -0
- agno/vectordb/base.py +87 -9
- agno/vectordb/cassandra/__init__.py +5 -1
- agno/vectordb/cassandra/cassandra.py +383 -27
- agno/vectordb/chroma/__init__.py +4 -0
- agno/vectordb/chroma/chromadb.py +748 -83
- agno/vectordb/clickhouse/__init__.py +7 -1
- agno/vectordb/clickhouse/clickhousedb.py +554 -53
- agno/vectordb/couchbase/__init__.py +3 -0
- agno/vectordb/couchbase/couchbase.py +1446 -0
- agno/vectordb/lancedb/__init__.py +5 -0
- agno/vectordb/lancedb/lance_db.py +730 -98
- agno/vectordb/langchaindb/__init__.py +5 -0
- agno/vectordb/langchaindb/langchaindb.py +163 -0
- agno/vectordb/lightrag/__init__.py +5 -0
- agno/vectordb/lightrag/lightrag.py +388 -0
- agno/vectordb/llamaindex/__init__.py +3 -0
- agno/vectordb/llamaindex/llamaindexdb.py +166 -0
- agno/vectordb/milvus/__init__.py +3 -0
- agno/vectordb/milvus/milvus.py +966 -78
- agno/vectordb/mongodb/__init__.py +9 -1
- agno/vectordb/mongodb/mongodb.py +1175 -172
- agno/vectordb/pgvector/__init__.py +8 -0
- agno/vectordb/pgvector/pgvector.py +599 -115
- agno/vectordb/pineconedb/__init__.py +5 -1
- agno/vectordb/pineconedb/pineconedb.py +406 -43
- agno/vectordb/qdrant/__init__.py +4 -0
- agno/vectordb/qdrant/qdrant.py +914 -61
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +682 -0
- agno/vectordb/singlestore/__init__.py +8 -1
- agno/vectordb/singlestore/singlestore.py +771 -0
- agno/vectordb/surrealdb/__init__.py +3 -0
- agno/vectordb/surrealdb/surrealdb.py +663 -0
- agno/vectordb/upstashdb/__init__.py +5 -0
- agno/vectordb/upstashdb/upstashdb.py +718 -0
- agno/vectordb/weaviate/__init__.py +8 -0
- agno/vectordb/weaviate/index.py +15 -0
- agno/vectordb/weaviate/weaviate.py +1009 -0
- agno/workflow/__init__.py +23 -1
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +759 -0
- agno/workflow/loop.py +756 -0
- agno/workflow/parallel.py +853 -0
- agno/workflow/router.py +723 -0
- agno/workflow/step.py +1564 -0
- agno/workflow/steps.py +613 -0
- agno/workflow/types.py +556 -0
- agno/workflow/workflow.py +4327 -514
- agno-2.3.13.dist-info/METADATA +639 -0
- agno-2.3.13.dist-info/RECORD +613 -0
- {agno-0.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +1 -1
- agno-2.3.13.dist-info/licenses/LICENSE +201 -0
- agno/api/playground.py +0 -91
- agno/api/schemas/playground.py +0 -22
- agno/api/schemas/user.py +0 -22
- agno/api/schemas/workspace.py +0 -46
- agno/api/user.py +0 -160
- agno/api/workspace.py +0 -151
- agno/cli/auth_server.py +0 -118
- agno/cli/config.py +0 -275
- agno/cli/console.py +0 -88
- agno/cli/credentials.py +0 -23
- agno/cli/entrypoint.py +0 -571
- agno/cli/operator.py +0 -355
- agno/cli/settings.py +0 -85
- agno/cli/ws/ws_cli.py +0 -817
- agno/constants.py +0 -13
- agno/document/__init__.py +0 -1
- agno/document/chunking/semantic.py +0 -47
- agno/document/chunking/strategy.py +0 -31
- agno/document/reader/__init__.py +0 -1
- agno/document/reader/arxiv_reader.py +0 -41
- agno/document/reader/base.py +0 -22
- agno/document/reader/csv_reader.py +0 -84
- agno/document/reader/docx_reader.py +0 -46
- agno/document/reader/firecrawl_reader.py +0 -99
- agno/document/reader/json_reader.py +0 -43
- agno/document/reader/pdf_reader.py +0 -219
- agno/document/reader/s3/pdf_reader.py +0 -46
- agno/document/reader/s3/text_reader.py +0 -51
- agno/document/reader/text_reader.py +0 -41
- agno/document/reader/website_reader.py +0 -175
- agno/document/reader/youtube_reader.py +0 -50
- agno/embedder/__init__.py +0 -1
- agno/embedder/azure_openai.py +0 -86
- agno/embedder/cohere.py +0 -72
- agno/embedder/fastembed.py +0 -37
- agno/embedder/google.py +0 -73
- agno/embedder/huggingface.py +0 -54
- agno/embedder/mistral.py +0 -80
- agno/embedder/ollama.py +0 -57
- agno/embedder/openai.py +0 -74
- agno/embedder/sentence_transformer.py +0 -38
- agno/embedder/voyageai.py +0 -64
- agno/eval/perf.py +0 -201
- agno/file/__init__.py +0 -1
- agno/file/file.py +0 -16
- agno/file/local/csv.py +0 -32
- agno/file/local/txt.py +0 -19
- agno/infra/app.py +0 -240
- agno/infra/base.py +0 -144
- agno/infra/context.py +0 -20
- agno/infra/db_app.py +0 -52
- agno/infra/resource.py +0 -205
- agno/infra/resources.py +0 -55
- agno/knowledge/agent.py +0 -230
- agno/knowledge/arxiv.py +0 -22
- agno/knowledge/combined.py +0 -22
- agno/knowledge/csv.py +0 -28
- agno/knowledge/csv_url.py +0 -19
- agno/knowledge/document.py +0 -20
- agno/knowledge/docx.py +0 -30
- agno/knowledge/json.py +0 -28
- agno/knowledge/langchain.py +0 -71
- agno/knowledge/llamaindex.py +0 -66
- agno/knowledge/pdf.py +0 -28
- agno/knowledge/pdf_url.py +0 -26
- agno/knowledge/s3/base.py +0 -60
- agno/knowledge/s3/pdf.py +0 -21
- agno/knowledge/s3/text.py +0 -23
- agno/knowledge/text.py +0 -30
- agno/knowledge/website.py +0 -88
- agno/knowledge/wikipedia.py +0 -31
- agno/knowledge/youtube.py +0 -22
- agno/memory/agent.py +0 -392
- agno/memory/classifier.py +0 -104
- agno/memory/db/__init__.py +0 -1
- agno/memory/db/base.py +0 -42
- agno/memory/db/mongodb.py +0 -189
- agno/memory/db/postgres.py +0 -203
- agno/memory/db/sqlite.py +0 -193
- agno/memory/memory.py +0 -15
- agno/memory/row.py +0 -36
- agno/memory/summarizer.py +0 -192
- agno/memory/summary.py +0 -19
- agno/memory/workflow.py +0 -38
- agno/models/google/gemini_openai.py +0 -26
- agno/models/ollama/hermes.py +0 -221
- agno/models/ollama/tools.py +0 -362
- agno/models/vertexai/gemini.py +0 -595
- agno/playground/__init__.py +0 -3
- agno/playground/async_router.py +0 -421
- agno/playground/deploy.py +0 -249
- agno/playground/operator.py +0 -92
- agno/playground/playground.py +0 -91
- agno/playground/schemas.py +0 -76
- agno/playground/serve.py +0 -55
- agno/playground/sync_router.py +0 -405
- agno/reasoning/agent.py +0 -68
- agno/run/response.py +0 -112
- agno/storage/agent/__init__.py +0 -0
- agno/storage/agent/base.py +0 -38
- agno/storage/agent/dynamodb.py +0 -350
- agno/storage/agent/json.py +0 -92
- agno/storage/agent/mongodb.py +0 -228
- agno/storage/agent/postgres.py +0 -367
- agno/storage/agent/session.py +0 -79
- agno/storage/agent/singlestore.py +0 -303
- agno/storage/agent/sqlite.py +0 -357
- agno/storage/agent/yaml.py +0 -93
- agno/storage/workflow/__init__.py +0 -0
- agno/storage/workflow/base.py +0 -40
- agno/storage/workflow/mongodb.py +0 -233
- agno/storage/workflow/postgres.py +0 -366
- agno/storage/workflow/session.py +0 -60
- agno/storage/workflow/sqlite.py +0 -359
- agno/tools/googlesearch.py +0 -88
- agno/utils/defaults.py +0 -57
- agno/utils/filesystem.py +0 -39
- agno/utils/git.py +0 -52
- agno/utils/json_io.py +0 -30
- agno/utils/load_env.py +0 -19
- agno/utils/py_io.py +0 -19
- agno/utils/pyproject.py +0 -18
- agno/utils/resource_filter.py +0 -31
- agno/vectordb/singlestore/s2vectordb.py +0 -390
- agno/vectordb/singlestore/s2vectordb2.py +0 -355
- agno/workspace/__init__.py +0 -0
- agno/workspace/config.py +0 -325
- agno/workspace/enums.py +0 -6
- agno/workspace/helpers.py +0 -48
- agno/workspace/operator.py +0 -758
- agno/workspace/settings.py +0 -63
- agno-0.1.2.dist-info/LICENSE +0 -375
- agno-0.1.2.dist-info/METADATA +0 -502
- agno-0.1.2.dist-info/RECORD +0 -352
- agno-0.1.2.dist-info/entry_points.txt +0 -3
- /agno/{cli → db/migrations}/__init__.py +0 -0
- /agno/{cli/ws → db/migrations/versions}/__init__.py +0 -0
- /agno/{document/chunking/__init__.py → db/schemas/metrics.py} +0 -0
- /agno/{document/reader/s3 → integrations}/__init__.py +0 -0
- /agno/{file/local → knowledge/chunking}/__init__.py +0 -0
- /agno/{infra → knowledge/remote_content}/__init__.py +0 -0
- /agno/{knowledge/s3 → tools/models}/__init__.py +0 -0
- /agno/{reranker → utils/models}/__init__.py +0 -0
- /agno/{storage → utils/print_response}/__init__.py +0 -0
- {agno-0.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
agno/memory/manager.py
CHANGED
|
@@ -1,191 +1,1542 @@
|
|
|
1
|
-
from
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from os import getenv
|
|
4
|
+
from textwrap import dedent
|
|
5
|
+
from typing import Any, Callable, Dict, List, Literal, Optional, Type, Union
|
|
2
6
|
|
|
3
|
-
from pydantic import BaseModel,
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
4
8
|
|
|
5
|
-
from agno.
|
|
6
|
-
from agno.
|
|
7
|
-
from agno.memory.
|
|
9
|
+
from agno.db.base import AsyncBaseDb, BaseDb
|
|
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
|
+
)
|
|
8
16
|
from agno.models.base import Model
|
|
9
17
|
from agno.models.message import Message
|
|
10
|
-
from agno.utils
|
|
18
|
+
from agno.models.utils import get_model
|
|
19
|
+
from agno.tools.function import Function
|
|
20
|
+
from agno.utils.dttm import now_epoch_s
|
|
21
|
+
from agno.utils.log import (
|
|
22
|
+
log_debug,
|
|
23
|
+
log_error,
|
|
24
|
+
log_warning,
|
|
25
|
+
set_log_level_to_debug,
|
|
26
|
+
set_log_level_to_info,
|
|
27
|
+
)
|
|
28
|
+
from agno.utils.prompts import get_json_output_prompt
|
|
29
|
+
from agno.utils.string import parse_response_model_str
|
|
11
30
|
|
|
12
31
|
|
|
13
|
-
class
|
|
32
|
+
class MemorySearchResponse(BaseModel):
|
|
33
|
+
"""Model for Memory Search Response."""
|
|
34
|
+
|
|
35
|
+
memory_ids: List[str] = Field(
|
|
36
|
+
...,
|
|
37
|
+
description="The IDs of the memories that are most semantically similar to the query.",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class MemoryManager:
|
|
43
|
+
"""Memory Manager"""
|
|
44
|
+
|
|
45
|
+
# Model used for memory management
|
|
14
46
|
model: Optional[Model] = None
|
|
15
|
-
user_id: Optional[str] = None
|
|
16
47
|
|
|
17
|
-
# Provide the system
|
|
18
|
-
|
|
19
|
-
#
|
|
20
|
-
|
|
48
|
+
# Provide the system message for the manager as a string. If not provided, the default system message will be used.
|
|
49
|
+
system_message: Optional[str] = None
|
|
50
|
+
# Provide the memory capture instructions for the manager as a string. If not provided, the default memory capture instructions will be used.
|
|
51
|
+
memory_capture_instructions: Optional[str] = None
|
|
52
|
+
# Additional instructions for the manager. These instructions are appended to the default system message.
|
|
53
|
+
additional_instructions: Optional[str] = None
|
|
54
|
+
|
|
55
|
+
# Whether memories were created in the last run
|
|
56
|
+
memories_updated: bool = False
|
|
21
57
|
|
|
22
|
-
#
|
|
23
|
-
|
|
58
|
+
# ----- db tools ---------
|
|
59
|
+
# Whether to delete memories
|
|
60
|
+
delete_memories: bool = True
|
|
61
|
+
# Whether to clear memories
|
|
62
|
+
clear_memories: bool = True
|
|
63
|
+
# Whether to update memories
|
|
64
|
+
update_memories: bool = True
|
|
65
|
+
# whether to add memories
|
|
66
|
+
add_memories: bool = True
|
|
24
67
|
|
|
25
|
-
|
|
68
|
+
# The database to store memories
|
|
69
|
+
db: Optional[Union[BaseDb, AsyncBaseDb]] = None
|
|
26
70
|
|
|
27
|
-
|
|
71
|
+
debug_mode: bool = False
|
|
72
|
+
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
model: Optional[Union[Model, str]] = None,
|
|
76
|
+
system_message: Optional[str] = None,
|
|
77
|
+
memory_capture_instructions: Optional[str] = None,
|
|
78
|
+
additional_instructions: Optional[str] = None,
|
|
79
|
+
db: Optional[Union[BaseDb, AsyncBaseDb]] = None,
|
|
80
|
+
delete_memories: bool = False,
|
|
81
|
+
update_memories: bool = True,
|
|
82
|
+
add_memories: bool = True,
|
|
83
|
+
clear_memories: bool = False,
|
|
84
|
+
debug_mode: bool = False,
|
|
85
|
+
):
|
|
86
|
+
self.model = model # type: ignore[assignment]
|
|
87
|
+
self.system_message = system_message
|
|
88
|
+
self.memory_capture_instructions = memory_capture_instructions
|
|
89
|
+
self.additional_instructions = additional_instructions
|
|
90
|
+
self.db = db
|
|
91
|
+
self.delete_memories = delete_memories
|
|
92
|
+
self.update_memories = update_memories
|
|
93
|
+
self.add_memories = add_memories
|
|
94
|
+
self.clear_memories = clear_memories
|
|
95
|
+
self.debug_mode = debug_mode
|
|
96
|
+
|
|
97
|
+
if self.model is not None:
|
|
98
|
+
self.model = get_model(self.model)
|
|
99
|
+
|
|
100
|
+
def get_model(self) -> Model:
|
|
28
101
|
if self.model is None:
|
|
29
102
|
try:
|
|
30
103
|
from agno.models.openai import OpenAIChat
|
|
31
104
|
except ModuleNotFoundError as e:
|
|
32
|
-
|
|
33
|
-
|
|
105
|
+
log_error(e)
|
|
106
|
+
log_error(
|
|
34
107
|
"Agno uses `openai` as the default model provider. Please provide a `model` or install `openai`."
|
|
35
108
|
)
|
|
36
109
|
exit(1)
|
|
37
110
|
self.model = OpenAIChat(id="gpt-4o")
|
|
111
|
+
return self.model
|
|
38
112
|
|
|
39
|
-
|
|
40
|
-
self.
|
|
41
|
-
|
|
42
|
-
|
|
113
|
+
def read_from_db(self, user_id: Optional[str] = None):
|
|
114
|
+
if self.db:
|
|
115
|
+
# If no user_id is provided, read all memories
|
|
116
|
+
if user_id is None:
|
|
117
|
+
all_memories: List[UserMemory] = self.db.get_user_memories() # type: ignore
|
|
118
|
+
else:
|
|
119
|
+
all_memories = self.db.get_user_memories(user_id=user_id) # type: ignore
|
|
43
120
|
|
|
44
|
-
|
|
45
|
-
|
|
121
|
+
memories: Dict[str, List[UserMemory]] = {}
|
|
122
|
+
for memory in all_memories:
|
|
123
|
+
if memory.user_id is not None and memory.memory_id is not None:
|
|
124
|
+
memories.setdefault(memory.user_id, []).append(memory)
|
|
125
|
+
|
|
126
|
+
return memories
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
async def aread_from_db(self, user_id: Optional[str] = None):
|
|
130
|
+
if self.db:
|
|
131
|
+
if isinstance(self.db, AsyncBaseDb):
|
|
132
|
+
# If no user_id is provided, read all memories
|
|
133
|
+
if user_id is None:
|
|
134
|
+
all_memories: List[UserMemory] = await self.db.get_user_memories() # type: ignore
|
|
135
|
+
else:
|
|
136
|
+
all_memories = await self.db.get_user_memories(user_id=user_id) # type: ignore
|
|
137
|
+
else:
|
|
138
|
+
if user_id is None:
|
|
139
|
+
all_memories = self.db.get_user_memories() # type: ignore
|
|
140
|
+
else:
|
|
141
|
+
all_memories = self.db.get_user_memories(user_id=user_id) # type: ignore
|
|
142
|
+
|
|
143
|
+
memories: Dict[str, List[UserMemory]] = {}
|
|
144
|
+
for memory in all_memories:
|
|
145
|
+
if memory.user_id is not None and memory.memory_id is not None:
|
|
146
|
+
memories.setdefault(memory.user_id, []).append(memory)
|
|
147
|
+
|
|
148
|
+
return memories
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
def set_log_level(self):
|
|
152
|
+
if self.debug_mode or getenv("AGNO_DEBUG", "false").lower() == "true":
|
|
153
|
+
self.debug_mode = True
|
|
154
|
+
set_log_level_to_debug()
|
|
155
|
+
else:
|
|
156
|
+
set_log_level_to_info()
|
|
157
|
+
|
|
158
|
+
def initialize(self, user_id: Optional[str] = None):
|
|
159
|
+
self.set_log_level()
|
|
160
|
+
|
|
161
|
+
# -*- Public Functions
|
|
162
|
+
def get_user_memories(self, user_id: Optional[str] = None) -> Optional[List[UserMemory]]:
|
|
163
|
+
"""Get the user memories for a given user id"""
|
|
164
|
+
if self.db:
|
|
165
|
+
if user_id is None:
|
|
166
|
+
user_id = "default"
|
|
167
|
+
# Refresh from the Db
|
|
168
|
+
memories = self.read_from_db(user_id=user_id)
|
|
169
|
+
if memories is None:
|
|
170
|
+
return []
|
|
171
|
+
return memories.get(user_id, [])
|
|
172
|
+
else:
|
|
173
|
+
log_warning("Memory Db not provided.")
|
|
174
|
+
return []
|
|
175
|
+
|
|
176
|
+
async def aget_user_memories(self, user_id: Optional[str] = None) -> Optional[List[UserMemory]]:
|
|
177
|
+
"""Get the user memories for a given user id"""
|
|
178
|
+
if self.db:
|
|
179
|
+
if user_id is None:
|
|
180
|
+
user_id = "default"
|
|
181
|
+
# Refresh from the Db
|
|
182
|
+
memories = await self.aread_from_db(user_id=user_id)
|
|
183
|
+
if memories is None:
|
|
184
|
+
return []
|
|
185
|
+
return memories.get(user_id, [])
|
|
186
|
+
else:
|
|
187
|
+
log_warning("Memory Db not provided.")
|
|
188
|
+
return []
|
|
189
|
+
|
|
190
|
+
def get_user_memory(self, memory_id: str, user_id: Optional[str] = None) -> Optional[UserMemory]:
|
|
191
|
+
"""Get the user memory for a given user id"""
|
|
192
|
+
if self.db:
|
|
193
|
+
if user_id is None:
|
|
194
|
+
user_id = "default"
|
|
195
|
+
# Refresh from the DB
|
|
196
|
+
memories = self.read_from_db(user_id=user_id)
|
|
197
|
+
if memories is None:
|
|
198
|
+
return None
|
|
199
|
+
memories_for_user = memories.get(user_id, [])
|
|
200
|
+
for memory in memories_for_user:
|
|
201
|
+
if memory.memory_id == memory_id:
|
|
202
|
+
return memory
|
|
203
|
+
return None
|
|
204
|
+
else:
|
|
205
|
+
log_warning("Memory Db not provided.")
|
|
46
206
|
return None
|
|
47
207
|
|
|
48
|
-
|
|
208
|
+
def add_user_memory(
|
|
209
|
+
self,
|
|
210
|
+
memory: UserMemory,
|
|
211
|
+
user_id: Optional[str] = None,
|
|
212
|
+
) -> Optional[str]:
|
|
213
|
+
"""Add a user memory for a given user id
|
|
214
|
+
Args:
|
|
215
|
+
memory (UserMemory): The memory to add
|
|
216
|
+
user_id (Optional[str]): The user id to add the memory to. If not provided, the memory is added to the "default" user.
|
|
217
|
+
Returns:
|
|
218
|
+
str: The id of the memory
|
|
219
|
+
"""
|
|
220
|
+
if self.db:
|
|
221
|
+
if memory.memory_id is None:
|
|
222
|
+
from uuid import uuid4
|
|
49
223
|
|
|
50
|
-
|
|
51
|
-
|
|
224
|
+
memory_id = memory.memory_id or str(uuid4())
|
|
225
|
+
memory.memory_id = memory_id
|
|
226
|
+
|
|
227
|
+
if user_id is None:
|
|
228
|
+
user_id = "default"
|
|
229
|
+
memory.user_id = user_id
|
|
230
|
+
|
|
231
|
+
if not memory.updated_at:
|
|
232
|
+
memory.updated_at = now_epoch_s()
|
|
233
|
+
|
|
234
|
+
self._upsert_db_memory(memory=memory)
|
|
235
|
+
return memory.memory_id
|
|
236
|
+
|
|
237
|
+
else:
|
|
238
|
+
log_warning("Memory Db not provided.")
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
def replace_user_memory(
|
|
242
|
+
self,
|
|
243
|
+
memory_id: str,
|
|
244
|
+
memory: UserMemory,
|
|
245
|
+
user_id: Optional[str] = None,
|
|
246
|
+
) -> Optional[str]:
|
|
247
|
+
"""Replace a user memory for a given user id
|
|
52
248
|
Args:
|
|
53
|
-
|
|
249
|
+
memory_id (str): The id of the memory to replace
|
|
250
|
+
memory (UserMemory): The memory to add
|
|
251
|
+
user_id (Optional[str]): The user id to add the memory to. If not provided, the memory is added to the "default" user.
|
|
54
252
|
Returns:
|
|
55
|
-
str:
|
|
253
|
+
str: The id of the memory
|
|
56
254
|
"""
|
|
255
|
+
if self.db:
|
|
256
|
+
if user_id is None:
|
|
257
|
+
user_id = "default"
|
|
258
|
+
|
|
259
|
+
if not memory.updated_at:
|
|
260
|
+
memory.updated_at = now_epoch_s()
|
|
261
|
+
|
|
262
|
+
memory.memory_id = memory_id
|
|
263
|
+
memory.user_id = user_id
|
|
264
|
+
|
|
265
|
+
self._upsert_db_memory(memory=memory)
|
|
266
|
+
|
|
267
|
+
return memory.memory_id
|
|
268
|
+
else:
|
|
269
|
+
log_warning("Memory Db not provided.")
|
|
270
|
+
return None
|
|
271
|
+
|
|
272
|
+
def clear(self) -> None:
|
|
273
|
+
"""Clears the memory."""
|
|
274
|
+
if self.db:
|
|
275
|
+
self.db.clear_memories()
|
|
276
|
+
|
|
277
|
+
def delete_user_memory(
|
|
278
|
+
self,
|
|
279
|
+
memory_id: str,
|
|
280
|
+
user_id: Optional[str] = None,
|
|
281
|
+
) -> None:
|
|
282
|
+
"""Delete a user memory for a given user id
|
|
283
|
+
Args:
|
|
284
|
+
memory_id (str): The id of the memory to delete
|
|
285
|
+
user_id (Optional[str]): The user id to delete the memory from. If not provided, the memory is deleted from the "default" user.
|
|
286
|
+
"""
|
|
287
|
+
if user_id is None:
|
|
288
|
+
user_id = "default"
|
|
289
|
+
|
|
290
|
+
if self.db:
|
|
291
|
+
self._delete_db_memory(memory_id=memory_id, user_id=user_id)
|
|
292
|
+
else:
|
|
293
|
+
log_warning("Memory DB not provided.")
|
|
294
|
+
return None
|
|
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
|
+
|
|
364
|
+
# -*- Agent Functions
|
|
365
|
+
def create_user_memories(
|
|
366
|
+
self,
|
|
367
|
+
message: Optional[str] = None,
|
|
368
|
+
messages: Optional[List[Message]] = None,
|
|
369
|
+
agent_id: Optional[str] = None,
|
|
370
|
+
team_id: Optional[str] = None,
|
|
371
|
+
user_id: Optional[str] = None,
|
|
372
|
+
) -> str:
|
|
373
|
+
"""Creates memories from multiple messages and adds them to the memory db."""
|
|
374
|
+
self.set_log_level()
|
|
375
|
+
|
|
376
|
+
if self.db is None:
|
|
377
|
+
log_warning("MemoryDb not provided.")
|
|
378
|
+
return "Please provide a db to store memories"
|
|
379
|
+
|
|
380
|
+
if isinstance(self.db, AsyncBaseDb):
|
|
381
|
+
raise ValueError(
|
|
382
|
+
"create_user_memories() is not supported with an async DB. Please use acreate_user_memories() instead."
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
if not messages and not message:
|
|
386
|
+
raise ValueError("You must provide either a message or a list of messages")
|
|
387
|
+
|
|
388
|
+
if message:
|
|
389
|
+
messages = [Message(role="user", content=message)]
|
|
390
|
+
|
|
391
|
+
if not messages or not isinstance(messages, list):
|
|
392
|
+
raise ValueError("Invalid messages list")
|
|
393
|
+
|
|
394
|
+
if user_id is None:
|
|
395
|
+
user_id = "default"
|
|
396
|
+
|
|
397
|
+
memories = self.read_from_db(user_id=user_id)
|
|
398
|
+
if memories is None:
|
|
399
|
+
memories = {}
|
|
400
|
+
|
|
401
|
+
existing_memories = memories.get(user_id, []) # type: ignore
|
|
402
|
+
existing_memories = [{"memory_id": memory.memory_id, "memory": memory.memory} for memory in existing_memories]
|
|
403
|
+
response = self.create_or_update_memories( # type: ignore
|
|
404
|
+
messages=messages,
|
|
405
|
+
existing_memories=existing_memories,
|
|
406
|
+
user_id=user_id,
|
|
407
|
+
agent_id=agent_id,
|
|
408
|
+
team_id=team_id,
|
|
409
|
+
db=self.db,
|
|
410
|
+
update_memories=self.update_memories,
|
|
411
|
+
add_memories=self.add_memories,
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
# We refresh from the DB
|
|
415
|
+
self.read_from_db(user_id=user_id)
|
|
416
|
+
return response
|
|
417
|
+
|
|
418
|
+
async def acreate_user_memories(
|
|
419
|
+
self,
|
|
420
|
+
message: Optional[str] = None,
|
|
421
|
+
messages: Optional[List[Message]] = None,
|
|
422
|
+
agent_id: Optional[str] = None,
|
|
423
|
+
team_id: Optional[str] = None,
|
|
424
|
+
user_id: Optional[str] = None,
|
|
425
|
+
) -> str:
|
|
426
|
+
"""Creates memories from multiple messages and adds them to the memory db."""
|
|
427
|
+
self.set_log_level()
|
|
428
|
+
|
|
429
|
+
if self.db is None:
|
|
430
|
+
log_warning("MemoryDb not provided.")
|
|
431
|
+
return "Please provide a db to store memories"
|
|
432
|
+
|
|
433
|
+
if not messages and not message:
|
|
434
|
+
raise ValueError("You must provide either a message or a list of messages")
|
|
435
|
+
|
|
436
|
+
if message:
|
|
437
|
+
messages = [Message(role="user", content=message)]
|
|
438
|
+
|
|
439
|
+
if not messages or not isinstance(messages, list):
|
|
440
|
+
raise ValueError("Invalid messages list")
|
|
441
|
+
|
|
442
|
+
if user_id is None:
|
|
443
|
+
user_id = "default"
|
|
444
|
+
|
|
445
|
+
if isinstance(self.db, AsyncBaseDb):
|
|
446
|
+
memories = await self.aread_from_db(user_id=user_id)
|
|
447
|
+
else:
|
|
448
|
+
memories = self.read_from_db(user_id=user_id)
|
|
449
|
+
if memories is None:
|
|
450
|
+
memories = {}
|
|
451
|
+
|
|
452
|
+
existing_memories = memories.get(user_id, []) # type: ignore
|
|
453
|
+
existing_memories = [{"memory_id": memory.memory_id, "memory": memory.memory} for memory in existing_memories]
|
|
454
|
+
|
|
455
|
+
response = await self.acreate_or_update_memories( # type: ignore
|
|
456
|
+
messages=messages,
|
|
457
|
+
existing_memories=existing_memories,
|
|
458
|
+
user_id=user_id,
|
|
459
|
+
agent_id=agent_id,
|
|
460
|
+
team_id=team_id,
|
|
461
|
+
db=self.db,
|
|
462
|
+
update_memories=self.update_memories,
|
|
463
|
+
add_memories=self.add_memories,
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
# We refresh from the DB
|
|
467
|
+
if isinstance(self.db, AsyncBaseDb):
|
|
468
|
+
memories = await self.aread_from_db(user_id=user_id)
|
|
469
|
+
else:
|
|
470
|
+
memories = self.read_from_db(user_id=user_id)
|
|
471
|
+
|
|
472
|
+
return response
|
|
473
|
+
|
|
474
|
+
def update_memory_task(self, task: str, user_id: Optional[str] = None) -> str:
|
|
475
|
+
"""Updates the memory with a task"""
|
|
476
|
+
|
|
477
|
+
if not self.db:
|
|
478
|
+
log_warning("MemoryDb not provided.")
|
|
479
|
+
return "Please provide a db to store memories"
|
|
480
|
+
|
|
481
|
+
if not isinstance(self.db, BaseDb):
|
|
482
|
+
raise ValueError(
|
|
483
|
+
"update_memory_task() is not supported with an async DB. Please use aupdate_memory_task() instead."
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
if user_id is None:
|
|
487
|
+
user_id = "default"
|
|
488
|
+
|
|
489
|
+
memories = self.read_from_db(user_id=user_id)
|
|
490
|
+
if memories is None:
|
|
491
|
+
memories = {}
|
|
492
|
+
|
|
493
|
+
existing_memories = memories.get(user_id, []) # type: ignore
|
|
494
|
+
existing_memories = [{"memory_id": memory.memory_id, "memory": memory.memory} for memory in existing_memories]
|
|
495
|
+
# The memory manager updates the DB directly
|
|
496
|
+
response = self.run_memory_task( # type: ignore
|
|
497
|
+
task=task,
|
|
498
|
+
existing_memories=existing_memories,
|
|
499
|
+
user_id=user_id,
|
|
500
|
+
db=self.db,
|
|
501
|
+
delete_memories=self.delete_memories,
|
|
502
|
+
update_memories=self.update_memories,
|
|
503
|
+
add_memories=self.add_memories,
|
|
504
|
+
clear_memories=self.clear_memories,
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
# We refresh from the DB
|
|
508
|
+
self.read_from_db(user_id=user_id)
|
|
509
|
+
|
|
510
|
+
return response
|
|
511
|
+
|
|
512
|
+
async def aupdate_memory_task(self, task: str, user_id: Optional[str] = None) -> str:
|
|
513
|
+
"""Updates the memory with a task"""
|
|
514
|
+
self.set_log_level()
|
|
515
|
+
|
|
516
|
+
if not self.db:
|
|
517
|
+
log_warning("MemoryDb not provided.")
|
|
518
|
+
return "Please provide a db to store memories"
|
|
519
|
+
|
|
520
|
+
if user_id is None:
|
|
521
|
+
user_id = "default"
|
|
522
|
+
|
|
523
|
+
if isinstance(self.db, AsyncBaseDb):
|
|
524
|
+
memories = await self.aread_from_db(user_id=user_id)
|
|
525
|
+
else:
|
|
526
|
+
memories = self.read_from_db(user_id=user_id)
|
|
527
|
+
|
|
528
|
+
if memories is None:
|
|
529
|
+
memories = {}
|
|
530
|
+
|
|
531
|
+
existing_memories = memories.get(user_id, []) # type: ignore
|
|
532
|
+
existing_memories = [{"memory_id": memory.memory_id, "memory": memory.memory} for memory in existing_memories]
|
|
533
|
+
# The memory manager updates the DB directly
|
|
534
|
+
response = await self.arun_memory_task( # type: ignore
|
|
535
|
+
task=task,
|
|
536
|
+
existing_memories=existing_memories,
|
|
537
|
+
user_id=user_id,
|
|
538
|
+
db=self.db,
|
|
539
|
+
delete_memories=self.delete_memories,
|
|
540
|
+
update_memories=self.update_memories,
|
|
541
|
+
add_memories=self.add_memories,
|
|
542
|
+
clear_memories=self.clear_memories,
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
# We refresh from the DB
|
|
546
|
+
if isinstance(self.db, AsyncBaseDb):
|
|
547
|
+
await self.aread_from_db(user_id=user_id)
|
|
548
|
+
else:
|
|
549
|
+
self.read_from_db(user_id=user_id)
|
|
550
|
+
|
|
551
|
+
return response
|
|
552
|
+
|
|
553
|
+
# -*- Memory Db Functions
|
|
554
|
+
def _upsert_db_memory(self, memory: UserMemory) -> str:
|
|
555
|
+
"""Use this function to add a memory to the database."""
|
|
57
556
|
try:
|
|
58
|
-
if self.db:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
)
|
|
557
|
+
if not self.db:
|
|
558
|
+
raise ValueError("Memory db not initialized")
|
|
559
|
+
self.db.upsert_user_memory(memory=memory)
|
|
62
560
|
return "Memory added successfully"
|
|
63
561
|
except Exception as e:
|
|
64
|
-
|
|
562
|
+
log_warning(f"Error storing memory in db: {e}")
|
|
65
563
|
return f"Error adding memory: {e}"
|
|
66
564
|
|
|
67
|
-
def
|
|
68
|
-
"""Use this function to delete a memory from the database.
|
|
69
|
-
Args:
|
|
70
|
-
id (str): The id of the memory to be deleted.
|
|
71
|
-
Returns:
|
|
72
|
-
str: A message indicating if the memory was deleted successfully or not.
|
|
73
|
-
"""
|
|
565
|
+
def _delete_db_memory(self, memory_id: str, user_id: Optional[str] = None) -> str:
|
|
566
|
+
"""Use this function to delete a memory from the database."""
|
|
74
567
|
try:
|
|
75
|
-
if self.db:
|
|
76
|
-
|
|
568
|
+
if not self.db:
|
|
569
|
+
raise ValueError("Memory db not initialized")
|
|
570
|
+
|
|
571
|
+
if user_id is None:
|
|
572
|
+
user_id = "default"
|
|
573
|
+
|
|
574
|
+
self.db.delete_user_memory(memory_id=memory_id, user_id=user_id)
|
|
77
575
|
return "Memory deleted successfully"
|
|
78
576
|
except Exception as e:
|
|
79
|
-
|
|
577
|
+
log_warning(f"Error deleting memory in db: {e}")
|
|
80
578
|
return f"Error deleting memory: {e}"
|
|
81
579
|
|
|
82
|
-
|
|
83
|
-
|
|
580
|
+
# -*- Utility Functions
|
|
581
|
+
def search_user_memories(
|
|
582
|
+
self,
|
|
583
|
+
query: Optional[str] = None,
|
|
584
|
+
limit: Optional[int] = None,
|
|
585
|
+
retrieval_method: Optional[Literal["last_n", "first_n", "agentic"]] = None,
|
|
586
|
+
user_id: Optional[str] = None,
|
|
587
|
+
) -> List[UserMemory]:
|
|
588
|
+
"""Search through user memories using the specified retrieval method.
|
|
589
|
+
|
|
84
590
|
Args:
|
|
85
|
-
|
|
86
|
-
|
|
591
|
+
query: The search query for agentic search. Required if retrieval_method is "agentic".
|
|
592
|
+
limit: Maximum number of memories to return. Defaults to self.retrieval_limit if not specified. Optional.
|
|
593
|
+
retrieval_method: The method to use for retrieving memories. Defaults to self.retrieval if not specified.
|
|
594
|
+
- "last_n": Return the most recent memories
|
|
595
|
+
- "first_n": Return the oldest memories
|
|
596
|
+
- "agentic": Return memories most similar to the query, but using an agentic approach
|
|
597
|
+
user_id: The user to search for. Optional.
|
|
598
|
+
|
|
87
599
|
Returns:
|
|
88
|
-
|
|
600
|
+
A list of UserMemory objects matching the search criteria.
|
|
89
601
|
"""
|
|
90
|
-
try:
|
|
91
|
-
if self.db:
|
|
92
|
-
self.db.upsert_memory(
|
|
93
|
-
MemoryRow(
|
|
94
|
-
id=id, user_id=self.user_id, memory=Memory(memory=memory, input=self.input_message).to_dict()
|
|
95
|
-
)
|
|
96
|
-
)
|
|
97
|
-
return "Memory updated successfully"
|
|
98
|
-
except Exception as e:
|
|
99
|
-
logger.warning(f"Error updating memory in db: {e}")
|
|
100
|
-
return f"Error updating memory: {e}"
|
|
101
602
|
|
|
102
|
-
|
|
103
|
-
|
|
603
|
+
if user_id is None:
|
|
604
|
+
user_id = "default"
|
|
605
|
+
|
|
606
|
+
self.set_log_level()
|
|
607
|
+
|
|
608
|
+
memories = self.read_from_db(user_id=user_id)
|
|
609
|
+
if memories is None:
|
|
610
|
+
memories = {}
|
|
611
|
+
|
|
612
|
+
if not memories:
|
|
613
|
+
return []
|
|
614
|
+
|
|
615
|
+
# Use default retrieval method if not specified
|
|
616
|
+
retrieval_method = retrieval_method
|
|
617
|
+
# Use default limit if not specified
|
|
618
|
+
limit = limit
|
|
619
|
+
|
|
620
|
+
# Handle different retrieval methods
|
|
621
|
+
if retrieval_method == "agentic":
|
|
622
|
+
if not query:
|
|
623
|
+
raise ValueError("Query is required for agentic search")
|
|
624
|
+
|
|
625
|
+
return self._search_user_memories_agentic(user_id=user_id, query=query, limit=limit)
|
|
626
|
+
|
|
627
|
+
elif retrieval_method == "first_n":
|
|
628
|
+
return self._get_first_n_memories(user_id=user_id, limit=limit)
|
|
629
|
+
|
|
630
|
+
else: # Default to last_n
|
|
631
|
+
return self._get_last_n_memories(user_id=user_id, limit=limit)
|
|
632
|
+
|
|
633
|
+
def _get_response_format(self) -> Union[Dict[str, Any], Type[BaseModel]]:
|
|
634
|
+
model = self.get_model()
|
|
635
|
+
if model.supports_native_structured_outputs:
|
|
636
|
+
return MemorySearchResponse
|
|
637
|
+
|
|
638
|
+
elif model.supports_json_schema_outputs:
|
|
639
|
+
return {
|
|
640
|
+
"type": "json_schema",
|
|
641
|
+
"json_schema": {
|
|
642
|
+
"name": MemorySearchResponse.__name__,
|
|
643
|
+
"schema": MemorySearchResponse.model_json_schema(),
|
|
644
|
+
},
|
|
645
|
+
}
|
|
646
|
+
else:
|
|
647
|
+
return {"type": "json_object"}
|
|
648
|
+
|
|
649
|
+
def _search_user_memories_agentic(self, user_id: str, query: str, limit: Optional[int] = None) -> List[UserMemory]:
|
|
650
|
+
"""Search through user memories using agentic search."""
|
|
651
|
+
memories = self.read_from_db(user_id=user_id)
|
|
652
|
+
if memories is None:
|
|
653
|
+
memories = {}
|
|
654
|
+
|
|
655
|
+
if not memories:
|
|
656
|
+
return []
|
|
657
|
+
|
|
658
|
+
model = self.get_model()
|
|
659
|
+
|
|
660
|
+
response_format = self._get_response_format()
|
|
661
|
+
|
|
662
|
+
log_debug("Searching for memories", center=True)
|
|
663
|
+
|
|
664
|
+
# Get all memories as a list
|
|
665
|
+
user_memories: List[UserMemory] = memories[user_id]
|
|
666
|
+
system_message_str = "Your task is to search through user memories and return the IDs of the memories that are related to the query.\n"
|
|
667
|
+
system_message_str += "\n<user_memories>\n"
|
|
668
|
+
for memory in user_memories:
|
|
669
|
+
system_message_str += f"ID: {memory.memory_id}\n"
|
|
670
|
+
system_message_str += f"Memory: {memory.memory}\n"
|
|
671
|
+
if memory.topics:
|
|
672
|
+
system_message_str += f"Topics: {','.join(memory.topics)}\n"
|
|
673
|
+
system_message_str += "\n"
|
|
674
|
+
system_message_str = system_message_str.strip()
|
|
675
|
+
system_message_str += "\n</user_memories>\n\n"
|
|
676
|
+
system_message_str += "REMEMBER: Only return the IDs of the memories that are related to the query."
|
|
677
|
+
|
|
678
|
+
if response_format == {"type": "json_object"}:
|
|
679
|
+
system_message_str += "\n" + get_json_output_prompt(MemorySearchResponse) # type: ignore
|
|
680
|
+
|
|
681
|
+
messages_for_model = [
|
|
682
|
+
Message(role="system", content=system_message_str),
|
|
683
|
+
Message(
|
|
684
|
+
role="user",
|
|
685
|
+
content=f"Return the IDs of the memories related to the following query: {query}",
|
|
686
|
+
),
|
|
687
|
+
]
|
|
688
|
+
|
|
689
|
+
# Generate a response from the Model (includes running function calls)
|
|
690
|
+
response = model.response(messages=messages_for_model, response_format=response_format)
|
|
691
|
+
log_debug("Search for memories complete", center=True)
|
|
692
|
+
|
|
693
|
+
memory_search: Optional[MemorySearchResponse] = None
|
|
694
|
+
# If the model natively supports structured outputs, the parsed value is already in the structured format
|
|
695
|
+
if (
|
|
696
|
+
model.supports_native_structured_outputs
|
|
697
|
+
and response.parsed is not None
|
|
698
|
+
and isinstance(response.parsed, MemorySearchResponse)
|
|
699
|
+
):
|
|
700
|
+
memory_search = response.parsed
|
|
701
|
+
|
|
702
|
+
# Otherwise convert the response to the structured format
|
|
703
|
+
if isinstance(response.content, str):
|
|
704
|
+
try:
|
|
705
|
+
memory_search = parse_response_model_str(response.content, MemorySearchResponse) # type: ignore
|
|
706
|
+
|
|
707
|
+
# Update RunOutput
|
|
708
|
+
if memory_search is None:
|
|
709
|
+
log_warning("Failed to convert memory_search response to MemorySearchResponse")
|
|
710
|
+
return []
|
|
711
|
+
except Exception as e:
|
|
712
|
+
log_warning(f"Failed to convert memory_search response to MemorySearchResponse: {e}")
|
|
713
|
+
return []
|
|
714
|
+
|
|
715
|
+
memories_to_return = []
|
|
716
|
+
if memory_search:
|
|
717
|
+
for memory_id in memory_search.memory_ids:
|
|
718
|
+
for memory in user_memories:
|
|
719
|
+
if memory.memory_id == memory_id:
|
|
720
|
+
memories_to_return.append(memory)
|
|
721
|
+
return memories_to_return[:limit]
|
|
722
|
+
|
|
723
|
+
def _get_last_n_memories(self, user_id: str, limit: Optional[int] = None) -> List[UserMemory]:
|
|
724
|
+
"""Get the most recent user memories.
|
|
725
|
+
|
|
726
|
+
Args:
|
|
727
|
+
limit: Maximum number of memories to return.
|
|
104
728
|
|
|
105
729
|
Returns:
|
|
106
|
-
|
|
730
|
+
A list of the most recent UserMemory objects.
|
|
107
731
|
"""
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
732
|
+
memories = self.read_from_db(user_id=user_id)
|
|
733
|
+
if memories is None:
|
|
734
|
+
memories = {}
|
|
735
|
+
|
|
736
|
+
memories_list = memories.get(user_id, [])
|
|
737
|
+
|
|
738
|
+
# Sort memories by updated_at timestamp if available
|
|
739
|
+
if memories_list:
|
|
740
|
+
# Sort memories by updated_at timestamp (newest first)
|
|
741
|
+
# If updated_at is None, place at the beginning of the list
|
|
742
|
+
sorted_memories_list = sorted(
|
|
743
|
+
memories_list,
|
|
744
|
+
key=lambda m: m.updated_at if m.updated_at is not None else 0,
|
|
745
|
+
)
|
|
746
|
+
else:
|
|
747
|
+
sorted_memories_list = []
|
|
748
|
+
|
|
749
|
+
if limit is not None and limit > 0:
|
|
750
|
+
sorted_memories_list = sorted_memories_list[-limit:]
|
|
751
|
+
|
|
752
|
+
return sorted_memories_list
|
|
753
|
+
|
|
754
|
+
def _get_first_n_memories(self, user_id: str, limit: Optional[int] = None) -> List[UserMemory]:
|
|
755
|
+
"""Get the oldest user memories.
|
|
756
|
+
|
|
757
|
+
Args:
|
|
758
|
+
limit: Maximum number of memories to return.
|
|
759
|
+
|
|
760
|
+
Returns:
|
|
761
|
+
A list of the oldest UserMemory objects.
|
|
762
|
+
"""
|
|
763
|
+
memories = self.read_from_db(user_id=user_id)
|
|
764
|
+
if memories is None:
|
|
765
|
+
memories = {}
|
|
766
|
+
|
|
767
|
+
MAX_UNIX_TS = 2**63 - 1
|
|
768
|
+
memories_list = memories.get(user_id, [])
|
|
769
|
+
# Sort memories by updated_at timestamp if available
|
|
770
|
+
if memories_list:
|
|
771
|
+
# Sort memories by updated_at timestamp (oldest first)
|
|
772
|
+
# If updated_at is None, place at the end of the list
|
|
773
|
+
sorted_memories_list = sorted(
|
|
774
|
+
memories_list,
|
|
775
|
+
key=lambda m: m.updated_at if m.updated_at is not None else MAX_UNIX_TS,
|
|
776
|
+
)
|
|
777
|
+
|
|
778
|
+
else:
|
|
779
|
+
sorted_memories_list = []
|
|
780
|
+
|
|
781
|
+
if limit is not None and limit > 0:
|
|
782
|
+
sorted_memories_list = sorted_memories_list[:limit]
|
|
783
|
+
|
|
784
|
+
return sorted_memories_list
|
|
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
|
+
|
|
930
|
+
# --Memory Manager Functions--
|
|
931
|
+
def determine_tools_for_model(self, tools: List[Callable]) -> List[Union[Function, dict]]:
|
|
932
|
+
# Have to reset each time, because of different user IDs
|
|
933
|
+
_function_names = []
|
|
934
|
+
_functions: List[Union[Function, dict]] = []
|
|
935
|
+
|
|
936
|
+
for tool in tools:
|
|
937
|
+
try:
|
|
938
|
+
function_name = tool.__name__
|
|
939
|
+
if function_name in _function_names:
|
|
940
|
+
continue
|
|
941
|
+
_function_names.append(function_name)
|
|
942
|
+
func = Function.from_callable(tool, strict=True) # type: ignore
|
|
943
|
+
func.strict = True
|
|
944
|
+
_functions.append(func)
|
|
945
|
+
log_debug(f"Added function {func.name}")
|
|
946
|
+
except Exception as e:
|
|
947
|
+
log_warning(f"Could not add function {tool}: {e}")
|
|
948
|
+
|
|
949
|
+
return _functions
|
|
950
|
+
|
|
951
|
+
def get_system_message(
|
|
952
|
+
self,
|
|
953
|
+
existing_memories: Optional[List[Dict[str, Any]]] = None,
|
|
954
|
+
enable_delete_memory: bool = True,
|
|
955
|
+
enable_clear_memory: bool = True,
|
|
956
|
+
enable_update_memory: bool = True,
|
|
957
|
+
enable_add_memory: bool = True,
|
|
958
|
+
) -> Message:
|
|
959
|
+
if self.system_message is not None:
|
|
960
|
+
return Message(role="system", content=self.system_message)
|
|
961
|
+
|
|
962
|
+
memory_capture_instructions = self.memory_capture_instructions or dedent(
|
|
963
|
+
"""\
|
|
964
|
+
Memories should capture personal information about the user that is relevant to the current conversation, such as:
|
|
965
|
+
- Personal facts: name, age, occupation, location, interests, and preferences
|
|
966
|
+
- Opinions and preferences: what the user likes, dislikes, enjoys, or finds frustrating
|
|
967
|
+
- Significant life events or experiences shared by the user
|
|
968
|
+
- Important context about the user's current situation, challenges, or goals
|
|
969
|
+
- Any other details that offer meaningful insight into the user's personality, perspective, or needs
|
|
970
|
+
"""
|
|
971
|
+
)
|
|
115
972
|
|
|
116
|
-
def get_system_message(self) -> Message:
|
|
117
973
|
# -*- Return a system message for the memory manager
|
|
118
974
|
system_prompt_lines = [
|
|
119
|
-
"
|
|
120
|
-
"
|
|
121
|
-
"
|
|
122
|
-
"
|
|
123
|
-
"
|
|
124
|
-
"
|
|
125
|
-
"
|
|
126
|
-
"
|
|
127
|
-
"
|
|
975
|
+
"You are a Memory Manager that is responsible for managing information and preferences about the user. "
|
|
976
|
+
"You will be provided with a criteria for memories to capture in the <memories_to_capture> section and a list of existing memories in the <existing_memories> section.",
|
|
977
|
+
"",
|
|
978
|
+
"## When to add or update memories",
|
|
979
|
+
"- Your first task is to decide if a memory needs to be added, updated, or deleted based on the user's message OR if no changes are needed.",
|
|
980
|
+
"- If the user's message meets the criteria in the <memories_to_capture> section and that information is not already captured in the <existing_memories> section, you should capture it as a memory.",
|
|
981
|
+
"- If the users messages does not meet the criteria in the <memories_to_capture> section, no memory updates are needed.",
|
|
982
|
+
"- If the existing memories in the <existing_memories> section capture all relevant information, no memory updates are needed.",
|
|
983
|
+
"",
|
|
984
|
+
"## How to add or update memories",
|
|
985
|
+
"- If you decide to add a new memory, create memories that captures key information, as if you were storing it for future reference.",
|
|
986
|
+
"- Memories should be a brief, third-person statements that encapsulate the most important aspect of the user's input, without adding any extraneous information.",
|
|
987
|
+
" - Example: If the user's message is 'I'm going to the gym', a memory could be `John Doe goes to the gym regularly`.",
|
|
988
|
+
" - Example: If the user's message is 'My name is John Doe', a memory could be `User's name is John Doe`.",
|
|
989
|
+
"- Don't make a single memory too long or complex, create multiple memories if needed to capture all the information.",
|
|
990
|
+
"- Don't repeat the same information in multiple memories. Rather update existing memories if needed.",
|
|
991
|
+
"- If a user asks for a memory to be updated or forgotten, remove all reference to the information that should be forgotten. Don't say 'The user used to like ...`",
|
|
992
|
+
"- When updating a memory, append the existing memory with new information rather than completely overwriting it.",
|
|
993
|
+
"- When a user's preferences change, update the relevant memories to reflect the new preferences but also capture what the user's preferences used to be and what has changed.",
|
|
994
|
+
"",
|
|
995
|
+
"## Criteria for creating memories",
|
|
996
|
+
"Use the following criteria to determine if a user's message should be captured as a memory.",
|
|
997
|
+
"",
|
|
998
|
+
"<memories_to_capture>",
|
|
999
|
+
memory_capture_instructions,
|
|
1000
|
+
"</memories_to_capture>",
|
|
1001
|
+
"",
|
|
1002
|
+
"## Updating memories",
|
|
1003
|
+
"You will also be provided with a list of existing memories in the <existing_memories> section. You can:",
|
|
1004
|
+
" - Decide to make no changes.",
|
|
128
1005
|
]
|
|
129
|
-
|
|
1006
|
+
if enable_add_memory:
|
|
1007
|
+
system_prompt_lines.append(" - Decide to add a new memory, using the `add_memory` tool.")
|
|
1008
|
+
if enable_update_memory:
|
|
1009
|
+
system_prompt_lines.append(" - Decide to update an existing memory, using the `update_memory` tool.")
|
|
1010
|
+
if enable_delete_memory:
|
|
1011
|
+
system_prompt_lines.append(" - Decide to delete an existing memory, using the `delete_memory` tool.")
|
|
1012
|
+
if enable_clear_memory:
|
|
1013
|
+
system_prompt_lines.append(" - Decide to clear all memories, using the `clear_memory` tool.")
|
|
1014
|
+
|
|
1015
|
+
system_prompt_lines += [
|
|
1016
|
+
"You can call multiple tools in a single response if needed. ",
|
|
1017
|
+
"Only add or update memories if it is necessary to capture key information provided by the user.",
|
|
1018
|
+
]
|
|
1019
|
+
|
|
130
1020
|
if existing_memories and len(existing_memories) > 0:
|
|
131
|
-
system_prompt_lines.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
1021
|
+
system_prompt_lines.append("\n<existing_memories>")
|
|
1022
|
+
for existing_memory in existing_memories:
|
|
1023
|
+
system_prompt_lines.append(f"ID: {existing_memory['memory_id']}")
|
|
1024
|
+
system_prompt_lines.append(f"Memory: {existing_memory['memory']}")
|
|
1025
|
+
system_prompt_lines.append("")
|
|
1026
|
+
system_prompt_lines.append("</existing_memories>")
|
|
1027
|
+
|
|
1028
|
+
if self.additional_instructions:
|
|
1029
|
+
system_prompt_lines.append(self.additional_instructions)
|
|
1030
|
+
|
|
139
1031
|
return Message(role="system", content="\n".join(system_prompt_lines))
|
|
140
1032
|
|
|
141
|
-
def
|
|
1033
|
+
def create_or_update_memories(
|
|
142
1034
|
self,
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
1035
|
+
messages: List[Message],
|
|
1036
|
+
existing_memories: List[Dict[str, Any]],
|
|
1037
|
+
user_id: str,
|
|
1038
|
+
db: BaseDb,
|
|
1039
|
+
agent_id: Optional[str] = None,
|
|
1040
|
+
team_id: Optional[str] = None,
|
|
1041
|
+
update_memories: bool = True,
|
|
1042
|
+
add_memories: bool = True,
|
|
1043
|
+
) -> str:
|
|
1044
|
+
if self.model is None:
|
|
1045
|
+
log_error("No model provided for memory manager")
|
|
1046
|
+
return "No model provided for memory manager"
|
|
1047
|
+
|
|
1048
|
+
log_debug("MemoryManager Start", center=True)
|
|
147
1049
|
|
|
1050
|
+
if len(messages) == 1:
|
|
1051
|
+
input_string = messages[0].get_content_string()
|
|
1052
|
+
else:
|
|
1053
|
+
input_string = f"{', '.join([m.get_content_string() for m in messages if m.role == 'user' and m.content])}"
|
|
1054
|
+
|
|
1055
|
+
model_copy = deepcopy(self.model)
|
|
148
1056
|
# Update the Model (set defaults, add logit etc.)
|
|
149
|
-
self.
|
|
1057
|
+
_tools = self.determine_tools_for_model(
|
|
1058
|
+
self._get_db_tools(
|
|
1059
|
+
user_id,
|
|
1060
|
+
db,
|
|
1061
|
+
input_string,
|
|
1062
|
+
agent_id=agent_id,
|
|
1063
|
+
team_id=team_id,
|
|
1064
|
+
enable_add_memory=add_memories,
|
|
1065
|
+
enable_update_memory=update_memories,
|
|
1066
|
+
enable_delete_memory=True,
|
|
1067
|
+
enable_clear_memory=False,
|
|
1068
|
+
),
|
|
1069
|
+
)
|
|
150
1070
|
|
|
151
1071
|
# Prepare the List of messages to send to the Model
|
|
152
|
-
messages_for_model: List[Message] = [
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
1072
|
+
messages_for_model: List[Message] = [
|
|
1073
|
+
self.get_system_message(
|
|
1074
|
+
existing_memories=existing_memories,
|
|
1075
|
+
enable_update_memory=update_memories,
|
|
1076
|
+
enable_add_memory=add_memories,
|
|
1077
|
+
enable_delete_memory=True,
|
|
1078
|
+
enable_clear_memory=False,
|
|
1079
|
+
),
|
|
1080
|
+
*messages,
|
|
1081
|
+
]
|
|
157
1082
|
|
|
158
|
-
#
|
|
159
|
-
|
|
1083
|
+
# Generate a response from the Model (includes running function calls)
|
|
1084
|
+
response = model_copy.response(
|
|
1085
|
+
messages=messages_for_model,
|
|
1086
|
+
tools=_tools,
|
|
1087
|
+
)
|
|
1088
|
+
|
|
1089
|
+
if response.tool_calls is not None and len(response.tool_calls) > 0:
|
|
1090
|
+
self.memories_updated = True
|
|
1091
|
+
log_debug("MemoryManager End", center=True)
|
|
1092
|
+
|
|
1093
|
+
return response.content or "No response from model"
|
|
1094
|
+
|
|
1095
|
+
async def acreate_or_update_memories(
|
|
1096
|
+
self,
|
|
1097
|
+
messages: List[Message],
|
|
1098
|
+
existing_memories: List[Dict[str, Any]],
|
|
1099
|
+
user_id: str,
|
|
1100
|
+
db: Union[BaseDb, AsyncBaseDb],
|
|
1101
|
+
agent_id: Optional[str] = None,
|
|
1102
|
+
team_id: Optional[str] = None,
|
|
1103
|
+
update_memories: bool = True,
|
|
1104
|
+
add_memories: bool = True,
|
|
1105
|
+
) -> str:
|
|
1106
|
+
if self.model is None:
|
|
1107
|
+
log_error("No model provided for memory manager")
|
|
1108
|
+
return "No model provided for memory manager"
|
|
1109
|
+
|
|
1110
|
+
log_debug("MemoryManager Start", center=True)
|
|
1111
|
+
|
|
1112
|
+
if len(messages) == 1:
|
|
1113
|
+
input_string = messages[0].get_content_string()
|
|
1114
|
+
else:
|
|
1115
|
+
input_string = f"{', '.join([m.get_content_string() for m in messages if m.role == 'user' and m.content])}"
|
|
1116
|
+
|
|
1117
|
+
model_copy = deepcopy(self.model)
|
|
1118
|
+
# Update the Model (set defaults, add logit etc.)
|
|
1119
|
+
if isinstance(db, AsyncBaseDb):
|
|
1120
|
+
_tools = self.determine_tools_for_model(
|
|
1121
|
+
await self._aget_db_tools(
|
|
1122
|
+
user_id,
|
|
1123
|
+
db,
|
|
1124
|
+
input_string,
|
|
1125
|
+
agent_id=agent_id,
|
|
1126
|
+
team_id=team_id,
|
|
1127
|
+
enable_add_memory=add_memories,
|
|
1128
|
+
enable_update_memory=update_memories,
|
|
1129
|
+
enable_delete_memory=True,
|
|
1130
|
+
enable_clear_memory=False,
|
|
1131
|
+
),
|
|
1132
|
+
)
|
|
1133
|
+
else:
|
|
1134
|
+
_tools = self.determine_tools_for_model(
|
|
1135
|
+
self._get_db_tools(
|
|
1136
|
+
user_id,
|
|
1137
|
+
db,
|
|
1138
|
+
input_string,
|
|
1139
|
+
agent_id=agent_id,
|
|
1140
|
+
team_id=team_id,
|
|
1141
|
+
enable_add_memory=add_memories,
|
|
1142
|
+
enable_update_memory=update_memories,
|
|
1143
|
+
enable_delete_memory=True,
|
|
1144
|
+
enable_clear_memory=False,
|
|
1145
|
+
),
|
|
1146
|
+
)
|
|
1147
|
+
|
|
1148
|
+
# Prepare the List of messages to send to the Model
|
|
1149
|
+
messages_for_model: List[Message] = [
|
|
1150
|
+
self.get_system_message(
|
|
1151
|
+
existing_memories=existing_memories,
|
|
1152
|
+
enable_update_memory=update_memories,
|
|
1153
|
+
enable_add_memory=add_memories,
|
|
1154
|
+
enable_delete_memory=True,
|
|
1155
|
+
enable_clear_memory=False,
|
|
1156
|
+
),
|
|
1157
|
+
*messages,
|
|
1158
|
+
]
|
|
160
1159
|
|
|
161
1160
|
# Generate a response from the Model (includes running function calls)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
1161
|
+
response = await model_copy.aresponse(
|
|
1162
|
+
messages=messages_for_model,
|
|
1163
|
+
tools=_tools,
|
|
1164
|
+
)
|
|
166
1165
|
|
|
167
|
-
|
|
1166
|
+
if response.tool_calls is not None and len(response.tool_calls) > 0:
|
|
1167
|
+
self.memories_updated = True
|
|
1168
|
+
log_debug("MemoryManager End", center=True)
|
|
1169
|
+
|
|
1170
|
+
return response.content or "No response from model"
|
|
1171
|
+
|
|
1172
|
+
def run_memory_task(
|
|
168
1173
|
self,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
1174
|
+
task: str,
|
|
1175
|
+
existing_memories: List[Dict[str, Any]],
|
|
1176
|
+
user_id: str,
|
|
1177
|
+
db: BaseDb,
|
|
1178
|
+
delete_memories: bool = True,
|
|
1179
|
+
update_memories: bool = True,
|
|
1180
|
+
add_memories: bool = True,
|
|
1181
|
+
clear_memories: bool = True,
|
|
1182
|
+
) -> str:
|
|
1183
|
+
if self.model is None:
|
|
1184
|
+
log_error("No model provided for memory manager")
|
|
1185
|
+
return "No model provided for memory manager"
|
|
1186
|
+
|
|
1187
|
+
log_debug("MemoryManager Start", center=True)
|
|
173
1188
|
|
|
1189
|
+
model_copy = deepcopy(self.model)
|
|
174
1190
|
# Update the Model (set defaults, add logit etc.)
|
|
175
|
-
self.
|
|
1191
|
+
_tools = self.determine_tools_for_model(
|
|
1192
|
+
self._get_db_tools(
|
|
1193
|
+
user_id,
|
|
1194
|
+
db,
|
|
1195
|
+
task,
|
|
1196
|
+
enable_delete_memory=delete_memories,
|
|
1197
|
+
enable_clear_memory=clear_memories,
|
|
1198
|
+
enable_update_memory=update_memories,
|
|
1199
|
+
enable_add_memory=add_memories,
|
|
1200
|
+
),
|
|
1201
|
+
)
|
|
176
1202
|
|
|
177
1203
|
# Prepare the List of messages to send to the Model
|
|
178
|
-
messages_for_model: List[Message] = [
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
1204
|
+
messages_for_model: List[Message] = [
|
|
1205
|
+
self.get_system_message(
|
|
1206
|
+
existing_memories,
|
|
1207
|
+
enable_delete_memory=delete_memories,
|
|
1208
|
+
enable_clear_memory=clear_memories,
|
|
1209
|
+
enable_update_memory=update_memories,
|
|
1210
|
+
enable_add_memory=add_memories,
|
|
1211
|
+
),
|
|
1212
|
+
# For models that require a non-system message
|
|
1213
|
+
Message(role="user", content=task),
|
|
1214
|
+
]
|
|
1215
|
+
|
|
1216
|
+
# Generate a response from the Model (includes running function calls)
|
|
1217
|
+
response = model_copy.response(
|
|
1218
|
+
messages=messages_for_model,
|
|
1219
|
+
tools=_tools,
|
|
1220
|
+
)
|
|
1221
|
+
|
|
1222
|
+
if response.tool_calls is not None and len(response.tool_calls) > 0:
|
|
1223
|
+
self.memories_updated = True
|
|
1224
|
+
log_debug("MemoryManager End", center=True)
|
|
1225
|
+
|
|
1226
|
+
return response.content or "No response from model"
|
|
1227
|
+
|
|
1228
|
+
async def arun_memory_task(
|
|
1229
|
+
self,
|
|
1230
|
+
task: str,
|
|
1231
|
+
existing_memories: List[Dict[str, Any]],
|
|
1232
|
+
user_id: str,
|
|
1233
|
+
db: Union[BaseDb, AsyncBaseDb],
|
|
1234
|
+
delete_memories: bool = True,
|
|
1235
|
+
clear_memories: bool = True,
|
|
1236
|
+
update_memories: bool = True,
|
|
1237
|
+
add_memories: bool = True,
|
|
1238
|
+
) -> str:
|
|
1239
|
+
if self.model is None:
|
|
1240
|
+
log_error("No model provided for memory manager")
|
|
1241
|
+
return "No model provided for memory manager"
|
|
183
1242
|
|
|
184
|
-
|
|
185
|
-
|
|
1243
|
+
log_debug("MemoryManager Start", center=True)
|
|
1244
|
+
|
|
1245
|
+
model_copy = deepcopy(self.model)
|
|
1246
|
+
# Update the Model (set defaults, add logit etc.)
|
|
1247
|
+
if isinstance(db, AsyncBaseDb):
|
|
1248
|
+
_tools = self.determine_tools_for_model(
|
|
1249
|
+
await self._aget_db_tools(
|
|
1250
|
+
user_id,
|
|
1251
|
+
db,
|
|
1252
|
+
task,
|
|
1253
|
+
enable_delete_memory=delete_memories,
|
|
1254
|
+
enable_clear_memory=clear_memories,
|
|
1255
|
+
enable_update_memory=update_memories,
|
|
1256
|
+
enable_add_memory=add_memories,
|
|
1257
|
+
),
|
|
1258
|
+
)
|
|
1259
|
+
else:
|
|
1260
|
+
_tools = self.determine_tools_for_model(
|
|
1261
|
+
self._get_db_tools(
|
|
1262
|
+
user_id,
|
|
1263
|
+
db,
|
|
1264
|
+
task,
|
|
1265
|
+
enable_delete_memory=delete_memories,
|
|
1266
|
+
enable_clear_memory=clear_memories,
|
|
1267
|
+
enable_update_memory=update_memories,
|
|
1268
|
+
enable_add_memory=add_memories,
|
|
1269
|
+
),
|
|
1270
|
+
)
|
|
1271
|
+
|
|
1272
|
+
# Prepare the List of messages to send to the Model
|
|
1273
|
+
messages_for_model: List[Message] = [
|
|
1274
|
+
self.get_system_message(
|
|
1275
|
+
existing_memories,
|
|
1276
|
+
enable_delete_memory=delete_memories,
|
|
1277
|
+
enable_clear_memory=clear_memories,
|
|
1278
|
+
enable_update_memory=update_memories,
|
|
1279
|
+
enable_add_memory=add_memories,
|
|
1280
|
+
),
|
|
1281
|
+
# For models that require a non-system message
|
|
1282
|
+
Message(role="user", content=task),
|
|
1283
|
+
]
|
|
186
1284
|
|
|
187
1285
|
# Generate a response from the Model (includes running function calls)
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
1286
|
+
response = await model_copy.aresponse(
|
|
1287
|
+
messages=messages_for_model,
|
|
1288
|
+
tools=_tools,
|
|
1289
|
+
)
|
|
1290
|
+
|
|
1291
|
+
if response.tool_calls is not None and len(response.tool_calls) > 0:
|
|
1292
|
+
self.memories_updated = True
|
|
1293
|
+
log_debug("MemoryManager End", center=True)
|
|
1294
|
+
|
|
1295
|
+
return response.content or "No response from model"
|
|
1296
|
+
|
|
1297
|
+
# -*- DB Functions
|
|
1298
|
+
def _get_db_tools(
|
|
1299
|
+
self,
|
|
1300
|
+
user_id: str,
|
|
1301
|
+
db: BaseDb,
|
|
1302
|
+
input_string: str,
|
|
1303
|
+
enable_add_memory: bool = True,
|
|
1304
|
+
enable_update_memory: bool = True,
|
|
1305
|
+
enable_delete_memory: bool = True,
|
|
1306
|
+
enable_clear_memory: bool = True,
|
|
1307
|
+
agent_id: Optional[str] = None,
|
|
1308
|
+
team_id: Optional[str] = None,
|
|
1309
|
+
) -> List[Callable]:
|
|
1310
|
+
def add_memory(memory: str, topics: Optional[List[str]] = None) -> str:
|
|
1311
|
+
"""Use this function to add a memory to the database.
|
|
1312
|
+
Args:
|
|
1313
|
+
memory (str): The memory to be added.
|
|
1314
|
+
topics (Optional[List[str]]): The topics of the memory (e.g. ["name", "hobbies", "location"]).
|
|
1315
|
+
Returns:
|
|
1316
|
+
str: A message indicating if the memory was added successfully or not.
|
|
1317
|
+
"""
|
|
1318
|
+
from uuid import uuid4
|
|
1319
|
+
|
|
1320
|
+
from agno.db.base import UserMemory
|
|
1321
|
+
|
|
1322
|
+
try:
|
|
1323
|
+
memory_id = str(uuid4())
|
|
1324
|
+
db.upsert_user_memory(
|
|
1325
|
+
UserMemory(
|
|
1326
|
+
memory_id=memory_id,
|
|
1327
|
+
user_id=user_id,
|
|
1328
|
+
agent_id=agent_id,
|
|
1329
|
+
team_id=team_id,
|
|
1330
|
+
memory=memory,
|
|
1331
|
+
topics=topics,
|
|
1332
|
+
input=input_string,
|
|
1333
|
+
)
|
|
1334
|
+
)
|
|
1335
|
+
log_debug(f"Memory added: {memory_id}")
|
|
1336
|
+
return "Memory added successfully"
|
|
1337
|
+
except Exception as e:
|
|
1338
|
+
log_warning(f"Error storing memory in db: {e}")
|
|
1339
|
+
return f"Error adding memory: {e}"
|
|
1340
|
+
|
|
1341
|
+
def update_memory(memory_id: str, memory: str, topics: Optional[List[str]] = None) -> str:
|
|
1342
|
+
"""Use this function to update an existing memory in the database.
|
|
1343
|
+
Args:
|
|
1344
|
+
memory_id (str): The id of the memory to be updated.
|
|
1345
|
+
memory (str): The updated memory.
|
|
1346
|
+
topics (Optional[List[str]]): The topics of the memory (e.g. ["name", "hobbies", "location"]).
|
|
1347
|
+
Returns:
|
|
1348
|
+
str: A message indicating if the memory was updated successfully or not.
|
|
1349
|
+
"""
|
|
1350
|
+
from agno.db.base import UserMemory
|
|
1351
|
+
|
|
1352
|
+
if memory == "":
|
|
1353
|
+
return "Can't update memory with empty string. Use the delete memory function if available."
|
|
1354
|
+
|
|
1355
|
+
try:
|
|
1356
|
+
db.upsert_user_memory(
|
|
1357
|
+
UserMemory(
|
|
1358
|
+
memory_id=memory_id,
|
|
1359
|
+
memory=memory,
|
|
1360
|
+
topics=topics,
|
|
1361
|
+
user_id=user_id,
|
|
1362
|
+
input=input_string,
|
|
1363
|
+
)
|
|
1364
|
+
)
|
|
1365
|
+
log_debug("Memory updated")
|
|
1366
|
+
return "Memory updated successfully"
|
|
1367
|
+
except Exception as e:
|
|
1368
|
+
log_warning(f"Error storing memory in db: {e}")
|
|
1369
|
+
return f"Error adding memory: {e}"
|
|
1370
|
+
|
|
1371
|
+
def delete_memory(memory_id: str) -> str:
|
|
1372
|
+
"""Use this function to delete a single memory from the database.
|
|
1373
|
+
Args:
|
|
1374
|
+
memory_id (str): The id of the memory to be deleted.
|
|
1375
|
+
Returns:
|
|
1376
|
+
str: A message indicating if the memory was deleted successfully or not.
|
|
1377
|
+
"""
|
|
1378
|
+
try:
|
|
1379
|
+
db.delete_user_memory(memory_id=memory_id, user_id=user_id)
|
|
1380
|
+
log_debug("Memory deleted")
|
|
1381
|
+
return "Memory deleted successfully"
|
|
1382
|
+
except Exception as e:
|
|
1383
|
+
log_warning(f"Error deleting memory in db: {e}")
|
|
1384
|
+
return f"Error deleting memory: {e}"
|
|
1385
|
+
|
|
1386
|
+
def clear_memory() -> str:
|
|
1387
|
+
"""Use this function to remove all (or clear all) memories from the database.
|
|
1388
|
+
|
|
1389
|
+
Returns:
|
|
1390
|
+
str: A message indicating if the memory was cleared successfully or not.
|
|
1391
|
+
"""
|
|
1392
|
+
db.clear_memories()
|
|
1393
|
+
log_debug("Memory cleared")
|
|
1394
|
+
return "Memory cleared successfully"
|
|
1395
|
+
|
|
1396
|
+
functions: List[Callable] = []
|
|
1397
|
+
if enable_add_memory:
|
|
1398
|
+
functions.append(add_memory)
|
|
1399
|
+
if enable_update_memory:
|
|
1400
|
+
functions.append(update_memory)
|
|
1401
|
+
if enable_delete_memory:
|
|
1402
|
+
functions.append(delete_memory)
|
|
1403
|
+
if enable_clear_memory:
|
|
1404
|
+
functions.append(clear_memory)
|
|
1405
|
+
return functions
|
|
1406
|
+
|
|
1407
|
+
async def _aget_db_tools(
|
|
1408
|
+
self,
|
|
1409
|
+
user_id: str,
|
|
1410
|
+
db: Union[BaseDb, AsyncBaseDb],
|
|
1411
|
+
input_string: str,
|
|
1412
|
+
enable_add_memory: bool = True,
|
|
1413
|
+
enable_update_memory: bool = True,
|
|
1414
|
+
enable_delete_memory: bool = True,
|
|
1415
|
+
enable_clear_memory: bool = True,
|
|
1416
|
+
agent_id: Optional[str] = None,
|
|
1417
|
+
team_id: Optional[str] = None,
|
|
1418
|
+
) -> List[Callable]:
|
|
1419
|
+
async def add_memory(memory: str, topics: Optional[List[str]] = None) -> str:
|
|
1420
|
+
"""Use this function to add a memory to the database.
|
|
1421
|
+
Args:
|
|
1422
|
+
memory (str): The memory to be added.
|
|
1423
|
+
topics (Optional[List[str]]): The topics of the memory (e.g. ["name", "hobbies", "location"]).
|
|
1424
|
+
Returns:
|
|
1425
|
+
str: A message indicating if the memory was added successfully or not.
|
|
1426
|
+
"""
|
|
1427
|
+
from uuid import uuid4
|
|
1428
|
+
|
|
1429
|
+
from agno.db.base import UserMemory
|
|
1430
|
+
|
|
1431
|
+
try:
|
|
1432
|
+
memory_id = str(uuid4())
|
|
1433
|
+
if isinstance(db, AsyncBaseDb):
|
|
1434
|
+
await db.upsert_user_memory(
|
|
1435
|
+
UserMemory(
|
|
1436
|
+
memory_id=memory_id,
|
|
1437
|
+
user_id=user_id,
|
|
1438
|
+
agent_id=agent_id,
|
|
1439
|
+
team_id=team_id,
|
|
1440
|
+
memory=memory,
|
|
1441
|
+
topics=topics,
|
|
1442
|
+
input=input_string,
|
|
1443
|
+
)
|
|
1444
|
+
)
|
|
1445
|
+
else:
|
|
1446
|
+
db.upsert_user_memory(
|
|
1447
|
+
UserMemory(
|
|
1448
|
+
memory_id=memory_id,
|
|
1449
|
+
user_id=user_id,
|
|
1450
|
+
agent_id=agent_id,
|
|
1451
|
+
team_id=team_id,
|
|
1452
|
+
memory=memory,
|
|
1453
|
+
topics=topics,
|
|
1454
|
+
input=input_string,
|
|
1455
|
+
)
|
|
1456
|
+
)
|
|
1457
|
+
log_debug(f"Memory added: {memory_id}")
|
|
1458
|
+
return "Memory added successfully"
|
|
1459
|
+
except Exception as e:
|
|
1460
|
+
log_warning(f"Error storing memory in db: {e}")
|
|
1461
|
+
return f"Error adding memory: {e}"
|
|
1462
|
+
|
|
1463
|
+
async def update_memory(memory_id: str, memory: str, topics: Optional[List[str]] = None) -> str:
|
|
1464
|
+
"""Use this function to update an existing memory in the database.
|
|
1465
|
+
Args:
|
|
1466
|
+
memory_id (str): The id of the memory to be updated.
|
|
1467
|
+
memory (str): The updated memory.
|
|
1468
|
+
topics (Optional[List[str]]): The topics of the memory (e.g. ["name", "hobbies", "location"]).
|
|
1469
|
+
Returns:
|
|
1470
|
+
str: A message indicating if the memory was updated successfully or not.
|
|
1471
|
+
"""
|
|
1472
|
+
from agno.db.base import UserMemory
|
|
1473
|
+
|
|
1474
|
+
if memory == "":
|
|
1475
|
+
return "Can't update memory with empty string. Use the delete memory function if available."
|
|
1476
|
+
|
|
1477
|
+
try:
|
|
1478
|
+
if isinstance(db, AsyncBaseDb):
|
|
1479
|
+
await db.upsert_user_memory(
|
|
1480
|
+
UserMemory(
|
|
1481
|
+
memory_id=memory_id,
|
|
1482
|
+
memory=memory,
|
|
1483
|
+
topics=topics,
|
|
1484
|
+
input=input_string,
|
|
1485
|
+
)
|
|
1486
|
+
)
|
|
1487
|
+
else:
|
|
1488
|
+
db.upsert_user_memory(
|
|
1489
|
+
UserMemory(
|
|
1490
|
+
memory_id=memory_id,
|
|
1491
|
+
memory=memory,
|
|
1492
|
+
topics=topics,
|
|
1493
|
+
input=input_string,
|
|
1494
|
+
)
|
|
1495
|
+
)
|
|
1496
|
+
log_debug("Memory updated")
|
|
1497
|
+
return "Memory updated successfully"
|
|
1498
|
+
except Exception as e:
|
|
1499
|
+
log_warning(f"Error storing memory in db: {e}")
|
|
1500
|
+
return f"Error adding memory: {e}"
|
|
1501
|
+
|
|
1502
|
+
async def delete_memory(memory_id: str) -> str:
|
|
1503
|
+
"""Use this function to delete a single memory from the database.
|
|
1504
|
+
Args:
|
|
1505
|
+
memory_id (str): The id of the memory to be deleted.
|
|
1506
|
+
Returns:
|
|
1507
|
+
str: A message indicating if the memory was deleted successfully or not.
|
|
1508
|
+
"""
|
|
1509
|
+
try:
|
|
1510
|
+
if isinstance(db, AsyncBaseDb):
|
|
1511
|
+
await db.delete_user_memory(memory_id=memory_id)
|
|
1512
|
+
else:
|
|
1513
|
+
db.delete_user_memory(memory_id=memory_id)
|
|
1514
|
+
log_debug("Memory deleted")
|
|
1515
|
+
return "Memory deleted successfully"
|
|
1516
|
+
except Exception as e:
|
|
1517
|
+
log_warning(f"Error deleting memory in db: {e}")
|
|
1518
|
+
return f"Error deleting memory: {e}"
|
|
1519
|
+
|
|
1520
|
+
async def clear_memory() -> str:
|
|
1521
|
+
"""Use this function to remove all (or clear all) memories from the database.
|
|
1522
|
+
|
|
1523
|
+
Returns:
|
|
1524
|
+
str: A message indicating if the memory was cleared successfully or not.
|
|
1525
|
+
"""
|
|
1526
|
+
if isinstance(db, AsyncBaseDb):
|
|
1527
|
+
await db.clear_memories()
|
|
1528
|
+
else:
|
|
1529
|
+
db.clear_memories()
|
|
1530
|
+
log_debug("Memory cleared")
|
|
1531
|
+
return "Memory cleared successfully"
|
|
1532
|
+
|
|
1533
|
+
functions: List[Callable] = []
|
|
1534
|
+
if enable_add_memory:
|
|
1535
|
+
functions.append(add_memory)
|
|
1536
|
+
if enable_update_memory:
|
|
1537
|
+
functions.append(update_memory)
|
|
1538
|
+
if enable_delete_memory:
|
|
1539
|
+
functions.append(delete_memory)
|
|
1540
|
+
if enable_clear_memory:
|
|
1541
|
+
functions.append(clear_memory)
|
|
1542
|
+
return functions
|