agno 2.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/agent/agent.py +5540 -2273
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- 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/async_postgres/__init__.py +3 -0
- agno/db/base.py +689 -6
- agno/db/dynamo/dynamo.py +933 -37
- agno/db/dynamo/schemas.py +174 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +831 -9
- agno/db/firestore/schemas.py +51 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +660 -12
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +287 -14
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +590 -14
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +43 -13
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/__init__.py +15 -1
- agno/db/mongo/async_mongo.py +2760 -0
- agno/db/mongo/mongo.py +879 -11
- agno/db/mongo/schemas.py +42 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/__init__.py +2 -1
- agno/db/mysql/async_mysql.py +2912 -0
- agno/db/mysql/mysql.py +946 -68
- agno/db/mysql/schemas.py +72 -10
- agno/db/mysql/utils.py +198 -7
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2579 -0
- agno/db/postgres/postgres.py +942 -57
- agno/db/postgres/schemas.py +81 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +671 -7
- agno/db/redis/schemas.py +50 -0
- agno/db/redis/utils.py +65 -7
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/evals.py +1 -0
- agno/db/schemas/memory.py +17 -2
- agno/db/singlestore/schemas.py +63 -0
- agno/db/singlestore/singlestore.py +949 -83
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2911 -0
- agno/db/sqlite/schemas.py +62 -0
- agno/db/sqlite/sqlite.py +965 -46
- agno/db/sqlite/utils.py +169 -8
- 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 +2 -0
- agno/eval/__init__.py +10 -0
- agno/eval/accuracy.py +75 -55
- agno/eval/agent_as_judge.py +861 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +16 -7
- agno/eval/reliability.py +28 -16
- agno/eval/utils.py +35 -17
- agno/exceptions.py +27 -2
- agno/filters.py +354 -0
- agno/guardrails/prompt_injection.py +1 -0
- agno/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/client.py +1 -1
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +4 -1
- agno/knowledge/chunking/semantic.py +9 -4
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +8 -0
- agno/knowledge/embedder/openai.py +8 -8
- agno/knowledge/embedder/sentence_transformer.py +6 -2
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/knowledge.py +1618 -318
- agno/knowledge/reader/base.py +6 -2
- agno/knowledge/reader/csv_reader.py +8 -10
- agno/knowledge/reader/docx_reader.py +5 -6
- agno/knowledge/reader/field_labeled_csv_reader.py +16 -20
- agno/knowledge/reader/json_reader.py +5 -4
- agno/knowledge/reader/markdown_reader.py +8 -8
- agno/knowledge/reader/pdf_reader.py +17 -19
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +32 -3
- agno/knowledge/reader/s3_reader.py +3 -3
- agno/knowledge/reader/tavily_reader.py +193 -0
- agno/knowledge/reader/text_reader.py +22 -10
- agno/knowledge/reader/web_search_reader.py +1 -48
- agno/knowledge/reader/website_reader.py +10 -10
- agno/knowledge/reader/wikipedia_reader.py +33 -1
- agno/knowledge/types.py +1 -0
- agno/knowledge/utils.py +72 -7
- agno/media.py +22 -6
- agno/memory/__init__.py +14 -1
- agno/memory/manager.py +544 -83
- agno/memory/strategies/__init__.py +15 -0
- agno/memory/strategies/base.py +66 -0
- agno/memory/strategies/summarize.py +196 -0
- agno/memory/strategies/types.py +37 -0
- agno/models/aimlapi/aimlapi.py +17 -0
- agno/models/anthropic/claude.py +515 -40
- agno/models/aws/bedrock.py +102 -21
- agno/models/aws/claude.py +131 -274
- agno/models/azure/ai_foundry.py +41 -19
- agno/models/azure/openai_chat.py +39 -8
- agno/models/base.py +1249 -525
- agno/models/cerebras/cerebras.py +91 -21
- agno/models/cerebras/cerebras_openai.py +21 -2
- agno/models/cohere/chat.py +40 -6
- agno/models/cometapi/cometapi.py +18 -1
- agno/models/dashscope/dashscope.py +2 -3
- agno/models/deepinfra/deepinfra.py +18 -1
- agno/models/deepseek/deepseek.py +69 -3
- agno/models/fireworks/fireworks.py +18 -1
- agno/models/google/gemini.py +877 -80
- agno/models/google/utils.py +22 -0
- agno/models/groq/groq.py +51 -18
- agno/models/huggingface/huggingface.py +17 -6
- agno/models/ibm/watsonx.py +16 -6
- agno/models/internlm/internlm.py +18 -1
- agno/models/langdb/langdb.py +13 -1
- agno/models/litellm/chat.py +44 -9
- agno/models/litellm/litellm_openai.py +18 -1
- agno/models/message.py +28 -5
- agno/models/meta/llama.py +47 -14
- agno/models/meta/llama_openai.py +22 -17
- agno/models/mistral/mistral.py +8 -4
- agno/models/nebius/nebius.py +6 -7
- agno/models/nvidia/nvidia.py +20 -3
- agno/models/ollama/chat.py +24 -8
- agno/models/openai/chat.py +104 -29
- agno/models/openai/responses.py +101 -81
- agno/models/openrouter/openrouter.py +60 -3
- agno/models/perplexity/perplexity.py +17 -1
- agno/models/portkey/portkey.py +7 -6
- agno/models/requesty/requesty.py +24 -4
- agno/models/response.py +73 -2
- agno/models/sambanova/sambanova.py +20 -3
- agno/models/siliconflow/siliconflow.py +19 -2
- agno/models/together/together.py +20 -3
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +20 -3
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +190 -0
- agno/models/vllm/vllm.py +19 -14
- agno/models/xai/xai.py +19 -2
- agno/os/app.py +549 -152
- agno/os/auth.py +190 -3
- agno/os/config.py +23 -0
- agno/os/interfaces/a2a/router.py +8 -11
- agno/os/interfaces/a2a/utils.py +1 -1
- agno/os/interfaces/agui/router.py +18 -3
- agno/os/interfaces/agui/utils.py +152 -39
- agno/os/interfaces/slack/router.py +55 -37
- agno/os/interfaces/slack/slack.py +9 -1
- agno/os/interfaces/whatsapp/router.py +0 -1
- agno/os/interfaces/whatsapp/security.py +3 -1
- agno/os/mcp.py +110 -52
- agno/os/middleware/__init__.py +2 -0
- agno/os/middleware/jwt.py +676 -112
- agno/os/router.py +40 -1478
- 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/evals.py +96 -39
- agno/os/routers/evals/schemas.py +65 -33
- agno/os/routers/evals/utils.py +80 -10
- agno/os/routers/health.py +10 -4
- agno/os/routers/knowledge/knowledge.py +196 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +279 -52
- agno/os/routers/memory/schemas.py +46 -17
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +462 -34
- 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 +256 -693
- agno/os/scopes.py +469 -0
- agno/os/utils.py +514 -36
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/openai.py +5 -0
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +155 -32
- agno/run/base.py +55 -3
- agno/run/requirement.py +181 -0
- agno/run/team.py +125 -38
- agno/run/workflow.py +72 -18
- agno/session/agent.py +102 -89
- agno/session/summary.py +56 -15
- agno/session/team.py +164 -90
- agno/session/workflow.py +405 -40
- agno/table.py +10 -0
- agno/team/team.py +3974 -1903
- agno/tools/dalle.py +2 -4
- agno/tools/eleven_labs.py +23 -25
- agno/tools/exa.py +21 -16
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +16 -10
- agno/tools/firecrawl.py +15 -7
- agno/tools/function.py +193 -38
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +271 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- 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 +3 -3
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/nano_banana.py +151 -0
- agno/tools/notion.py +204 -0
- agno/tools/parallel.py +314 -0
- agno/tools/postgres.py +76 -36
- agno/tools/redshift.py +406 -0
- agno/tools/scrapegraph.py +1 -1
- agno/tools/shopify.py +1519 -0
- agno/tools/slack.py +18 -3
- agno/tools/spotify.py +919 -0
- agno/tools/tavily.py +146 -0
- agno/tools/toolkit.py +25 -0
- agno/tools/workflow.py +8 -1
- agno/tools/yfinance.py +12 -11
- 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/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +151 -3
- agno/utils/gemini.py +15 -5
- agno/utils/hooks.py +118 -4
- agno/utils/http.py +113 -2
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +92 -2
- agno/utils/media.py +187 -1
- agno/utils/merge_dict.py +3 -3
- agno/utils/message.py +60 -0
- agno/utils/models/ai_foundry.py +9 -2
- agno/utils/models/claude.py +49 -14
- agno/utils/models/cohere.py +9 -2
- agno/utils/models/llama.py +9 -2
- agno/utils/models/mistral.py +4 -2
- agno/utils/print_response/agent.py +109 -16
- agno/utils/print_response/team.py +223 -30
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/streamlit.py +1 -1
- agno/utils/team.py +98 -9
- agno/utils/tokens.py +657 -0
- agno/vectordb/base.py +39 -7
- agno/vectordb/cassandra/cassandra.py +21 -5
- agno/vectordb/chroma/chromadb.py +43 -12
- agno/vectordb/clickhouse/clickhousedb.py +21 -5
- agno/vectordb/couchbase/couchbase.py +29 -5
- agno/vectordb/lancedb/lance_db.py +92 -181
- agno/vectordb/langchaindb/langchaindb.py +24 -4
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/llamaindexdb.py +25 -5
- agno/vectordb/milvus/milvus.py +50 -37
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +36 -30
- agno/vectordb/pgvector/pgvector.py +201 -77
- agno/vectordb/pineconedb/pineconedb.py +41 -23
- agno/vectordb/qdrant/qdrant.py +67 -54
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +682 -0
- agno/vectordb/singlestore/singlestore.py +50 -29
- agno/vectordb/surrealdb/surrealdb.py +31 -41
- agno/vectordb/upstashdb/upstashdb.py +34 -6
- agno/vectordb/weaviate/weaviate.py +53 -14
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +120 -18
- agno/workflow/loop.py +77 -10
- agno/workflow/parallel.py +231 -143
- agno/workflow/router.py +118 -17
- agno/workflow/step.py +609 -170
- agno/workflow/steps.py +73 -6
- agno/workflow/types.py +96 -21
- agno/workflow/workflow.py +2039 -262
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/METADATA +201 -66
- agno-2.3.13.dist-info/RECORD +613 -0
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -679
- agno/tools/memori.py +0 -339
- agno-2.1.2.dist-info/RECORD +0 -543
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +0 -0
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/licenses/LICENSE +0 -0
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
|
@@ -16,23 +16,23 @@ class ContentStatus(str, Enum):
|
|
|
16
16
|
class ContentStatusResponse(BaseModel):
|
|
17
17
|
"""Response model for content status endpoint."""
|
|
18
18
|
|
|
19
|
-
status: ContentStatus
|
|
20
|
-
status_message: str = ""
|
|
19
|
+
status: ContentStatus = Field(..., description="Current processing status of the content")
|
|
20
|
+
status_message: str = Field("", description="Status message or error details")
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class ContentResponseSchema(BaseModel):
|
|
24
|
-
id: str
|
|
25
|
-
name: Optional[str] = None
|
|
26
|
-
description: Optional[str] = None
|
|
27
|
-
type: Optional[str] = None
|
|
28
|
-
size: Optional[str] = None
|
|
29
|
-
linked_to: Optional[str] = None
|
|
30
|
-
metadata: Optional[dict] = None
|
|
31
|
-
access_count: Optional[int] = None
|
|
32
|
-
status: Optional[ContentStatus] = None
|
|
33
|
-
status_message: Optional[str] = None
|
|
34
|
-
created_at: Optional[datetime] = None
|
|
35
|
-
updated_at: Optional[datetime] = None
|
|
24
|
+
id: str = Field(..., description="Unique identifier for the content")
|
|
25
|
+
name: Optional[str] = Field(None, description="Name of the content")
|
|
26
|
+
description: Optional[str] = Field(None, description="Description of the content")
|
|
27
|
+
type: Optional[str] = Field(None, description="MIME type of the content")
|
|
28
|
+
size: Optional[str] = Field(None, description="Size of the content in bytes")
|
|
29
|
+
linked_to: Optional[str] = Field(None, description="ID of related content if linked")
|
|
30
|
+
metadata: Optional[dict] = Field(None, description="Additional metadata as key-value pairs")
|
|
31
|
+
access_count: Optional[int] = Field(None, description="Number of times content has been accessed", ge=0)
|
|
32
|
+
status: Optional[ContentStatus] = Field(None, description="Processing status of the content")
|
|
33
|
+
status_message: Optional[str] = Field(None, description="Status message or error details")
|
|
34
|
+
created_at: Optional[datetime] = Field(None, description="Timestamp when content was created")
|
|
35
|
+
updated_at: Optional[datetime] = Field(None, description="Timestamp when content was last updated")
|
|
36
36
|
|
|
37
37
|
@classmethod
|
|
38
38
|
def from_dict(cls, content: Dict[str, Any]) -> "ContentResponseSchema":
|
|
@@ -99,20 +99,80 @@ class ContentUpdateSchema(BaseModel):
|
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
class ReaderSchema(BaseModel):
|
|
102
|
-
id: str
|
|
103
|
-
name: Optional[str] = None
|
|
104
|
-
description: Optional[str] = None
|
|
105
|
-
chunkers: Optional[List[str]] = None
|
|
102
|
+
id: str = Field(..., description="Unique identifier for the reader")
|
|
103
|
+
name: Optional[str] = Field(None, description="Name of the reader")
|
|
104
|
+
description: Optional[str] = Field(None, description="Description of the reader's capabilities")
|
|
105
|
+
chunkers: Optional[List[str]] = Field(None, description="List of supported chunking strategies")
|
|
106
106
|
|
|
107
107
|
|
|
108
108
|
class ChunkerSchema(BaseModel):
|
|
109
109
|
key: str
|
|
110
110
|
name: Optional[str] = None
|
|
111
111
|
description: Optional[str] = None
|
|
112
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class VectorDbSchema(BaseModel):
|
|
116
|
+
id: str = Field(..., description="Unique identifier for the vector database")
|
|
117
|
+
name: Optional[str] = Field(None, description="Name of the vector database")
|
|
118
|
+
description: Optional[str] = Field(None, description="Description of the vector database")
|
|
119
|
+
search_types: Optional[List[str]] = Field(
|
|
120
|
+
None, description="List of supported search types (vector, keyword, hybrid)"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class VectorSearchResult(BaseModel):
|
|
125
|
+
"""Schema for search result documents."""
|
|
126
|
+
|
|
127
|
+
id: str = Field(..., description="Unique identifier for the search result document")
|
|
128
|
+
content: str = Field(..., description="Content text of the document")
|
|
129
|
+
name: Optional[str] = Field(None, description="Name of the document")
|
|
130
|
+
meta_data: Optional[Dict[str, Any]] = Field(None, description="Metadata associated with the document")
|
|
131
|
+
usage: Optional[Dict[str, Any]] = Field(None, description="Usage statistics (e.g., token counts)")
|
|
132
|
+
reranking_score: Optional[float] = Field(None, description="Reranking score for relevance", ge=0.0, le=1.0)
|
|
133
|
+
content_id: Optional[str] = Field(None, description="ID of the source content")
|
|
134
|
+
content_origin: Optional[str] = Field(None, description="Origin URL or source of the content")
|
|
135
|
+
size: Optional[int] = Field(None, description="Size of the content in bytes", ge=0)
|
|
136
|
+
|
|
137
|
+
@classmethod
|
|
138
|
+
def from_document(cls, document) -> "VectorSearchResult":
|
|
139
|
+
"""Convert a Document object to a serializable VectorSearchResult."""
|
|
140
|
+
return cls(
|
|
141
|
+
id=document.id,
|
|
142
|
+
content=document.content,
|
|
143
|
+
name=getattr(document, "name", None),
|
|
144
|
+
meta_data=getattr(document, "meta_data", None),
|
|
145
|
+
usage=getattr(document, "usage", None),
|
|
146
|
+
reranking_score=getattr(document, "reranking_score", None),
|
|
147
|
+
content_id=getattr(document, "content_id", None),
|
|
148
|
+
content_origin=getattr(document, "content_origin", None),
|
|
149
|
+
size=getattr(document, "size", None),
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class VectorSearchRequestSchema(BaseModel):
|
|
154
|
+
"""Schema for vector search request."""
|
|
155
|
+
|
|
156
|
+
class Meta(BaseModel):
|
|
157
|
+
"""Inline metadata schema for pagination."""
|
|
158
|
+
|
|
159
|
+
limit: int = Field(20, description="Number of results per page", ge=1, le=100)
|
|
160
|
+
page: int = Field(1, description="Page number", ge=1)
|
|
161
|
+
|
|
162
|
+
query: str = Field(..., description="The search query text")
|
|
163
|
+
db_id: Optional[str] = Field(None, description="The content database ID to search in")
|
|
164
|
+
vector_db_ids: Optional[List[str]] = Field(None, description="List of vector database IDs to search in")
|
|
165
|
+
search_type: Optional[str] = Field(None, description="The type of search to perform (vector, keyword, hybrid)")
|
|
166
|
+
max_results: Optional[int] = Field(None, description="The maximum number of results to return", ge=1, le=1000)
|
|
167
|
+
filters: Optional[Dict[str, Any]] = Field(None, description="Filters to apply to the search results")
|
|
168
|
+
meta: Optional[Meta] = Field(
|
|
169
|
+
None, description="Pagination metadata. Limit and page number to return a subset of results."
|
|
170
|
+
)
|
|
112
171
|
|
|
113
172
|
|
|
114
173
|
class ConfigResponseSchema(BaseModel):
|
|
115
|
-
readers: Optional[Dict[str, ReaderSchema]] = None
|
|
116
|
-
readersForType: Optional[Dict[str, List[str]]] = None
|
|
117
|
-
chunkers: Optional[Dict[str, ChunkerSchema]] = None
|
|
118
|
-
filters: Optional[List[str]] = None
|
|
174
|
+
readers: Optional[Dict[str, ReaderSchema]] = Field(None, description="Available content readers")
|
|
175
|
+
readersForType: Optional[Dict[str, List[str]]] = Field(None, description="Mapping of content types to reader IDs")
|
|
176
|
+
chunkers: Optional[Dict[str, ChunkerSchema]] = Field(None, description="Available chunking strategies")
|
|
177
|
+
filters: Optional[List[str]] = Field(None, description="Available filter tags")
|
|
178
|
+
vector_dbs: Optional[List[VectorDbSchema]] = Field(None, description="Configured vector databases")
|
agno/os/routers/memory/memory.py
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import math
|
|
3
|
-
from typing import List, Optional
|
|
3
|
+
from typing import List, Optional, Union, cast
|
|
4
4
|
from uuid import uuid4
|
|
5
5
|
|
|
6
6
|
from fastapi import Depends, HTTPException, Path, Query, Request
|
|
7
7
|
from fastapi.routing import APIRouter
|
|
8
8
|
|
|
9
|
-
from agno.db.base import BaseDb
|
|
9
|
+
from agno.db.base import AsyncBaseDb, BaseDb
|
|
10
10
|
from agno.db.schemas import UserMemory
|
|
11
|
+
from agno.models.utils import get_model
|
|
11
12
|
from agno.os.auth import get_authentication_dependency
|
|
12
13
|
from agno.os.routers.memory.schemas import (
|
|
13
14
|
DeleteMemoriesRequest,
|
|
15
|
+
OptimizeMemoriesRequest,
|
|
16
|
+
OptimizeMemoriesResponse,
|
|
14
17
|
UserMemoryCreateSchema,
|
|
15
18
|
UserMemorySchema,
|
|
16
19
|
UserStatsSchema,
|
|
@@ -31,7 +34,9 @@ from agno.os.utils import get_db
|
|
|
31
34
|
logger = logging.getLogger(__name__)
|
|
32
35
|
|
|
33
36
|
|
|
34
|
-
def get_memory_router(
|
|
37
|
+
def get_memory_router(
|
|
38
|
+
dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]], settings: AgnoAPISettings = AgnoAPISettings(), **kwargs
|
|
39
|
+
) -> APIRouter:
|
|
35
40
|
"""Create memory router with comprehensive OpenAPI documentation for user memory management endpoints."""
|
|
36
41
|
router = APIRouter(
|
|
37
42
|
dependencies=[Depends(get_authentication_dependency(settings))],
|
|
@@ -47,7 +52,7 @@ def get_memory_router(dbs: dict[str, BaseDb], settings: AgnoAPISettings = AgnoAP
|
|
|
47
52
|
return attach_routes(router=router, dbs=dbs)
|
|
48
53
|
|
|
49
54
|
|
|
50
|
-
def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
55
|
+
def attach_routes(router: APIRouter, dbs: dict[str, list[Union[BaseDb, AsyncBaseDb]]]) -> APIRouter:
|
|
51
56
|
@router.post(
|
|
52
57
|
"/memories",
|
|
53
58
|
response_model=UserMemorySchema,
|
|
@@ -83,6 +88,7 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
83
88
|
request: Request,
|
|
84
89
|
payload: UserMemoryCreateSchema,
|
|
85
90
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for memory storage"),
|
|
91
|
+
table: Optional[str] = Query(default=None, description="Table to use for memory storage"),
|
|
86
92
|
) -> UserMemorySchema:
|
|
87
93
|
if hasattr(request.state, "user_id"):
|
|
88
94
|
user_id = request.state.user_id
|
|
@@ -91,16 +97,30 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
91
97
|
if payload.user_id is None:
|
|
92
98
|
raise HTTPException(status_code=400, detail="User ID is required")
|
|
93
99
|
|
|
94
|
-
db = get_db(dbs, db_id)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
db = await get_db(dbs, db_id, table)
|
|
101
|
+
|
|
102
|
+
if isinstance(db, AsyncBaseDb):
|
|
103
|
+
db = cast(AsyncBaseDb, db)
|
|
104
|
+
user_memory = await db.upsert_user_memory(
|
|
105
|
+
memory=UserMemory(
|
|
106
|
+
memory_id=str(uuid4()),
|
|
107
|
+
memory=payload.memory,
|
|
108
|
+
topics=payload.topics or [],
|
|
109
|
+
user_id=payload.user_id,
|
|
110
|
+
),
|
|
111
|
+
deserialize=False,
|
|
112
|
+
)
|
|
113
|
+
else:
|
|
114
|
+
user_memory = db.upsert_user_memory(
|
|
115
|
+
memory=UserMemory(
|
|
116
|
+
memory_id=str(uuid4()),
|
|
117
|
+
memory=payload.memory,
|
|
118
|
+
topics=payload.topics or [],
|
|
119
|
+
user_id=payload.user_id,
|
|
120
|
+
),
|
|
121
|
+
deserialize=False,
|
|
122
|
+
)
|
|
123
|
+
|
|
104
124
|
if not user_memory:
|
|
105
125
|
raise HTTPException(status_code=500, detail="Failed to create memory")
|
|
106
126
|
|
|
@@ -122,9 +142,14 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
122
142
|
memory_id: str = Path(description="Memory ID to delete"),
|
|
123
143
|
user_id: Optional[str] = Query(default=None, description="User ID to delete memory for"),
|
|
124
144
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for deletion"),
|
|
145
|
+
table: Optional[str] = Query(default=None, description="Table to use for deletion"),
|
|
125
146
|
) -> None:
|
|
126
|
-
db = get_db(dbs, db_id)
|
|
127
|
-
db
|
|
147
|
+
db = await get_db(dbs, db_id, table)
|
|
148
|
+
if isinstance(db, AsyncBaseDb):
|
|
149
|
+
db = cast(AsyncBaseDb, db)
|
|
150
|
+
await db.delete_user_memory(memory_id=memory_id, user_id=user_id)
|
|
151
|
+
else:
|
|
152
|
+
db.delete_user_memory(memory_id=memory_id, user_id=user_id)
|
|
128
153
|
|
|
129
154
|
@router.delete(
|
|
130
155
|
"/memories",
|
|
@@ -144,9 +169,14 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
144
169
|
async def delete_memories(
|
|
145
170
|
request: DeleteMemoriesRequest,
|
|
146
171
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for deletion"),
|
|
172
|
+
table: Optional[str] = Query(default=None, description="Table to use for deletion"),
|
|
147
173
|
) -> None:
|
|
148
|
-
db = get_db(dbs, db_id)
|
|
149
|
-
db
|
|
174
|
+
db = await get_db(dbs, db_id, table)
|
|
175
|
+
if isinstance(db, AsyncBaseDb):
|
|
176
|
+
db = cast(AsyncBaseDb, db)
|
|
177
|
+
await db.delete_user_memories(memory_ids=request.memory_ids, user_id=request.user_id)
|
|
178
|
+
else:
|
|
179
|
+
db.delete_user_memories(memory_ids=request.memory_ids, user_id=request.user_id)
|
|
150
180
|
|
|
151
181
|
@router.get(
|
|
152
182
|
"/memories",
|
|
@@ -193,26 +223,44 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
193
223
|
sort_by: Optional[str] = Query(default="updated_at", description="Field to sort memories by"),
|
|
194
224
|
sort_order: Optional[SortOrder] = Query(default="desc", description="Sort order (asc or desc)"),
|
|
195
225
|
db_id: Optional[str] = Query(default=None, description="Database ID to query memories from"),
|
|
226
|
+
table: Optional[str] = Query(default=None, description="The database table to use"),
|
|
196
227
|
) -> PaginatedResponse[UserMemorySchema]:
|
|
197
|
-
db = get_db(dbs, db_id)
|
|
228
|
+
db = await get_db(dbs, db_id, table)
|
|
198
229
|
|
|
199
230
|
if hasattr(request.state, "user_id"):
|
|
200
231
|
user_id = request.state.user_id
|
|
201
232
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
233
|
+
if isinstance(db, AsyncBaseDb):
|
|
234
|
+
db = cast(AsyncBaseDb, db)
|
|
235
|
+
user_memories, total_count = await db.get_user_memories(
|
|
236
|
+
limit=limit,
|
|
237
|
+
page=page,
|
|
238
|
+
user_id=user_id,
|
|
239
|
+
agent_id=agent_id,
|
|
240
|
+
team_id=team_id,
|
|
241
|
+
topics=topics,
|
|
242
|
+
search_content=search_content,
|
|
243
|
+
sort_by=sort_by,
|
|
244
|
+
sort_order=sort_order,
|
|
245
|
+
deserialize=False,
|
|
246
|
+
)
|
|
247
|
+
else:
|
|
248
|
+
user_memories, total_count = db.get_user_memories( # type: ignore
|
|
249
|
+
limit=limit,
|
|
250
|
+
page=page,
|
|
251
|
+
user_id=user_id,
|
|
252
|
+
agent_id=agent_id,
|
|
253
|
+
team_id=team_id,
|
|
254
|
+
topics=topics,
|
|
255
|
+
search_content=search_content,
|
|
256
|
+
sort_by=sort_by,
|
|
257
|
+
sort_order=sort_order,
|
|
258
|
+
deserialize=False,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
memories = [UserMemorySchema.from_dict(user_memory) for user_memory in user_memories] # type: ignore
|
|
214
262
|
return PaginatedResponse(
|
|
215
|
-
data=[
|
|
263
|
+
data=[memory for memory in memories if memory is not None],
|
|
216
264
|
meta=PaginationInfo(
|
|
217
265
|
page=page,
|
|
218
266
|
limit=limit,
|
|
@@ -249,12 +297,22 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
249
297
|
},
|
|
250
298
|
)
|
|
251
299
|
async def get_memory(
|
|
300
|
+
request: Request,
|
|
252
301
|
memory_id: str = Path(description="Memory ID to retrieve"),
|
|
253
302
|
user_id: Optional[str] = Query(default=None, description="User ID to query memory for"),
|
|
254
303
|
db_id: Optional[str] = Query(default=None, description="Database ID to query memory from"),
|
|
304
|
+
table: Optional[str] = Query(default=None, description="Table to query memory from"),
|
|
255
305
|
) -> UserMemorySchema:
|
|
256
|
-
db = get_db(dbs, db_id)
|
|
257
|
-
|
|
306
|
+
db = await get_db(dbs, db_id, table)
|
|
307
|
+
|
|
308
|
+
if hasattr(request.state, "user_id"):
|
|
309
|
+
user_id = request.state.user_id
|
|
310
|
+
|
|
311
|
+
if isinstance(db, AsyncBaseDb):
|
|
312
|
+
db = cast(AsyncBaseDb, db)
|
|
313
|
+
user_memory = await db.get_user_memory(memory_id=memory_id, user_id=user_id, deserialize=False)
|
|
314
|
+
else:
|
|
315
|
+
user_memory = db.get_user_memory(memory_id=memory_id, user_id=user_id, deserialize=False)
|
|
258
316
|
if not user_memory:
|
|
259
317
|
raise HTTPException(status_code=404, detail=f"Memory with ID {memory_id} not found")
|
|
260
318
|
|
|
@@ -293,9 +351,14 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
293
351
|
)
|
|
294
352
|
async def get_topics(
|
|
295
353
|
db_id: Optional[str] = Query(default=None, description="Database ID to query topics from"),
|
|
354
|
+
table: Optional[str] = Query(default=None, description="Table to query topics from"),
|
|
296
355
|
) -> List[str]:
|
|
297
|
-
db = get_db(dbs, db_id)
|
|
298
|
-
|
|
356
|
+
db = await get_db(dbs, db_id, table)
|
|
357
|
+
if isinstance(db, AsyncBaseDb):
|
|
358
|
+
db = cast(AsyncBaseDb, db)
|
|
359
|
+
return await db.get_all_memory_topics()
|
|
360
|
+
else:
|
|
361
|
+
return db.get_all_memory_topics()
|
|
299
362
|
|
|
300
363
|
@router.patch(
|
|
301
364
|
"/memories/{memory_id}",
|
|
@@ -335,9 +398,8 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
335
398
|
payload: UserMemoryCreateSchema,
|
|
336
399
|
memory_id: str = Path(description="Memory ID to update"),
|
|
337
400
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for update"),
|
|
401
|
+
table: Optional[str] = Query(default=None, description="Table to use for update"),
|
|
338
402
|
) -> UserMemorySchema:
|
|
339
|
-
db = get_db(dbs, db_id)
|
|
340
|
-
|
|
341
403
|
if hasattr(request.state, "user_id"):
|
|
342
404
|
user_id = request.state.user_id
|
|
343
405
|
payload.user_id = user_id
|
|
@@ -345,15 +407,29 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
345
407
|
if payload.user_id is None:
|
|
346
408
|
raise HTTPException(status_code=400, detail="User ID is required")
|
|
347
409
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
410
|
+
db = await get_db(dbs, db_id, table)
|
|
411
|
+
|
|
412
|
+
if isinstance(db, AsyncBaseDb):
|
|
413
|
+
db = cast(AsyncBaseDb, db)
|
|
414
|
+
user_memory = await db.upsert_user_memory(
|
|
415
|
+
memory=UserMemory(
|
|
416
|
+
memory_id=memory_id,
|
|
417
|
+
memory=payload.memory,
|
|
418
|
+
topics=payload.topics or [],
|
|
419
|
+
user_id=payload.user_id,
|
|
420
|
+
),
|
|
421
|
+
deserialize=False,
|
|
422
|
+
)
|
|
423
|
+
else:
|
|
424
|
+
user_memory = db.upsert_user_memory(
|
|
425
|
+
memory=UserMemory(
|
|
426
|
+
memory_id=memory_id,
|
|
427
|
+
memory=payload.memory,
|
|
428
|
+
topics=payload.topics or [],
|
|
429
|
+
user_id=payload.user_id,
|
|
430
|
+
),
|
|
431
|
+
deserialize=False,
|
|
432
|
+
)
|
|
357
433
|
if not user_memory:
|
|
358
434
|
raise HTTPException(status_code=500, detail="Failed to update memory")
|
|
359
435
|
|
|
@@ -393,13 +469,24 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
393
469
|
limit: Optional[int] = Query(default=20, description="Number of user statistics to return per page"),
|
|
394
470
|
page: Optional[int] = Query(default=1, description="Page number for pagination"),
|
|
395
471
|
db_id: Optional[str] = Query(default=None, description="Database ID to query statistics from"),
|
|
472
|
+
table: Optional[str] = Query(default=None, description="Table to query statistics from"),
|
|
396
473
|
) -> PaginatedResponse[UserStatsSchema]:
|
|
397
|
-
db = get_db(dbs, db_id)
|
|
474
|
+
db = await get_db(dbs, db_id, table)
|
|
398
475
|
try:
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
)
|
|
476
|
+
# Ensure limit and page are integers
|
|
477
|
+
limit = int(limit) if limit is not None else 20
|
|
478
|
+
page = int(page) if page is not None else 1
|
|
479
|
+
if isinstance(db, AsyncBaseDb):
|
|
480
|
+
db = cast(AsyncBaseDb, db)
|
|
481
|
+
user_stats, total_count = await db.get_user_memory_stats(
|
|
482
|
+
limit=limit,
|
|
483
|
+
page=page,
|
|
484
|
+
)
|
|
485
|
+
else:
|
|
486
|
+
user_stats, total_count = db.get_user_memory_stats(
|
|
487
|
+
limit=limit,
|
|
488
|
+
page=page,
|
|
489
|
+
)
|
|
403
490
|
return PaginatedResponse(
|
|
404
491
|
data=[UserStatsSchema.from_dict(stats) for stats in user_stats],
|
|
405
492
|
meta=PaginationInfo(
|
|
@@ -413,6 +500,146 @@ def attach_routes(router: APIRouter, dbs: dict[str, BaseDb]) -> APIRouter:
|
|
|
413
500
|
except Exception as e:
|
|
414
501
|
raise HTTPException(status_code=500, detail=f"Failed to get user statistics: {str(e)}")
|
|
415
502
|
|
|
503
|
+
@router.post(
|
|
504
|
+
"/optimize-memories",
|
|
505
|
+
response_model=OptimizeMemoriesResponse,
|
|
506
|
+
status_code=200,
|
|
507
|
+
operation_id="optimize_memories",
|
|
508
|
+
summary="Optimize User Memories",
|
|
509
|
+
description=(
|
|
510
|
+
"Optimize all memories for a given user using the default summarize strategy. "
|
|
511
|
+
"This operation combines all memories into a single comprehensive summary, "
|
|
512
|
+
"achieving maximum token reduction while preserving all key information. "
|
|
513
|
+
"To use a custom model, specify the model parameter in 'provider:model_id' format "
|
|
514
|
+
"(e.g., 'openai:gpt-4o-mini', 'anthropic:claude-3-5-sonnet-20241022'). "
|
|
515
|
+
"If not specified, uses MemoryManager's default model (gpt-4o). "
|
|
516
|
+
"Set apply=false to preview optimization results without saving to database."
|
|
517
|
+
),
|
|
518
|
+
responses={
|
|
519
|
+
200: {
|
|
520
|
+
"description": "Memories optimized successfully",
|
|
521
|
+
"content": {
|
|
522
|
+
"application/json": {
|
|
523
|
+
"example": {
|
|
524
|
+
"memories": [
|
|
525
|
+
{
|
|
526
|
+
"memory_id": "f9361a69-2997-40c7-ae4e-a5861d434047",
|
|
527
|
+
"memory": "User has a 3-year-old golden retriever named Max who loves fetch and walks. Lives in San Francisco's Mission district, works as a product manager in tech. Enjoys hiking Bay Area trails, trying new restaurants (especially Japanese, Thai, Mexican), and learning piano for 1.5 years.",
|
|
528
|
+
"topics": ["pets", "location", "work", "hobbies", "food_preferences"],
|
|
529
|
+
"user_id": "user2",
|
|
530
|
+
"updated_at": "2025-11-18T10:30:00Z",
|
|
531
|
+
}
|
|
532
|
+
],
|
|
533
|
+
"memories_before": 4,
|
|
534
|
+
"memories_after": 1,
|
|
535
|
+
"tokens_before": 450,
|
|
536
|
+
"tokens_after": 180,
|
|
537
|
+
"tokens_saved": 270,
|
|
538
|
+
"reduction_percentage": 60.0,
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
},
|
|
542
|
+
},
|
|
543
|
+
400: {
|
|
544
|
+
"description": "Bad request - User ID is required or invalid model string format",
|
|
545
|
+
"model": BadRequestResponse,
|
|
546
|
+
},
|
|
547
|
+
404: {"description": "No memories found for user", "model": NotFoundResponse},
|
|
548
|
+
500: {"description": "Failed to optimize memories", "model": InternalServerErrorResponse},
|
|
549
|
+
},
|
|
550
|
+
)
|
|
551
|
+
async def optimize_memories(
|
|
552
|
+
request: OptimizeMemoriesRequest,
|
|
553
|
+
db_id: Optional[str] = Query(default=None, description="Database ID to use for optimization"),
|
|
554
|
+
table: Optional[str] = Query(default=None, description="Table to use for optimization"),
|
|
555
|
+
) -> OptimizeMemoriesResponse:
|
|
556
|
+
"""Optimize user memories using the default summarize strategy."""
|
|
557
|
+
from agno.memory import MemoryManager
|
|
558
|
+
from agno.memory.strategies.types import MemoryOptimizationStrategyType
|
|
559
|
+
|
|
560
|
+
try:
|
|
561
|
+
# Get database instance
|
|
562
|
+
db = await get_db(dbs, db_id, table)
|
|
563
|
+
|
|
564
|
+
# Create memory manager with optional model
|
|
565
|
+
if request.model:
|
|
566
|
+
try:
|
|
567
|
+
model_instance = get_model(request.model)
|
|
568
|
+
except ValueError as e:
|
|
569
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
570
|
+
memory_manager = MemoryManager(model=model_instance, db=db)
|
|
571
|
+
else:
|
|
572
|
+
# No model specified - use MemoryManager's default
|
|
573
|
+
memory_manager = MemoryManager(db=db)
|
|
574
|
+
|
|
575
|
+
# Get current memories to count tokens before optimization
|
|
576
|
+
if isinstance(db, AsyncBaseDb):
|
|
577
|
+
memories_before = await memory_manager.aget_user_memories(user_id=request.user_id)
|
|
578
|
+
else:
|
|
579
|
+
memories_before = memory_manager.get_user_memories(user_id=request.user_id)
|
|
580
|
+
|
|
581
|
+
if not memories_before:
|
|
582
|
+
raise HTTPException(status_code=404, detail=f"No memories found for user {request.user_id}")
|
|
583
|
+
|
|
584
|
+
# Count tokens before optimization
|
|
585
|
+
from agno.memory.strategies.summarize import SummarizeStrategy
|
|
586
|
+
|
|
587
|
+
strategy = SummarizeStrategy()
|
|
588
|
+
tokens_before = strategy.count_tokens(memories_before)
|
|
589
|
+
memories_before_count = len(memories_before)
|
|
590
|
+
|
|
591
|
+
# Optimize memories with default SUMMARIZE strategy
|
|
592
|
+
if isinstance(db, AsyncBaseDb):
|
|
593
|
+
optimized_memories = await memory_manager.aoptimize_memories(
|
|
594
|
+
user_id=request.user_id,
|
|
595
|
+
strategy=MemoryOptimizationStrategyType.SUMMARIZE,
|
|
596
|
+
apply=request.apply,
|
|
597
|
+
)
|
|
598
|
+
else:
|
|
599
|
+
optimized_memories = memory_manager.optimize_memories(
|
|
600
|
+
user_id=request.user_id,
|
|
601
|
+
strategy=MemoryOptimizationStrategyType.SUMMARIZE,
|
|
602
|
+
apply=request.apply,
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
# Count tokens after optimization
|
|
606
|
+
tokens_after = strategy.count_tokens(optimized_memories)
|
|
607
|
+
memories_after_count = len(optimized_memories)
|
|
608
|
+
|
|
609
|
+
# Calculate statistics
|
|
610
|
+
tokens_saved = tokens_before - tokens_after
|
|
611
|
+
reduction_percentage = (tokens_saved / tokens_before * 100.0) if tokens_before > 0 else 0.0
|
|
612
|
+
|
|
613
|
+
# Convert to schema objects
|
|
614
|
+
optimized_memory_schemas = [
|
|
615
|
+
UserMemorySchema(
|
|
616
|
+
memory_id=mem.memory_id or "",
|
|
617
|
+
memory=mem.memory or "",
|
|
618
|
+
topics=mem.topics,
|
|
619
|
+
agent_id=mem.agent_id,
|
|
620
|
+
team_id=mem.team_id,
|
|
621
|
+
user_id=mem.user_id,
|
|
622
|
+
updated_at=mem.updated_at,
|
|
623
|
+
)
|
|
624
|
+
for mem in optimized_memories
|
|
625
|
+
]
|
|
626
|
+
|
|
627
|
+
return OptimizeMemoriesResponse(
|
|
628
|
+
memories=optimized_memory_schemas,
|
|
629
|
+
memories_before=memories_before_count,
|
|
630
|
+
memories_after=memories_after_count,
|
|
631
|
+
tokens_before=tokens_before,
|
|
632
|
+
tokens_after=tokens_after,
|
|
633
|
+
tokens_saved=tokens_saved,
|
|
634
|
+
reduction_percentage=reduction_percentage,
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
except HTTPException:
|
|
638
|
+
raise
|
|
639
|
+
except Exception as e:
|
|
640
|
+
logger.error(f"Failed to optimize memories for user {request.user_id}: {str(e)}")
|
|
641
|
+
raise HTTPException(status_code=500, detail=f"Failed to optimize memories: {str(e)}")
|
|
642
|
+
|
|
416
643
|
return router
|
|
417
644
|
|
|
418
645
|
|