letta-nightly 0.6.3.dev20241211050151__py3-none-any.whl → 0.6.3.dev20241212015858__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.

Potentially problematic release.


This version of letta-nightly might be problematic. Click here for more details.

@@ -1,10 +1,6 @@
1
1
  letta/__init__.py,sha256=XzSCdK-rlcvOAsPJarW8Ri5qelGVdpX237ds-yjHniY,1035
2
2
  letta/__main__.py,sha256=6Hs2PV7EYc5Tid4g4OtcLXhqVHiNYTGzSBdoOnW2HXA,29
3
- letta/agent.py,sha256=D4staw-NnTzsG31kV7cvb7fvLIpkeZii1rXsQIRPC0s,78058
4
- letta/agent_store/db.py,sha256=Rzgodcr-eT6DXnFr5bt4psiVpeH0Gy1BS16Ethf2CHw,19016
5
- letta/agent_store/milvus.py,sha256=xUu-D9a6N10MuGJ-R-QWR2IHX77ueqAp88tV4gg9B4M,8470
6
- letta/agent_store/qdrant.py,sha256=6_33V-FEDpT9LG5zmr6-3y9slw1YFLswxpahiyMkvHA,7880
7
- letta/agent_store/storage.py,sha256=F0kpHr8ALaph630ItxZ1ijYOMNq7P7sRrkP-2LkywVU,6547
3
+ letta/agent.py,sha256=3jXQZ2Yjg_szr7-1zMzaPq1R5qpoAg5BalpUqaX_uig,78003
8
4
  letta/benchmark/benchmark.py,sha256=ebvnwfp3yezaXOQyGXkYCDYpsmre-b9hvNtnyx4xkG0,3701
9
5
  letta/benchmark/constants.py,sha256=aXc5gdpMGJT327VuxsT5FngbCK2J41PQYeICBO7g_RE,536
10
6
  letta/chat_only_agent.py,sha256=606FQybmeN9s04rIFQlDkWOHirrT0p48_3pMKY8d5Ts,4740
@@ -18,7 +14,7 @@ letta/client/utils.py,sha256=OJlAKWrldc4I6M1WpcTWNtPJ4wfxlzlZqWLfCozkFtI,2872
18
14
  letta/config.py,sha256=06F-kPu9r6AMOvz5uX6WKVrgE8h6QqSxWni3TT16ZdY,18923
19
15
  letta/constants.py,sha256=l2IyZpVG-d_raFFX5bN6mCbprnnc1IA-z-oCKPoyPSM,6902
20
16
  letta/credentials.py,sha256=D9mlcPsdDWlIIXQQD8wSPE9M_QvsRrb0p3LB5i9OF5Q,5806
21
- letta/data_sources/connectors.py,sha256=o6XhyeONu4FB7CM7l9K705M9zV8jE2ExddYy_ZjgYgs,10010
17
+ letta/data_sources/connectors.py,sha256=zMN6EGZ-BRQdF8l6ZUqhONdn6H4lBNH7h5zKKclxyMg,7245
22
18
  letta/data_sources/connectors_helper.py,sha256=2TQjCt74fCgT5sw1AP8PalDEk06jPBbhrPG4HVr-WLs,3371
23
19
  letta/embeddings.py,sha256=XyecUdJaG3ENi5v3wAexVcgpG2g7_oTEEYLgUpm-Zvs,9080
24
20
  letta/errors.py,sha256=Voy_BP0W_M816-vWudKLBlgydRufPPA-Q2PNd-SvZYc,3897
@@ -83,7 +79,7 @@ letta/local_llm/webui/legacy_settings.py,sha256=BLmd3TSx5StnY3ibjwaxYATPt_Lvq-o1
83
79
  letta/local_llm/webui/settings.py,sha256=gmLHfiOl1u4JmlAZU2d2O8YKF9lafdakyjwR_ftVPh8,552
84
80
  letta/log.py,sha256=FxkAk2f8Bl-u9dfImSj1DYnjAsmV6PL3tjTSnEiNP48,2218
85
81
  letta/main.py,sha256=3rvn5rHMpChmgDsYKAH8GX7lLVsIzxLmkPhDJt6nFKA,19067
