letta-nightly 0.7.21.dev20250522104246__py3-none-any.whl → 0.7.22.dev20250523104244__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.
Files changed (50) hide show
  1. letta/__init__.py +2 -2
  2. letta/agents/base_agent.py +4 -2
  3. letta/agents/letta_agent.py +3 -10
  4. letta/agents/letta_agent_batch.py +6 -6
  5. letta/cli/cli.py +0 -316
  6. letta/cli/cli_load.py +0 -52
  7. letta/client/client.py +2 -1554
  8. letta/data_sources/connectors.py +4 -2
  9. letta/functions/ast_parsers.py +33 -43
  10. letta/groups/sleeptime_multi_agent_v2.py +49 -13
  11. letta/jobs/llm_batch_job_polling.py +3 -3
  12. letta/jobs/scheduler.py +20 -19
  13. letta/llm_api/anthropic_client.py +3 -0
  14. letta/llm_api/google_vertex_client.py +5 -0
  15. letta/llm_api/openai_client.py +5 -0
  16. letta/main.py +2 -362
  17. letta/server/db.py +5 -0
  18. letta/server/rest_api/routers/v1/agents.py +72 -43
  19. letta/server/rest_api/routers/v1/llms.py +2 -2
  20. letta/server/rest_api/routers/v1/messages.py +5 -3
  21. letta/server/rest_api/routers/v1/sandbox_configs.py +18 -18
  22. letta/server/rest_api/routers/v1/sources.py +49 -36
  23. letta/server/server.py +53 -22
  24. letta/services/agent_manager.py +797 -124
  25. letta/services/block_manager.py +14 -62
  26. letta/services/group_manager.py +37 -0
  27. letta/services/identity_manager.py +9 -0
  28. letta/services/job_manager.py +17 -0
  29. letta/services/llm_batch_manager.py +88 -64
  30. letta/services/message_manager.py +19 -0
  31. letta/services/organization_manager.py +10 -0
  32. letta/services/passage_manager.py +13 -0
  33. letta/services/per_agent_lock_manager.py +4 -0
  34. letta/services/provider_manager.py +34 -0
  35. letta/services/sandbox_config_manager.py +130 -0
  36. letta/services/source_manager.py +59 -44
  37. letta/services/step_manager.py +8 -1
  38. letta/services/tool_manager.py +21 -0
  39. letta/services/tool_sandbox/e2b_sandbox.py +4 -2
  40. letta/services/tool_sandbox/local_sandbox.py +7 -3
  41. letta/services/user_manager.py +16 -0
  42. {letta_nightly-0.7.21.dev20250522104246.dist-info → letta_nightly-0.7.22.dev20250523104244.dist-info}/METADATA +1 -1
  43. {letta_nightly-0.7.21.dev20250522104246.dist-info → letta_nightly-0.7.22.dev20250523104244.dist-info}/RECORD +46 -50
  44. letta/__main__.py +0 -3
  45. letta/benchmark/benchmark.py +0 -98
  46. letta/benchmark/constants.py +0 -14
  47. letta/cli/cli_config.py +0 -227
  48. {letta_nightly-0.7.21.dev20250522104246.dist-info → letta_nightly-0.7.22.dev20250523104244.dist-info}/LICENSE +0 -0
  49. {letta_nightly-0.7.21.dev20250522104246.dist-info → letta_nightly-0.7.22.dev20250523104244.dist-info}/WHEEL +0 -0
  50. {letta_nightly-0.7.21.dev20250522104246.dist-info → letta_nightly-0.7.22.dev20250523104244.dist-info}/entry_points.txt +0 -0
@@ -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
- )
@@ -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)