letta-nightly 0.6.3.dev20241211104238__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.

letta/agent.py CHANGED
@@ -8,7 +8,6 @@ from typing import List, Literal, Optional, Tuple, Union
8
8
 
9
9
  from tqdm import tqdm
10
10
 
11
- from letta.agent_store.storage import StorageConnector
12
11
  from letta.constants import (
13
12
  BASE_TOOLS,
14
13
  CLI_WARNING_PREFIX,
@@ -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, passage_store: StorageConnector, source_manager: SourceManager, actor: "User", agent_id: Optional[str] = None):
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
- passage_store.insert_many(passages)
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
- passage_store.insert_many(passages)
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
- passage_store = StorageConnector.get_storage_connector(TableType.PASSAGES, self.config, user_id=actor.id)
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(connector, source, passage_store, self.source_manager, actor=user, agent_id=agent_id)
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
- passage_conn = StorageConnector.get_storage_connector(TableType.PASSAGES, self.config, user_id=actor.id)
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
- llm_models.extend(provider.list_llm_models())
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
- embedding_models.extend(provider.list_embedding_models())
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.dev20241211104238
3
+ Version: 0.6.3.dev20241212015858
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 guide [below](#-quickstart-pip))._
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="Letta logo" src="assets/example_ade_screenshot.png" width="800">
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=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
@@ -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.dev20241211104238.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
237
- letta_nightly-0.6.3.dev20241211104238.dist-info/METADATA,sha256=OzyQk10br4NZoCh6A9oPl4_JKTjAt0P_Ws3-o56VDo0,19560
238
- letta_nightly-0.6.3.dev20241211104238.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
239
- letta_nightly-0.6.3.dev20241211104238.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
240
- letta_nightly-0.6.3.dev20241211104238.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,,