86
- letta/memory.py,sha256=R4Z_MYD5ve04FBe2yohux1pzp8MTjOSI1AZ5oLCdGRA,14568
82
+ letta/memory.py,sha256=DO7aevi0HQBSX94wB5NVTe-rPgsXiYC1in9mJxTUnUo,3579
87
83
  letta/metadata.py,sha256=iU2KZewl_oqyB6mjKeSGH33BsvRUPeWb1K_qRgme5jo,15507
88
84
  letta/o1_agent.py,sha256=Jbc4Id15tMQ_1ek74hxRUJdfTegmsaIHQyRNc31g6dA,3092
89
85
  letta/offline_memory_agent.py,sha256=Pa4HmKMFYhmkzl6SyHHtHpdYIZkrJqbG0-fwPAU6Muo,7912
@@ -154,7 +150,7 @@ letta/schemas/letta_request.py,sha256=Hfb66FB1tXmrpEX4_yLQVfTrTSMbPYeEvZY-vqW9Tj
154
150
  letta/schemas/letta_response.py,sha256=vQV5uqe-kq9fc4wCo-sVB_PJt5yUk8DB2zOgHsrmN-k,6189
155
151
  letta/schemas/llm_config.py,sha256=RbgnCaqYd_yl-Xs7t-DEI1NhpKD8WiVWjxcwq5mZd5M,4467
156
152
  letta/schemas/memory.py,sha256=80Y7gqWQQndhNkJ-0j38d2m619gTlfes_qJNA6_ant8,10040
157
- letta/schemas/message.py,sha256=GFuHcfPU5kgRXgu70osF9H03Mv0JcO5nbURTYR3XXDg,34154
153
+ letta/schemas/message.py,sha256=QF6OrCVfh5ETo-KJ2KZpleAuvisBIMMsIL3uH6h_kMM,34161
158
154
  letta/schemas/openai/chat_completion_request.py,sha256=AOIwgbN3CZKVqkuXeMHeSa53u4h0wVq69t3T_LJ0vIE,3389
159
155
  letta/schemas/openai/chat_completion_response.py,sha256=ub-oVSyLpuJd-5_yzCSIRR8tD3GM83IeDO1c1uAATa4,3970
160
156
  letta/schemas/openai/chat_completions.py,sha256=V0ZPIIk-ds3O6MAkNHMz8zh1hqMFSPrTcYr88WDYzWE,3588
@@ -199,7 +195,7 @@ letta/server/rest_api/routers/v1/tools.py,sha256=ajYUo_cgUBDfm4Ja_EufxSdhBWoAb5N
199
195
  letta/server/rest_api/routers/v1/users.py,sha256=M1wEr2IyHzuRwINYxLXTkrbAH3osLe_cWjzrWrzR1aw,3729
200
196
  letta/server/rest_api/static_files.py,sha256=NG8sN4Z5EJ8JVQdj19tkFa9iQ1kBPTab9f_CUxd_u4Q,3143
201
197
  letta/server/rest_api/utils.py,sha256=6Z0T0kHIwDAgTIFh38Q1JQ_nhANgdqXcSlsuY41pL6M,3158
202
- letta/server/server.py,sha256=H6TxXsh7Bahgn-8Jf9iLnkbeL3G8Y8U0tetc4D_1lIQ,85587
198
+ letta/server/server.py,sha256=Y6Oft1vCoLzTxqdP8ZU8JBtTBURZFwa4NWM_LIvR7O4,85433
203
199
  letta/server/startup.sh,sha256=722uKJWB2C4q3vjn39De2zzPacaZNw_1fN1SpLGjKIo,1569
204
200
  letta/server/static_files/assets/index-048c9598.js,sha256=mR16XppvselwKCcNgONs4L7kZEVa4OEERm4lNZYtLSk,146819
205
201
  letta/server/static_files/assets/index-0e31b727.css,sha256=DjG3J3RSFLcinzoisOYYLiyTAIe5Uaxge3HE-DmQIsU,7688
@@ -228,13 +224,13 @@ letta/services/tool_manager.py,sha256=lfrfWyxiFUWcEf-nATHs7r76XWutMYbOPyePs543ZO
228
224
  letta/services/tool_sandbox_env/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
