agno 2.0.1__py3-none-any.whl → 2.3.0__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 +6015 -2823
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +385 -6
- agno/db/dynamo/dynamo.py +388 -81
- agno/db/dynamo/schemas.py +47 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +435 -64
- agno/db/firestore/schemas.py +11 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +384 -42
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +351 -66
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +339 -48
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +510 -37
- 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 +2036 -0
- agno/db/mongo/mongo.py +653 -76
- agno/db/mongo/schemas.py +13 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/mysql.py +687 -25
- agno/db/mysql/schemas.py +61 -37
- agno/db/mysql/utils.py +60 -2
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2001 -0
- agno/db/postgres/postgres.py +676 -57
- agno/db/postgres/schemas.py +43 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +344 -38
- agno/db/redis/schemas.py +18 -0
- agno/db/redis/utils.py +60 -2
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/memory.py +13 -0
- agno/db/singlestore/schemas.py +26 -1
- agno/db/singlestore/singlestore.py +687 -53
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2371 -0
- agno/db/sqlite/schemas.py +24 -0
- agno/db/sqlite/sqlite.py +774 -85
- agno/db/sqlite/utils.py +168 -5
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +309 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1361 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +50 -22
- agno/eval/accuracy.py +50 -43
- agno/eval/performance.py +6 -3
- agno/eval/reliability.py +6 -3
- agno/eval/utils.py +33 -16
- agno/exceptions.py +68 -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/integrations/discord/client.py +1 -0
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +1 -1
- agno/knowledge/chunking/semantic.py +40 -8
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/aws_bedrock.py +9 -4
- agno/knowledge/embedder/azure_openai.py +54 -0
- agno/knowledge/embedder/base.py +2 -0
- agno/knowledge/embedder/cohere.py +184 -5
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/google.py +79 -1
- agno/knowledge/embedder/huggingface.py +9 -4
- agno/knowledge/embedder/jina.py +63 -0
- agno/knowledge/embedder/mistral.py +78 -11
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +13 -0
- agno/knowledge/embedder/openai.py +37 -65
- agno/knowledge/embedder/sentence_transformer.py +8 -4
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/embedder/voyageai.py +69 -16
- agno/knowledge/knowledge.py +594 -186
- agno/knowledge/reader/base.py +9 -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 +290 -0
- agno/knowledge/reader/json_reader.py +6 -5
- agno/knowledge/reader/markdown_reader.py +13 -13
- agno/knowledge/reader/pdf_reader.py +43 -68
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +51 -6
- agno/knowledge/reader/s3_reader.py +3 -15
- agno/knowledge/reader/tavily_reader.py +194 -0
- agno/knowledge/reader/text_reader.py +13 -13
- agno/knowledge/reader/web_search_reader.py +2 -43
- agno/knowledge/reader/website_reader.py +43 -25
- agno/knowledge/reranker/__init__.py +2 -8
- agno/knowledge/types.py +9 -0
- agno/knowledge/utils.py +20 -0
- agno/media.py +72 -0
- agno/memory/manager.py +336 -82
- agno/models/aimlapi/aimlapi.py +2 -2
- agno/models/anthropic/claude.py +183 -37
- agno/models/aws/bedrock.py +52 -112
- agno/models/aws/claude.py +33 -1
- agno/models/azure/ai_foundry.py +33 -15
- agno/models/azure/openai_chat.py +25 -8
- agno/models/base.py +999 -519
- agno/models/cerebras/cerebras.py +19 -13
- agno/models/cerebras/cerebras_openai.py +8 -5
- agno/models/cohere/chat.py +27 -1
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +57 -0
- agno/models/dashscope/dashscope.py +1 -0
- agno/models/deepinfra/deepinfra.py +2 -2
- agno/models/deepseek/deepseek.py +2 -2
- agno/models/fireworks/fireworks.py +2 -2
- agno/models/google/gemini.py +103 -31
- agno/models/groq/groq.py +28 -11
- agno/models/huggingface/huggingface.py +2 -1
- agno/models/internlm/internlm.py +2 -2
- agno/models/langdb/langdb.py +4 -4
- agno/models/litellm/chat.py +18 -1
- agno/models/litellm/litellm_openai.py +2 -2
- agno/models/llama_cpp/__init__.py +5 -0
- agno/models/llama_cpp/llama_cpp.py +22 -0
- agno/models/message.py +139 -0
- agno/models/meta/llama.py +27 -10
- agno/models/meta/llama_openai.py +5 -17
- agno/models/nebius/nebius.py +6 -6
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +22 -0
- agno/models/nvidia/nvidia.py +2 -2
- agno/models/ollama/chat.py +59 -5
- agno/models/openai/chat.py +69 -29
- agno/models/openai/responses.py +103 -106
- agno/models/openrouter/openrouter.py +41 -3
- agno/models/perplexity/perplexity.py +4 -5
- agno/models/portkey/portkey.py +3 -3
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +52 -0
- agno/models/response.py +77 -1
- agno/models/sambanova/sambanova.py +2 -2
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +25 -0
- agno/models/together/together.py +2 -2
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +2 -2
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +96 -0
- agno/models/vllm/vllm.py +1 -0
- agno/models/xai/xai.py +3 -2
- agno/os/app.py +543 -178
- agno/os/auth.py +24 -14
- agno/os/config.py +1 -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 +250 -0
- agno/os/interfaces/a2a/utils.py +924 -0
- agno/os/interfaces/agui/agui.py +23 -7
- agno/os/interfaces/agui/router.py +27 -3
- agno/os/interfaces/agui/utils.py +242 -142
- agno/os/interfaces/base.py +6 -2
- agno/os/interfaces/slack/router.py +81 -23
- agno/os/interfaces/slack/slack.py +29 -14
- agno/os/interfaces/whatsapp/router.py +11 -4
- agno/os/interfaces/whatsapp/whatsapp.py +14 -7
- agno/os/mcp.py +111 -54
- agno/os/middleware/__init__.py +7 -0
- agno/os/middleware/jwt.py +233 -0
- agno/os/router.py +556 -139
- agno/os/routers/evals/evals.py +71 -34
- agno/os/routers/evals/schemas.py +31 -31
- agno/os/routers/evals/utils.py +6 -5
- agno/os/routers/health.py +31 -0
- agno/os/routers/home.py +52 -0
- agno/os/routers/knowledge/knowledge.py +185 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +158 -53
- agno/os/routers/memory/schemas.py +20 -16
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +499 -38
- agno/os/schema.py +308 -198
- agno/os/utils.py +401 -41
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/azure_ai_foundry.py +2 -2
- agno/reasoning/deepseek.py +2 -2
- agno/reasoning/default.py +3 -1
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/groq.py +2 -2
- agno/reasoning/ollama.py +2 -2
- agno/reasoning/openai.py +7 -2
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +248 -94
- agno/run/base.py +44 -5
- agno/run/team.py +238 -97
- agno/run/workflow.py +144 -33
- agno/session/agent.py +105 -89
- agno/session/summary.py +65 -25
- agno/session/team.py +176 -96
- agno/session/workflow.py +406 -40
- agno/team/team.py +3854 -1610
- agno/tools/dalle.py +2 -4
- agno/tools/decorator.py +4 -2
- agno/tools/duckduckgo.py +15 -11
- agno/tools/e2b.py +14 -7
- agno/tools/eleven_labs.py +23 -25
- agno/tools/exa.py +21 -16
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +350 -0
- agno/tools/firecrawl.py +4 -4
- agno/tools/function.py +250 -30
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +270 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- agno/tools/knowledge.py +3 -3
- 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 +11 -17
- agno/tools/memori.py +1 -53
- agno/tools/memory.py +419 -0
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/notion.py +204 -0
- agno/tools/parallel.py +314 -0
- agno/tools/scrapegraph.py +58 -31
- agno/tools/searxng.py +2 -2
- agno/tools/serper.py +2 -2
- agno/tools/slack.py +18 -3
- agno/tools/spider.py +2 -2
- agno/tools/tavily.py +146 -0
- agno/tools/whatsapp.py +1 -1
- agno/tools/workflow.py +278 -0
- agno/tools/yfinance.py +12 -11
- agno/utils/agent.py +820 -0
- agno/utils/audio.py +27 -0
- agno/utils/common.py +90 -1
- agno/utils/events.py +217 -2
- agno/utils/gemini.py +180 -22
- agno/utils/hooks.py +57 -0
- agno/utils/http.py +111 -0
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +92 -2
- agno/utils/media.py +188 -10
- agno/utils/merge_dict.py +22 -1
- agno/utils/message.py +60 -0
- agno/utils/models/claude.py +40 -11
- agno/utils/print_response/agent.py +105 -21
- agno/utils/print_response/team.py +103 -38
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/reasoning.py +22 -1
- agno/utils/serialize.py +32 -0
- agno/utils/streamlit.py +16 -10
- agno/utils/string.py +41 -0
- agno/utils/team.py +98 -9
- agno/utils/tools.py +1 -1
- agno/vectordb/base.py +23 -4
- agno/vectordb/cassandra/cassandra.py +65 -9
- agno/vectordb/chroma/chromadb.py +182 -38
- agno/vectordb/clickhouse/clickhousedb.py +64 -11
- agno/vectordb/couchbase/couchbase.py +105 -10
- agno/vectordb/lancedb/lance_db.py +124 -133
- agno/vectordb/langchaindb/langchaindb.py +25 -7
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/__init__.py +3 -0
- agno/vectordb/llamaindex/llamaindexdb.py +46 -7
- agno/vectordb/milvus/milvus.py +126 -9
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +112 -7
- agno/vectordb/pgvector/pgvector.py +142 -21
- agno/vectordb/pineconedb/pineconedb.py +80 -8
- agno/vectordb/qdrant/qdrant.py +125 -39
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +694 -0
- agno/vectordb/singlestore/singlestore.py +111 -25
- agno/vectordb/surrealdb/surrealdb.py +31 -5
- agno/vectordb/upstashdb/upstashdb.py +76 -8
- agno/vectordb/weaviate/weaviate.py +86 -15
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +112 -18
- agno/workflow/loop.py +69 -10
- agno/workflow/parallel.py +266 -118
- agno/workflow/router.py +110 -17
- agno/workflow/step.py +638 -129
- agno/workflow/steps.py +65 -6
- agno/workflow/types.py +61 -23
- agno/workflow/workflow.py +2085 -272
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/METADATA +182 -58
- agno-2.3.0.dist-info/RECORD +577 -0
- agno/knowledge/reader/url_reader.py +0 -128
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -610
- agno/utils/models/aws_claude.py +0 -170
- agno-2.0.1.dist-info/RECORD +0 -515
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
agno/db/postgres/postgres.py
CHANGED
|
@@ -4,30 +4,35 @@ from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
|
|
|
4
4
|
from uuid import uuid4
|
|
5
5
|
|
|
6
6
|
from agno.db.base import BaseDb, SessionType
|
|
7
|
+
from agno.db.migrations.manager import MigrationManager
|
|
7
8
|
from agno.db.postgres.schemas import get_table_schema_definition
|
|
8
9
|
from agno.db.postgres.utils import (
|
|
9
10
|
apply_sorting,
|
|
10
11
|
bulk_upsert_metrics,
|
|
11
12
|
calculate_date_metrics,
|
|
12
13
|
create_schema,
|
|
14
|
+
deserialize_cultural_knowledge,
|
|
13
15
|
fetch_all_sessions_data,
|
|
14
16
|
get_dates_to_calculate_metrics_for,
|
|
15
17
|
is_table_available,
|
|
16
18
|
is_valid_table,
|
|
19
|
+
serialize_cultural_knowledge,
|
|
17
20
|
)
|
|
21
|
+
from agno.db.schemas.culture import CulturalKnowledge
|
|
18
22
|
from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
|
|
19
23
|
from agno.db.schemas.knowledge import KnowledgeRow
|
|
20
24
|
from agno.db.schemas.memory import UserMemory
|
|
21
25
|
from agno.session import AgentSession, Session, TeamSession, WorkflowSession
|
|
22
26
|
from agno.utils.log import log_debug, log_error, log_info, log_warning
|
|
27
|
+
from agno.utils.string import generate_id
|
|
23
28
|
|
|
24
29
|
try:
|
|
25
|
-
from sqlalchemy import Index, String, UniqueConstraint, func, update
|
|
30
|
+
from sqlalchemy import Index, String, UniqueConstraint, func, select, update
|
|
26
31
|
from sqlalchemy.dialects import postgresql
|
|
27
32
|
from sqlalchemy.engine import Engine, create_engine
|
|
28
33
|
from sqlalchemy.orm import scoped_session, sessionmaker
|
|
29
34
|
from sqlalchemy.schema import Column, MetaData, Table
|
|
30
|
-
from sqlalchemy.sql.expression import
|
|
35
|
+
from sqlalchemy.sql.expression import text
|
|
31
36
|
except ImportError:
|
|
32
37
|
raise ImportError("`sqlalchemy` not installed. Please install it using `pip install sqlalchemy`")
|
|
33
38
|
|
|
@@ -39,10 +44,12 @@ class PostgresDb(BaseDb):
|
|
|
39
44
|
db_engine: Optional[Engine] = None,
|
|
40
45
|
db_schema: Optional[str] = None,
|
|
41
46
|
session_table: Optional[str] = None,
|
|
47
|
+
culture_table: Optional[str] = None,
|
|
42
48
|
memory_table: Optional[str] = None,
|
|
43
49
|
metrics_table: Optional[str] = None,
|
|
44
50
|
eval_table: Optional[str] = None,
|
|
45
51
|
knowledge_table: Optional[str] = None,
|
|
52
|
+
versions_table: Optional[str] = None,
|
|
46
53
|
id: Optional[str] = None,
|
|
47
54
|
):
|
|
48
55
|
"""
|
|
@@ -62,12 +69,29 @@ class PostgresDb(BaseDb):
|
|
|
62
69
|
metrics_table (Optional[str]): Name of the table to store metrics.
|
|
63
70
|
eval_table (Optional[str]): Name of the table to store evaluation runs data.
|
|
64
71
|
knowledge_table (Optional[str]): Name of the table to store knowledge content.
|
|
72
|
+
culture_table (Optional[str]): Name of the table to store cultural knowledge.
|
|
73
|
+
versions_table (Optional[str]): Name of the table to store schema versions.
|
|
65
74
|
id (Optional[str]): ID of the database.
|
|
66
75
|
|
|
67
76
|
Raises:
|
|
68
77
|
ValueError: If neither db_url nor db_engine is provided.
|
|
69
78
|
ValueError: If none of the tables are provided.
|
|
70
79
|
"""
|
|
80
|
+
_engine: Optional[Engine] = db_engine
|
|
81
|
+
if _engine is None and db_url is not None:
|
|
82
|
+
_engine = create_engine(db_url)
|
|
83
|
+
if _engine is None:
|
|
84
|
+
raise ValueError("One of db_url or db_engine must be provided")
|
|
85
|
+
|
|
86
|
+
self.db_url: Optional[str] = db_url
|
|
87
|
+
self.db_engine: Engine = _engine
|
|
88
|
+
|
|
89
|
+
if id is None:
|
|
90
|
+
base_seed = db_url or str(db_engine.url) # type: ignore
|
|
91
|
+
schema_suffix = db_schema if db_schema is not None else "ai"
|
|
92
|
+
seed = f"{base_seed}#{schema_suffix}"
|
|
93
|
+
id = generate_id(seed)
|
|
94
|
+
|
|
71
95
|
super().__init__(
|
|
72
96
|
id=id,
|
|
73
97
|
session_table=session_table,
|
|
@@ -75,16 +99,10 @@ class PostgresDb(BaseDb):
|
|
|
75
99
|
metrics_table=metrics_table,
|
|
76
100
|
eval_table=eval_table,
|
|
77
101
|
knowledge_table=knowledge_table,
|
|
102
|
+
culture_table=culture_table,
|
|
103
|
+
versions_table=versions_table,
|
|
78
104
|
)
|
|
79
105
|
|
|
80
|
-
_engine: Optional[Engine] = db_engine
|
|
81
|
-
if _engine is None and db_url is not None:
|
|
82
|
-
_engine = create_engine(db_url)
|
|
83
|
-
if _engine is None:
|
|
84
|
-
raise ValueError("One of db_url or db_engine must be provided")
|
|
85
|
-
|
|
86
|
-
self.db_url: Optional[str] = db_url
|
|
87
|
-
self.db_engine: Engine = _engine
|
|
88
106
|
self.db_schema: str = db_schema if db_schema is not None else "ai"
|
|
89
107
|
self.metadata: MetaData = MetaData()
|
|
90
108
|
|
|
@@ -92,6 +110,37 @@ class PostgresDb(BaseDb):
|
|
|
92
110
|
self.Session: scoped_session = scoped_session(sessionmaker(bind=self.db_engine))
|
|
93
111
|
|
|
94
112
|
# -- DB methods --
|
|
113
|
+
def table_exists(self, table_name: str) -> bool:
|
|
114
|
+
"""Check if a table with the given name exists in the Postgres database.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
table_name: Name of the table to check
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
bool: True if the table exists in the database, False otherwise
|
|
121
|
+
"""
|
|
122
|
+
with self.Session() as sess:
|
|
123
|
+
return is_table_available(session=sess, table_name=table_name, db_schema=self.db_schema)
|
|
124
|
+
|
|
125
|
+
def _create_all_tables(self):
|
|
126
|
+
"""Create all tables for the database."""
|
|
127
|
+
tables_to_create = [
|
|
128
|
+
(self.session_table_name, "sessions"),
|
|
129
|
+
(self.memory_table_name, "memories"),
|
|
130
|
+
(self.metrics_table_name, "metrics"),
|
|
131
|
+
(self.eval_table_name, "evals"),
|
|
132
|
+
(self.knowledge_table_name, "knowledge"),
|
|
133
|
+
(self.versions_table_name, "versions"),
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
for table_name, table_type in tables_to_create:
|
|
137
|
+
if table_name != self.versions_table_name:
|
|
138
|
+
# Also store the schema version for the created table
|
|
139
|
+
latest_schema_version = MigrationManager(self).latest_schema_version
|
|
140
|
+
self.upsert_schema_version(table_name=table_name, version=latest_schema_version.public)
|
|
141
|
+
|
|
142
|
+
self._create_table(table_name=table_name, table_type=table_type, db_schema=self.db_schema)
|
|
143
|
+
|
|
95
144
|
def _create_table(self, table_name: str, table_type: str, db_schema: str) -> Table:
|
|
96
145
|
"""
|
|
97
146
|
Create a table with the appropriate schema based on the table type.
|
|
@@ -170,7 +219,7 @@ class PostgresDb(BaseDb):
|
|
|
170
219
|
except Exception as e:
|
|
171
220
|
log_error(f"Error creating index {idx.name}: {e}")
|
|
172
221
|
|
|
173
|
-
|
|
222
|
+
log_debug(f"Successfully created table {table_name} in schema {db_schema}")
|
|
174
223
|
return table
|
|
175
224
|
|
|
176
225
|
except Exception as e:
|
|
@@ -223,6 +272,24 @@ class PostgresDb(BaseDb):
|
|
|
223
272
|
)
|
|
224
273
|
return self.knowledge_table
|
|
225
274
|
|
|
275
|
+
if table_type == "culture":
|
|
276
|
+
self.culture_table = self._get_or_create_table(
|
|
277
|
+
table_name=self.culture_table_name,
|
|
278
|
+
table_type="culture",
|
|
279
|
+
db_schema=self.db_schema,
|
|
280
|
+
create_table_if_not_found=create_table_if_not_found,
|
|
281
|
+
)
|
|
282
|
+
return self.culture_table
|
|
283
|
+
|
|
284
|
+
if table_type == "versions":
|
|
285
|
+
self.versions_table = self._get_or_create_table(
|
|
286
|
+
table_name=self.versions_table_name,
|
|
287
|
+
table_type="versions",
|
|
288
|
+
db_schema=self.db_schema,
|
|
289
|
+
create_table_if_not_found=create_table_if_not_found,
|
|
290
|
+
)
|
|
291
|
+
return self.versions_table
|
|
292
|
+
|
|
226
293
|
raise ValueError(f"Unknown table type: {table_type}")
|
|
227
294
|
|
|
228
295
|
def _get_or_create_table(
|
|
@@ -247,6 +314,11 @@ class PostgresDb(BaseDb):
|
|
|
247
314
|
if not create_table_if_not_found:
|
|
248
315
|
return None
|
|
249
316
|
|
|
317
|
+
if table_name != self.versions_table_name:
|
|
318
|
+
# Also store the schema version for the created table
|
|
319
|
+
latest_schema_version = MigrationManager(self).latest_schema_version
|
|
320
|
+
self.upsert_schema_version(table_name=table_name, version=latest_schema_version.public)
|
|
321
|
+
|
|
250
322
|
return self._create_table(table_name=table_name, table_type=table_type, db_schema=db_schema)
|
|
251
323
|
|
|
252
324
|
if not is_valid_table(
|
|
@@ -265,8 +337,43 @@ class PostgresDb(BaseDb):
|
|
|
265
337
|
log_error(f"Error loading existing table {db_schema}.{table_name}: {e}")
|
|
266
338
|
raise
|
|
267
339
|
|
|
268
|
-
|
|
340
|
+
def get_latest_schema_version(self, table_name: str):
|
|
341
|
+
"""Get the latest version of the database schema."""
|
|
342
|
+
table = self._get_table(table_type="versions", create_table_if_not_found=True)
|
|
343
|
+
if table is None:
|
|
344
|
+
return "2.0.0"
|
|
345
|
+
with self.Session() as sess:
|
|
346
|
+
stmt = select(table)
|
|
347
|
+
# Latest version for the given table
|
|
348
|
+
stmt = stmt.where(table.c.table_name == table_name)
|
|
349
|
+
stmt = stmt.order_by(table.c.version.desc()).limit(1)
|
|
350
|
+
result = sess.execute(stmt).fetchone()
|
|
351
|
+
if result is None:
|
|
352
|
+
return "2.0.0"
|
|
353
|
+
version_dict = dict(result._mapping)
|
|
354
|
+
return version_dict.get("version") or "2.0.0"
|
|
355
|
+
|
|
356
|
+
def upsert_schema_version(self, table_name: str, version: str) -> None:
|
|
357
|
+
"""Upsert the schema version into the database."""
|
|
358
|
+
table = self._get_table(table_type="versions", create_table_if_not_found=True)
|
|
359
|
+
if table is None:
|
|
360
|
+
return
|
|
361
|
+
current_datetime = datetime.now().isoformat()
|
|
362
|
+
with self.Session() as sess, sess.begin():
|
|
363
|
+
stmt = postgresql.insert(table).values(
|
|
364
|
+
table_name=table_name,
|
|
365
|
+
version=version,
|
|
366
|
+
created_at=current_datetime, # Store as ISO format string
|
|
367
|
+
updated_at=current_datetime,
|
|
368
|
+
)
|
|
369
|
+
# Update version if table_name already exists
|
|
370
|
+
stmt = stmt.on_conflict_do_update(
|
|
371
|
+
index_elements=["table_name"],
|
|
372
|
+
set_=dict(version=version, updated_at=current_datetime),
|
|
373
|
+
)
|
|
374
|
+
sess.execute(stmt)
|
|
269
375
|
|
|
376
|
+
# -- Session methods --
|
|
270
377
|
def delete_session(self, session_id: str) -> bool:
|
|
271
378
|
"""
|
|
272
379
|
Delete a session from the database.
|
|
@@ -299,7 +406,7 @@ class PostgresDb(BaseDb):
|
|
|
299
406
|
|
|
300
407
|
except Exception as e:
|
|
301
408
|
log_error(f"Error deleting session: {e}")
|
|
302
|
-
|
|
409
|
+
raise e
|
|
303
410
|
|
|
304
411
|
def delete_sessions(self, session_ids: List[str]) -> None:
|
|
305
412
|
"""Delete all given sessions from the database.
|
|
@@ -324,6 +431,7 @@ class PostgresDb(BaseDb):
|
|
|
324
431
|
|
|
325
432
|
except Exception as e:
|
|
326
433
|
log_error(f"Error deleting sessions: {e}")
|
|
434
|
+
raise e
|
|
327
435
|
|
|
328
436
|
def get_session(
|
|
329
437
|
self,
|
|
@@ -337,8 +445,8 @@ class PostgresDb(BaseDb):
|
|
|
337
445
|
|
|
338
446
|
Args:
|
|
339
447
|
session_id (str): ID of the session to read.
|
|
448
|
+
session_type (SessionType): Type of session to get.
|
|
340
449
|
user_id (Optional[str]): User ID to filter by. Defaults to None.
|
|
341
|
-
session_type (Optional[SessionType]): Type of session to read. Defaults to None.
|
|
342
450
|
deserialize (Optional[bool]): Whether to serialize the session. Defaults to True.
|
|
343
451
|
|
|
344
452
|
Returns:
|
|
@@ -359,9 +467,6 @@ class PostgresDb(BaseDb):
|
|
|
359
467
|
|
|
360
468
|
if user_id is not None:
|
|
361
469
|
stmt = stmt.where(table.c.user_id == user_id)
|
|
362
|
-
if session_type is not None:
|
|
363
|
-
session_type_value = session_type.value if isinstance(session_type, SessionType) else session_type
|
|
364
|
-
stmt = stmt.where(table.c.session_type == session_type_value)
|
|
365
470
|
result = sess.execute(stmt).fetchone()
|
|
366
471
|
if result is None:
|
|
367
472
|
return None
|
|
@@ -382,7 +487,7 @@ class PostgresDb(BaseDb):
|
|
|
382
487
|
|
|
383
488
|
except Exception as e:
|
|
384
489
|
log_error(f"Exception reading from session table: {e}")
|
|
385
|
-
|
|
490
|
+
raise e
|
|
386
491
|
|
|
387
492
|
def get_sessions(
|
|
388
493
|
self,
|
|
@@ -402,6 +507,7 @@ class PostgresDb(BaseDb):
|
|
|
402
507
|
Get all sessions in the given table. Can filter by user_id and entity_id.
|
|
403
508
|
|
|
404
509
|
Args:
|
|
510
|
+
session_type (Optional[SessionType]): The type of session to get.
|
|
405
511
|
user_id (Optional[str]): The ID of the user to filter by.
|
|
406
512
|
entity_id (Optional[str]): The ID of the agent / workflow to filter by.
|
|
407
513
|
start_timestamp (Optional[int]): The start timestamp to filter by.
|
|
@@ -484,7 +590,7 @@ class PostgresDb(BaseDb):
|
|
|
484
590
|
|
|
485
591
|
except Exception as e:
|
|
486
592
|
log_error(f"Exception reading from session table: {e}")
|
|
487
|
-
|
|
593
|
+
raise e
|
|
488
594
|
|
|
489
595
|
def rename_session(
|
|
490
596
|
self, session_id: str, session_type: SessionType, session_name: str, deserialize: Optional[bool] = True
|
|
@@ -551,7 +657,7 @@ class PostgresDb(BaseDb):
|
|
|
551
657
|
|
|
552
658
|
except Exception as e:
|
|
553
659
|
log_error(f"Exception renaming session: {e}")
|
|
554
|
-
|
|
660
|
+
raise e
|
|
555
661
|
|
|
556
662
|
def upsert_session(
|
|
557
663
|
self, session: Session, deserialize: Optional[bool] = True
|
|
@@ -691,12 +797,189 @@ class PostgresDb(BaseDb):
|
|
|
691
797
|
|
|
692
798
|
except Exception as e:
|
|
693
799
|
log_error(f"Exception upserting into sessions table: {e}")
|
|
694
|
-
|
|
800
|
+
raise e
|
|
801
|
+
|
|
802
|
+
def upsert_sessions(
|
|
803
|
+
self, sessions: List[Session], deserialize: Optional[bool] = True, preserve_updated_at: bool = False
|
|
804
|
+
) -> List[Union[Session, Dict[str, Any]]]:
|
|
805
|
+
"""
|
|
806
|
+
Bulk insert or update multiple sessions.
|
|
807
|
+
|
|
808
|
+
Args:
|
|
809
|
+
sessions (List[Session]): The list of session data to upsert.
|
|
810
|
+
deserialize (Optional[bool]): Whether to deserialize the sessions. Defaults to True.
|
|
811
|
+
preserve_updated_at (bool): If True, preserve the updated_at from the session object.
|
|
812
|
+
|
|
813
|
+
Returns:
|
|
814
|
+
List[Union[Session, Dict[str, Any]]]: List of upserted sessions
|
|
815
|
+
|
|
816
|
+
Raises:
|
|
817
|
+
Exception: If an error occurs during bulk upsert.
|
|
818
|
+
"""
|
|
819
|
+
try:
|
|
820
|
+
if not sessions:
|
|
821
|
+
return []
|
|
822
|
+
|
|
823
|
+
table = self._get_table(table_type="sessions", create_table_if_not_found=True)
|
|
824
|
+
if table is None:
|
|
825
|
+
return []
|
|
826
|
+
|
|
827
|
+
# Group sessions by type for better handling
|
|
828
|
+
agent_sessions = [s for s in sessions if isinstance(s, AgentSession)]
|
|
829
|
+
team_sessions = [s for s in sessions if isinstance(s, TeamSession)]
|
|
830
|
+
workflow_sessions = [s for s in sessions if isinstance(s, WorkflowSession)]
|
|
831
|
+
|
|
832
|
+
results: List[Union[Session, Dict[str, Any]]] = []
|
|
833
|
+
|
|
834
|
+
# Bulk upsert agent sessions
|
|
835
|
+
if agent_sessions:
|
|
836
|
+
session_records = []
|
|
837
|
+
for agent_session in agent_sessions:
|
|
838
|
+
session_dict = agent_session.to_dict()
|
|
839
|
+
# Use preserved updated_at if flag is set (even if None), otherwise use current time
|
|
840
|
+
updated_at = session_dict.get("updated_at") if preserve_updated_at else int(time.time())
|
|
841
|
+
session_records.append(
|
|
842
|
+
{
|
|
843
|
+
"session_id": session_dict.get("session_id"),
|
|
844
|
+
"session_type": SessionType.AGENT.value,
|
|
845
|
+
"agent_id": session_dict.get("agent_id"),
|
|
846
|
+
"user_id": session_dict.get("user_id"),
|
|
847
|
+
"agent_data": session_dict.get("agent_data"),
|
|
848
|
+
"session_data": session_dict.get("session_data"),
|
|
849
|
+
"summary": session_dict.get("summary"),
|
|
850
|
+
"metadata": session_dict.get("metadata"),
|
|
851
|
+
"runs": session_dict.get("runs"),
|
|
852
|
+
"created_at": session_dict.get("created_at"),
|
|
853
|
+
"updated_at": updated_at,
|
|
854
|
+
}
|
|
855
|
+
)
|
|
856
|
+
|
|
857
|
+
with self.Session() as sess, sess.begin():
|
|
858
|
+
stmt: Any = postgresql.insert(table)
|
|
859
|
+
update_columns = {
|
|
860
|
+
col.name: stmt.excluded[col.name]
|
|
861
|
+
for col in table.columns
|
|
862
|
+
if col.name not in ["id", "session_id", "created_at"]
|
|
863
|
+
}
|
|
864
|
+
stmt = stmt.on_conflict_do_update(index_elements=["session_id"], set_=update_columns).returning(
|
|
865
|
+
table
|
|
866
|
+
)
|
|
867
|
+
|
|
868
|
+
result = sess.execute(stmt, session_records)
|
|
869
|
+
for row in result.fetchall():
|
|
870
|
+
session_dict = dict(row._mapping)
|
|
871
|
+
if deserialize:
|
|
872
|
+
deserialized_agent_session = AgentSession.from_dict(session_dict)
|
|
873
|
+
if deserialized_agent_session is None:
|
|
874
|
+
continue
|
|
875
|
+
results.append(deserialized_agent_session)
|
|
876
|
+
else:
|
|
877
|
+
results.append(session_dict)
|
|
878
|
+
|
|
879
|
+
# Bulk upsert team sessions
|
|
880
|
+
if team_sessions:
|
|
881
|
+
session_records = []
|
|
882
|
+
for team_session in team_sessions:
|
|
883
|
+
session_dict = team_session.to_dict()
|
|
884
|
+
# Use preserved updated_at if flag is set (even if None), otherwise use current time
|
|
885
|
+
updated_at = session_dict.get("updated_at") if preserve_updated_at else int(time.time())
|
|
886
|
+
session_records.append(
|
|
887
|
+
{
|
|
888
|
+
"session_id": session_dict.get("session_id"),
|
|
889
|
+
"session_type": SessionType.TEAM.value,
|
|
890
|
+
"team_id": session_dict.get("team_id"),
|
|
891
|
+
"user_id": session_dict.get("user_id"),
|
|
892
|
+
"team_data": session_dict.get("team_data"),
|
|
893
|
+
"session_data": session_dict.get("session_data"),
|
|
894
|
+
"summary": session_dict.get("summary"),
|
|
895
|
+
"metadata": session_dict.get("metadata"),
|
|
896
|
+
"runs": session_dict.get("runs"),
|
|
897
|
+
"created_at": session_dict.get("created_at"),
|
|
898
|
+
"updated_at": updated_at,
|
|
899
|
+
}
|
|
900
|
+
)
|
|
901
|
+
|
|
902
|
+
with self.Session() as sess, sess.begin():
|
|
903
|
+
stmt = postgresql.insert(table)
|
|
904
|
+
update_columns = {
|
|
905
|
+
col.name: stmt.excluded[col.name]
|
|
906
|
+
for col in table.columns
|
|
907
|
+
if col.name not in ["id", "session_id", "created_at"]
|
|
908
|
+
}
|
|
909
|
+
stmt = stmt.on_conflict_do_update(index_elements=["session_id"], set_=update_columns).returning(
|
|
910
|
+
table
|
|
911
|
+
)
|
|
912
|
+
|
|
913
|
+
result = sess.execute(stmt, session_records)
|
|
914
|
+
for row in result.fetchall():
|
|
915
|
+
session_dict = dict(row._mapping)
|
|
916
|
+
if deserialize:
|
|
917
|
+
deserialized_team_session = TeamSession.from_dict(session_dict)
|
|
918
|
+
if deserialized_team_session is None:
|
|
919
|
+
continue
|
|
920
|
+
results.append(deserialized_team_session)
|
|
921
|
+
else:
|
|
922
|
+
results.append(session_dict)
|
|
923
|
+
|
|
924
|
+
# Bulk upsert workflow sessions
|
|
925
|
+
if workflow_sessions:
|
|
926
|
+
session_records = []
|
|
927
|
+
for workflow_session in workflow_sessions:
|
|
928
|
+
session_dict = workflow_session.to_dict()
|
|
929
|
+
# Use preserved updated_at if flag is set (even if None), otherwise use current time
|
|
930
|
+
updated_at = session_dict.get("updated_at") if preserve_updated_at else int(time.time())
|
|
931
|
+
session_records.append(
|
|
932
|
+
{
|
|
933
|
+
"session_id": session_dict.get("session_id"),
|
|
934
|
+
"session_type": SessionType.WORKFLOW.value,
|
|
935
|
+
"workflow_id": session_dict.get("workflow_id"),
|
|
936
|
+
"user_id": session_dict.get("user_id"),
|
|
937
|
+
"workflow_data": session_dict.get("workflow_data"),
|
|
938
|
+
"session_data": session_dict.get("session_data"),
|
|
939
|
+
"summary": session_dict.get("summary"),
|
|
940
|
+
"metadata": session_dict.get("metadata"),
|
|
941
|
+
"runs": session_dict.get("runs"),
|
|
942
|
+
"created_at": session_dict.get("created_at"),
|
|
943
|
+
"updated_at": updated_at,
|
|
944
|
+
}
|
|
945
|
+
)
|
|
946
|
+
|
|
947
|
+
with self.Session() as sess, sess.begin():
|
|
948
|
+
stmt = postgresql.insert(table)
|
|
949
|
+
update_columns = {
|
|
950
|
+
col.name: stmt.excluded[col.name]
|
|
951
|
+
for col in table.columns
|
|
952
|
+
if col.name not in ["id", "session_id", "created_at"]
|
|
953
|
+
}
|
|
954
|
+
stmt = stmt.on_conflict_do_update(index_elements=["session_id"], set_=update_columns).returning(
|
|
955
|
+
table
|
|
956
|
+
)
|
|
957
|
+
|
|
958
|
+
result = sess.execute(stmt, session_records)
|
|
959
|
+
for row in result.fetchall():
|
|
960
|
+
session_dict = dict(row._mapping)
|
|
961
|
+
if deserialize:
|
|
962
|
+
deserialized_workflow_session = WorkflowSession.from_dict(session_dict)
|
|
963
|
+
if deserialized_workflow_session is None:
|
|
964
|
+
continue
|
|
965
|
+
results.append(deserialized_workflow_session)
|
|
966
|
+
else:
|
|
967
|
+
results.append(session_dict)
|
|
968
|
+
|
|
969
|
+
return results
|
|
970
|
+
|
|
971
|
+
except Exception as e:
|
|
972
|
+
log_error(f"Exception bulk upserting sessions: {e}")
|
|
973
|
+
return []
|
|
695
974
|
|
|
696
975
|
# -- Memory methods --
|
|
697
|
-
def delete_user_memory(self, memory_id: str):
|
|
976
|
+
def delete_user_memory(self, memory_id: str, user_id: Optional[str] = None):
|
|
698
977
|
"""Delete a user memory from the database.
|
|
699
978
|
|
|
979
|
+
Args:
|
|
980
|
+
memory_id (str): The ID of the memory to delete.
|
|
981
|
+
user_id (Optional[str]): The ID of the user to filter by. Defaults to None.
|
|
982
|
+
|
|
700
983
|
Returns:
|
|
701
984
|
bool: True if deletion was successful, False otherwise.
|
|
702
985
|
|
|
@@ -710,6 +993,10 @@ class PostgresDb(BaseDb):
|
|
|
710
993
|
|
|
711
994
|
with self.Session() as sess, sess.begin():
|
|
712
995
|
delete_stmt = table.delete().where(table.c.memory_id == memory_id)
|
|
996
|
+
|
|
997
|
+
if user_id is not None:
|
|
998
|
+
delete_stmt = delete_stmt.where(table.c.user_id == user_id)
|
|
999
|
+
|
|
713
1000
|
result = sess.execute(delete_stmt)
|
|
714
1001
|
|
|
715
1002
|
success = result.rowcount > 0
|
|
@@ -720,12 +1007,14 @@ class PostgresDb(BaseDb):
|
|
|
720
1007
|
|
|
721
1008
|
except Exception as e:
|
|
722
1009
|
log_error(f"Error deleting user memory: {e}")
|
|
1010
|
+
raise e
|
|
723
1011
|
|
|
724
|
-
def delete_user_memories(self, memory_ids: List[str]) -> None:
|
|
1012
|
+
def delete_user_memories(self, memory_ids: List[str], user_id: Optional[str] = None) -> None:
|
|
725
1013
|
"""Delete user memories from the database.
|
|
726
1014
|
|
|
727
1015
|
Args:
|
|
728
1016
|
memory_ids (List[str]): The IDs of the memories to delete.
|
|
1017
|
+
user_id (Optional[str]): The ID of the user to filter by. Defaults to None.
|
|
729
1018
|
|
|
730
1019
|
Raises:
|
|
731
1020
|
Exception: If an error occurs during deletion.
|
|
@@ -737,6 +1026,10 @@ class PostgresDb(BaseDb):
|
|
|
737
1026
|
|
|
738
1027
|
with self.Session() as sess, sess.begin():
|
|
739
1028
|
delete_stmt = table.delete().where(table.c.memory_id.in_(memory_ids))
|
|
1029
|
+
|
|
1030
|
+
if user_id is not None:
|
|
1031
|
+
delete_stmt = delete_stmt.where(table.c.user_id == user_id)
|
|
1032
|
+
|
|
740
1033
|
result = sess.execute(delete_stmt)
|
|
741
1034
|
|
|
742
1035
|
if result.rowcount == 0:
|
|
@@ -746,6 +1039,7 @@ class PostgresDb(BaseDb):
|
|
|
746
1039
|
|
|
747
1040
|
except Exception as e:
|
|
748
1041
|
log_error(f"Error deleting user memories: {e}")
|
|
1042
|
+
raise e
|
|
749
1043
|
|
|
750
1044
|
def get_all_memory_topics(self) -> List[str]:
|
|
751
1045
|
"""Get all memory topics from the database.
|
|
@@ -760,6 +1054,7 @@ class PostgresDb(BaseDb):
|
|
|
760
1054
|
|
|
761
1055
|
with self.Session() as sess, sess.begin():
|
|
762
1056
|
stmt = select(func.json_array_elements_text(table.c.topics))
|
|
1057
|
+
|
|
763
1058
|
result = sess.execute(stmt).fetchall()
|
|
764
1059
|
|
|
765
1060
|
return list(set([record[0] for record in result]))
|
|
@@ -769,13 +1064,14 @@ class PostgresDb(BaseDb):
|
|
|
769
1064
|
return []
|
|
770
1065
|
|
|
771
1066
|
def get_user_memory(
|
|
772
|
-
self, memory_id: str, deserialize: Optional[bool] = True
|
|
1067
|
+
self, memory_id: str, deserialize: Optional[bool] = True, user_id: Optional[str] = None
|
|
773
1068
|
) -> Optional[Union[UserMemory, Dict[str, Any]]]:
|
|
774
1069
|
"""Get a memory from the database.
|
|
775
1070
|
|
|
776
1071
|
Args:
|
|
777
1072
|
memory_id (str): The ID of the memory to get.
|
|
778
1073
|
deserialize (Optional[bool]): Whether to serialize the memory. Defaults to True.
|
|
1074
|
+
user_id (Optional[str]): The ID of the user to filter by. Defaults to None.
|
|
779
1075
|
|
|
780
1076
|
Returns:
|
|
781
1077
|
Union[UserMemory, Dict[str, Any], None]:
|
|
@@ -793,6 +1089,9 @@ class PostgresDb(BaseDb):
|
|
|
793
1089
|
with self.Session() as sess, sess.begin():
|
|
794
1090
|
stmt = select(table).where(table.c.memory_id == memory_id)
|
|
795
1091
|
|
|
1092
|
+
if user_id is not None:
|
|
1093
|
+
stmt = stmt.where(table.c.user_id == user_id)
|
|
1094
|
+
|
|
796
1095
|
result = sess.execute(stmt).fetchone()
|
|
797
1096
|
if not result:
|
|
798
1097
|
return None
|
|
@@ -805,7 +1104,7 @@ class PostgresDb(BaseDb):
|
|
|
805
1104
|
|
|
806
1105
|
except Exception as e:
|
|
807
1106
|
log_error(f"Exception reading from memory table: {e}")
|
|
808
|
-
|
|
1107
|
+
raise e
|
|
809
1108
|
|
|
810
1109
|
def get_user_memories(
|
|
811
1110
|
self,
|
|
@@ -888,7 +1187,7 @@ class PostgresDb(BaseDb):
|
|
|
888
1187
|
|
|
889
1188
|
except Exception as e:
|
|
890
1189
|
log_error(f"Exception reading from memory table: {e}")
|
|
891
|
-
|
|
1190
|
+
raise e
|
|
892
1191
|
|
|
893
1192
|
def clear_memories(self) -> None:
|
|
894
1193
|
"""Delete all memories from the database.
|
|
@@ -905,7 +1204,8 @@ class PostgresDb(BaseDb):
|
|
|
905
1204
|
sess.execute(table.delete())
|
|
906
1205
|
|
|
907
1206
|
except Exception as e:
|
|
908
|
-
|
|
1207
|
+
log_error(f"Exception deleting all memories: {e}")
|
|
1208
|
+
raise e
|
|
909
1209
|
|
|
910
1210
|
def get_user_memory_stats(
|
|
911
1211
|
self, limit: Optional[int] = None, page: Optional[int] = None
|
|
@@ -972,7 +1272,7 @@ class PostgresDb(BaseDb):
|
|
|
972
1272
|
|
|
973
1273
|
except Exception as e:
|
|
974
1274
|
log_error(f"Exception getting user memory stats: {e}")
|
|
975
|
-
|
|
1275
|
+
raise e
|
|
976
1276
|
|
|
977
1277
|
def upsert_user_memory(
|
|
978
1278
|
self, memory: UserMemory, deserialize: Optional[bool] = True
|
|
@@ -1000,6 +1300,8 @@ class PostgresDb(BaseDb):
|
|
|
1000
1300
|
if memory.memory_id is None:
|
|
1001
1301
|
memory.memory_id = str(uuid4())
|
|
1002
1302
|
|
|
1303
|
+
current_time = int(time.time())
|
|
1304
|
+
|
|
1003
1305
|
stmt = postgresql.insert(table).values(
|
|
1004
1306
|
memory_id=memory.memory_id,
|
|
1005
1307
|
memory=memory.memory,
|
|
@@ -1008,7 +1310,9 @@ class PostgresDb(BaseDb):
|
|
|
1008
1310
|
agent_id=memory.agent_id,
|
|
1009
1311
|
team_id=memory.team_id,
|
|
1010
1312
|
topics=memory.topics,
|
|
1011
|
-
|
|
1313
|
+
feedback=memory.feedback,
|
|
1314
|
+
created_at=memory.created_at,
|
|
1315
|
+
updated_at=memory.created_at,
|
|
1012
1316
|
)
|
|
1013
1317
|
stmt = stmt.on_conflict_do_update( # type: ignore
|
|
1014
1318
|
index_elements=["memory_id"],
|
|
@@ -1018,7 +1322,10 @@ class PostgresDb(BaseDb):
|
|
|
1018
1322
|
input=memory.input,
|
|
1019
1323
|
agent_id=memory.agent_id,
|
|
1020
1324
|
team_id=memory.team_id,
|
|
1021
|
-
|
|
1325
|
+
feedback=memory.feedback,
|
|
1326
|
+
updated_at=current_time,
|
|
1327
|
+
# Preserve created_at on update - don't overwrite existing value
|
|
1328
|
+
created_at=table.c.created_at,
|
|
1022
1329
|
),
|
|
1023
1330
|
).returning(table)
|
|
1024
1331
|
|
|
@@ -1034,7 +1341,89 @@ class PostgresDb(BaseDb):
|
|
|
1034
1341
|
|
|
1035
1342
|
except Exception as e:
|
|
1036
1343
|
log_error(f"Exception upserting user memory: {e}")
|
|
1037
|
-
|
|
1344
|
+
raise e
|
|
1345
|
+
|
|
1346
|
+
def upsert_memories(
|
|
1347
|
+
self, memories: List[UserMemory], deserialize: Optional[bool] = True, preserve_updated_at: bool = False
|
|
1348
|
+
) -> List[Union[UserMemory, Dict[str, Any]]]:
|
|
1349
|
+
"""
|
|
1350
|
+
Bulk insert or update multiple memories in the database for improved performance.
|
|
1351
|
+
|
|
1352
|
+
Args:
|
|
1353
|
+
memories (List[UserMemory]): The list of memories to upsert.
|
|
1354
|
+
deserialize (Optional[bool]): Whether to deserialize the memories. Defaults to True.
|
|
1355
|
+
preserve_updated_at (bool): If True, preserve the updated_at from the memory object.
|
|
1356
|
+
If False (default), set updated_at to current time.
|
|
1357
|
+
|
|
1358
|
+
Returns:
|
|
1359
|
+
List[Union[UserMemory, Dict[str, Any]]]: List of upserted memories
|
|
1360
|
+
|
|
1361
|
+
Raises:
|
|
1362
|
+
Exception: If an error occurs during bulk upsert.
|
|
1363
|
+
"""
|
|
1364
|
+
try:
|
|
1365
|
+
if not memories:
|
|
1366
|
+
return []
|
|
1367
|
+
|
|
1368
|
+
table = self._get_table(table_type="memories", create_table_if_not_found=True)
|
|
1369
|
+
if table is None:
|
|
1370
|
+
return []
|
|
1371
|
+
|
|
1372
|
+
# Prepare memory records for bulk insert
|
|
1373
|
+
memory_records = []
|
|
1374
|
+
current_time = int(time.time())
|
|
1375
|
+
|
|
1376
|
+
for memory in memories:
|
|
1377
|
+
if memory.memory_id is None:
|
|
1378
|
+
memory.memory_id = str(uuid4())
|
|
1379
|
+
|
|
1380
|
+
# Use preserved updated_at if flag is set (even if None), otherwise use current time
|
|
1381
|
+
updated_at = memory.updated_at if preserve_updated_at else current_time
|
|
1382
|
+
|
|
1383
|
+
memory_records.append(
|
|
1384
|
+
{
|
|
1385
|
+
"memory_id": memory.memory_id,
|
|
1386
|
+
"memory": memory.memory,
|
|
1387
|
+
"input": memory.input,
|
|
1388
|
+
"user_id": memory.user_id,
|
|
1389
|
+
"agent_id": memory.agent_id,
|
|
1390
|
+
"team_id": memory.team_id,
|
|
1391
|
+
"topics": memory.topics,
|
|
1392
|
+
"feedback": memory.feedback,
|
|
1393
|
+
"created_at": memory.created_at,
|
|
1394
|
+
"updated_at": updated_at,
|
|
1395
|
+
}
|
|
1396
|
+
)
|
|
1397
|
+
|
|
1398
|
+
results: List[Union[UserMemory, Dict[str, Any]]] = []
|
|
1399
|
+
|
|
1400
|
+
with self.Session() as sess, sess.begin():
|
|
1401
|
+
insert_stmt = postgresql.insert(table)
|
|
1402
|
+
update_columns = {
|
|
1403
|
+
col.name: insert_stmt.excluded[col.name]
|
|
1404
|
+
for col in table.columns
|
|
1405
|
+
if col.name not in ["memory_id", "created_at"] # Don't update primary key or created_at
|
|
1406
|
+
}
|
|
1407
|
+
stmt = insert_stmt.on_conflict_do_update(index_elements=["memory_id"], set_=update_columns).returning(
|
|
1408
|
+
table
|
|
1409
|
+
)
|
|
1410
|
+
|
|
1411
|
+
result = sess.execute(stmt, memory_records)
|
|
1412
|
+
for row in result.fetchall():
|
|
1413
|
+
memory_dict = dict(row._mapping)
|
|
1414
|
+
if deserialize:
|
|
1415
|
+
deserialized_memory = UserMemory.from_dict(memory_dict)
|
|
1416
|
+
if deserialized_memory is None:
|
|
1417
|
+
continue
|
|
1418
|
+
results.append(deserialized_memory)
|
|
1419
|
+
else:
|
|
1420
|
+
results.append(memory_dict)
|
|
1421
|
+
|
|
1422
|
+
return results
|
|
1423
|
+
|
|
1424
|
+
except Exception as e:
|
|
1425
|
+
log_error(f"Exception bulk upserting memories: {e}")
|
|
1426
|
+
return []
|
|
1038
1427
|
|
|
1039
1428
|
# -- Metrics methods --
|
|
1040
1429
|
def _get_all_sessions_for_metrics_calculation(
|
|
@@ -1078,7 +1467,7 @@ class PostgresDb(BaseDb):
|
|
|
1078
1467
|
|
|
1079
1468
|
except Exception as e:
|
|
1080
1469
|
log_error(f"Exception reading from sessions table: {e}")
|
|
1081
|
-
|
|
1470
|
+
raise e
|
|
1082
1471
|
|
|
1083
1472
|
def _get_metrics_calculation_starting_date(self, table: Table) -> Optional[date]:
|
|
1084
1473
|
"""Get the first date for which metrics calculation is needed:
|
|
@@ -1185,7 +1574,7 @@ class PostgresDb(BaseDb):
|
|
|
1185
1574
|
|
|
1186
1575
|
except Exception as e:
|
|
1187
1576
|
log_error(f"Exception refreshing metrics: {e}")
|
|
1188
|
-
|
|
1577
|
+
raise e
|
|
1189
1578
|
|
|
1190
1579
|
def get_metrics(
|
|
1191
1580
|
self,
|
|
@@ -1226,8 +1615,8 @@ class PostgresDb(BaseDb):
|
|
|
1226
1615
|
return [row._mapping for row in result], latest_updated_at
|
|
1227
1616
|
|
|
1228
1617
|
except Exception as e:
|
|
1229
|
-
|
|
1230
|
-
|
|
1618
|
+
log_error(f"Exception getting metrics: {e}")
|
|
1619
|
+
raise e
|
|
1231
1620
|
|
|
1232
1621
|
# -- Knowledge methods --
|
|
1233
1622
|
def delete_knowledge_content(self, id: str):
|
|
@@ -1236,17 +1625,18 @@ class PostgresDb(BaseDb):
|
|
|
1236
1625
|
Args:
|
|
1237
1626
|
id (str): The ID of the knowledge row to delete.
|
|
1238
1627
|
"""
|
|
1239
|
-
table = self._get_table(table_type="knowledge")
|
|
1240
|
-
if table is None:
|
|
1241
|
-
return
|
|
1242
|
-
|
|
1243
1628
|
try:
|
|
1629
|
+
table = self._get_table(table_type="knowledge")
|
|
1630
|
+
if table is None:
|
|
1631
|
+
return
|
|
1632
|
+
|
|
1244
1633
|
with self.Session() as sess, sess.begin():
|
|
1245
1634
|
stmt = table.delete().where(table.c.id == id)
|
|
1246
1635
|
sess.execute(stmt)
|
|
1247
1636
|
|
|
1248
1637
|
except Exception as e:
|
|
1249
1638
|
log_error(f"Exception deleting knowledge content: {e}")
|
|
1639
|
+
raise e
|
|
1250
1640
|
|
|
1251
1641
|
def get_knowledge_content(self, id: str) -> Optional[KnowledgeRow]:
|
|
1252
1642
|
"""Get a knowledge row from the database.
|
|
@@ -1257,11 +1647,11 @@ class PostgresDb(BaseDb):
|
|
|
1257
1647
|
Returns:
|
|
1258
1648
|
Optional[KnowledgeRow]: The knowledge row, or None if it doesn't exist.
|
|
1259
1649
|
"""
|
|
1260
|
-
table = self._get_table(table_type="knowledge")
|
|
1261
|
-
if table is None:
|
|
1262
|
-
return None
|
|
1263
|
-
|
|
1264
1650
|
try:
|
|
1651
|
+
table = self._get_table(table_type="knowledge")
|
|
1652
|
+
if table is None:
|
|
1653
|
+
return None
|
|
1654
|
+
|
|
1265
1655
|
with self.Session() as sess, sess.begin():
|
|
1266
1656
|
stmt = select(table).where(table.c.id == id)
|
|
1267
1657
|
result = sess.execute(stmt).fetchone()
|
|
@@ -1272,7 +1662,7 @@ class PostgresDb(BaseDb):
|
|
|
1272
1662
|
|
|
1273
1663
|
except Exception as e:
|
|
1274
1664
|
log_error(f"Exception getting knowledge content: {e}")
|
|
1275
|
-
|
|
1665
|
+
raise e
|
|
1276
1666
|
|
|
1277
1667
|
def get_knowledge_contents(
|
|
1278
1668
|
self,
|
|
@@ -1296,11 +1686,11 @@ class PostgresDb(BaseDb):
|
|
|
1296
1686
|
Raises:
|
|
1297
1687
|
Exception: If an error occurs during retrieval.
|
|
1298
1688
|
"""
|
|
1299
|
-
table = self._get_table(table_type="knowledge")
|
|
1300
|
-
if table is None:
|
|
1301
|
-
return [], 0
|
|
1302
|
-
|
|
1303
1689
|
try:
|
|
1690
|
+
table = self._get_table(table_type="knowledge")
|
|
1691
|
+
if table is None:
|
|
1692
|
+
return [], 0
|
|
1693
|
+
|
|
1304
1694
|
with self.Session() as sess, sess.begin():
|
|
1305
1695
|
stmt = select(table)
|
|
1306
1696
|
|
|
@@ -1323,7 +1713,7 @@ class PostgresDb(BaseDb):
|
|
|
1323
1713
|
|
|
1324
1714
|
except Exception as e:
|
|
1325
1715
|
log_error(f"Exception getting knowledge contents: {e}")
|
|
1326
|
-
|
|
1716
|
+
raise e
|
|
1327
1717
|
|
|
1328
1718
|
def upsert_knowledge_content(self, knowledge_row: KnowledgeRow):
|
|
1329
1719
|
"""Upsert knowledge content in the database.
|
|
@@ -1401,7 +1791,7 @@ class PostgresDb(BaseDb):
|
|
|
1401
1791
|
|
|
1402
1792
|
except Exception as e:
|
|
1403
1793
|
log_error(f"Error upserting knowledge row: {e}")
|
|
1404
|
-
|
|
1794
|
+
raise e
|
|
1405
1795
|
|
|
1406
1796
|
# -- Eval methods --
|
|
1407
1797
|
def create_eval_run(self, eval_run: EvalRunRecord) -> Optional[EvalRunRecord]:
|
|
@@ -1434,7 +1824,7 @@ class PostgresDb(BaseDb):
|
|
|
1434
1824
|
|
|
1435
1825
|
except Exception as e:
|
|
1436
1826
|
log_error(f"Error creating eval run: {e}")
|
|
1437
|
-
|
|
1827
|
+
raise e
|
|
1438
1828
|
|
|
1439
1829
|
def delete_eval_run(self, eval_run_id: str) -> None:
|
|
1440
1830
|
"""Delete an eval run from the database.
|
|
@@ -1458,6 +1848,7 @@ class PostgresDb(BaseDb):
|
|
|
1458
1848
|
|
|
1459
1849
|
except Exception as e:
|
|
1460
1850
|
log_error(f"Error deleting eval run {eval_run_id}: {e}")
|
|
1851
|
+
raise e
|
|
1461
1852
|
|
|
1462
1853
|
def delete_eval_runs(self, eval_run_ids: List[str]) -> None:
|
|
1463
1854
|
"""Delete multiple eval runs from the database.
|
|
@@ -1481,6 +1872,7 @@ class PostgresDb(BaseDb):
|
|
|
1481
1872
|
|
|
1482
1873
|
except Exception as e:
|
|
1483
1874
|
log_error(f"Error deleting eval runs {eval_run_ids}: {e}")
|
|
1875
|
+
raise e
|
|
1484
1876
|
|
|
1485
1877
|
def get_eval_run(
|
|
1486
1878
|
self, eval_run_id: str, deserialize: Optional[bool] = True
|
|
@@ -1518,7 +1910,7 @@ class PostgresDb(BaseDb):
|
|
|
1518
1910
|
|
|
1519
1911
|
except Exception as e:
|
|
1520
1912
|
log_error(f"Exception getting eval run {eval_run_id}: {e}")
|
|
1521
|
-
|
|
1913
|
+
raise e
|
|
1522
1914
|
|
|
1523
1915
|
def get_eval_runs(
|
|
1524
1916
|
self,
|
|
@@ -1613,7 +2005,7 @@ class PostgresDb(BaseDb):
|
|
|
1613
2005
|
|
|
1614
2006
|
except Exception as e:
|
|
1615
2007
|
log_error(f"Exception getting eval runs: {e}")
|
|
1616
|
-
|
|
2008
|
+
raise e
|
|
1617
2009
|
|
|
1618
2010
|
def rename_eval_run(
|
|
1619
2011
|
self, eval_run_id: str, name: str, deserialize: Optional[bool] = True
|
|
@@ -1649,7 +2041,234 @@ class PostgresDb(BaseDb):
|
|
|
1649
2041
|
|
|
1650
2042
|
except Exception as e:
|
|
1651
2043
|
log_error(f"Error upserting eval run name {eval_run_id}: {e}")
|
|
1652
|
-
|
|
2044
|
+
raise e
|
|
2045
|
+
|
|
2046
|
+
# -- Culture methods --
|
|
2047
|
+
|
|
2048
|
+
def clear_cultural_knowledge(self) -> None:
|
|
2049
|
+
"""Delete all cultural knowledge from the database.
|
|
2050
|
+
|
|
2051
|
+
Raises:
|
|
2052
|
+
Exception: If an error occurs during deletion.
|
|
2053
|
+
"""
|
|
2054
|
+
try:
|
|
2055
|
+
table = self._get_table(table_type="culture")
|
|
2056
|
+
if table is None:
|
|
2057
|
+
return
|
|
2058
|
+
|
|
2059
|
+
with self.Session() as sess, sess.begin():
|
|
2060
|
+
sess.execute(table.delete())
|
|
2061
|
+
|
|
2062
|
+
except Exception as e:
|
|
2063
|
+
log_warning(f"Exception deleting all cultural knowledge: {e}")
|
|
2064
|
+
raise e
|
|
2065
|
+
|
|
2066
|
+
def delete_cultural_knowledge(self, id: str) -> None:
|
|
2067
|
+
"""Delete a cultural knowledge entry from the database.
|
|
2068
|
+
|
|
2069
|
+
Args:
|
|
2070
|
+
id (str): The ID of the cultural knowledge to delete.
|
|
2071
|
+
|
|
2072
|
+
Raises:
|
|
2073
|
+
Exception: If an error occurs during deletion.
|
|
2074
|
+
"""
|
|
2075
|
+
try:
|
|
2076
|
+
table = self._get_table(table_type="culture")
|
|
2077
|
+
if table is None:
|
|
2078
|
+
return
|
|
2079
|
+
|
|
2080
|
+
with self.Session() as sess, sess.begin():
|
|
2081
|
+
delete_stmt = table.delete().where(table.c.id == id)
|
|
2082
|
+
result = sess.execute(delete_stmt)
|
|
2083
|
+
|
|
2084
|
+
success = result.rowcount > 0
|
|
2085
|
+
if success:
|
|
2086
|
+
log_debug(f"Successfully deleted cultural knowledge id: {id}")
|
|
2087
|
+
else:
|
|
2088
|
+
log_debug(f"No cultural knowledge found with id: {id}")
|
|
2089
|
+
|
|
2090
|
+
except Exception as e:
|
|
2091
|
+
log_error(f"Error deleting cultural knowledge: {e}")
|
|
2092
|
+
raise e
|
|
2093
|
+
|
|
2094
|
+
def get_cultural_knowledge(
|
|
2095
|
+
self, id: str, deserialize: Optional[bool] = True
|
|
2096
|
+
) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
|
|
2097
|
+
"""Get a cultural knowledge entry from the database.
|
|
2098
|
+
|
|
2099
|
+
Args:
|
|
2100
|
+
id (str): The ID of the cultural knowledge to get.
|
|
2101
|
+
deserialize (Optional[bool]): Whether to deserialize the cultural knowledge. Defaults to True.
|
|
2102
|
+
|
|
2103
|
+
Returns:
|
|
2104
|
+
Optional[Union[CulturalKnowledge, Dict[str, Any]]]: The cultural knowledge entry, or None if it doesn't exist.
|
|
2105
|
+
|
|
2106
|
+
Raises:
|
|
2107
|
+
Exception: If an error occurs during retrieval.
|
|
2108
|
+
"""
|
|
2109
|
+
try:
|
|
2110
|
+
table = self._get_table(table_type="culture")
|
|
2111
|
+
if table is None:
|
|
2112
|
+
return None
|
|
2113
|
+
|
|
2114
|
+
with self.Session() as sess, sess.begin():
|
|
2115
|
+
stmt = select(table).where(table.c.id == id)
|
|
2116
|
+
result = sess.execute(stmt).fetchone()
|
|
2117
|
+
if result is None:
|
|
2118
|
+
return None
|
|
2119
|
+
|
|
2120
|
+
db_row = dict(result._mapping)
|
|
2121
|
+
if not db_row or not deserialize:
|
|
2122
|
+
return db_row
|
|
2123
|
+
|
|
2124
|
+
return deserialize_cultural_knowledge(db_row)
|
|
2125
|
+
|
|
2126
|
+
except Exception as e:
|
|
2127
|
+
log_error(f"Exception reading from cultural knowledge table: {e}")
|
|
2128
|
+
raise e
|
|
2129
|
+
|
|
2130
|
+
def get_all_cultural_knowledge(
|
|
2131
|
+
self,
|
|
2132
|
+
name: Optional[str] = None,
|
|
2133
|
+
agent_id: Optional[str] = None,
|
|
2134
|
+
team_id: Optional[str] = None,
|
|
2135
|
+
limit: Optional[int] = None,
|
|
2136
|
+
page: Optional[int] = None,
|
|
2137
|
+
sort_by: Optional[str] = None,
|
|
2138
|
+
sort_order: Optional[str] = None,
|
|
2139
|
+
deserialize: Optional[bool] = True,
|
|
2140
|
+
) -> Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
|
|
2141
|
+
"""Get all cultural knowledge from the database as CulturalKnowledge objects.
|
|
2142
|
+
|
|
2143
|
+
Args:
|
|
2144
|
+
name (Optional[str]): The name of the cultural knowledge to filter by.
|
|
2145
|
+
agent_id (Optional[str]): The ID of the agent to filter by.
|
|
2146
|
+
team_id (Optional[str]): The ID of the team to filter by.
|
|
2147
|
+
limit (Optional[int]): The maximum number of cultural knowledge entries to return.
|
|
2148
|
+
page (Optional[int]): The page number.
|
|
2149
|
+
sort_by (Optional[str]): The column to sort by.
|
|
2150
|
+
sort_order (Optional[str]): The order to sort by.
|
|
2151
|
+
deserialize (Optional[bool]): Whether to deserialize the cultural knowledge. Defaults to True.
|
|
2152
|
+
|
|
2153
|
+
Returns:
|
|
2154
|
+
Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
|
|
2155
|
+
- When deserialize=True: List of CulturalKnowledge objects
|
|
2156
|
+
- When deserialize=False: List of CulturalKnowledge dictionaries and total count
|
|
2157
|
+
|
|
2158
|
+
Raises:
|
|
2159
|
+
Exception: If an error occurs during retrieval.
|
|
2160
|
+
"""
|
|
2161
|
+
try:
|
|
2162
|
+
table = self._get_table(table_type="culture")
|
|
2163
|
+
if table is None:
|
|
2164
|
+
return [] if deserialize else ([], 0)
|
|
2165
|
+
|
|
2166
|
+
with self.Session() as sess, sess.begin():
|
|
2167
|
+
stmt = select(table)
|
|
2168
|
+
|
|
2169
|
+
# Filtering
|
|
2170
|
+
if name is not None:
|
|
2171
|
+
stmt = stmt.where(table.c.name == name)
|
|
2172
|
+
if agent_id is not None:
|
|
2173
|
+
stmt = stmt.where(table.c.agent_id == agent_id)
|
|
2174
|
+
if team_id is not None:
|
|
2175
|
+
stmt = stmt.where(table.c.team_id == team_id)
|
|
2176
|
+
|
|
2177
|
+
# Get total count after applying filtering
|
|
2178
|
+
count_stmt = select(func.count()).select_from(stmt.alias())
|
|
2179
|
+
total_count = sess.execute(count_stmt).scalar()
|
|
2180
|
+
|
|
2181
|
+
# Sorting
|
|
2182
|
+
stmt = apply_sorting(stmt, table, sort_by, sort_order)
|
|
2183
|
+
# Paginating
|
|
2184
|
+
if limit is not None:
|
|
2185
|
+
stmt = stmt.limit(limit)
|
|
2186
|
+
if page is not None:
|
|
2187
|
+
stmt = stmt.offset((page - 1) * limit)
|
|
2188
|
+
|
|
2189
|
+
result = sess.execute(stmt).fetchall()
|
|
2190
|
+
if not result:
|
|
2191
|
+
return [] if deserialize else ([], 0)
|
|
2192
|
+
|
|
2193
|
+
db_rows = [dict(record._mapping) for record in result]
|
|
2194
|
+
|
|
2195
|
+
if not deserialize:
|
|
2196
|
+
return db_rows, total_count
|
|
2197
|
+
|
|
2198
|
+
return [deserialize_cultural_knowledge(row) for row in db_rows]
|
|
2199
|
+
|
|
2200
|
+
except Exception as e:
|
|
2201
|
+
log_error(f"Error reading from cultural knowledge table: {e}")
|
|
2202
|
+
raise e
|
|
2203
|
+
|
|
2204
|
+
def upsert_cultural_knowledge(
|
|
2205
|
+
self, cultural_knowledge: CulturalKnowledge, deserialize: Optional[bool] = True
|
|
2206
|
+
) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
|
|
2207
|
+
"""Upsert a cultural knowledge entry into the database.
|
|
2208
|
+
|
|
2209
|
+
Args:
|
|
2210
|
+
cultural_knowledge (CulturalKnowledge): The cultural knowledge to upsert.
|
|
2211
|
+
deserialize (Optional[bool]): Whether to deserialize the cultural knowledge. Defaults to True.
|
|
2212
|
+
|
|
2213
|
+
Returns:
|
|
2214
|
+
Optional[CulturalKnowledge]: The upserted cultural knowledge entry.
|
|
2215
|
+
|
|
2216
|
+
Raises:
|
|
2217
|
+
Exception: If an error occurs during upsert.
|
|
2218
|
+
"""
|
|
2219
|
+
try:
|
|
2220
|
+
table = self._get_table(table_type="culture", create_table_if_not_found=True)
|
|
2221
|
+
if table is None:
|
|
2222
|
+
return None
|
|
2223
|
+
|
|
2224
|
+
if cultural_knowledge.id is None:
|
|
2225
|
+
cultural_knowledge.id = str(uuid4())
|
|
2226
|
+
|
|
2227
|
+
# Serialize content, categories, and notes into a JSON dict for DB storage
|
|
2228
|
+
content_dict = serialize_cultural_knowledge(cultural_knowledge)
|
|
2229
|
+
|
|
2230
|
+
with self.Session() as sess, sess.begin():
|
|
2231
|
+
stmt = postgresql.insert(table).values(
|
|
2232
|
+
id=cultural_knowledge.id,
|
|
2233
|
+
name=cultural_knowledge.name,
|
|
2234
|
+
summary=cultural_knowledge.summary,
|
|
2235
|
+
content=content_dict if content_dict else None,
|
|
2236
|
+
metadata=cultural_knowledge.metadata,
|
|
2237
|
+
input=cultural_knowledge.input,
|
|
2238
|
+
created_at=cultural_knowledge.created_at,
|
|
2239
|
+
updated_at=int(time.time()),
|
|
2240
|
+
agent_id=cultural_knowledge.agent_id,
|
|
2241
|
+
team_id=cultural_knowledge.team_id,
|
|
2242
|
+
)
|
|
2243
|
+
stmt = stmt.on_conflict_do_update( # type: ignore
|
|
2244
|
+
index_elements=["id"],
|
|
2245
|
+
set_=dict(
|
|
2246
|
+
name=cultural_knowledge.name,
|
|
2247
|
+
summary=cultural_knowledge.summary,
|
|
2248
|
+
content=content_dict if content_dict else None,
|
|
2249
|
+
metadata=cultural_knowledge.metadata,
|
|
2250
|
+
input=cultural_knowledge.input,
|
|
2251
|
+
updated_at=int(time.time()),
|
|
2252
|
+
agent_id=cultural_knowledge.agent_id,
|
|
2253
|
+
team_id=cultural_knowledge.team_id,
|
|
2254
|
+
),
|
|
2255
|
+
).returning(table)
|
|
2256
|
+
|
|
2257
|
+
result = sess.execute(stmt)
|
|
2258
|
+
row = result.fetchone()
|
|
2259
|
+
|
|
2260
|
+
if row is None:
|
|
2261
|
+
return None
|
|
2262
|
+
|
|
2263
|
+
db_row = dict(row._mapping)
|
|
2264
|
+
if not db_row or not deserialize:
|
|
2265
|
+
return db_row
|
|
2266
|
+
|
|
2267
|
+
return deserialize_cultural_knowledge(db_row)
|
|
2268
|
+
|
|
2269
|
+
except Exception as e:
|
|
2270
|
+
log_error(f"Error upserting cultural knowledge: {e}")
|
|
2271
|
+
raise e
|
|
1653
2272
|
|
|
1654
2273
|
# -- Migrations --
|
|
1655
2274
|
|
|
@@ -1692,17 +2311,17 @@ class PostgresDb(BaseDb):
|
|
|
1692
2311
|
if v1_table_type == "agent_sessions":
|
|
1693
2312
|
for session in sessions:
|
|
1694
2313
|
self.upsert_session(session)
|
|
1695
|
-
log_info(f"Migrated {len(sessions)} Agent sessions to table: {self.
|
|
2314
|
+
log_info(f"Migrated {len(sessions)} Agent sessions to table: {self.session_table_name}")
|
|
1696
2315
|
|
|
1697
2316
|
elif v1_table_type == "team_sessions":
|
|
1698
2317
|
for session in sessions:
|
|
1699
2318
|
self.upsert_session(session)
|
|
1700
|
-
log_info(f"Migrated {len(sessions)} Team sessions to table: {self.
|
|
2319
|
+
log_info(f"Migrated {len(sessions)} Team sessions to table: {self.session_table_name}")
|
|
1701
2320
|
|
|
1702
2321
|
elif v1_table_type == "workflow_sessions":
|
|
1703
2322
|
for session in sessions:
|
|
1704
2323
|
self.upsert_session(session)
|
|
1705
|
-
log_info(f"Migrated {len(sessions)} Workflow sessions to table: {self.
|
|
2324
|
+
log_info(f"Migrated {len(sessions)} Workflow sessions to table: {self.session_table_name}")
|
|
1706
2325
|
|
|
1707
2326
|
elif v1_table_type == "memories":
|
|
1708
2327
|
for memory in memories:
|