letta-nightly 0.7.21.dev20250521233415__py3-none-any.whl → 0.7.22.dev20250523081403__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.
- letta/__init__.py +2 -2
- letta/agents/base_agent.py +4 -2
- letta/agents/letta_agent.py +3 -10
- letta/agents/letta_agent_batch.py +6 -6
- letta/cli/cli.py +0 -316
- letta/cli/cli_load.py +0 -52
- letta/client/client.py +2 -1554
- letta/data_sources/connectors.py +4 -2
- letta/functions/ast_parsers.py +33 -43
- letta/groups/sleeptime_multi_agent_v2.py +49 -13
- letta/jobs/llm_batch_job_polling.py +3 -3
- letta/jobs/scheduler.py +20 -19
- letta/llm_api/anthropic_client.py +3 -0
- letta/llm_api/google_vertex_client.py +5 -0
- letta/llm_api/openai_client.py +5 -0
- letta/main.py +2 -362
- letta/server/db.py +5 -0
- letta/server/rest_api/routers/v1/agents.py +72 -43
- letta/server/rest_api/routers/v1/llms.py +2 -2
- letta/server/rest_api/routers/v1/messages.py +5 -3
- letta/server/rest_api/routers/v1/sandbox_configs.py +18 -18
- letta/server/rest_api/routers/v1/sources.py +49 -36
- letta/server/server.py +53 -22
- letta/services/agent_manager.py +797 -124
- letta/services/block_manager.py +14 -62
- letta/services/group_manager.py +37 -0
- letta/services/identity_manager.py +9 -0
- letta/services/job_manager.py +17 -0
- letta/services/llm_batch_manager.py +88 -64
- letta/services/message_manager.py +19 -0
- letta/services/organization_manager.py +10 -0
- letta/services/passage_manager.py +13 -0
- letta/services/per_agent_lock_manager.py +4 -0
- letta/services/provider_manager.py +34 -0
- letta/services/sandbox_config_manager.py +130 -0
- letta/services/source_manager.py +59 -44
- letta/services/step_manager.py +8 -1
- letta/services/tool_manager.py +21 -0
- letta/services/tool_sandbox/e2b_sandbox.py +4 -2
- letta/services/tool_sandbox/local_sandbox.py +7 -3
- letta/services/user_manager.py +16 -0
- {letta_nightly-0.7.21.dev20250521233415.dist-info → letta_nightly-0.7.22.dev20250523081403.dist-info}/METADATA +1 -1
- {letta_nightly-0.7.21.dev20250521233415.dist-info → letta_nightly-0.7.22.dev20250523081403.dist-info}/RECORD +46 -50
- letta/__main__.py +0 -3
- letta/benchmark/benchmark.py +0 -98
- letta/benchmark/constants.py +0 -14
- letta/cli/cli_config.py +0 -227
- {letta_nightly-0.7.21.dev20250521233415.dist-info → letta_nightly-0.7.22.dev20250523081403.dist-info}/LICENSE +0 -0
- {letta_nightly-0.7.21.dev20250521233415.dist-info → letta_nightly-0.7.22.dev20250523081403.dist-info}/WHEEL +0 -0
- {letta_nightly-0.7.21.dev20250521233415.dist-info → letta_nightly-0.7.22.dev20250523081403.dist-info}/entry_points.txt +0 -0
letta/main.py
CHANGED
@@ -1,374 +1,14 @@
|
|
1
1
|
import os
|
2
|
-
import sys
|
3
|
-
import traceback
|
4
2
|
|
5
|
-
import questionary
|
6
|
-
import requests
|
7
3
|
import typer
|
8
|
-
from rich.console import Console
|
9
4
|
|
10
|
-
|
11
|
-
import letta.errors as errors
|
12
|
-
import letta.system as system
|
13
|
-
|
14
|
-
# import benchmark
|
15
|
-
from letta import create_client
|
16
|
-
from letta.benchmark.benchmark import bench
|
17
|
-
from letta.cli.cli import delete_agent, open_folder, run, server, version
|
18
|
-
from letta.cli.cli_config import add, add_tool, configure, delete, list, list_tools
|
5
|
+
from letta.cli.cli import server
|
19
6
|
from letta.cli.cli_load import app as load_app
|
20
|
-
from letta.config import LettaConfig
|
21
|
-
from letta.constants import FUNC_FAILED_HEARTBEAT_MESSAGE, REQ_HEARTBEAT_MESSAGE
|
22
|
-
|
23
|
-
# from letta.interface import CLIInterface as interface # for printing to terminal
|
24
|
-
from letta.streaming_interface import AgentRefreshStreamingInterface
|
25
|
-
|
26
|
-
# interface = interface()
|
27
7
|
|
28
8
|
# disable composio print on exit
|
29
9
|
os.environ["COMPOSIO_DISABLE_VERSION_CHECK"] = "true"
|
30
10
|
|
31
11
|
app = typer.Typer(pretty_exceptions_enable=False)
|
32
|
-
app.command(name="run")(run)
|
33
|
-
app.command(name="version")(version)
|
34
|
-
app.command(name="configure")(configure)
|
35
|
-
app.command(name="list")(list)
|
36
|
-
app.command(name="add")(add)
|
37
|
-
app.command(name="add-tool")(add_tool)
|
38
|
-
app.command(name="list-tools")(list_tools)
|
39
|
-
app.command(name="delete")(delete)
|
40
12
|
app.command(name="server")(server)
|
41
|
-
app.command(name="folder")(open_folder)
|
42
|
-
# load data commands
|
43
|
-
app.add_typer(load_app, name="load")
|
44
|
-
# benchmark command
|
45
|
-
app.command(name="benchmark")(bench)
|
46
|
-
# delete agents
|
47
|
-
app.command(name="delete-agent")(delete_agent)
|
48
|
-
|
49
|
-
|
50
|
-
def clear_line(console, strip_ui=False):
|
51
|
-
if strip_ui:
|
52
|
-
return
|
53
|
-
if os.name == "nt": # for windows
|
54
|
-
console.print("\033[A\033[K", end="")
|
55
|
-
else: # for linux
|
56
|
-
sys.stdout.write("\033[2K\033[G")
|
57
|
-
sys.stdout.flush()
|
58
|
-
|
59
|
-
|
60
|
-
def run_agent_loop(
|
61
|
-
letta_agent: agent.Agent,
|
62
|
-
config: LettaConfig,
|
63
|
-
first: bool,
|
64
|
-
no_verify: bool = False,
|
65
|
-
strip_ui: bool = False,
|
66
|
-
stream: bool = False,
|
67
|
-
):
|
68
|
-
if isinstance(letta_agent.interface, AgentRefreshStreamingInterface):
|
69
|
-
# letta_agent.interface.toggle_streaming(on=stream)
|
70
|
-
if not stream:
|
71
|
-
letta_agent.interface = letta_agent.interface.nonstreaming_interface
|
72
|
-
|
73
|
-
if hasattr(letta_agent.interface, "console"):
|
74
|
-
console = letta_agent.interface.console
|
75
|
-
else:
|
76
|
-
console = Console()
|
77
|
-
|
78
|
-
counter = 0
|
79
|
-
user_input = None
|
80
|
-
skip_next_user_input = False
|
81
|
-
user_message = None
|
82
|
-
USER_GOES_FIRST = first
|
83
|
-
|
84
|
-
if not USER_GOES_FIRST:
|
85
|
-
console.input("[bold cyan]Hit enter to begin (will request first Letta message)[/bold cyan]\n")
|
86
|
-
clear_line(console, strip_ui=strip_ui)
|
87
|
-
print()
|
88
|
-
|
89
|
-
multiline_input = False
|
90
|
-
|
91
|
-
# create client
|
92
|
-
client = create_client()
|
93
|
-
|
94
|
-
# run loops
|
95
|
-
while True:
|
96
|
-
if not skip_next_user_input and (counter > 0 or USER_GOES_FIRST):
|
97
|
-
# Ask for user input
|
98
|
-
if not stream:
|
99
|
-
print()
|
100
|
-
user_input = questionary.text(
|
101
|
-
"Enter your message:",
|
102
|
-
multiline=multiline_input,
|
103
|
-
qmark=">",
|
104
|
-
).ask()
|
105
|
-
clear_line(console, strip_ui=strip_ui)
|
106
|
-
if not stream:
|
107
|
-
print()
|
108
|
-
|
109
|
-
# Gracefully exit on Ctrl-C/D
|
110
|
-
if user_input is None:
|
111
|
-
user_input = "/exit"
|
112
|
-
|
113
|
-
user_input = user_input.rstrip()
|
114
|
-
|
115
|
-
if user_input.startswith("!"):
|
116
|
-
print(f"Commands for CLI begin with '/' not '!'")
|
117
|
-
continue
|
118
|
-
|
119
|
-
if user_input == "":
|
120
|
-
# no empty messages allowed
|
121
|
-
print("Empty input received. Try again!")
|
122
|
-
continue
|
123
|
-
|
124
|
-
# Handle CLI commands
|
125
|
-
# Commands to not get passed as input to Letta
|
126
|
-
if user_input.startswith("/"):
|
127
|
-
# updated agent save functions
|
128
|
-
if user_input.lower() == "/exit":
|
129
|
-
# letta_agent.save()
|
130
|
-
agent.save_agent(letta_agent)
|
131
|
-
break
|
132
|
-
elif user_input.lower() == "/save" or user_input.lower() == "/savechat":
|
133
|
-
# letta_agent.save()
|
134
|
-
agent.save_agent(letta_agent)
|
135
|
-
continue
|
136
|
-
elif user_input.lower() == "/attach":
|
137
|
-
# TODO: check if agent already has it
|
138
|
-
|
139
|
-
# TODO: check to ensure source embedding dimentions/model match agents, and disallow attachment if not
|
140
|
-
# TODO: alternatively, only list sources with compatible embeddings, and print warning about non-compatible sources
|
141
|
-
|
142
|
-
sources = client.list_sources()
|
143
|
-
if len(sources) == 0:
|
144
|
-
typer.secho(
|
145
|
-
'No sources available. You must load a souce with "letta load ..." before running /attach.',
|
146
|
-
fg=typer.colors.RED,
|
147
|
-
bold=True,
|
148
|
-
)
|
149
|
-
continue
|
150
|
-
|
151
|
-
# determine what sources are valid to be attached to this agent
|
152
|
-
valid_options = []
|
153
|
-
invalid_options = []
|
154
|
-
for source in sources:
|
155
|
-
if source.embedding_config == letta_agent.agent_state.embedding_config:
|
156
|
-
valid_options.append(source.name)
|
157
|
-
else:
|
158
|
-
# print warning about invalid sources
|
159
|
-
typer.secho(
|
160
|
-
f"Source {source.name} exists but has embedding dimentions {source.embedding_dim} from model {source.embedding_model}, while the agent uses embedding dimentions {letta_agent.agent_state.embedding_config.embedding_dim} and model {letta_agent.agent_state.embedding_config.embedding_model}",
|
161
|
-
fg=typer.colors.YELLOW,
|
162
|
-
)
|
163
|
-
invalid_options.append(source.name)
|
164
|
-
|
165
|
-
# prompt user for data source selection
|
166
|
-
data_source = questionary.select("Select data source", choices=valid_options).ask()
|
167
|
-
|
168
|
-
# attach new data
|
169
|
-
client.attach_source_to_agent(agent_id=letta_agent.agent_state.id, source_name=data_source)
|
170
13
|
|
171
|
-
|
172
|
-
|
173
|
-
elif user_input.lower() == "/dump" or user_input.lower().startswith("/dump "):
|
174
|
-
# Check if there's an additional argument that's an integer
|
175
|
-
command = user_input.strip().split()
|
176
|
-
amount = int(command[1]) if len(command) > 1 and command[1].isdigit() else 0
|
177
|
-
if amount == 0:
|
178
|
-
letta_agent.interface.print_messages(letta_agent._messages, dump=True)
|
179
|
-
else:
|
180
|
-
letta_agent.interface.print_messages(letta_agent._messages[-min(amount, len(letta_agent.messages)) :], dump=True)
|
181
|
-
continue
|
182
|
-
|
183
|
-
elif user_input.lower() == "/dumpraw":
|
184
|
-
letta_agent.interface.print_messages_raw(letta_agent._messages)
|
185
|
-
continue
|
186
|
-
|
187
|
-
elif user_input.lower() == "/memory":
|
188
|
-
print(f"\nDumping memory contents:\n")
|
189
|
-
print(f"{letta_agent.agent_state.memory.compile()}")
|
190
|
-
print(f"{letta_agent.archival_memory.compile()}")
|
191
|
-
continue
|
192
|
-
|
193
|
-
elif user_input.lower() == "/model":
|
194
|
-
print(f"Current model: {letta_agent.agent_state.llm_config.model}")
|
195
|
-
continue
|
196
|
-
|
197
|
-
elif user_input.lower() == "/summarize":
|
198
|
-
try:
|
199
|
-
letta_agent.summarize_messages_inplace()
|
200
|
-
typer.secho(
|
201
|
-
f"/summarize succeeded",
|
202
|
-
fg=typer.colors.GREEN,
|
203
|
-
bold=True,
|
204
|
-
)
|
205
|
-
except (errors.LLMError, requests.exceptions.HTTPError) as e:
|
206
|
-
typer.secho(
|
207
|
-
f"/summarize failed:\n{e}",
|
208
|
-
fg=typer.colors.RED,
|
209
|
-
bold=True,
|
210
|
-
)
|
211
|
-
continue
|
212
|
-
|
213
|
-
elif user_input.lower() == "/tokens":
|
214
|
-
tokens = letta_agent.count_tokens()
|
215
|
-
typer.secho(
|
216
|
-
f"{tokens}/{letta_agent.agent_state.llm_config.context_window}",
|
217
|
-
fg=typer.colors.GREEN,
|
218
|
-
bold=True,
|
219
|
-
)
|
220
|
-
continue
|
221
|
-
|
222
|
-
elif user_input.lower().startswith("/add_function"):
|
223
|
-
try:
|
224
|
-
if len(user_input) < len("/add_function "):
|
225
|
-
print("Missing function name after the command")
|
226
|
-
continue
|
227
|
-
function_name = user_input[len("/add_function ") :].strip()
|
228
|
-
result = letta_agent.add_function(function_name)
|
229
|
-
typer.secho(
|
230
|
-
f"/add_function succeeded: {result}",
|
231
|
-
fg=typer.colors.GREEN,
|
232
|
-
bold=True,
|
233
|
-
)
|
234
|
-
except ValueError as e:
|
235
|
-
typer.secho(
|
236
|
-
f"/add_function failed:\n{e}",
|
237
|
-
fg=typer.colors.RED,
|
238
|
-
bold=True,
|
239
|
-
)
|
240
|
-
continue
|
241
|
-
elif user_input.lower().startswith("/remove_function"):
|
242
|
-
try:
|
243
|
-
if len(user_input) < len("/remove_function "):
|
244
|
-
print("Missing function name after the command")
|
245
|
-
continue
|
246
|
-
function_name = user_input[len("/remove_function ") :].strip()
|
247
|
-
result = letta_agent.remove_function(function_name)
|
248
|
-
typer.secho(
|
249
|
-
f"/remove_function succeeded: {result}",
|
250
|
-
fg=typer.colors.GREEN,
|
251
|
-
bold=True,
|
252
|
-
)
|
253
|
-
except ValueError as e:
|
254
|
-
typer.secho(
|
255
|
-
f"/remove_function failed:\n{e}",
|
256
|
-
fg=typer.colors.RED,
|
257
|
-
bold=True,
|
258
|
-
)
|
259
|
-
continue
|
260
|
-
|
261
|
-
# No skip options
|
262
|
-
elif user_input.lower() == "/wipe":
|
263
|
-
letta_agent = agent.Agent(letta_agent.interface)
|
264
|
-
user_message = None
|
265
|
-
|
266
|
-
elif user_input.lower() == "/heartbeat":
|
267
|
-
user_message = system.get_heartbeat()
|
268
|
-
|
269
|
-
elif user_input.lower() == "/memorywarning":
|
270
|
-
user_message = system.get_token_limit_warning()
|
271
|
-
|
272
|
-
elif user_input.lower() == "//":
|
273
|
-
multiline_input = not multiline_input
|
274
|
-
continue
|
275
|
-
|
276
|
-
elif user_input.lower() == "/" or user_input.lower() == "/help":
|
277
|
-
questionary.print("CLI commands", "bold")
|
278
|
-
for cmd, desc in USER_COMMANDS:
|
279
|
-
questionary.print(cmd, "bold")
|
280
|
-
questionary.print(f" {desc}")
|
281
|
-
continue
|
282
|
-
else:
|
283
|
-
print(f"Unrecognized command: {user_input}")
|
284
|
-
continue
|
285
|
-
|
286
|
-
else:
|
287
|
-
# If message did not begin with command prefix, pass inputs to Letta
|
288
|
-
# Handle user message and append to messages
|
289
|
-
user_message = str(user_input)
|
290
|
-
|
291
|
-
skip_next_user_input = False
|
292
|
-
|
293
|
-
def process_agent_step(user_message, no_verify):
|
294
|
-
# TODO(charles): update to use agent.step() instead of inner_step()
|
295
|
-
|
296
|
-
if user_message is None:
|
297
|
-
step_response = letta_agent.inner_step(
|
298
|
-
messages=[],
|
299
|
-
first_message=False,
|
300
|
-
skip_verify=no_verify,
|
301
|
-
stream=stream,
|
302
|
-
)
|
303
|
-
else:
|
304
|
-
step_response = letta_agent.step_user_message(
|
305
|
-
user_message_str=user_message,
|
306
|
-
first_message=False,
|
307
|
-
skip_verify=no_verify,
|
308
|
-
stream=stream,
|
309
|
-
)
|
310
|
-
new_messages = step_response.messages
|
311
|
-
heartbeat_request = step_response.heartbeat_request
|
312
|
-
function_failed = step_response.function_failed
|
313
|
-
token_warning = step_response.in_context_memory_warning
|
314
|
-
step_response.usage
|
315
|
-
|
316
|
-
agent.save_agent(letta_agent)
|
317
|
-
skip_next_user_input = False
|
318
|
-
if token_warning:
|
319
|
-
user_message = system.get_token_limit_warning()
|
320
|
-
skip_next_user_input = True
|
321
|
-
elif function_failed:
|
322
|
-
user_message = system.get_heartbeat(FUNC_FAILED_HEARTBEAT_MESSAGE)
|
323
|
-
skip_next_user_input = True
|
324
|
-
elif heartbeat_request:
|
325
|
-
user_message = system.get_heartbeat(REQ_HEARTBEAT_MESSAGE)
|
326
|
-
skip_next_user_input = True
|
327
|
-
|
328
|
-
return new_messages, user_message, skip_next_user_input
|
329
|
-
|
330
|
-
while True:
|
331
|
-
try:
|
332
|
-
if strip_ui:
|
333
|
-
_, user_message, skip_next_user_input = process_agent_step(user_message, no_verify)
|
334
|
-
break
|
335
|
-
else:
|
336
|
-
if stream:
|
337
|
-
# Don't display the "Thinking..." if streaming
|
338
|
-
_, user_message, skip_next_user_input = process_agent_step(user_message, no_verify)
|
339
|
-
else:
|
340
|
-
with console.status("[bold cyan]Thinking...") as status:
|
341
|
-
_, user_message, skip_next_user_input = process_agent_step(user_message, no_verify)
|
342
|
-
break
|
343
|
-
except KeyboardInterrupt:
|
344
|
-
print("User interrupt occurred.")
|
345
|
-
retry = questionary.confirm("Retry agent.step()?").ask()
|
346
|
-
if not retry:
|
347
|
-
break
|
348
|
-
except Exception:
|
349
|
-
print("An exception occurred when running agent.step(): ")
|
350
|
-
traceback.print_exc()
|
351
|
-
retry = questionary.confirm("Retry agent.step()?").ask()
|
352
|
-
if not retry:
|
353
|
-
break
|
354
|
-
|
355
|
-
counter += 1
|
356
|
-
|
357
|
-
print("Finished.")
|
358
|
-
|
359
|
-
|
360
|
-
USER_COMMANDS = [
|
361
|
-
("//", "toggle multiline input mode"),
|
362
|
-
("/exit", "exit the CLI"),
|
363
|
-
("/save", "save a checkpoint of the current agent/conversation state"),
|
364
|
-
("/load", "load a saved checkpoint"),
|
365
|
-
("/dump <count>", "view the last <count> messages (all if <count> is omitted)"),
|
366
|
-
("/memory", "print the current contents of agent memory"),
|
367
|
-
("/pop <count>", "undo <count> messages in the conversation (default is 3)"),
|
368
|
-
("/retry", "pops the last answer and tries to get another one"),
|
369
|
-
("/rethink <text>", "changes the inner thoughts of the last agent message"),
|
370
|
-
("/rewrite <text>", "changes the reply of the last agent message"),
|
371
|
-
("/heartbeat", "send a heartbeat system message to the agent"),
|
372
|
-
("/memorywarning", "send a memory warning system message to the agent"),
|
373
|
-
("/attach", "attach data source to agent"),
|
374
|
-
]
|
14
|
+
app.add_typer(load_app, name="load")
|
letta/server/db.py
CHANGED
@@ -13,6 +13,9 @@ from sqlalchemy.orm import sessionmaker
|
|
13
13
|
from letta.config import LettaConfig
|
14
14
|
from letta.log import get_logger
|
15
15
|
from letta.settings import settings
|
16
|
+
from letta.tracing import trace_method
|
17
|
+
|
18
|
+
logger = get_logger(__name__)
|
16
19
|
|
17
20
|
logger = get_logger(__name__)
|
18
21
|
|
@@ -202,6 +205,7 @@ class DatabaseRegistry:
|
|
202
205
|
self.initialize_async()
|
203
206
|
return self._async_session_factories.get(name)
|
204
207
|
|
208
|
+
@trace_method
|
205
209
|
@contextmanager
|
206
210
|
def session(self, name: str = "default") -> Generator[Any, None, None]:
|
207
211
|
"""Context manager for database sessions."""
|
@@ -215,6 +219,7 @@ class DatabaseRegistry:
|
|
215
219
|
finally:
|
216
220
|
session.close()
|
217
221
|
|
222
|
+
@trace_method
|
218
223
|
@asynccontextmanager
|
219
224
|
async def async_session(self, name: str = "default") -> AsyncGenerator[AsyncSession, None]:
|
220
225
|
"""Async context manager for database sessions."""
|
@@ -13,10 +13,11 @@ from starlette.responses import Response, StreamingResponse
|
|
13
13
|
|
14
14
|
from letta.agents.letta_agent import LettaAgent
|
15
15
|
from letta.constants import DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG
|
16
|
+
from letta.groups.sleeptime_multi_agent_v2 import SleeptimeMultiAgentV2
|
16
17
|
from letta.helpers.datetime_helpers import get_utc_timestamp_ns
|
17
18
|
from letta.log import get_logger
|
18
19
|
from letta.orm.errors import NoResultFound
|
19
|
-
from letta.schemas.agent import AgentState,
|
20
|
+
from letta.schemas.agent import AgentState, CreateAgent, UpdateAgent
|
20
21
|
from letta.schemas.block import Block, BlockUpdate
|
21
22
|
from letta.schemas.group import Group
|
22
23
|
from letta.schemas.job import JobStatus, JobUpdate, LettaRequestConfig
|
@@ -212,7 +213,7 @@ async def retrieve_agent_context_window(
|
|
212
213
|
"""
|
213
214
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
214
215
|
try:
|
215
|
-
return await server.
|
216
|
+
return await server.agent_manager.get_context_window(agent_id=agent_id, actor=actor)
|
216
217
|
except Exception as e:
|
217
218
|
traceback.print_exc()
|
218
219
|
raise e
|
@@ -297,7 +298,7 @@ def detach_tool(
|
|
297
298
|
|
298
299
|
|
299
300
|
@router.patch("/{agent_id}/sources/attach/{source_id}", response_model=AgentState, operation_id="attach_source_to_agent")
|
300
|
-
def attach_source(
|
301
|
+
async def attach_source(
|
301
302
|
agent_id: str,
|
302
303
|
source_id: str,
|
303
304
|
background_tasks: BackgroundTasks,
|
@@ -310,7 +311,7 @@ def attach_source(
|
|
310
311
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
311
312
|
agent = server.agent_manager.attach_source(agent_id=agent_id, source_id=source_id, actor=actor)
|
312
313
|
if agent.enable_sleeptime:
|
313
|
-
source = server.source_manager.
|
314
|
+
source = await server.source_manager.get_source_by_id_async(source_id=source_id)
|
314
315
|
background_tasks.add_task(server.sleeptime_document_ingest, agent, source, actor)
|
315
316
|
return agent
|
316
317
|
|
@@ -355,7 +356,7 @@ async def retrieve_agent(
|
|
355
356
|
|
356
357
|
|
357
358
|
@router.delete("/{agent_id}", response_model=None, operation_id="delete_agent")
|
358
|
-
def delete_agent(
|
359
|
+
async def delete_agent(
|
359
360
|
agent_id: str,
|
360
361
|
server: "SyncServer" = Depends(get_letta_server),
|
361
362
|
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
@@ -363,9 +364,9 @@ def delete_agent(
|
|
363
364
|
"""
|
364
365
|
Delete an agent.
|
365
366
|
"""
|
366
|
-
actor = server.user_manager.
|
367
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
367
368
|
try:
|
368
|
-
server.agent_manager.
|
369
|
+
await server.agent_manager.delete_agent_async(agent_id=agent_id, actor=actor)
|
369
370
|
return JSONResponse(status_code=status.HTTP_200_OK, content={"message": f"Agent id={agent_id} successfully deleted"})
|
370
371
|
except NoResultFound:
|
371
372
|
raise HTTPException(status_code=404, detail=f"Agent agent_id={agent_id} not found for user_id={actor.id}.")
|
@@ -386,7 +387,7 @@ async def list_agent_sources(
|
|
386
387
|
|
387
388
|
# TODO: remove? can also get with agent blocks
|
388
389
|
@router.get("/{agent_id}/core-memory", response_model=Memory, operation_id="retrieve_agent_memory")
|
389
|
-
def retrieve_agent_memory(
|
390
|
+
async def retrieve_agent_memory(
|
390
391
|
agent_id: str,
|
391
392
|
server: "SyncServer" = Depends(get_letta_server),
|
392
393
|
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
@@ -395,13 +396,13 @@ def retrieve_agent_memory(
|
|
395
396
|
Retrieve the memory state of a specific agent.
|
396
397
|
This endpoint fetches the current memory state of the agent identified by the user ID and agent ID.
|
397
398
|
"""
|
398
|
-
actor = server.user_manager.
|
399
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
399
400
|
|
400
|
-
return server.
|
401
|
+
return await server.get_agent_memory_async(agent_id=agent_id, actor=actor)
|
401
402
|
|
402
403
|
|
403
404
|
@router.get("/{agent_id}/core-memory/blocks/{block_label}", response_model=Block, operation_id="retrieve_core_memory_block")
|
404
|
-
def retrieve_block(
|
405
|
+
async def retrieve_block(
|
405
406
|
agent_id: str,
|
406
407
|
block_label: str,
|
407
408
|
server: "SyncServer" = Depends(get_letta_server),
|
@@ -410,10 +411,10 @@ def retrieve_block(
|
|
410
411
|
"""
|
411
412
|
Retrieve a core memory block from an agent.
|
412
413
|
"""
|
413
|
-
actor = server.user_manager.
|
414
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
414
415
|
|
415
416
|
try:
|
416
|
-
return server.agent_manager.
|
417
|
+
return await server.agent_manager.get_block_with_label_async(agent_id=agent_id, block_label=block_label, actor=actor)
|
417
418
|
except NoResultFound as e:
|
418
419
|
raise HTTPException(status_code=404, detail=str(e))
|
419
420
|
|
@@ -453,13 +454,13 @@ async def modify_block(
|
|
453
454
|
)
|
454
455
|
|
455
456
|
# This should also trigger a system prompt change in the agent
|
456
|
-
server.agent_manager.
|
457
|
+
await server.agent_manager.rebuild_system_prompt_async(agent_id=agent_id, actor=actor, force=True, update_timestamp=False)
|
457
458
|
|
458
459
|
return block
|
459
460
|
|
460
461
|
|
461
462
|
@router.patch("/{agent_id}/core-memory/blocks/attach/{block_id}", response_model=AgentState, operation_id="attach_core_memory_block")
|
462
|
-
def attach_block(
|
463
|
+
async def attach_block(
|
463
464
|
agent_id: str,
|
464
465
|
block_id: str,
|
465
466
|
server: "SyncServer" = Depends(get_letta_server),
|
@@ -468,12 +469,12 @@ def attach_block(
|
|
468
469
|
"""
|
469
470
|
Attach a core memoryblock to an agent.
|
470
471
|
"""
|
471
|
-
actor = server.user_manager.
|
472
|
-
return server.agent_manager.
|
472
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
473
|
+
return await server.agent_manager.attach_block_async(agent_id=agent_id, block_id=block_id, actor=actor)
|
473
474
|
|
474
475
|
|
475
476
|
@router.patch("/{agent_id}/core-memory/blocks/detach/{block_id}", response_model=AgentState, operation_id="detach_core_memory_block")
|
476
|
-
def detach_block(
|
477
|
+
async def detach_block(
|
477
478
|
agent_id: str,
|
478
479
|
block_id: str,
|
479
480
|
server: "SyncServer" = Depends(get_letta_server),
|
@@ -482,8 +483,8 @@ def detach_block(
|
|
482
483
|
"""
|
483
484
|
Detach a core memory block from an agent.
|
484
485
|
"""
|
485
|
-
actor = server.user_manager.
|
486
|
-
return server.agent_manager.
|
486
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
487
|
+
return await server.agent_manager.detach_block_async(agent_id=agent_id, block_id=block_id, actor=actor)
|
487
488
|
|
488
489
|
|
489
490
|
@router.get("/{agent_id}/archival-memory", response_model=List[Passage], operation_id="list_passages")
|
@@ -637,22 +638,35 @@ async def send_message(
|
|
637
638
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
638
639
|
# TODO: This is redundant, remove soon
|
639
640
|
agent = await server.agent_manager.get_agent_by_id_async(agent_id, actor)
|
640
|
-
agent_eligible =
|
641
|
+
agent_eligible = agent.enable_sleeptime or not agent.multi_agent_group
|
641
642
|
experimental_header = request_obj.headers.get("X-EXPERIMENTAL") or "false"
|
642
643
|
feature_enabled = settings.use_experimental or experimental_header.lower() == "true"
|
643
644
|
model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "together", "google_ai", "google_vertex"]
|
644
645
|
|
645
646
|
if agent_eligible and feature_enabled and model_compatible:
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
647
|
+
if agent.enable_sleeptime:
|
648
|
+
experimental_agent = SleeptimeMultiAgentV2(
|
649
|
+
agent_id=agent_id,
|
650
|
+
message_manager=server.message_manager,
|
651
|
+
agent_manager=server.agent_manager,
|
652
|
+
block_manager=server.block_manager,
|
653
|
+
passage_manager=server.passage_manager,
|
654
|
+
group_manager=server.group_manager,
|
655
|
+
job_manager=server.job_manager,
|
656
|
+
actor=actor,
|
657
|
+
group=agent.multi_agent_group,
|
658
|
+
)
|
659
|
+
else:
|
660
|
+
experimental_agent = LettaAgent(
|
661
|
+
agent_id=agent_id,
|
662
|
+
message_manager=server.message_manager,
|
663
|
+
agent_manager=server.agent_manager,
|
664
|
+
block_manager=server.block_manager,
|
665
|
+
passage_manager=server.passage_manager,
|
666
|
+
actor=actor,
|
667
|
+
step_manager=server.step_manager,
|
668
|
+
telemetry_manager=server.telemetry_manager if settings.llm_api_logging else NoopTelemetryManager(),
|
669
|
+
)
|
656
670
|
|
657
671
|
result = await experimental_agent.step(request.messages, max_steps=10, use_assistant_message=request.use_assistant_message)
|
658
672
|
else:
|
@@ -697,23 +711,38 @@ async def send_message_streaming(
|
|
697
711
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
698
712
|
# TODO: This is redundant, remove soon
|
699
713
|
agent = await server.agent_manager.get_agent_by_id_async(agent_id, actor)
|
700
|
-
agent_eligible =
|
714
|
+
agent_eligible = agent.enable_sleeptime or not agent.multi_agent_group
|
701
715
|
experimental_header = request_obj.headers.get("X-EXPERIMENTAL") or "false"
|
702
716
|
feature_enabled = settings.use_experimental or experimental_header.lower() == "true"
|
703
717
|
model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "together", "google_ai", "google_vertex"]
|
704
718
|
model_compatible_token_streaming = agent.llm_config.model_endpoint_type in ["anthropic", "openai"]
|
705
719
|
|
706
|
-
if agent_eligible and feature_enabled and model_compatible
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
720
|
+
if agent_eligible and feature_enabled and model_compatible:
|
721
|
+
if agent.enable_sleeptime:
|
722
|
+
experimental_agent = SleeptimeMultiAgentV2(
|
723
|
+
agent_id=agent_id,
|
724
|
+
message_manager=server.message_manager,
|
725
|
+
agent_manager=server.agent_manager,
|
726
|
+
block_manager=server.block_manager,
|
727
|
+
passage_manager=server.passage_manager,
|
728
|
+
group_manager=server.group_manager,
|
729
|
+
job_manager=server.job_manager,
|
730
|
+
actor=actor,
|
731
|
+
step_manager=server.step_manager,
|
732
|
+
telemetry_manager=server.telemetry_manager if settings.llm_api_logging else NoopTelemetryManager(),
|
733
|
+
group=agent.multi_agent_group,
|
734
|
+
)
|
735
|
+
else:
|
736
|
+
experimental_agent = LettaAgent(
|
737
|
+
agent_id=agent_id,
|
738
|
+
message_manager=server.message_manager,
|
739
|
+
agent_manager=server.agent_manager,
|
740
|
+
block_manager=server.block_manager,
|
741
|
+
passage_manager=server.passage_manager,
|
742
|
+
actor=actor,
|
743
|
+
step_manager=server.step_manager,
|
744
|
+
telemetry_manager=server.telemetry_manager if settings.llm_api_logging else NoopTelemetryManager(),
|
745
|
+
)
|
717
746
|
from letta.server.rest_api.streaming_response import StreamingResponseWithStatusCode
|
718
747
|
|
719
748
|
if request.stream_tokens and model_compatible_token_streaming:
|
@@ -23,7 +23,7 @@ async def list_llm_models(
|
|
23
23
|
# Extract user_id from header, default to None if not present
|
24
24
|
):
|
25
25
|
"""List available LLM models using the asynchronous implementation for improved performance"""
|
26
|
-
actor = server.user_manager.
|
26
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
27
27
|
|
28
28
|
models = await server.list_llm_models_async(
|
29
29
|
provider_category=provider_category,
|
@@ -42,7 +42,7 @@ async def list_embedding_models(
|
|
42
42
|
# Extract user_id from header, default to None if not present
|
43
43
|
):
|
44
44
|
"""List available embedding models using the asynchronous implementation for improved performance"""
|
45
|
-
actor = server.user_manager.
|
45
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
46
46
|
models = await server.list_embedding_models_async(actor=actor)
|
47
47
|
|
48
48
|
return models
|