229
225
  letta/services/tools_agents_manager.py,sha256=kF6yIsUO154_Q3l-cEeSU56QW-VUr_M6B-9csqhHBkg,4847
230
226
  letta/services/user_manager.py,sha256=cfXgnVlnfqPCk7c-wYRsU3RegHs-4stksk186RW89Do,4403
231
- letta/settings.py,sha256=IBODJU3ZxFQAgzpB-nZtBbYuak6ad9rwhjLWv9oCgBo,3664
227
+ letta/settings.py,sha256=o3eydtE_NrNz5GJ37VPQPXZri-qlz671PxFIJIzveRU,3712
232
228
  letta/streaming_interface.py,sha256=_FPUWy58j50evHcpXyd7zB1wWqeCc71NCFeWh_TBvnw,15736
233
229
  letta/streaming_utils.py,sha256=329fsvj1ZN0r0LpQtmMPZ2vSxkDBIUUwvGHZFkjm2I8,11745
234
230
  letta/system.py,sha256=buKYPqG5n2x41hVmWpu6JUpyd7vTWED9Km2_M7dLrvk,6960
235
231
  letta/utils.py,sha256=L8c6S77gyMYFgVP6ncGRaNbGjWtg6BOU_whI1vjt9Ts,32915
236
- letta_nightly-0.6.3.dev20241211050151.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
237
- letta_nightly-0.6.3.dev20241211050151.dist-info/METADATA,sha256=pzK7XMc5nR-XJOyIEFDuWEtTkaRERhb3oDgt2Cj1FQQ,19560
238
- letta_nightly-0.6.3.dev20241211050151.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
239
- letta_nightly-0.6.3.dev20241211050151.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
240
- letta_nightly-0.6.3.dev20241211050151.dist-info/RECORD,,
232
+ letta_nightly-0.6.3.dev20241212015858.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
233
+ letta_nightly-0.6.3.dev20241212015858.dist-info/METADATA,sha256=6HeN_TZpUhDFRKUvNLPZpQ7wO2wKgD73b8SFJkqsnSo,21689
234
+ letta_nightly-0.6.3.dev20241212015858.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
235
+ letta_nightly-0.6.3.dev20241212015858.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
236
+ letta_nightly-0.6.3.dev20241212015858.dist-info/RECORD,,
letta/agent_store/db.py DELETED
@@ -1,467 +0,0 @@
1
- import base64
2
- import json
3
- import os
4
- from datetime import datetime
5
- from typing import Dict, List, Optional
6
-
7
- import numpy as np
8
- from sqlalchemy import (
9
- BINARY,
10
- Column,
11
- DateTime,
12
- Index,
13
- String,
14
- TypeDecorator,
15
- and_,
16
- asc,
17
- desc,
18
- or_,
19
- select,
20
- text,
21
- )
22
- from sqlalchemy.orm import mapped_column
23
- from sqlalchemy.orm.session import close_all_sessions
24
- from sqlalchemy.sql import func
25
- from sqlalchemy_json import MutableJson
26
- from tqdm import tqdm
27
-
28
- from letta.agent_store.storage import StorageConnector, TableType
29
- from letta.config import LettaConfig
30
- from letta.constants import MAX_EMBEDDING_DIM
31
- from letta.metadata import EmbeddingConfigColumn
32
- from letta.orm.base import Base
33
- from letta.orm.file import FileMetadata as FileMetadataModel
34
-
35
- # from letta.schemas.message import Message, Passage, Record, RecordType, ToolCall
36
- from letta.orm.passage import Passage as PassageModel
37
- from letta.settings import settings
38
-
39
- config = LettaConfig()
40
-
41
-
42
- class CommonVector(TypeDecorator):
43
- """Common type for representing vectors in SQLite"""
44
-
45
- impl = BINARY
46
- cache_ok = True
47
-
48
- def load_dialect_impl(self, dialect):
49
- return dialect.type_descriptor(BINARY())
50
-
51
- def process_bind_param(self, value, dialect):
52
- if value is None:
53
- return value
54
- # Ensure value is a numpy array
55
- if isinstance(value, list):
56
- value = np.array(value, dtype=np.float32)
57
- # Serialize numpy array to bytes, then encode to base64 for universal compatibility
58
- return base64.b64encode(value.tobytes())
59
-
60
- def process_result_value(self, value, dialect):
61
- if not value:
62
- return value
63
- # Check database type and deserialize accordingly
64
- if dialect.name == "sqlite":
65
- # Decode from base64 and convert back to numpy array
66
- value = base64.b64decode(value)
67
- # For PostgreSQL, value is already in bytes
68
- return np.frombuffer(value, dtype=np.float32)
69
-
70
- class SQLStorageConnector(StorageConnector):
71
- def __init__(self, table_type: str, config: LettaConfig, user_id, agent_id=None):
72
- super().__init__(table_type=table_type, config=config, user_id=user_id, agent_id=agent_id)
73
- self.config = config
74
-
75
- def get_filters(self, filters: Optional[Dict] = {}):
76
- if filters is not None:
77
- filter_conditions = {**self.filters, **filters}
78
- else:
79
- filter_conditions = self.filters
80
- all_filters = [getattr(self.db_model, key) == value for key, value in filter_conditions.items()]
81
- return all_filters
82
-
83
- def get_all_paginated(self, filters: Optional[Dict] = {}, page_size: Optional[int] = 1000, offset=0):
84
- filters = self.get_filters(filters)
85
- while True:
86
- # Retrieve a chunk of records with the given page_size
87
- with self.session_maker() as session:
88
- db_record_chunk = session.query(self.db_model).filter(*filters).offset(offset).limit(page_size).all()
89
-
90
- # If the chunk is empty, we've retrieved all records
91
- if not db_record_chunk:
92
- break
93
-
94
- # Yield a list of Record objects converted from the chunk
95
- yield [record.to_record() for record in db_record_chunk]
96
-
97
- # Increment the offset to get the next chunk in the next iteration
98
- offset += page_size
99
-
100
- def get_all_cursor(
101
- self,
102
- filters: Optional[Dict] = {},
103
- after: str = None,
104
- before: str = None,
105
- limit: Optional[int] = 1000,
106
- order_by: str = "created_at",
107
- reverse: bool = False,
108
- ):
109
- """Get all that returns a cursor (record.id) and records"""
110
- filters = self.get_filters(filters)
111
-
112
- # generate query
113
- with self.session_maker() as session:
114
- query = session.query(self.db_model).filter(*filters)
115
- # query = query.order_by(asc(self.db_model.id))
116
-
117
- # records are sorted by the order_by field first, and then by the ID if two fields are the same
118
- if reverse:
119
- query = query.order_by(desc(getattr(self.db_model, order_by)), asc(self.db_model.id))
120
- else:
121
- query = query.order_by(asc(getattr(self.db_model, order_by)), asc(self.db_model.id))
122
-
123
- # cursor logic: filter records based on before/after ID
124
- if after:
125
- after_value = getattr(self.get(id=after), order_by)
126
- sort_exp = getattr(self.db_model, order_by) > after_value
127
- query = query.filter(
128
- or_(sort_exp, and_(getattr(self.db_model, order_by) == after_value, self.db_model.id > after)) # tiebreaker case
129
- )
130
- if before:
131
- before_value = getattr(self.get(id=before), order_by)
132
- sort_exp = getattr(self.db_model, order_by) < before_value
133
- query = query.filter(or_(sort_exp, and_(getattr(self.db_model, order_by) == before_value, self.db_model.id < before)))
134
-
135
- # get records
136
- db_record_chunk = query.limit(limit).all()
137
- if not db_record_chunk:
138
- return (None, [])
139
- records = [record.to_record() for record in db_record_chunk]
140
- next_cursor = db_record_chunk[-1].id
141
- assert isinstance(next_cursor, str)
142
-
143
- # return (cursor, list[records])
144
- return (next_cursor, records)
145
-
146
- def get_all(self, filters: Optional[Dict] = {}, limit=None):
147
- filters = self.get_filters(filters)
148
- with self.session_maker() as session:
149
- if limit:
150
- db_records = session.query(self.db_model).filter(*filters).limit(limit).all()
151
- else:
152
- db_records = session.query(self.db_model).filter(*filters).all()
153
- return [record.to_record() for record in db_records]
154
-
155
- def get(self, id: str):
156
- with self.session_maker() as session:
157
- db_record = session.get(self.db_model, id)
158
- if db_record is None:
159
- return None
160
- return db_record.to_record()
161
-
162
- def size(self, filters: Optional[Dict] = {}) -> int:
163
- # return size of table
164
- filters = self.get_filters(filters)
165
- with self.session_maker() as session:
166
- return session.query(self.db_model).filter(*filters).count()
167
-
168
- def insert(self, record):
169
- raise NotImplementedError
170
-
171
- def insert_many(self, records, show_progress=False):
172
- raise NotImplementedError
173
-
174
- def query(self, query: str, query_vec: List[float], top_k: int = 10, filters: Optional[Dict] = {}):
175
- raise NotImplementedError("Vector query not implemented for SQLStorageConnector")
176
-
177
- def save(self):
178
- return
179
-
180
- def list_data_sources(self):
181
- assert self.table_type == TableType.ARCHIVAL_MEMORY, f"list_data_sources only implemented for ARCHIVAL_MEMORY"
182
- with self.session_maker() as session:
183
- unique_data_sources = session.query(self.db_model.data_source).filter(*self.filters).distinct().all()
184
- return unique_data_sources
185
-
186
- def query_date(self, start_date, end_date, limit=None, offset=0):
187
- filters = self.get_filters({})
188
- with self.session_maker() as session:
189
- query = (
190
- session.query(self.db_model)
191
- .filter(*filters)
192
- .filter(self.db_model.created_at >= start_date)
193
- .filter(self.db_model.created_at <= end_date)
194
- .filter(self.db_model.role != "system")
195
- .filter(self.db_model.role != "tool")
196
- .offset(offset)
197
- )
198
- if limit:
199
- query = query.limit(limit)
200
- results = query.all()
201
- return [result.to_record() for result in results]
202
-
203
- def query_text(self, query, limit=None, offset=0):
204
- # todo: make fuzz https://stackoverflow.com/questions/42388956/create-a-full-text-search-index-with-sqlalchemy-on-postgresql/42390204#42390204
205
- filters = self.get_filters({})
206
- with self.session_maker() as session:
207
- query = (
208
- session.query(self.db_model)
209
- .filter(*filters)
210
- .filter(func.lower(self.db_model.text).contains(func.lower(query)))
211
- .filter(self.db_model.role != "system")
212
- .filter(self.db_model.role != "tool")
213
- .offset(offset)
214
- )
215
- if limit:
216
- query = query.limit(limit)
217
- results = query.all()
218
- # return [self.type(**vars(result)) for result in results]
219
- return [result.to_record() for result in results]
220
-
221
- # Should be used only in tests!
222
- def delete_table(self):
223
- close_all_sessions()
224
- with self.session_maker() as session:
225
- self.db_model.__table__.drop(session.bind)
226
- session.commit()
227
-
228
- def delete(self, filters: Optional[Dict] = {}):
229
- filters = self.get_filters(filters)
230
- with self.session_maker() as session:
231
- session.query(self.db_model).filter(*filters).delete()
232
- session.commit()
233
-
234
-
235
- class PostgresStorageConnector(SQLStorageConnector):
236
- """Storage via Postgres"""
237
-
238
- # TODO: this should probably eventually be moved into a parent DB class
239
-
240
- def __init__(self, table_type: str, config: LettaConfig, user_id, agent_id=None):
241
- from pgvector.sqlalchemy import Vector
242
-
243
- super().__init__(table_type=table_type, config=config, user_id=user_id, agent_id=agent_id)
244
-
245
- # construct URI from enviornment variables
246
- if settings.pg_uri:
247
- self.uri = settings.pg_uri
248
-
249
- # use config URI
250
- # TODO: remove this eventually (config should NOT contain URI)
251
- if table_type == TableType.ARCHIVAL_MEMORY or table_type == TableType.PASSAGES:
252
- self.uri = self.config.archival_storage_uri
253
- self.db_model = PassageModel
254
- if self.config.archival_storage_uri is None:
255
- raise ValueError(f"Must specify archival_storage_uri in config {self.config.config_path}")
256
- elif table_type == TableType.FILES:
257
- self.uri = self.config.metadata_storage_uri
258
- self.db_model = FileMetadataModel
259
- if self.config.metadata_storage_uri is None:
260
- raise ValueError(f"Must specify metadata_storage_uri in config {self.config.config_path}")
261
- else:
262
- raise ValueError(f"Table type {table_type} not implemented")
263
-
264
- if settings.pg_uri:
265
- for c in self.db_model.__table__.columns:
266
- if c.name == "embedding":
267
- assert isinstance(c.type, Vector), f"Embedding column must be of type Vector, got {c.type}"
268
-
269
- from letta.server.server import db_context
270
-
271
- self.session_maker = db_context
272
-
273
- # TODO: move to DB init
274
- if settings.pg_uri:
275
- with self.session_maker() as session:
276
- session.execute(text("CREATE EXTENSION IF NOT EXISTS vector")) # Enables the vector extension
277
-
278
- def query(self, query: str, query_vec: List[float], top_k: int = 10, filters: Optional[Dict] = {}):
279
- filters = self.get_filters(filters)
280
- with self.session_maker() as session:
281
- results = session.scalars(
282
- select(self.db_model).filter(*filters).order_by(self.db_model.embedding.l2_distance(query_vec)).limit(top_k)
283
- ).all()
284
-
285
- # Convert the results into Passage objects
286
- records = [result.to_record() for result in results]
287
- return records
288
-
289
- def insert_many(self, records, exists_ok=True, show_progress=False):
290
- # TODO: this is terrible, should eventually be done the same way for all types (migrate to SQLModel)
291
- if len(records) == 0:
292
- return
293
-
294
- added_ids = [] # avoid adding duplicates
295
- # NOTE: this has not great performance due to the excessive commits
296
- with self.session_maker() as session:
297
- iterable = tqdm(records) if show_progress else records
298
- for record in iterable:
299
- # db_record = self.db_model(**vars(record))
300
-
301
- if record.id in added_ids:
302
- continue
303
-
304
- existing_record = session.query(self.db_model).filter_by(id=record.id).first()
305
- if existing_record:
306
- if exists_ok:
307
- fields = record.model_dump()
308
- fields.pop("id")
309
- session.query(self.db_model).filter(self.db_model.id == record.id).update(fields)
310
- print(f"Updated record with id {record.id}")
311
- session.commit()
312
- else:
313
- raise ValueError(f"Record with id {record.id} already exists.")
314
-
315
- else:
316
- db_record = self.db_model(**record.dict())
317
- session.add(db_record)
318
- # print(f"Added record with id {record.id}")
319
- session.commit()
320
-
321
- added_ids.append(record.id)
322
-
323
- def insert(self, record, exists_ok=True):
324
- self.insert_many([record], exists_ok=exists_ok)
325
-
326
- def update(self, record):
327
- """
328
- Updates a record in the database based on the provided Record object.
329
- """
330
- with self.session_maker() as session:
331
- # Find the record by its ID
332
- db_record = session.query(self.db_model).filter_by(id=record.id).first()
333
- if not db_record:
334
- raise ValueError(f"Record with id {record.id} does not exist.")
335
-
336
- # Update the record with new values from the provided Record object
337
- for attr, value in vars(record).items():
338
- setattr(db_record, attr, value)
339
-
340
- # Commit the changes to the database
341
- session.commit()
342
-
343
- def str_to_datetime(self, str_date: str) -> datetime:
344
- val = str_date.split("-")
345
- _datetime = datetime(int(val[0]), int(val[1]), int(val[2]))
346
- return _datetime
347
-
348
- def query_date(self, start_date, end_date, limit=None, offset=0):
349
- filters = self.get_filters({})
350
- _start_date = self.str_to_datetime(start_date) if isinstance(start_date, str) else start_date
351
- _end_date = self.str_to_datetime(end_date) if isinstance(end_date, str) else end_date
352
- with self.session_maker() as session:
353
- query = (
354
- session.query(self.db_model)
355
- .filter(*filters)
356
- .filter(self.db_model.created_at >= _start_date)
357
- .filter(self.db_model.created_at <= _end_date)
358
- .filter(self.db_model.role != "system")
359
- .filter(self.db_model.role != "tool")
360
- .offset(offset)
361
- )
362
- if limit:
363
- query = query.limit(limit)
364
- results = query.all()
365
- return [result.to_record() for result in results]
366
-
367
-
368
- class SQLLiteStorageConnector(SQLStorageConnector):
369
- def __init__(self, table_type: str, config: LettaConfig, user_id, agent_id=None):
370
- super().__init__(table_type=table_type, config=config, user_id=user_id, agent_id=agent_id)
371
-
372
- # get storage URI
373
- if table_type == TableType.ARCHIVAL_MEMORY or table_type == TableType.PASSAGES:
374
- self.db_model = PassageModel
375
- if settings.letta_pg_uri_no_default:
376
- self.uri = settings.letta_pg_uri_no_default
377
- else:
378
- # For SQLite, use the archival storage path
379
- self.path = config.archival_storage_path
380
- self.uri = f"sqlite:///{os.path.join(config.archival_storage_path, 'letta.db')}"
381
- elif table_type == TableType.FILES:
382
- self.path = self.config.metadata_storage_path
383
- if self.path is None:
384
- raise ValueError(f"Must specify metadata_storage_path in config.")
385
- self.db_model = FileMetadataModel
386
-
387
- else:
388
- raise ValueError(f"Table type {table_type} not implemented")
389
-
390
- self.path = os.path.join(self.path, f"sqlite.db")
391
-
392
- from letta.server.server import db_context
393
-
394
- self.session_maker = db_context
395
-
396
- # Need this in order to allow UUIDs to be stored successfully in the sqlite database
397
- # import sqlite3
398
- # import uuid
399
- #
400
- # sqlite3.register_adapter(uuid.UUID, lambda u: u.bytes_le)
401
- # sqlite3.register_converter("UUID", lambda b: uuid.UUID(bytes_le=b))
402
-
403
- def insert_many(self, records, exists_ok=True, show_progress=False):
404
- # TODO: this is terrible, should eventually be done the same way for all types (migrate to SQLModel)
405
- if len(records) == 0:
406
- return
407
-
408
- added_ids = [] # avoid adding duplicates
409
- # NOTE: this has not great performance due to the excessive commits
410
- with self.session_maker() as session:
411
- iterable = tqdm(records) if show_progress else records
412
- for record in iterable:
413
- # db_record = self.db_model(**vars(record))
414
-
415
- if record.id in added_ids:
416
- continue
417
-
418
- existing_record = session.query(self.db_model).filter_by(id=record.id).first()
419
- if existing_record:
420
- if exists_ok:
421
- fields = record.model_dump()
422
- fields.pop("id")
423
- session.query(self.db_model).filter(self.db_model.id == record.id).update(fields)
424
- session.commit()
425
- else:
426
- raise ValueError(f"Record with id {record.id} already exists.")
427
-
428
- else:
429
- db_record = self.db_model(**record.dict())
430
- session.add(db_record)
431
- session.commit()
432
-
433
- added_ids.append(record.id)
434
-
435
- def insert(self, record, exists_ok=True):
436
- self.insert_many([record], exists_ok=exists_ok)
437
-
438
- def update(self, record):
439
- """
440
- Updates an existing record in the database with values from the provided record object.
441
- """
442
- if not record.id:
443
- raise ValueError("Record must have an id.")
444
-
445
- with self.session_maker() as session:
446
- # Fetch the existing record from the database
447
- db_record = session.query(self.db_model).filter_by(id=record.id).first()
448
- if not db_record:
449
- raise ValueError(f"Record with id {record.id} does not exist.")
450
-
451
- # Update the database record with values from the provided record object
452
- for column in self.db_model.__table__.columns:
453
- column_name = column.name
454
- if hasattr(record, column_name):
455
- new_value = getattr(record, column_name)
456
- setattr(db_record, column_name, new_value)
457
-
458
- # Commit the changes to the database
459
- session.commit()
460
-
461
-
462
- def attach_base():
463
- # This should be invoked in server.py to make sure Base gets initialized properly
464
- # DO NOT REMOVE
465
- from letta.utils import printd
466
-
467
- printd("Initializing database...")