letta-nightly 0.6.3.dev20241211104238__py3-none-any.whl → 0.6.3.dev20241212104231__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.
- letta/agent.py +0 -1
- letta/data_sources/connectors.py +4 -75
- letta/memory.py +0 -277
- letta/server/server.py +13 -12
- letta/settings.py +3 -3
- {letta_nightly-0.6.3.dev20241211104238.dist-info → letta_nightly-0.6.3.dev20241212104231.dist-info}/METADATA +26 -14
- {letta_nightly-0.6.3.dev20241211104238.dist-info → letta_nightly-0.6.3.dev20241212104231.dist-info}/RECORD +10 -14
- letta/agent_store/db.py +0 -467
- letta/agent_store/milvus.py +0 -198
- letta/agent_store/qdrant.py +0 -201
- letta/agent_store/storage.py +0 -186
- {letta_nightly-0.6.3.dev20241211104238.dist-info → letta_nightly-0.6.3.dev20241212104231.dist-info}/LICENSE +0 -0
- {letta_nightly-0.6.3.dev20241211104238.dist-info → letta_nightly-0.6.3.dev20241212104231.dist-info}/WHEEL +0 -0
- {letta_nightly-0.6.3.dev20241211104238.dist-info → letta_nightly-0.6.3.dev20241212104231.dist-info}/entry_points.txt +0 -0
letta/agent.py
CHANGED
letta/data_sources/connectors.py
CHANGED
|
@@ -2,7 +2,6 @@ from typing import Dict, Iterator, List, Tuple, Optional
|
|
|
2
2
|
|
|
3
3
|
import typer
|
|
4
4
|
|
|
5
|
-
from letta.agent_store.storage import StorageConnector
|
|
6
5
|
from letta.data_sources.connectors_helper import (
|
|
7
6
|
assert_all_files_exist_locally,
|
|
8
7
|
extract_metadata_from_files,
|
|
@@ -12,6 +11,7 @@ from letta.embeddings import embedding_model
|
|
|
12
11
|
from letta.schemas.file import FileMetadata
|
|
13
12
|
from letta.schemas.passage import Passage
|
|
14
13
|
from letta.schemas.source import Source
|
|
14
|
+
from letta.services.passage_manager import PassageManager
|
|
15
15
|
from letta.services.source_manager import SourceManager
|
|
16
16
|
from letta.utils import create_uuid_from_string
|
|
17
17
|
|
|
@@ -42,7 +42,7 @@ class DataConnector:
|
|
|
42
42
|
"""
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
def load_data(connector: DataConnector, source: Source,
|
|
45
|
+
def load_data(connector: DataConnector, source: Source, passage_manager: PassageManager, source_manager: SourceManager, actor: "User", agent_id: Optional[str] = None):
|
|
46
46
|
"""Load data from a connector (generates file and passages) into a specified source_id, associated with a user_id."""
|
|
47
47
|
embedding_config = source.embedding_config
|
|
48
48
|
|
|
@@ -103,14 +103,14 @@ def load_data(connector: DataConnector, source: Source, passage_store: StorageCo
|
|
|
103
103
|
embedding_to_document_name[hashable_embedding] = file_name
|
|
104
104
|
if len(passages) >= 100:
|
|
105
105
|
# insert passages into passage store
|
|
106
|
-
|
|
106
|
+
passage_manager.create_many_passages(passages, actor)
|
|
107
107
|
|
|
108
108
|
passage_count += len(passages)
|
|
109
109
|
passages = []
|
|
110
110
|
|
|
111
111
|
if len(passages) > 0:
|
|
112
112
|
# insert passages into passage store
|
|
113
|
-
|
|
113
|
+
passage_manager.create_many_passages(passages, actor)
|
|
114
114
|
passage_count += len(passages)
|
|
115
115
|
|
|
116
116
|
return passage_count, file_count
|
|
@@ -170,74 +170,3 @@ class DirectoryConnector(DataConnector):
|
|
|
170
170
|
nodes = parser.get_nodes_from_documents(documents)
|
|
171
171
|
for node in nodes:
|
|
172
172
|
yield node.text, None
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
"""
|
|
176
|
-
The below isn't used anywhere, it isn't tested, and pretty much should be deleted.
|
|
177
|
-
- Matt
|
|
178
|
-
"""
|
|
179
|
-
# class WebConnector(DirectoryConnector):
|
|
180
|
-
# def __init__(self, urls: List[str] = None, html_to_text: bool = True):
|
|
181
|
-
# self.urls = urls
|
|
182
|
-
# self.html_to_text = html_to_text
|
|
183
|
-
#
|
|
184
|
-
# def generate_files(self) -> Iterator[Tuple[str, Dict]]: # -> Iterator[Document]:
|
|
185
|
-
# from llama_index.readers.web import SimpleWebPageReader
|
|
186
|
-
#
|
|
187
|
-
# files = SimpleWebPageReader(html_to_text=self.html_to_text).load_data(self.urls)
|
|
188
|
-
# for document in files:
|
|
189
|
-
# yield document.text, {"url": document.id_}
|
|
190
|
-
#
|
|
191
|
-
#
|
|
192
|
-
# class VectorDBConnector(DataConnector):
|
|
193
|
-
# # NOTE: this class has not been properly tested, so is unlikely to work
|
|
194
|
-
# # TODO: allow loading multiple tables (1:1 mapping between FileMetadata and Table)
|
|
195
|
-
#
|
|
196
|
-
# def __init__(
|
|
197
|
-
# self,
|
|
198
|
-
# name: str,
|
|
199
|
-
# uri: str,
|
|
200
|
-
# table_name: str,
|
|
201
|
-
# text_column: str,
|
|
202
|
-
# embedding_column: str,
|
|
203
|
-
# embedding_dim: int,
|
|
204
|
-
# ):
|
|
205
|
-
# self.name = name
|
|
206
|
-
# self.uri = uri
|
|
207
|
-
# self.table_name = table_name
|
|
208
|
-
# self.text_column = text_column
|
|
209
|
-
# self.embedding_column = embedding_column
|
|
210
|
-
# self.embedding_dim = embedding_dim
|
|
211
|
-
#
|
|
212
|
-
# # connect to db table
|
|
213
|
-
# from sqlalchemy import create_engine
|
|
214
|
-
#
|
|
215
|
-
# self.engine = create_engine(uri)
|
|
216
|
-
#
|
|
217
|
-
# def generate_files(self) -> Iterator[Tuple[str, Dict]]: # -> Iterator[Document]:
|
|
218
|
-
# yield self.table_name, None
|
|
219
|
-
#
|
|
220
|
-
# def generate_passages(self, file_text: str, file: FileMetadata, chunk_size: int = 1024) -> Iterator[Tuple[str, Dict]]: # -> Iterator[Passage]:
|
|
221
|
-
# from pgvector.sqlalchemy import Vector
|
|
222
|
-
# from sqlalchemy import Inspector, MetaData, Table, select
|
|
223
|
-
#
|
|
224
|
-
# metadata = MetaData()
|
|
225
|
-
# # Create an inspector to inspect the database
|
|
226
|
-
# inspector = Inspector.from_engine(self.engine)
|
|
227
|
-
# table_names = inspector.get_table_names()
|
|
228
|
-
# assert self.table_name in table_names, f"Table {self.table_name} not found in database: tables that exist {table_names}."
|
|
229
|
-
#
|
|
230
|
-
# table = Table(self.table_name, metadata, autoload_with=self.engine)
|
|
231
|
-
#
|
|
232
|
-
# # Prepare a select statement
|
|
233
|
-
# select_statement = select(table.c[self.text_column], table.c[self.embedding_column].cast(Vector(self.embedding_dim)))
|
|
234
|
-
#
|
|
235
|
-
# # Execute the query and fetch the results
|
|
236
|
-
# # TODO: paginate results
|
|
237
|
-
# with self.engine.connect() as connection:
|
|
238
|
-
# result = connection.execute(select_statement).fetchall()
|
|
239
|
-
#
|
|
240
|
-
# for text, embedding in result:
|
|
241
|
-
# # assume that embeddings are the same model as in config
|
|
242
|
-
# # TODO: don't re-compute embedding
|
|
243
|
-
# yield text, {"embedding": embedding}
|
letta/memory.py
CHANGED
|
@@ -87,280 +87,3 @@ def summarize_messages(
|
|
|
87
87
|
printd(f"summarize_messages gpt reply: {response.choices[0]}")
|
|
88
88
|
reply = response.choices[0].message.content
|
|
89
89
|
return reply
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
class ArchivalMemory(ABC):
|
|
93
|
-
@abstractmethod
|
|
94
|
-
def insert(self, memory_string: str):
|
|
95
|
-
"""Insert new archival memory
|
|
96
|
-
|
|
97
|
-
:param memory_string: Memory string to insert
|
|
98
|
-
:type memory_string: str
|
|
99
|
-
"""
|
|
100
|
-
|
|
101
|
-
@abstractmethod
|
|
102
|
-
def search(self, query_string, count=None, start=None) -> Tuple[List[str], int]:
|
|
103
|
-
"""Search archival memory
|
|
104
|
-
|
|
105
|
-
:param query_string: Query string
|
|
106
|
-
:type query_string: str
|
|
107
|
-
:param count: Number of results to return (None for all)
|
|
108
|
-
:type count: Optional[int]
|
|
109
|
-
:param start: Offset to start returning results from (None if 0)
|
|
110
|
-
:type start: Optional[int]
|
|
111
|
-
|
|
112
|
-
:return: Tuple of (list of results, total number of results)
|
|
113
|
-
"""
|
|
114
|
-
|
|
115
|
-
@abstractmethod
|
|
116
|
-
def compile(self) -> str:
|
|
117
|
-
"""Convert archival memory into a string representation for a prompt"""
|
|
118
|
-
|
|
119
|
-
@abstractmethod
|
|
120
|
-
def count(self) -> int:
|
|
121
|
-
"""Count the number of memories in the archival memory"""
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
class RecallMemory(ABC):
|
|
125
|
-
@abstractmethod
|
|
126
|
-
def text_search(self, query_string, count=None, start=None):
|
|
127
|
-
"""Search messages that match query_string in recall memory"""
|
|
128
|
-
|
|
129
|
-
@abstractmethod
|
|
130
|
-
def date_search(self, start_date, end_date, count=None, start=None):
|
|
131
|
-
"""Search messages between start_date and end_date in recall memory"""
|
|
132
|
-
|
|
133
|
-
@abstractmethod
|
|
134
|
-
def compile(self) -> str:
|
|
135
|
-
"""Convert recall memory into a string representation for a prompt"""
|
|
136
|
-
|
|
137
|
-
@abstractmethod
|
|
138
|
-
def count(self) -> int:
|
|
139
|
-
"""Count the number of memories in the recall memory"""
|
|
140
|
-
|
|
141
|
-
@abstractmethod
|
|
142
|
-
def insert(self, message: Message):
|
|
143
|
-
"""Insert message into recall memory"""
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
class DummyRecallMemory(RecallMemory):
|
|
147
|
-
"""Dummy in-memory version of a recall memory database (eg run on MongoDB)
|
|
148
|
-
|
|
149
|
-
Recall memory here is basically just a full conversation history with the user.
|
|
150
|
-
Queryable via string matching, or date matching.
|
|
151
|
-
|
|
152
|
-
Recall Memory: The AI's capability to search through past interactions,
|
|
153
|
-
effectively allowing it to 'remember' prior engagements with a user.
|
|
154
|
-
"""
|
|
155
|
-
|
|
156
|
-
def __init__(self, message_database=None, restrict_search_to_summaries=False):
|
|
157
|
-
self._message_logs = [] if message_database is None else message_database # consists of full message dicts
|
|
158
|
-
|
|
159
|
-
# If true, the pool of messages that can be queried are the automated summaries only
|
|
160
|
-
# (generated when the conversation window needs to be shortened)
|
|
161
|
-
self.restrict_search_to_summaries = restrict_search_to_summaries
|
|
162
|
-
|
|
163
|
-
def __len__(self):
|
|
164
|
-
return len(self._message_logs)
|
|
165
|
-
|
|
166
|
-
def count(self) -> int:
|
|
167
|
-
return len(self)
|
|
168
|
-
|
|
169
|
-
def compile(self) -> str:
|
|
170
|
-
# don't dump all the conversations, just statistics
|
|
171
|
-
system_count = user_count = assistant_count = function_count = other_count = 0
|
|
172
|
-
for msg in self._message_logs:
|
|
173
|
-
role = msg["message"]["role"]
|
|
174
|
-
if role == "system":
|
|
175
|
-
system_count += 1
|
|
176
|
-
elif role == "user":
|
|
177
|
-
user_count += 1
|
|
178
|
-
elif role == "assistant":
|
|
179
|
-
assistant_count += 1
|
|
180
|
-
elif role == "function":
|
|
181
|
-
function_count += 1
|
|
182
|
-
else:
|
|
183
|
-
other_count += 1
|
|
184
|
-
memory_str = (
|
|
185
|
-
f"Statistics:"
|
|
186
|
-
+ f"\n{len(self._message_logs)} total messages"
|
|
187
|
-
+ f"\n{system_count} system"
|
|
188
|
-
+ f"\n{user_count} user"
|
|
189
|
-
+ f"\n{assistant_count} assistant"
|
|
190
|
-
+ f"\n{function_count} function"
|
|
191
|
-
+ f"\n{other_count} other"
|
|
192
|
-
)
|
|
193
|
-
return f"\n### RECALL MEMORY ###" + f"\n{memory_str}"
|
|
194
|
-
|
|
195
|
-
def insert(self, message):
|
|
196
|
-
raise NotImplementedError("This should be handled by the PersistenceManager, recall memory is just a search layer on top")
|
|
197
|
-
|
|
198
|
-
def text_search(self, query_string, count=None, start=None):
|
|
199
|
-
# in the dummy version, run an (inefficient) case-insensitive match search
|
|
200
|
-
message_pool = [d for d in self._message_logs if d["message"]["role"] not in ["system", "function"]]
|
|
201
|
-
start = 0 if start is None else int(start)
|
|
202
|
-
count = 0 if count is None else int(count)
|
|
203
|
-
|
|
204
|
-
printd(
|
|
205
|
-
f"recall_memory.text_search: searching for {query_string} (c={count}, s={start}) in {len(self._message_logs)} total messages"
|
|
206
|
-
)
|
|
207
|
-
matches = [
|
|
208
|
-
d for d in message_pool if d["message"]["content"] is not None and query_string.lower() in d["message"]["content"].lower()
|
|
209
|
-
]
|
|
210
|
-
printd(f"recall_memory - matches:\n{matches[start:start+count]}")
|
|
211
|
-
|
|
212
|
-
# start/count support paging through results
|
|
213
|
-
if start is not None and count is not None:
|
|
214
|
-
return matches[start : start + count], len(matches)
|
|
215
|
-
elif start is None and count is not None:
|
|
216
|
-
return matches[:count], len(matches)
|
|
217
|
-
elif start is not None and count is None:
|
|
218
|
-
return matches[start:], len(matches)
|
|
219
|
-
else:
|
|
220
|
-
return matches, len(matches)
|
|
221
|
-
|
|
222
|
-
def date_search(self, start_date, end_date, count=None, start=None):
|
|
223
|
-
message_pool = [d for d in self._message_logs if d["message"]["role"] not in ["system", "function"]]
|
|
224
|
-
|
|
225
|
-
# First, validate the start_date and end_date format
|
|
226
|
-
if not validate_date_format(start_date) or not validate_date_format(end_date):
|
|
227
|
-
raise ValueError("Invalid date format. Expected format: YYYY-MM-DD")
|
|
228
|
-
|
|
229
|
-
# Convert dates to datetime objects for comparison
|
|
230
|
-
start_date_dt = datetime.datetime.strptime(start_date, "%Y-%m-%d")
|
|
231
|
-
end_date_dt = datetime.datetime.strptime(end_date, "%Y-%m-%d")
|
|
232
|
-
|
|
233
|
-
# Next, match items inside self._message_logs
|
|
234
|
-
matches = [
|
|
235
|
-
d
|
|
236
|
-
for d in message_pool
|
|
237
|
-
if start_date_dt <= datetime.datetime.strptime(extract_date_from_timestamp(d["timestamp"]), "%Y-%m-%d") <= end_date_dt
|
|
238
|
-
]
|
|
239
|
-
|
|
240
|
-
# start/count support paging through results
|
|
241
|
-
start = 0 if start is None else int(start)
|
|
242
|
-
count = 0 if count is None else int(count)
|
|
243
|
-
if start is not None and count is not None:
|
|
244
|
-
return matches[start : start + count], len(matches)
|
|
245
|
-
elif start is None and count is not None:
|
|
246
|
-
return matches[:count], len(matches)
|
|
247
|
-
elif start is not None and count is None:
|
|
248
|
-
return matches[start:], len(matches)
|
|
249
|
-
else:
|
|
250
|
-
return matches, len(matches)
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
class EmbeddingArchivalMemory(ArchivalMemory):
|
|
254
|
-
"""Archival memory with embedding based search"""
|
|
255
|
-
|
|
256
|
-
def __init__(self, agent_state: AgentState, top_k: int = 100):
|
|
257
|
-
"""Init function for archival memory
|
|
258
|
-
|
|
259
|
-
:param archival_memory_database: name of dataset to pre-fill archival with
|
|
260
|
-
:type archival_memory_database: str
|
|
261
|
-
"""
|
|
262
|
-
from letta.agent_store.storage import StorageConnector
|
|
263
|
-
|
|
264
|
-
self.top_k = top_k
|
|
265
|
-
self.agent_state = agent_state
|
|
266
|
-
|
|
267
|
-
# create embedding model
|
|
268
|
-
self.embed_model = embedding_model(agent_state.embedding_config)
|
|
269
|
-
if agent_state.embedding_config.embedding_chunk_size is None:
|
|
270
|
-
raise ValueError(f"Must set {agent_state.embedding_config.embedding_chunk_size}")
|
|
271
|
-
else:
|
|
272
|
-
self.embedding_chunk_size = agent_state.embedding_config.embedding_chunk_size
|
|
273
|
-
|
|
274
|
-
# create storage backend
|
|
275
|
-
self.storage = StorageConnector.get_archival_storage_connector(user_id=agent_state.user_id, agent_id=agent_state.id)
|
|
276
|
-
# TODO: have some mechanism for cleanup otherwise will lead to OOM
|
|
277
|
-
self.cache = {}
|
|
278
|
-
|
|
279
|
-
def create_passage(self, text, embedding):
|
|
280
|
-
return Passage(
|
|
281
|
-
user_id=self.agent_state.user_id,
|
|
282
|
-
agent_id=self.agent_state.id,
|
|
283
|
-
text=text,
|
|
284
|
-
embedding=embedding,
|
|
285
|
-
embedding_config=self.agent_state.embedding_config,
|
|
286
|
-
)
|
|
287
|
-
|
|
288
|
-
def save(self):
|
|
289
|
-
"""Save the index to disk"""
|
|
290
|
-
self.storage.save()
|
|
291
|
-
|
|
292
|
-
def insert(self, memory_string, return_ids=False) -> Union[bool, List[str]]:
|
|
293
|
-
"""Embed and save memory string"""
|
|
294
|
-
|
|
295
|
-
if not isinstance(memory_string, str):
|
|
296
|
-
raise TypeError("memory must be a string")
|
|
297
|
-
|
|
298
|
-
try:
|
|
299
|
-
passages = []
|
|
300
|
-
|
|
301
|
-
# breakup string into passages
|
|
302
|
-
for text in parse_and_chunk_text(memory_string, self.embedding_chunk_size):
|
|
303
|
-
embedding = self.embed_model.get_text_embedding(text)
|
|
304
|
-
# fixing weird bug where type returned isn't a list, but instead is an object
|
|
305
|
-
# eg: embedding={'object': 'list', 'data': [{'object': 'embedding', 'embedding': [-0.0071973633, -0.07893023,
|
|
306
|
-
if isinstance(embedding, dict):
|
|
307
|
-
try:
|
|
308
|
-
embedding = embedding["data"][0]["embedding"]
|
|
309
|
-
except (KeyError, IndexError):
|
|
310
|
-
# TODO as a fallback, see if we can find any lists in the payload
|
|
311
|
-
raise TypeError(
|
|
312
|
-
f"Got back an unexpected payload from text embedding function, type={type(embedding)}, value={embedding}"
|
|
313
|
-
)
|
|
314
|
-
passages.append(self.create_passage(text, embedding))
|
|
315
|
-
|
|
316
|
-
# grab the return IDs before the list gets modified
|
|
317
|
-
ids = [str(p.id) for p in passages]
|
|
318
|
-
|
|
319
|
-
# insert passages
|
|
320
|
-
self.storage.insert_many(passages)
|
|
321
|
-
|
|
322
|
-
if return_ids:
|
|
323
|
-
return ids
|
|
324
|
-
else:
|
|
325
|
-
return True
|
|
326
|
-
|
|
327
|
-
except Exception as e:
|
|
328
|
-
print("Archival insert error", e)
|
|
329
|
-
raise e
|
|
330
|
-
|
|
331
|
-
def search(self, query_string, count=None, start=None):
|
|
332
|
-
"""Search query string"""
|
|
333
|
-
start = 0 if start is None else int(start)
|
|
334
|
-
count = self.top_k if count is None else int(count)
|
|
335
|
-
|
|
336
|
-
if not isinstance(query_string, str):
|
|
337
|
-
return TypeError("query must be a string")
|
|
338
|
-
|
|
339
|
-
try:
|
|
340
|
-
if query_string not in self.cache:
|
|
341
|
-
# self.cache[query_string] = self.retriever.retrieve(query_string)
|
|
342
|
-
query_vec = query_embedding(self.embed_model, query_string)
|
|
343
|
-
self.cache[query_string] = self.storage.query(query_string, query_vec, top_k=self.top_k)
|
|
344
|
-
|
|
345
|
-
end = min(count + start, len(self.cache[query_string]))
|
|
346
|
-
|
|
347
|
-
results = self.cache[query_string][start:end]
|
|
348
|
-
results = [{"timestamp": get_local_time(), "content": node.text} for node in results]
|
|
349
|
-
return results, len(results)
|
|
350
|
-
except Exception as e:
|
|
351
|
-
print("Archival search error", e)
|
|
352
|
-
raise e
|
|
353
|
-
|
|
354
|
-
def compile(self) -> str:
|
|
355
|
-
limit = 10
|
|
356
|
-
passages = []
|
|
357
|
-
for passage in list(self.storage.get_all(limit=limit)): # TODO: only get first 10
|
|
358
|
-
passages.append(str(passage.text))
|
|
359
|
-
memory_str = "\n".join(passages)
|
|
360
|
-
return f"\n### ARCHIVAL MEMORY ###" + f"\n{memory_str}" + f"\nSize: {self.storage.size()}"
|
|
361
|
-
|
|
362
|
-
def __len__(self):
|
|
363
|
-
return self.storage.size()
|
|
364
|
-
|
|
365
|
-
def count(self) -> int:
|
|
366
|
-
return len(self)
|
letta/server/server.py
CHANGED
|
@@ -16,7 +16,6 @@ import letta.constants as constants
|
|
|
16
16
|
import letta.server.utils as server_utils
|
|
17
17
|
import letta.system as system
|
|
18
18
|
from letta.agent import Agent, save_agent
|
|
19
|
-
from letta.agent_store.storage import StorageConnector, TableType
|
|
20
19
|
from letta.chat_only_agent import ChatOnlyAgent
|
|
21
20
|
from letta.credentials import LettaCredentials
|
|
22
21
|
from letta.data_sources.connectors import DataConnector, load_data
|
|
@@ -558,7 +557,6 @@ class SyncServer(Server):
|
|
|
558
557
|
raise ValueError(command)
|
|
559
558
|
|
|
560
559
|
# attach data to agent from source
|
|
561
|
-
source_connector = StorageConnector.get_storage_connector(TableType.PASSAGES, self.config, user_id=user_id)
|
|
562
560
|
letta_agent.attach_source(
|
|
563
561
|
user=self.user_manager.get_user_by_id(user_id=user_id),
|
|
564
562
|
source_id=data_source,
|
|
@@ -1571,8 +1569,7 @@ class SyncServer(Server):
|
|
|
1571
1569
|
self.source_manager.delete_source(source_id=source_id, actor=actor)
|
|
1572
1570
|
|
|
1573
1571
|
# delete data from passage store
|
|
1574
|
-
|
|
1575
|
-
passage_store.delete({"source_id": source_id})
|
|
1572
|
+
self.passage_manager.delete_passages(actor=actor, limit=None, source_id=source_id)
|
|
1576
1573
|
|
|
1577
1574
|
# TODO: delete data from agent passage stores (?)
|
|
1578
1575
|
|
|
@@ -1614,11 +1611,10 @@ class SyncServer(Server):
|
|
|
1614
1611
|
if source is None:
|
|
1615
1612
|
raise ValueError(f"Data source {source_name} does not exist for user {user_id}")
|
|
1616
1613
|
|
|
1617
|
-
# get the data connectors
|
|
1618
|
-
passage_store = StorageConnector.get_storage_connector(TableType.PASSAGES, self.config, user_id=user_id)
|
|
1619
|
-
|
|
1620
1614
|
# load data into the document store
|
|
1621
|
-
passage_count, document_count = load_data(
|
|
1615
|
+
passage_count, document_count = load_data(
|
|
1616
|
+
connector, source, self.passage_manager, self.source_manager, actor=user, agent_id=agent_id
|
|
1617
|
+
)
|
|
1622
1618
|
return passage_count, document_count
|
|
1623
1619
|
|
|
1624
1620
|
def attach_source_to_agent(
|
|
@@ -1694,8 +1690,7 @@ class SyncServer(Server):
|
|
|
1694
1690
|
for source in sources:
|
|
1695
1691
|
|
|
1696
1692
|
# count number of passages
|
|
1697
|
-
|
|
1698
|
-
num_passages = passage_conn.size({"source_id": source.id})
|
|
1693
|
+
num_passages = self.passage_manager.size(actor=actor, source_id=source.id)
|
|
1699
1694
|
|
|
1700
1695
|
# TODO: add when files table implemented
|
|
1701
1696
|
## count number of files
|
|
@@ -1806,14 +1801,20 @@ class SyncServer(Server):
|
|
|
1806
1801
|
|
|
1807
1802
|
llm_models = []
|
|
1808
1803
|
for provider in self._enabled_providers:
|
|
1809
|
-
|
|
1804
|
+
try:
|
|
1805
|
+
llm_models.extend(provider.list_llm_models())
|
|
1806
|
+
except Exception as e:
|
|
1807
|
+
warnings.warn(f"An error occurred while listing LLM models for provider {provider}: {e}")
|
|
1810
1808
|
return llm_models
|
|
1811
1809
|
|
|
1812
1810
|
def list_embedding_models(self) -> List[EmbeddingConfig]:
|
|
1813
1811
|
"""List available embedding models"""
|
|
1814
1812
|
embedding_models = []
|
|
1815
1813
|
for provider in self._enabled_providers:
|
|
1816
|
-
|
|
1814
|
+
try:
|
|
1815
|
+
embedding_models.extend(provider.list_embedding_models())
|
|
1816
|
+
except Exception as e:
|
|
1817
|
+
warnings.warn(f"An error occurred while listing embedding models for provider {provider}: {e}")
|
|
1817
1818
|
return embedding_models
|
|
1818
1819
|
|
|
1819
1820
|
def add_llm_model(self, request: LLMConfig) -> LLMConfig:
|
letta/settings.py
CHANGED
|
@@ -17,7 +17,7 @@ class ToolSettings(BaseSettings):
|
|
|
17
17
|
|
|
18
18
|
class ModelSettings(BaseSettings):
|
|
19
19
|
|
|
20
|
-
model_config = SettingsConfigDict(env_file='.env')
|
|
20
|
+
model_config = SettingsConfigDict(env_file='.env', extra='ignore')
|
|
21
21
|
|
|
22
22
|
# env_prefix='my_prefix_'
|
|
23
23
|
|
|
@@ -64,7 +64,7 @@ cors_origins = ["http://letta.localhost", "http://localhost:8283", "http://local
|
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
class Settings(BaseSettings):
|
|
67
|
-
model_config = SettingsConfigDict(env_prefix="letta_")
|
|
67
|
+
model_config = SettingsConfigDict(env_prefix="letta_", extra='ignore')
|
|
68
68
|
|
|
69
69
|
letta_dir: Optional[Path] = Field(Path.home() / ".letta", env="LETTA_DIR")
|
|
70
70
|
debug: Optional[bool] = False
|
|
@@ -103,7 +103,7 @@ class Settings(BaseSettings):
|
|
|
103
103
|
|
|
104
104
|
|
|
105
105
|
class TestSettings(Settings):
|
|
106
|
-
model_config = SettingsConfigDict(env_prefix="letta_test_")
|
|
106
|
+
model_config = SettingsConfigDict(env_prefix="letta_test_", extra='ignore')
|
|
107
107
|
|
|
108
108
|
letta_dir: Optional[Path] = Field(Path.home() / ".letta/test", env="LETTA_TEST_DIR")
|
|
109
109
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: letta-nightly
|
|
3
|
-
Version: 0.6.3.
|
|
3
|
+
Version: 0.6.3.dev20241212104231
|
|
4
4
|
Summary: Create LLM agents with long-term memory and custom tools
|
|
5
5
|
License: Apache License
|
|
6
6
|
Author: Letta Team
|
|
@@ -82,9 +82,9 @@ Description-Content-Type: text/markdown
|
|
|
82
82
|
|
|
83
83
|
<p align="center">
|
|
84
84
|
<picture>
|
|
85
|
-
<source media="(prefers-color-scheme: dark)" srcset="assets/Letta-logo-RGB_GreyonTransparent_cropped_small.png">
|
|
86
|
-
<source media="(prefers-color-scheme: light)" srcset="assets/Letta-logo-RGB_OffBlackonTransparent_cropped_small.png">
|
|
87
|
-
<img alt="Letta logo" src="assets/Letta-logo-RGB_GreyonOffBlack_cropped_small.png" width="500">
|
|
85
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/letta-ai/letta/refs/heads/main/assets/Letta-logo-RGB_GreyonTransparent_cropped_small.png">
|
|
86
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/letta-ai/letta/refs/heads/main/assets/Letta-logo-RGB_OffBlackonTransparent_cropped_small.png">
|
|
87
|
+
<img alt="Letta logo" src="https://raw.githubusercontent.com/letta-ai/letta/refs/heads/main/assets/Letta-logo-RGB_GreyonOffBlack_cropped_small.png" width="500">
|
|
88
88
|
</picture>
|
|
89
89
|
</p>
|
|
90
90
|
|
|
@@ -95,9 +95,9 @@ Description-Content-Type: text/markdown
|
|
|
95
95
|
|
|
96
96
|
<p align="center">
|
|
97
97
|
<picture>
|
|
98
|
-
<source media="(prefers-color-scheme: dark)" srcset="assets/example_ade_screenshot.png">
|
|
99
|
-
<source media="(prefers-color-scheme: light)" srcset="assets/example_ade_screenshot_light.png">
|
|
100
|
-
<img alt="Letta logo" src="assets/example_ade_screenshot.png" width="800">
|
|
98
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/letta-ai/letta/refs/heads/main/assets/example_ade_screenshot.png">
|
|
99
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/letta-ai/letta/refs/heads/main/assets/example_ade_screenshot_light.png">
|
|
100
|
+
<img alt="Letta logo" src="https://raw.githubusercontent.com/letta-ai/letta/refs/heads/main/assets/example_ade_screenshot.png" width="800">
|
|
101
101
|
</picture>
|
|
102
102
|
</p>
|
|
103
103
|
|
|
@@ -133,7 +133,7 @@ Description-Content-Type: text/markdown
|
|
|
133
133
|
|
|
134
134
|
## ⚡ Quickstart
|
|
135
135
|
|
|
136
|
-
_The recommended way to use Letta is to run use Docker. To install Docker, see [Docker's installation guide](https://docs.docker.com/get-docker/). For issues with installing Docker, see [Docker's troubleshooting guide](https://docs.docker.com/desktop/troubleshoot-and-support/troubleshoot/). You can also install Letta using `pip` (see
|
|
136
|
+
_The recommended way to use Letta is to run use Docker. To install Docker, see [Docker's installation guide](https://docs.docker.com/get-docker/). For issues with installing Docker, see [Docker's troubleshooting guide](https://docs.docker.com/desktop/troubleshoot-and-support/troubleshoot/). You can also install Letta using `pip` (see instructions [below](#-quickstart-pip))._
|
|
137
137
|
|
|
138
138
|
### 🌖 Run the Letta server
|
|
139
139
|
|
|
@@ -171,9 +171,9 @@ Once the Letta server is running, you can access it via port `8283` (e.g. sendin
|
|
|
171
171
|
|
|
172
172
|
<p align="center">
|
|
173
173
|
<picture>
|
|
174
|
-
<source media="(prefers-color-scheme: dark)" srcset="assets/example_ade_screenshot.png">
|
|
175
|
-
<source media="(prefers-color-scheme: light)" srcset="assets/example_ade_screenshot_light.png">
|
|
176
|
-
<img alt="
|
|
174
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/letta-ai/letta/refs/heads/main/assets/example_ade_screenshot.png">
|
|
175
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/letta-ai/letta/refs/heads/main/assets/example_ade_screenshot_light.png">
|
|
176
|
+
<img alt="ADE screenshot" src="https://raw.githubusercontent.com/letta-ai/letta/refs/heads/main/assets/example_ade_screenshot.png" width="800">
|
|
177
177
|
</picture>
|
|
178
178
|
</p>
|
|
179
179
|
|
|
@@ -186,9 +186,9 @@ To connect the ADE with your local Letta server, simply:
|
|
|
186
186
|
|
|
187
187
|
<p align="center">
|
|
188
188
|
<picture>
|
|
189
|
-
<source media="(prefers-color-scheme: dark)" srcset="assets/example_ade_screenshot_agents.png">
|
|
190
|
-
<source media="(prefers-color-scheme: light)" srcset="assets/example_ade_screenshot_agents_light.png">
|
|
191
|
-
<img alt="Letta logo" src="assets/example_ade_screenshot_agents.png" width="800">
|
|
189
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/letta-ai/letta/refs/heads/main/assets/example_ade_screenshot_agents.png">
|
|
190
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/letta-ai/letta/refs/heads/main/assets/example_ade_screenshot_agents_light.png">
|
|
191
|
+
<img alt="Letta logo" src="https://raw.githubusercontent.com/letta-ai/letta/refs/heads/main/assets/example_ade_screenshot_agents.png" width="800">
|
|
192
192
|
</picture>
|
|
193
193
|
</p>
|
|
194
194
|
|
|
@@ -217,6 +217,18 @@ If your Letta server isn't running on `localhost` (for example, you deployed it
|
|
|
217
217
|
|
|
218
218
|
No, you can install Letta using `pip` (via `pip install -U letta`), as well as from source (via `poetry install`). See instructions below.
|
|
219
219
|
|
|
220
|
+
> _"What's the difference between installing with `pip` vs `Docker`?"_
|
|
221
|
+
|
|
222
|
+
Letta gives your agents persistence (they live indefinitely) by storing all your agent data in a database. Letta is designed to be used with a [PostgreSQL](https://en.wikipedia.org/wiki/PostgreSQL) (the world's most popular database), however, it is not possible to install PostgreSQL via `pip`, so the `pip` install of Letta defaults to using [SQLite](https://www.sqlite.org/). If you have a PostgreSQL instance running on your own computer, you can still connect Letta (installed via `pip`) to PostgreSQL by setting the environment variable `LETTA_PG_URI`.
|
|
223
|
+
|
|
224
|
+
**Database migrations are not officially supported for Letta when using SQLite**, so you would like to ensure that if you're able to upgrade to the latest Letta version and migrate your Letta agents data, make sure that you're using PostgreSQL as your Letta database backend. Full compatability table below:
|
|
225
|
+
|
|
226
|
+
| Installation method | Start server command | Database backend | Data migrations supported? |
|
|
227
|
+
|---|---|---|---|
|
|
228
|
+
| `pip install letta` | `letta server` | SQLite | ❌ |
|
|
229
|
+
| `pip install letta` | `export LETTA_PG_URI=...` + `letta server` | PostgreSQL | ✅ |
|
|
230
|
+
| *[Install Docker](https://www.docker.com/get-started/)* |`docker run ...` ([full command](#-run-the-letta-server)) | PostgreSQL | ✅ |
|
|
231
|
+
|
|
220
232
|
> _"How do I use the ADE locally?"_
|
|
221
233
|
|
|
222
234
|
To connect the ADE to your local Letta server, simply run your Letta server (make sure you can access `localhost:8283`) and go to [https://app.letta.com](https://app.letta.com). If you would like to use the old version of the ADE (that runs on `localhost`), downgrade to Letta version `<=0.5.0`.
|
|
@@ -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=
|
|
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=
|
|
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=
|
|
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
|
|
@@ -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=
|
|
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=
|
|
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.
|
|
237
|
-
letta_nightly-0.6.3.
|
|
238
|
-
letta_nightly-0.6.3.
|
|
239
|
-
letta_nightly-0.6.3.
|
|
240
|
-
letta_nightly-0.6.3.
|
|
232
|
+
letta_nightly-0.6.3.dev20241212104231.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
|
|
233
|
+
letta_nightly-0.6.3.dev20241212104231.dist-info/METADATA,sha256=wbMkitQ5FROZGwS8bfy2ZlJpScQ4kaY88YGZKn1UfrQ,21689
|
|
234
|
+
letta_nightly-0.6.3.dev20241212104231.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
235
|
+
letta_nightly-0.6.3.dev20241212104231.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
|
|
236
|
+
letta_nightly-0.6.3.dev20241212104231.dist-info/RECORD,,
|