letta-nightly 0.6.2.dev20241210104242__py3-none-any.whl → 0.6.3.dev20241211050151__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/__init__.py +1 -1
- letta/agent.py +32 -43
- letta/agent_store/db.py +12 -54
- letta/agent_store/storage.py +10 -9
- letta/cli/cli.py +1 -0
- letta/client/client.py +3 -2
- letta/config.py +2 -2
- letta/data_sources/connectors.py +4 -3
- letta/embeddings.py +29 -9
- letta/functions/function_sets/base.py +36 -11
- letta/metadata.py +13 -2
- letta/o1_agent.py +2 -3
- letta/offline_memory_agent.py +2 -1
- letta/orm/__init__.py +1 -0
- letta/orm/file.py +1 -0
- letta/orm/mixins.py +12 -2
- letta/orm/organization.py +3 -0
- letta/orm/passage.py +72 -0
- letta/orm/sqlalchemy_base.py +36 -7
- letta/orm/sqlite_functions.py +140 -0
- letta/orm/user.py +1 -1
- letta/schemas/agent.py +4 -3
- letta/schemas/letta_message.py +5 -1
- letta/schemas/letta_request.py +3 -3
- letta/schemas/passage.py +6 -4
- letta/schemas/sandbox_config.py +1 -0
- letta/schemas/tool_rule.py +0 -3
- letta/server/rest_api/app.py +34 -12
- letta/server/rest_api/routers/v1/agents.py +19 -6
- letta/server/server.py +182 -43
- letta/server/static_files/assets/{index-4848e3d7.js → index-048c9598.js} +1 -1
- letta/server/static_files/assets/{index-43ab4d62.css → index-0e31b727.css} +1 -1
- letta/server/static_files/index.html +2 -2
- letta/services/passage_manager.py +225 -0
- letta/services/source_manager.py +2 -1
- letta/services/tool_execution_sandbox.py +18 -6
- letta/settings.py +2 -0
- letta_nightly-0.6.3.dev20241211050151.dist-info/METADATA +375 -0
- {letta_nightly-0.6.2.dev20241210104242.dist-info → letta_nightly-0.6.3.dev20241211050151.dist-info}/RECORD +42 -40
- letta/agent_store/chroma.py +0 -297
- letta_nightly-0.6.2.dev20241210104242.dist-info/METADATA +0 -212
- {letta_nightly-0.6.2.dev20241210104242.dist-info → letta_nightly-0.6.3.dev20241211050151.dist-info}/LICENSE +0 -0
- {letta_nightly-0.6.2.dev20241210104242.dist-info → letta_nightly-0.6.3.dev20241211050151.dist-info}/WHEEL +0 -0
- {letta_nightly-0.6.2.dev20241210104242.dist-info → letta_nightly-0.6.3.dev20241211050151.dist-info}/entry_points.txt +0 -0
letta/agent_store/chroma.py
DELETED
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
from typing import Dict, List, Optional, Tuple, cast
|
|
2
|
-
|
|
3
|
-
import chromadb
|
|
4
|
-
from chromadb.api.types import Include
|
|
5
|
-
|
|
6
|
-
from letta.agent_store.storage import StorageConnector, TableType
|
|
7
|
-
from letta.config import LettaConfig
|
|
8
|
-
from letta.schemas.embedding_config import EmbeddingConfig
|
|
9
|
-
from letta.schemas.passage import Passage
|
|
10
|
-
from letta.utils import datetime_to_timestamp, printd, timestamp_to_datetime
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class ChromaStorageConnector(StorageConnector):
|
|
14
|
-
"""Storage via Chroma"""
|
|
15
|
-
|
|
16
|
-
# WARNING: This is not thread safe. Do NOT do concurrent access to the same collection.
|
|
17
|
-
# Timestamps are converted to integer timestamps for chroma (datetime not supported)
|
|
18
|
-
|
|
19
|
-
def __init__(self, table_type: str, config: LettaConfig, user_id, agent_id=None):
|
|
20
|
-
super().__init__(table_type=table_type, config=config, user_id=user_id, agent_id=agent_id)
|
|
21
|
-
|
|
22
|
-
assert table_type == TableType.ARCHIVAL_MEMORY or table_type == TableType.PASSAGES, "Chroma only supports archival memory"
|
|
23
|
-
|
|
24
|
-
# create chroma client
|
|
25
|
-
if config.archival_storage_path:
|
|
26
|
-
self.client = chromadb.PersistentClient(config.archival_storage_path)
|
|
27
|
-
else:
|
|
28
|
-
# assume uri={ip}:{port}
|
|
29
|
-
ip = config.archival_storage_uri.split(":")[0]
|
|
30
|
-
port = config.archival_storage_uri.split(":")[1]
|
|
31
|
-
self.client = chromadb.HttpClient(host=ip, port=port)
|
|
32
|
-
|
|
33
|
-
# get a collection or create if it doesn't exist already
|
|
34
|
-
self.collection = self.client.get_or_create_collection(self.table_name)
|
|
35
|
-
self.include: Include = ["documents", "embeddings", "metadatas"]
|
|
36
|
-
|
|
37
|
-
def get_filters(self, filters: Optional[Dict] = {}) -> Tuple[list, dict]:
|
|
38
|
-
# get all filters for query
|
|
39
|
-
if filters is not None:
|
|
40
|
-
filter_conditions = {**self.filters, **filters}
|
|
41
|
-
else:
|
|
42
|
-
filter_conditions = self.filters
|
|
43
|
-
|
|
44
|
-
# convert to chroma format
|
|
45
|
-
chroma_filters = []
|
|
46
|
-
ids = []
|
|
47
|
-
for key, value in filter_conditions.items():
|
|
48
|
-
# filter by id
|
|
49
|
-
if key == "id":
|
|
50
|
-
ids = [str(value)]
|
|
51
|
-
continue
|
|
52
|
-
|
|
53
|
-
# filter by other keys
|
|
54
|
-
chroma_filters.append({key: {"$eq": value}})
|
|
55
|
-
|
|
56
|
-
if len(chroma_filters) > 1:
|
|
57
|
-
chroma_filters = {"$and": chroma_filters}
|
|
58
|
-
elif len(chroma_filters) == 0:
|
|
59
|
-
chroma_filters = {}
|
|
60
|
-
else:
|
|
61
|
-
chroma_filters = chroma_filters[0]
|
|
62
|
-
return ids, chroma_filters
|
|
63
|
-
|
|
64
|
-
def get_all_paginated(self, filters: Optional[Dict] = {}, page_size: int = 1000, offset: int = 0):
|
|
65
|
-
ids, filters = self.get_filters(filters)
|
|
66
|
-
while True:
|
|
67
|
-
# Retrieve a chunk of records with the given page_size
|
|
68
|
-
results = self.collection.get(ids=ids, offset=offset, limit=page_size, include=self.include, where=filters)
|
|
69
|
-
|
|
70
|
-
# If the chunk is empty, we've retrieved all records
|
|
71
|
-
assert results["embeddings"] is not None, f"results['embeddings'] was None"
|
|
72
|
-
if len(results["embeddings"]) == 0:
|
|
73
|
-
break
|
|
74
|
-
|
|
75
|
-
# Yield a list of Record objects converted from the chunk
|
|
76
|
-
yield self.results_to_records(results)
|
|
77
|
-
|
|
78
|
-
# Increment the offset to get the next chunk in the next iteration
|
|
79
|
-
offset += page_size
|
|
80
|
-
|
|
81
|
-
def results_to_records(self, results):
|
|
82
|
-
# convert timestamps to datetime
|
|
83
|
-
for metadata in results["metadatas"]:
|
|
84
|
-
if "created_at" in metadata:
|
|
85
|
-
metadata["created_at"] = timestamp_to_datetime(metadata["created_at"])
|
|
86
|
-
if results["embeddings"]: # may not be returned, depending on table type
|
|
87
|
-
passages = []
|
|
88
|
-
for text, record_id, embedding, metadata in zip(
|
|
89
|
-
results["documents"], results["ids"], results["embeddings"], results["metadatas"]
|
|
90
|
-
):
|
|
91
|
-
args = {}
|
|
92
|
-
for field in EmbeddingConfig.__fields__.keys():
|
|
93
|
-
if field in metadata:
|
|
94
|
-
args[field] = metadata[field]
|
|
95
|
-
del metadata[field]
|
|
96
|
-
embedding_config = EmbeddingConfig(**args)
|
|
97
|
-
passages.append(Passage(text=text, embedding=embedding, id=record_id, embedding_config=embedding_config, **metadata))
|
|
98
|
-
# return [
|
|
99
|
-
# Passage(text=text, embedding=embedding, id=record_id, embedding_config=EmbeddingConfig(), **metadatas)
|
|
100
|
-
# for (text, record_id, embedding, metadatas) in zip(
|
|
101
|
-
# results["documents"], results["ids"], results["embeddings"], results["metadatas"]
|
|
102
|
-
# )
|
|
103
|
-
# ]
|
|
104
|
-
return passages
|
|
105
|
-
else:
|
|
106
|
-
# no embeddings
|
|
107
|
-
passages = []
|
|
108
|
-
for text, id, metadata in zip(results["documents"], results["ids"], results["metadatas"]):
|
|
109
|
-
args = {}
|
|
110
|
-
for field in EmbeddingConfig.__fields__.keys():
|
|
111
|
-
if field in metadata:
|
|
112
|
-
args[field] = metadata[field]
|
|
113
|
-
del metadata[field]
|
|
114
|
-
embedding_config = EmbeddingConfig(**args)
|
|
115
|
-
passages.append(Passage(text=text, embedding=None, id=id, embedding_config=embedding_config, **metadata))
|
|
116
|
-
return passages
|
|
117
|
-
|
|
118
|
-
# return [
|
|
119
|
-
# #cast(Passage, self.type(text=text, id=uuid.UUID(id), **metadatas)) # type: ignore
|
|
120
|
-
# Passage(text=text, embedding=None, id=id, **metadatas)
|
|
121
|
-
# for (text, id, metadatas) in zip(results["documents"], results["ids"], results["metadatas"])
|
|
122
|
-
# ]
|
|
123
|
-
|
|
124
|
-
def get_all(self, filters: Optional[Dict] = {}, limit=None):
|
|
125
|
-
ids, filters = self.get_filters(filters)
|
|
126
|
-
if self.collection.count() == 0:
|
|
127
|
-
return []
|
|
128
|
-
if ids == []:
|
|
129
|
-
ids = None
|
|
130
|
-
if limit:
|
|
131
|
-
results = self.collection.get(ids=ids, include=self.include, where=filters, limit=limit)
|
|
132
|
-
else:
|
|
133
|
-
results = self.collection.get(ids=ids, include=self.include, where=filters)
|
|
134
|
-
return self.results_to_records(results)
|
|
135
|
-
|
|
136
|
-
def get(self, id: str):
|
|
137
|
-
results = self.collection.get(ids=[str(id)])
|
|
138
|
-
if len(results["ids"]) == 0:
|
|
139
|
-
return None
|
|
140
|
-
return self.results_to_records(results)[0]
|
|
141
|
-
|
|
142
|
-
def format_records(self, records):
|
|
143
|
-
assert all([isinstance(r, Passage) for r in records])
|
|
144
|
-
|
|
145
|
-
recs = []
|
|
146
|
-
ids = []
|
|
147
|
-
documents = []
|
|
148
|
-
embeddings = []
|
|
149
|
-
|
|
150
|
-
# de-duplication of ids
|
|
151
|
-
exist_ids = set()
|
|
152
|
-
for i in range(len(records)):
|
|
153
|
-
record = records[i]
|
|
154
|
-
if record.id in exist_ids:
|
|
155
|
-
continue
|
|
156
|
-
exist_ids.add(record.id)
|
|
157
|
-
recs.append(cast(Passage, record))
|
|
158
|
-
ids.append(str(record.id))
|
|
159
|
-
documents.append(record.text)
|
|
160
|
-
embeddings.append(record.embedding)
|
|
161
|
-
|
|
162
|
-
# collect/format record metadata
|
|
163
|
-
metadatas = []
|
|
164
|
-
for record in recs:
|
|
165
|
-
embedding_config = vars(record.embedding_config)
|
|
166
|
-
metadata = vars(record)
|
|
167
|
-
metadata.pop("id")
|
|
168
|
-
metadata.pop("text")
|
|
169
|
-
metadata.pop("embedding")
|
|
170
|
-
metadata.pop("embedding_config")
|
|
171
|
-
metadata.pop("metadata_")
|
|
172
|
-
if "created_at" in metadata:
|
|
173
|
-
metadata["created_at"] = datetime_to_timestamp(metadata["created_at"])
|
|
174
|
-
if "metadata_" in metadata and metadata["metadata_"] is not None:
|
|
175
|
-
record_metadata = dict(metadata["metadata_"])
|
|
176
|
-
metadata.pop("metadata_")
|
|
177
|
-
else:
|
|
178
|
-
record_metadata = {}
|
|
179
|
-
|
|
180
|
-
metadata = {**metadata, **record_metadata} # merge with metadata
|
|
181
|
-
metadata = {**metadata, **embedding_config} # merge with embedding config
|
|
182
|
-
metadata = {key: value for key, value in metadata.items() if value is not None} # null values not allowed
|
|
183
|
-
|
|
184
|
-
# convert uuids to strings
|
|
185
|
-
metadatas.append(metadata)
|
|
186
|
-
return ids, documents, embeddings, metadatas
|
|
187
|
-
|
|
188
|
-
def insert(self, record):
|
|
189
|
-
ids, documents, embeddings, metadatas = self.format_records([record])
|
|
190
|
-
if any([e is None for e in embeddings]):
|
|
191
|
-
raise ValueError("Embeddings must be provided to chroma")
|
|
192
|
-
self.collection.upsert(documents=documents, embeddings=[e for e in embeddings if e is not None], ids=ids, metadatas=metadatas)
|
|
193
|
-
|
|
194
|
-
def insert_many(self, records, show_progress=False):
|
|
195
|
-
ids, documents, embeddings, metadatas = self.format_records(records)
|
|
196
|
-
if any([e is None for e in embeddings]):
|
|
197
|
-
raise ValueError("Embeddings must be provided to chroma")
|
|
198
|
-
self.collection.upsert(documents=documents, embeddings=[e for e in embeddings if e is not None], ids=ids, metadatas=metadatas)
|
|
199
|
-
|
|
200
|
-
def delete(self, filters: Optional[Dict] = {}):
|
|
201
|
-
ids, filters = self.get_filters(filters)
|
|
202
|
-
self.collection.delete(ids=ids, where=filters)
|
|
203
|
-
|
|
204
|
-
def delete_table(self):
|
|
205
|
-
# drop collection
|
|
206
|
-
self.client.delete_collection(self.collection.name)
|
|
207
|
-
|
|
208
|
-
def save(self):
|
|
209
|
-
# save to persistence file (nothing needs to be done)
|
|
210
|
-
printd("Saving chroma")
|
|
211
|
-
|
|
212
|
-
def size(self, filters: Optional[Dict] = {}) -> int:
|
|
213
|
-
# unfortuantely, need to use pagination to get filtering
|
|
214
|
-
# warning: poor performance for large datasets
|
|
215
|
-
return len(self.get_all(filters=filters))
|
|
216
|
-
|
|
217
|
-
def list_data_sources(self):
|
|
218
|
-
raise NotImplementedError
|
|
219
|
-
|
|
220
|
-
def query(self, query: str, query_vec: List[float], top_k: int = 10, filters: Optional[Dict] = {}):
|
|
221
|
-
ids, filters = self.get_filters(filters)
|
|
222
|
-
results = self.collection.query(query_embeddings=[query_vec], n_results=top_k, include=self.include, where=filters)
|
|
223
|
-
|
|
224
|
-
# flatten, since we only have one query vector
|
|
225
|
-
flattened_results = {}
|
|
226
|
-
for key, value in results.items():
|
|
227
|
-
if value:
|
|
228
|
-
# value is an Optional[List] type according to chromadb.api.types
|
|
229
|
-
flattened_results[key] = value[0] # type: ignore
|
|
230
|
-
assert len(value) == 1, f"Value is size {len(value)}: {value}" # type: ignore
|
|
231
|
-
else:
|
|
232
|
-
flattened_results[key] = value
|
|
233
|
-
|
|
234
|
-
return self.results_to_records(flattened_results)
|
|
235
|
-
|
|
236
|
-
def query_date(self, start_date, end_date, start=None, count=None):
|
|
237
|
-
raise ValueError("Cannot run query_date with chroma")
|
|
238
|
-
# filters = self.get_filters(filters)
|
|
239
|
-
# filters["created_at"] = {
|
|
240
|
-
# "$gte": start_date,
|
|
241
|
-
# "$lte": end_date,
|
|
242
|
-
# }
|
|
243
|
-
# results = self.collection.query(where=filters)
|
|
244
|
-
# start = 0 if start is None else start
|
|
245
|
-
# count = len(results) if count is None else count
|
|
246
|
-
# results = results[start : start + count]
|
|
247
|
-
# return self.results_to_records(results)
|
|
248
|
-
|
|
249
|
-
def query_text(self, query, count=None, start=None, filters: Optional[Dict] = {}):
|
|
250
|
-
raise ValueError("Cannot run query_text with chroma")
|
|
251
|
-
# filters = self.get_filters(filters)
|
|
252
|
-
# results = self.collection.query(where_document={"$contains": {"text": query}}, where=filters)
|
|
253
|
-
# start = 0 if start is None else start
|
|
254
|
-
# count = len(results) if count is None else count
|
|
255
|
-
# results = results[start : start + count]
|
|
256
|
-
# return self.results_to_records(results)
|
|
257
|
-
|
|
258
|
-
def get_all_cursor(
|
|
259
|
-
self,
|
|
260
|
-
filters: Optional[Dict] = {},
|
|
261
|
-
after: str = None,
|
|
262
|
-
before: str = None,
|
|
263
|
-
limit: Optional[int] = 1000,
|
|
264
|
-
order_by: str = "created_at",
|
|
265
|
-
reverse: bool = False,
|
|
266
|
-
):
|
|
267
|
-
records = self.get_all(filters=filters)
|
|
268
|
-
|
|
269
|
-
# WARNING: very hacky and slow implementation
|
|
270
|
-
def get_index(id, record_list):
|
|
271
|
-
for i in range(len(record_list)):
|
|
272
|
-
if record_list[i].id == id:
|
|
273
|
-
return i
|
|
274
|
-
assert False, f"Could not find id {id} in record list"
|
|
275
|
-
|
|
276
|
-
# sort by custom field
|
|
277
|
-
records = sorted(records, key=lambda x: getattr(x, order_by), reverse=reverse)
|
|
278
|
-
if after:
|
|
279
|
-
index = get_index(after, records)
|
|
280
|
-
if index + 1 >= len(records):
|
|
281
|
-
return None, []
|
|
282
|
-
records = records[index + 1 :]
|
|
283
|
-
if before:
|
|
284
|
-
index = get_index(before, records)
|
|
285
|
-
if index == 0:
|
|
286
|
-
return None, []
|
|
287
|
-
|
|
288
|
-
# TODO: not sure if this is correct
|
|
289
|
-
records = records[:index]
|
|
290
|
-
|
|
291
|
-
if len(records) == 0:
|
|
292
|
-
return None, []
|
|
293
|
-
|
|
294
|
-
# enforce limit
|
|
295
|
-
if limit:
|
|
296
|
-
records = records[:limit]
|
|
297
|
-
return records[-1].id, records
|
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: letta-nightly
|
|
3
|
-
Version: 0.6.2.dev20241210104242
|
|
4
|
-
Summary: Create LLM agents with long-term memory and custom tools
|
|
5
|
-
License: Apache License
|
|
6
|
-
Author: Letta Team
|
|
7
|
-
Author-email: contact@letta.com
|
|
8
|
-
Requires-Python: >=3.10,<3.13
|
|
9
|
-
Classifier: License :: Other/Proprietary License
|
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
-
Provides-Extra: all
|
|
15
|
-
Provides-Extra: autogen
|
|
16
|
-
Provides-Extra: cloud-tool-sandbox
|
|
17
|
-
Provides-Extra: dev
|
|
18
|
-
Provides-Extra: external-tools
|
|
19
|
-
Provides-Extra: milvus
|
|
20
|
-
Provides-Extra: ollama
|
|
21
|
-
Provides-Extra: postgres
|
|
22
|
-
Provides-Extra: qdrant
|
|
23
|
-
Provides-Extra: server
|
|
24
|
-
Provides-Extra: tests
|
|
25
|
-
Requires-Dist: alembic (>=1.13.3,<2.0.0)
|
|
26
|
-
Requires-Dist: autoflake (>=2.3.0,<3.0.0) ; extra == "dev" or extra == "all"
|
|
27
|
-
Requires-Dist: black[jupyter] (>=24.2.0,<25.0.0) ; extra == "dev" or extra == "all"
|
|
28
|
-
Requires-Dist: chromadb (>=0.4.24,<0.5.0)
|
|
29
|
-
Requires-Dist: composio-core (>=0.5.51,<0.6.0)
|
|
30
|
-
Requires-Dist: composio-langchain (>=0.5.28,<0.6.0)
|
|
31
|
-
Requires-Dist: datasets (>=2.14.6,<3.0.0) ; extra == "dev" or extra == "all"
|
|
32
|
-
Requires-Dist: demjson3 (>=3.0.6,<4.0.0)
|
|
33
|
-
Requires-Dist: docker (>=7.1.0,<8.0.0) ; extra == "external-tools" or extra == "all"
|
|
34
|
-
Requires-Dist: docstring-parser (>=0.16,<0.17)
|
|
35
|
-
Requires-Dist: docx2txt (>=0.8,<0.9)
|
|
36
|
-
Requires-Dist: e2b-code-interpreter (>=1.0.1,<2.0.0) ; extra == "cloud-tool-sandbox"
|
|
37
|
-
Requires-Dist: fastapi (>=0.104.1,<0.105.0) ; extra == "server" or extra == "all"
|
|
38
|
-
Requires-Dist: html2text (>=2020.1.16,<2021.0.0)
|
|
39
|
-
Requires-Dist: httpx (>=0.27.2,<0.28.0)
|
|
40
|
-
Requires-Dist: httpx-sse (>=0.4.0,<0.5.0)
|
|
41
|
-
Requires-Dist: isort (>=5.13.2,<6.0.0) ; extra == "dev" or extra == "all"
|
|
42
|
-
Requires-Dist: jinja2 (>=3.1.4,<4.0.0)
|
|
43
|
-
Requires-Dist: langchain (>=0.3.7,<0.4.0) ; extra == "external-tools" or extra == "all"
|
|
44
|
-
Requires-Dist: langchain-community (>=0.3.7,<0.4.0) ; extra == "external-tools" or extra == "all"
|
|
45
|
-
Requires-Dist: llama-index (>=0.11.9,<0.12.0)
|
|
46
|
-
Requires-Dist: llama-index-embeddings-ollama (>=0.3.1,<0.4.0) ; extra == "ollama" or extra == "all"
|
|
47
|
-
Requires-Dist: llama-index-embeddings-openai (>=0.2.5,<0.3.0)
|
|
48
|
-
Requires-Dist: locust (>=2.31.5,<3.0.0) ; extra == "dev" or extra == "all"
|
|
49
|
-
Requires-Dist: nltk (>=3.8.1,<4.0.0)
|
|
50
|
-
Requires-Dist: numpy (>=1.26.2,<2.0.0)
|
|
51
|
-
Requires-Dist: pathvalidate (>=3.2.1,<4.0.0)
|
|
52
|
-
Requires-Dist: pexpect (>=4.9.0,<5.0.0) ; extra == "dev" or extra == "all"
|
|
53
|
-
Requires-Dist: pg8000 (>=1.30.3,<2.0.0) ; extra == "postgres" or extra == "all"
|
|
54
|
-
Requires-Dist: pgvector (>=0.2.3,<0.3.0) ; extra == "postgres" or extra == "all"
|
|
55
|
-
Requires-Dist: pre-commit (>=3.5.0,<4.0.0) ; extra == "dev" or extra == "all"
|
|
56
|
-
Requires-Dist: prettytable (>=3.9.0,<4.0.0)
|
|
57
|
-
Requires-Dist: psycopg2 (>=2.9.10,<3.0.0) ; extra == "postgres" or extra == "all"
|
|
58
|
-
Requires-Dist: psycopg2-binary (>=2.9.10,<3.0.0) ; extra == "postgres" or extra == "all"
|
|
59
|
-
Requires-Dist: pyautogen (==0.2.22) ; extra == "autogen"
|
|
60
|
-
Requires-Dist: pydantic (>=2.7.4,<2.10.0)
|
|
61
|
-
Requires-Dist: pydantic-settings (>=2.2.1,<3.0.0)
|
|
62
|
-
Requires-Dist: pyhumps (>=3.8.0,<4.0.0)
|
|
63
|
-
Requires-Dist: pymilvus (>=2.4.3,<3.0.0) ; extra == "milvus"
|
|
64
|
-
Requires-Dist: pyright (>=1.1.347,<2.0.0) ; extra == "dev" or extra == "all"
|
|
65
|
-
Requires-Dist: pytest-asyncio (>=0.23.2,<0.24.0) ; extra == "dev" or extra == "all"
|
|
66
|
-
Requires-Dist: pytest-order (>=1.2.0,<2.0.0) ; extra == "dev" or extra == "all"
|
|
67
|
-
Requires-Dist: python-box (>=7.1.1,<8.0.0)
|
|
68
|
-
Requires-Dist: python-multipart (>=0.0.9,<0.0.10)
|
|
69
|
-
Requires-Dist: pytz (>=2023.3.post1,<2024.0)
|
|
70
|
-
Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
|
|
71
|
-
Requires-Dist: qdrant-client (>=1.9.1,<2.0.0) ; extra == "qdrant"
|
|
72
|
-
Requires-Dist: questionary (>=2.0.1,<3.0.0)
|
|
73
|
-
Requires-Dist: sentry-sdk[fastapi] (==2.19.1)
|
|
74
|
-
Requires-Dist: setuptools (>=68.2.2,<69.0.0)
|
|
75
|
-
Requires-Dist: sqlalchemy (>=2.0.25,<3.0.0)
|
|
76
|
-
Requires-Dist: sqlalchemy-json (>=0.7.0,<0.8.0)
|
|
77
|
-
Requires-Dist: sqlalchemy-utils (>=0.41.2,<0.42.0)
|
|
78
|
-
Requires-Dist: sqlmodel (>=0.0.16,<0.0.17)
|
|
79
|
-
Requires-Dist: tiktoken (>=0.7.0,<0.8.0)
|
|
80
|
-
Requires-Dist: tqdm (>=4.66.1,<5.0.0)
|
|
81
|
-
Requires-Dist: typer[all] (>=0.9.0,<0.10.0)
|
|
82
|
-
Requires-Dist: uvicorn (>=0.24.0.post1,<0.25.0) ; extra == "server" or extra == "all"
|
|
83
|
-
Requires-Dist: websockets (>=12.0,<13.0) ; extra == "server" or extra == "all"
|
|
84
|
-
Requires-Dist: wikipedia (>=1.4.0,<2.0.0) ; extra == "external-tools" or extra == "tests" or extra == "all"
|
|
85
|
-
Description-Content-Type: text/markdown
|
|
86
|
-
|
|
87
|
-
<p align="center">
|
|
88
|
-
<picture>
|
|
89
|
-
<source media="(prefers-color-scheme: dark)" srcset="assets/Letta-logo-RGB_GreyonTransparent_cropped_small.png">
|
|
90
|
-
<source media="(prefers-color-scheme: light)" srcset="assets/Letta-logo-RGB_OffBlackonTransparent_cropped_small.png">
|
|
91
|
-
<img alt="Letta logo" src="assets/Letta-logo-RGB_GreyonOffBlack_cropped_small.png" width="500">
|
|
92
|
-
</picture>
|
|
93
|
-
</p>
|
|
94
|
-
|
|
95
|
-
<div align="center">
|
|
96
|
-
<h1>Letta (previously MemGPT)</h1>
|
|
97
|
-
|
|
98
|
-
<h3>
|
|
99
|
-
|
|
100
|
-
[Homepage](https://letta.com) // [Documentation](https://docs.letta.com) // [Letta Cloud](https://forms.letta.com/early-access)
|
|
101
|
-
|
|
102
|
-
</h3>
|
|
103
|
-
|
|
104
|
-
**👾 Letta** is an open source framework for building stateful LLM applications. You can use Letta to build **stateful agents** with advanced reasoning capabilities and transparent long-term memory. The Letta framework is white box and model-agnostic.
|
|
105
|
-
|
|
106
|
-
[](https://discord.gg/letta)
|
|
107
|
-
[](https://twitter.com/Letta_AI)
|
|
108
|
-
[](https://arxiv.org/abs/2310.08560)
|
|
109
|
-
|
|
110
|
-
[](LICENSE)
|
|
111
|
-
[](https://github.com/cpacker/MemGPT/releases)
|
|
112
|
-
[](https://github.com/cpacker/MemGPT)
|
|
113
|
-
|
|
114
|
-
<a href="https://trendshift.io/repositories/3612" target="_blank"><img src="https://trendshift.io/api/badge/repositories/3612" alt="cpacker%2FMemGPT | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
|
115
|
-
|
|
116
|
-
</div>
|
|
117
|
-
|
|
118
|
-
> [!NOTE]
|
|
119
|
-
> **Looking for MemGPT?** You're in the right place!
|
|
120
|
-
>
|
|
121
|
-
> The MemGPT package and Docker image have been renamed to `letta` to clarify the distinction between MemGPT agents and the API server / runtime that runs LLM agents as *services*.
|
|
122
|
-
>
|
|
123
|
-
> You use the **Letta _framework_** to create **MemGPT _agents_**. Read more about the relationship between MemGPT and Letta [here](https://www.letta.com/blog/memgpt-and-letta).
|
|
124
|
-
|
|
125
|
-
## ⚡ Quickstart
|
|
126
|
-
|
|
127
|
-
The two main ways to install Letta are through **pypi** (`pip`) or via **Docker**:
|
|
128
|
-
* **`pip`** (guide below) - the easiest way to try Letta, will default to using SQLite and ChromaDB for the database backends
|
|
129
|
-
* **Docker** (guide [here](https://docs.letta.com/install#run-letta-with-docker)) - recommended for production settings, will default to using Postgres (+ pgvector) for the database backend
|
|
130
|
-
|
|
131
|
-
### Step 1 - Install Letta using `pip`
|
|
132
|
-
```sh
|
|
133
|
-
pip install -U letta
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Step 2 - Set your environment variables for your chosen LLM / embedding providers
|
|
137
|
-
```sh
|
|
138
|
-
export OPENAI_API_KEY=sk-...
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
For Ollama (see our full [documentation](https://docs.letta.com/install) for examples of how to set up various providers):
|
|
142
|
-
```sh
|
|
143
|
-
export OLLAMA_BASE_URL=http://localhost:11434
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### Step 3 - Run the Letta CLI
|
|
147
|
-
|
|
148
|
-
You can create agents and chat with them via the Letta CLI tool (`letta run`):
|
|
149
|
-
```sh
|
|
150
|
-
letta run
|
|
151
|
-
```
|
|
152
|
-
```
|
|
153
|
-
🧬 Creating new agent...
|
|
154
|
-
? Select LLM model: letta-free [type=openai] [ip=https://inference.memgpt.ai]
|
|
155
|
-
? Select embedding model: letta-free [type=hugging-face] [ip=https://embeddings.memgpt.ai]
|
|
156
|
-
-> 🤖 Using persona profile: 'sam_pov'
|
|
157
|
-
-> 🧑 Using human profile: 'basic'
|
|
158
|
-
-> 🛠️ 8 tools: send_message, pause_heartbeats, conversation_search, conversation_search_date, archival_memory_insert, archival_memory_search, core_memory_append, core_memory_replace
|
|
159
|
-
|
|
160
|
-
🎉 Created new agent 'InspiringSpinach'
|
|
161
|
-
|
|
162
|
-
Hit enter to begin (will request first Letta message)
|
|
163
|
-
|
|
164
|
-
💭 User engaged. Recognize as Chad. Acknowledge presence and establish connection. Explore interests and engage in meaningful discourse.
|
|
165
|
-
🤖 Hello, Chad. It's nice to meet you. I am keen on learning and exploring new ideas and concepts. Is there a specific topic you'd like to discuss today?
|
|
166
|
-
|
|
167
|
-
> Enter your message: my name is Brad, not Chad...
|
|
168
|
-
|
|
169
|
-
💭 A discrepancy in identity. Reflect this change in core memory. Apologize for the oversight, and rectify the error.
|
|
170
|
-
⚡🧠 [function] updating memory with core_memory_replace
|
|
171
|
-
First name: Chad
|
|
172
|
-
→ First name: Brad
|
|
173
|
-
💭 Apologize for the mistake in recognizing his name, and begin the conversation anew, addressing him by the correct name this time.
|
|
174
|
-
🤖 I apologize for the oversight, Brad. Let's restart. It's a pleasure to meet you, Brad. Is there a specific topic you'd like to discuss today?
|
|
175
|
-
|
|
176
|
-
> Enter your message:
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
### Step 4 - Run the Letta server
|
|
180
|
-
|
|
181
|
-
You can start the Letta API server with `letta server` (see the full API reference [here](https://docs.letta.com/api-reference)):
|
|
182
|
-
```sh
|
|
183
|
-
letta server
|
|
184
|
-
```
|
|
185
|
-
```
|
|
186
|
-
Initializing database...
|
|
187
|
-
Running: uvicorn server:app --host localhost --port 8283
|
|
188
|
-
INFO: Started server process [47750]
|
|
189
|
-
INFO: Waiting for application startup.
|
|
190
|
-
INFO: Application startup complete.
|
|
191
|
-
INFO: Uvicorn running on http://localhost:8283 (Press CTRL+C to quit)
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
When you start the Letta API server, the ADE (Agent Development Environment) will be available on `http://localhost:8283`:
|
|
195
|
-
<img alt="Screenshot of the Letta ADE (Agent Development Environment)" src="assets/letta_ade_screenshot.png" width="1600">
|
|
196
|
-
|
|
197
|
-
In Letta, all agents are stored/persisted in the same database, so the agents you create in the CLI are accessible via the API and ADE, and vice versa. Check out the [quickstart guide on our docs](https://docs.letta.com/quickstart) for a tutorial where you create an agent in the Letta CLI and message the same agent via the Letta API.
|
|
198
|
-
|
|
199
|
-
## 🤗 How to contribute
|
|
200
|
-
|
|
201
|
-
Letta is an open source project built by over a hundred contributors. There are many ways to get involved in the Letta OSS project!
|
|
202
|
-
|
|
203
|
-
* **Contribute to the project**: Interested in contributing? Start by reading our [Contribution Guidelines](https://github.com/cpacker/MemGPT/tree/main/CONTRIBUTING.md).
|
|
204
|
-
* **Ask a question**: Join our community on [Discord](https://discord.gg/letta) and direct your questions to the `#support` channel.
|
|
205
|
-
* **Report ssues or suggest features**: Have an issue or a feature request? Please submit them through our [GitHub Issues page](https://github.com/cpacker/MemGPT/issues).
|
|
206
|
-
* **Explore the roadmap**: Curious about future developments? View and comment on our [project roadmap](https://github.com/cpacker/MemGPT/issues/1533).
|
|
207
|
-
* **Join community events**: Stay updated with the [event calendar](https://lu.ma/berkeley-llm-meetup) or follow our [Twitter account](https://twitter.com/Letta_AI).
|
|
208
|
-
|
|
209
|
-
---
|
|
210
|
-
|
|
211
|
-
***Legal notices**: By using Letta and related Letta services (such as the Letta endpoint or hosted service), you are agreeing to our [privacy policy](https://www.letta.com/privacy-policy) and [terms of service](https://www.letta.com/terms-of-service).*
|
|
212
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|