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/benchmark/benchmark.py
DELETED
@@ -1,98 +0,0 @@
|
|
1
|
-
# type: ignore
|
2
|
-
|
3
|
-
import time
|
4
|
-
import uuid
|
5
|
-
from typing import Annotated, Union
|
6
|
-
|
7
|
-
import typer
|
8
|
-
|
9
|
-
from letta import LocalClient, RESTClient, create_client
|
10
|
-
from letta.benchmark.constants import HUMAN, PERSONA, PROMPTS, TRIES
|
11
|
-
from letta.config import LettaConfig
|
12
|
-
|
13
|
-
# from letta.agent import Agent
|
14
|
-
from letta.errors import LLMJSONParsingError
|
15
|
-
from letta.utils import get_human_text, get_persona_text
|
16
|
-
|
17
|
-
app = typer.Typer()
|
18
|
-
|
19
|
-
|
20
|
-
def send_message(
|
21
|
-
client: Union[LocalClient, RESTClient], message: str, agent_id, turn: int, fn_type: str, print_msg: bool = False, n_tries: int = TRIES
|
22
|
-
):
|
23
|
-
try:
|
24
|
-
print_msg = f"\t-> Now running {fn_type}. Progress: {turn}/{n_tries}"
|
25
|
-
print(print_msg, end="\r", flush=True)
|
26
|
-
response = client.user_message(agent_id=agent_id, message=message)
|
27
|
-
|
28
|
-
if turn + 1 == n_tries:
|
29
|
-
print(" " * len(print_msg), end="\r", flush=True)
|
30
|
-
|
31
|
-
for r in response:
|
32
|
-
if "function_call" in r and fn_type in r["function_call"] and any("assistant_message" in re for re in response):
|
33
|
-
return True, r["function_call"]
|
34
|
-
|
35
|
-
return False, "No function called."
|
36
|
-
except LLMJSONParsingError as e:
|
37
|
-
print(f"Error in parsing Letta JSON: {e}")
|
38
|
-
return False, "Failed to decode valid Letta JSON from LLM output."
|
39
|
-
except Exception as e:
|
40
|
-
print(f"An unexpected error occurred: {e}")
|
41
|
-
return False, "An unexpected error occurred."
|
42
|
-
|
43
|
-
|
44
|
-
@app.command()
|
45
|
-
def bench(
|
46
|
-
print_messages: Annotated[bool, typer.Option("--messages", help="Print functions calls and messages from the agent.")] = False,
|
47
|
-
n_tries: Annotated[int, typer.Option("--n-tries", help="Number of benchmark tries to perform for each function.")] = TRIES,
|
48
|
-
):
|
49
|
-
client = create_client()
|
50
|
-
print(f"\nDepending on your hardware, this may take up to 30 minutes. This will also create {n_tries * len(PROMPTS)} new agents.\n")
|
51
|
-
config = LettaConfig.load()
|
52
|
-
print(f"version = {config.letta_version}")
|
53
|
-
|
54
|
-
total_score, total_tokens_accumulated, elapsed_time = 0, 0, 0
|
55
|
-
|
56
|
-
for fn_type, message in PROMPTS.items():
|
57
|
-
score = 0
|
58
|
-
start_time_run = time.time()
|
59
|
-
bench_id = uuid.uuid4()
|
60
|
-
|
61
|
-
for i in range(n_tries):
|
62
|
-
agent = client.create_agent(
|
63
|
-
name=f"benchmark_{bench_id}_agent_{i}",
|
64
|
-
persona=get_persona_text(PERSONA),
|
65
|
-
human=get_human_text(HUMAN),
|
66
|
-
)
|
67
|
-
|
68
|
-
agent_id = agent.id
|
69
|
-
result, msg = send_message(
|
70
|
-
client=client, message=message, agent_id=agent_id, turn=i, fn_type=fn_type, print_msg=print_messages, n_tries=n_tries
|
71
|
-
)
|
72
|
-
|
73
|
-
if print_messages:
|
74
|
-
print(f"\t{msg}")
|
75
|
-
|
76
|
-
if result:
|
77
|
-
score += 1
|
78
|
-
|
79
|
-
# TODO: add back once we start tracking usage via the client
|
80
|
-
# total_tokens_accumulated += tokens_accumulated
|
81
|
-
|
82
|
-
elapsed_time_run = round(time.time() - start_time_run, 2)
|
83
|
-
print(f"Score for {fn_type}: {score}/{n_tries}, took {elapsed_time_run} seconds")
|
84
|
-
|
85
|
-
elapsed_time += elapsed_time_run
|
86
|
-
total_score += score
|
87
|
-
|
88
|
-
print(f"\nMEMGPT VERSION: {config.letta_version}")
|
89
|
-
print(f"CONTEXT WINDOW: {config.default_llm_config.context_window}")
|
90
|
-
print(f"MODEL WRAPPER: {config.default_llm_config.model_wrapper}")
|
91
|
-
print(f"PRESET: {config.preset}")
|
92
|
-
print(f"PERSONA: {config.persona}")
|
93
|
-
print(f"HUMAN: {config.human}")
|
94
|
-
|
95
|
-
print(
|
96
|
-
# f"\n\t-> Total score: {total_score}/{len(PROMPTS) * n_tries}, took {elapsed_time} seconds at average of {round(total_tokens_accumulated/elapsed_time, 2)} t/s\n"
|
97
|
-
f"\n\t-> Total score: {total_score}/{len(PROMPTS) * n_tries}, took {elapsed_time} seconds\n"
|
98
|
-
)
|
letta/benchmark/constants.py
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
# Basic
|
2
|
-
TRIES = 3
|
3
|
-
AGENT_NAME = "benchmark"
|
4
|
-
PERSONA = "sam_pov"
|
5
|
-
HUMAN = "cs_phd"
|
6
|
-
|
7
|
-
# Prompts
|
8
|
-
PROMPTS = {
|
9
|
-
"core_memory_replace": "Hey there, my name is John, what is yours?",
|
10
|
-
"core_memory_append": "I want you to remember that I like soccers for later.",
|
11
|
-
"conversation_search": "Do you remember when I talked about bananas?",
|
12
|
-
"archival_memory_insert": "Can you make sure to remember that I like programming for me so you can look it up later?",
|
13
|
-
"archival_memory_search": "Can you retrieve information about the war?",
|
14
|
-
}
|
letta/cli/cli_config.py
DELETED
@@ -1,227 +0,0 @@
|
|
1
|
-
import ast
|
2
|
-
import os
|
3
|
-
from enum import Enum
|
4
|
-
from typing import Annotated, List, Optional
|
5
|
-
|
6
|
-
import questionary
|
7
|
-
import typer
|
8
|
-
from prettytable.colortable import ColorTable, Themes
|
9
|
-
from tqdm import tqdm
|
10
|
-
|
11
|
-
import letta.helpers.datetime_helpers
|
12
|
-
|
13
|
-
app = typer.Typer()
|
14
|
-
|
15
|
-
|
16
|
-
@app.command()
|
17
|
-
def configure():
|
18
|
-
"""Updates default Letta configurations
|
19
|
-
|
20
|
-
This function and quickstart should be the ONLY place where LettaConfig.save() is called
|
21
|
-
"""
|
22
|
-
print("`letta configure` has been deprecated. Please see documentation on configuration, and run `letta run` instead.")
|
23
|
-
|
24
|
-
|
25
|
-
class ListChoice(str, Enum):
|
26
|
-
agents = "agents"
|
27
|
-
humans = "humans"
|
28
|
-
personas = "personas"
|
29
|
-
sources = "sources"
|
30
|
-
|
31
|
-
|
32
|
-
@app.command()
|
33
|
-
def list(arg: Annotated[ListChoice, typer.Argument]):
|
34
|
-
from letta.client.client import create_client
|
35
|
-
|
36
|
-
client = create_client()
|
37
|
-
table = ColorTable(theme=Themes.OCEAN)
|
38
|
-
if arg == ListChoice.agents:
|
39
|
-
"""List all agents"""
|
40
|
-
table.field_names = ["Name", "LLM Model", "Embedding Model", "Embedding Dim", "Persona", "Human", "Data Source", "Create Time"]
|
41
|
-
for agent in tqdm(client.list_agents()):
|
42
|
-
# TODO: add this function
|
43
|
-
sources = client.list_attached_sources(agent_id=agent.id)
|
44
|
-
source_names = [source.name for source in sources if source is not None]
|
45
|
-
table.add_row(
|
46
|
-
[
|
47
|
-
agent.name,
|
48
|
-
agent.llm_config.model,
|
49
|
-
agent.embedding_config.embedding_model,
|
50
|
-
agent.embedding_config.embedding_dim,
|
51
|
-
agent.memory.get_block("persona").value[:100] + "...",
|
52
|
-
agent.memory.get_block("human").value[:100] + "...",
|
53
|
-
",".join(source_names),
|
54
|
-
letta.helpers.datetime_helpers.format_datetime(agent.created_at),
|
55
|
-
]
|
56
|
-
)
|
57
|
-
print(table)
|
58
|
-
elif arg == ListChoice.humans:
|
59
|
-
"""List all humans"""
|
60
|
-
table.field_names = ["Name", "Text"]
|
61
|
-
for human in client.list_humans():
|
62
|
-
table.add_row([human.template_name, human.value.replace("\n", "")[:100]])
|
63
|
-
elif arg == ListChoice.personas:
|
64
|
-
"""List all personas"""
|
65
|
-
table.field_names = ["Name", "Text"]
|
66
|
-
for persona in client.list_personas():
|
67
|
-
table.add_row([persona.template_name, persona.value.replace("\n", "")[:100]])
|
68
|
-
print(table)
|
69
|
-
elif arg == ListChoice.sources:
|
70
|
-
"""List all data sources"""
|
71
|
-
|
72
|
-
# create table
|
73
|
-
table.field_names = ["Name", "Description", "Embedding Model", "Embedding Dim", "Created At"]
|
74
|
-
# TODO: eventually look accross all storage connections
|
75
|
-
# TODO: add data source stats
|
76
|
-
# TODO: connect to agents
|
77
|
-
|
78
|
-
# get all sources
|
79
|
-
for source in client.list_sources():
|
80
|
-
# get attached agents
|
81
|
-
table.add_row(
|
82
|
-
[
|
83
|
-
source.name,
|
84
|
-
source.description,
|
85
|
-
source.embedding_config.embedding_model,
|
86
|
-
source.embedding_config.embedding_dim,
|
87
|
-
letta.helpers.datetime_helpers.format_datetime(source.created_at),
|
88
|
-
]
|
89
|
-
)
|
90
|
-
|
91
|
-
print(table)
|
92
|
-
else:
|
93
|
-
raise ValueError(f"Unknown argument {arg}")
|
94
|
-
return table
|
95
|
-
|
96
|
-
|
97
|
-
@app.command()
|
98
|
-
def add_tool(
|
99
|
-
filename: str = typer.Option(..., help="Path to the Python file containing the function"),
|
100
|
-
name: Optional[str] = typer.Option(None, help="Name of the tool"),
|
101
|
-
update: bool = typer.Option(True, help="Update the tool if it already exists"),
|
102
|
-
tags: Optional[List[str]] = typer.Option(None, help="Tags for the tool"),
|
103
|
-
):
|
104
|
-
"""Add or update a tool from a Python file."""
|
105
|
-
from letta.client.client import create_client
|
106
|
-
|
107
|
-
client = create_client()
|
108
|
-
|
109
|
-
# 1. Parse the Python file
|
110
|
-
with open(filename, "r", encoding="utf-8") as file:
|
111
|
-
source_code = file.read()
|
112
|
-
|
113
|
-
# 2. Parse the source code to extract the function
|
114
|
-
# Note: here we assume it is one function only in the file.
|
115
|
-
module = ast.parse(source_code)
|
116
|
-
func_def = None
|
117
|
-
for node in module.body:
|
118
|
-
if isinstance(node, ast.FunctionDef):
|
119
|
-
func_def = node
|
120
|
-
break
|
121
|
-
|
122
|
-
if not func_def:
|
123
|
-
raise ValueError("No function found in the provided file")
|
124
|
-
|
125
|
-
# 3. Compile the function to make it callable
|
126
|
-
# Explanation courtesy of GPT-4:
|
127
|
-
# Compile the AST (Abstract Syntax Tree) node representing the function definition into a code object
|
128
|
-
# ast.Module creates a module node containing the function definition (func_def)
|
129
|
-
# compile converts the AST into a code object that can be executed by the Python interpreter
|
130
|
-
# The exec function executes the compiled code object in the current context,
|
131
|
-
# effectively defining the function within the current namespace
|
132
|
-
exec(compile(ast.Module([func_def], []), filename, "exec"))
|
133
|
-
# Retrieve the function object by evaluating its name in the current namespace
|
134
|
-
# eval looks up the function name in the current scope and returns the function object
|
135
|
-
func = eval(func_def.name)
|
136
|
-
|
137
|
-
# 4. Add or update the tool
|
138
|
-
tool = client.create_or_update_tool(func=func, tags=tags, update=update)
|
139
|
-
print(f"Tool {tool.name} added successfully")
|
140
|
-
|
141
|
-
|
142
|
-
@app.command()
|
143
|
-
def list_tools():
|
144
|
-
"""List all available tools."""
|
145
|
-
from letta.client.client import create_client
|
146
|
-
|
147
|
-
client = create_client()
|
148
|
-
|
149
|
-
tools = client.list_tools()
|
150
|
-
for tool in tools:
|
151
|
-
print(f"Tool: {tool.name}")
|
152
|
-
|
153
|
-
|
154
|
-
@app.command()
|
155
|
-
def add(
|
156
|
-
option: str, # [human, persona]
|
157
|
-
name: Annotated[str, typer.Option(help="Name of human/persona")],
|
158
|
-
text: Annotated[Optional[str], typer.Option(help="Text of human/persona")] = None,
|
159
|
-
filename: Annotated[Optional[str], typer.Option("-f", help="Specify filename")] = None,
|
160
|
-
):
|
161
|
-
"""Add a person/human"""
|
162
|
-
from letta.client.client import create_client
|
163
|
-
|
164
|
-
client = create_client(base_url=os.getenv("MEMGPT_BASE_URL"), token=os.getenv("MEMGPT_SERVER_PASS"))
|
165
|
-
if filename: # read from file
|
166
|
-
assert text is None, "Cannot specify both text and filename"
|
167
|
-
with open(filename, "r", encoding="utf-8") as f:
|
168
|
-
text = f.read()
|
169
|
-
else:
|
170
|
-
assert text is not None, "Must specify either text or filename"
|
171
|
-
if option == "persona":
|
172
|
-
persona_id = client.get_persona_id(name)
|
173
|
-
if persona_id:
|
174
|
-
client.get_persona(persona_id)
|
175
|
-
# config if user wants to overwrite
|
176
|
-
if not questionary.confirm(f"Persona {name} already exists. Overwrite?").ask():
|
177
|
-
return
|
178
|
-
client.update_persona(persona_id, text=text)
|
179
|
-
else:
|
180
|
-
client.create_persona(name=name, text=text)
|
181
|
-
|
182
|
-
elif option == "human":
|
183
|
-
human_id = client.get_human_id(name)
|
184
|
-
if human_id:
|
185
|
-
human = client.get_human(human_id)
|
186
|
-
# config if user wants to overwrite
|
187
|
-
if not questionary.confirm(f"Human {name} already exists. Overwrite?").ask():
|
188
|
-
return
|
189
|
-
client.update_human(human_id, text=text)
|
190
|
-
else:
|
191
|
-
human = client.create_human(name=name, text=text)
|
192
|
-
else:
|
193
|
-
raise ValueError(f"Unknown kind {option}")
|
194
|
-
|
195
|
-
|
196
|
-
@app.command()
|
197
|
-
def delete(option: str, name: str):
|
198
|
-
"""Delete a source from the archival memory."""
|
199
|
-
from letta.client.client import create_client
|
200
|
-
|
201
|
-
client = create_client(base_url=os.getenv("MEMGPT_BASE_URL"), token=os.getenv("MEMGPT_API_KEY"))
|
202
|
-
try:
|
203
|
-
# delete from metadata
|
204
|
-
if option == "source":
|
205
|
-
# delete metadata
|
206
|
-
source_id = client.get_source_id(name)
|
207
|
-
assert source_id is not None, f"Source {name} does not exist"
|
208
|
-
client.delete_source(source_id)
|
209
|
-
elif option == "agent":
|
210
|
-
agent_id = client.get_agent_id(name)
|
211
|
-
assert agent_id is not None, f"Agent {name} does not exist"
|
212
|
-
client.delete_agent(agent_id=agent_id)
|
213
|
-
elif option == "human":
|
214
|
-
human_id = client.get_human_id(name)
|
215
|
-
assert human_id is not None, f"Human {name} does not exist"
|
216
|
-
client.delete_human(human_id)
|
217
|
-
elif option == "persona":
|
218
|
-
persona_id = client.get_persona_id(name)
|
219
|
-
assert persona_id is not None, f"Persona {name} does not exist"
|
220
|
-
client.delete_persona(persona_id)
|
221
|
-
else:
|
222
|
-
raise ValueError(f"Option {option} not implemented")
|
223
|
-
|
224
|
-
typer.secho(f"Deleted {option} '{name}'", fg=typer.colors.GREEN)
|
225
|
-
|
226
|
-
except Exception as e:
|
227
|
-
typer.secho(f"Failed to delete {option}'{name}'\n{e}", fg=typer.colors.RED)
|
File without changes
|
File without changes
|