letta-nightly 0.7.12.dev20250509104216__py3-none-any.whl → 0.7.13.dev20250510172445__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 CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "0.7.12"
1
+ __version__ = "0.7.13"
2
2
 
3
3
  # import clients
4
4
  from letta.client.client import LocalClient, RESTClient, create_client
letta/cli/cli.py CHANGED
@@ -42,6 +42,7 @@ def server(
42
42
  port: Annotated[Optional[int], typer.Option(help="Port to run the server on")] = None,
43
43
  host: Annotated[Optional[str], typer.Option(help="Host to run the server on (default to localhost)")] = None,
44
44
  debug: Annotated[bool, typer.Option(help="Turn debugging output on")] = False,
45
+ reload: Annotated[bool, typer.Option(help="Enable hot-reload")] = False,
45
46
  ade: Annotated[bool, typer.Option(help="Allows remote access")] = False, # NOTE: deprecated
46
47
  secure: Annotated[bool, typer.Option(help="Adds simple security access")] = False,
47
48
  localhttps: Annotated[bool, typer.Option(help="Setup local https")] = False,
@@ -61,7 +62,7 @@ def server(
61
62
  try:
62
63
  from letta.server.rest_api.app import start_server
63
64
 
64
- start_server(port=port, host=host, debug=debug)
65
+ start_server(port=port, host=host, debug=debug, reload=reload)
65
66
 
66
67
  except KeyboardInterrupt:
67
68
  # Handle CTRL-C
@@ -1,3 +1,4 @@
1
+ import json
1
2
  import uuid
2
3
  from typing import List, Optional
3
4
 
letta/llm_api/helpers.py CHANGED
@@ -337,6 +337,10 @@ def calculate_summarizer_cutoff(in_context_messages: List[Message], token_counts
337
337
  )
338
338
  break
339
339
 
340
+ # includes the tool response to be summarized after a tool call so we don't have any hanging tool calls after trimming.
341
+ if i + 1 < len(in_context_messages_openai) and in_context_messages_openai[i + 1]["role"] == "tool":
342
+ cutoff += 1
343
+
340
344
  logger.info(f"Evicting {cutoff}/{len(in_context_messages)} messages...")
341
345
  return cutoff + 1
342
346
 
@@ -215,6 +215,9 @@ def create(
215
215
  chat_completion_request=data,
216
216
  stream_interface=stream_interface,
217
217
  name=name,
218
+ # NOTE: needs to be true for OpenAI proxies that use the `reasoning_content` field
219
+ # For example, DeepSeek, or LM Studio
220
+ expect_reasoning_content=False,
218
221
  )
219
222
  else: # Client did not request token streaming (expect a blocking backend response)
220
223
  data.stream = False
@@ -272,6 +275,9 @@ def create(
272
275
  chat_completion_request=data,
273
276
  stream_interface=stream_interface,
274
277
  name=name,
278
+ # TODO turn on to support reasoning content from xAI reasoners:
279
+ # https://docs.x.ai/docs/guides/reasoning#reasoning
280
+ expect_reasoning_content=False,
275
281
  )
276
282
  else: # Client did not request token streaming (expect a blocking backend response)
277
283
  data.stream = False
@@ -486,7 +492,10 @@ def create(
486
492
  if stream:
487
493
  raise NotImplementedError(f"Streaming not yet implemented for TogetherAI (via the /completions endpoint).")
488
494
 
489
- if model_settings.together_api_key is None and llm_config.model_endpoint == "https://api.together.ai/v1/completions":
495
+ if model_settings.together_api_key is None and (
496
+ llm_config.model_endpoint == "https://api.together.ai/v1/completions"
497
+ or llm_config.model_endpoint == "https://api.together.xyz/v1/completions"
498
+ ):
490
499
  raise LettaConfigurationError(message="TogetherAI key is missing from letta config file", missing_fields=["together_api_key"])
491
500
 
492
501
  return get_chat_completion(
@@ -560,6 +569,8 @@ def create(
560
569
  chat_completion_request=data,
561
570
  stream_interface=stream_interface,
562
571
  name=name,
572
+ # TODO should we toggle for R1 vs V3?
573
+ expect_reasoning_content=True,
563
574
  )
564
575
  else: # Client did not request token streaming (expect a blocking backend response)
565
576
  data.stream = False
letta/llm_api/openai.py CHANGED
@@ -8,7 +8,13 @@ from letta.constants import LETTA_MODEL_ENDPOINT
8
8
  from letta.errors import ErrorCode, LLMAuthenticationError, LLMError
9
9
  from letta.helpers.datetime_helpers import timestamp_to_datetime
10
10
  from letta.llm_api.helpers import add_inner_thoughts_to_functions, convert_to_structured_output, make_post_request
11
- from letta.llm_api.openai_client import accepts_developer_role, supports_parallel_tool_calling, supports_temperature_param
11
+ from letta.llm_api.openai_client import (
12
+ accepts_developer_role,
13
+ requires_auto_tool_choice,
14
+ supports_parallel_tool_calling,
15
+ supports_structured_output,
16
+ supports_temperature_param,
17
+ )
12
18
  from letta.local_llm.constants import INNER_THOUGHTS_KWARG, INNER_THOUGHTS_KWARG_DESCRIPTION, INNER_THOUGHTS_KWARG_DESCRIPTION_GO_FIRST
13
19
  from letta.local_llm.utils import num_tokens_from_functions, num_tokens_from_messages
14
20
  from letta.log import get_logger
@@ -49,10 +55,7 @@ def openai_check_valid_api_key(base_url: str, api_key: Union[str, None]) -> None
49
55
  else:
50
56
  raise ValueError("No API key provided")
51
57
 
52
-
53
- def openai_get_model_list(
54
- url: str, api_key: Optional[str] = None, fix_url: Optional[bool] = False, extra_params: Optional[dict] = None
55
- ) -> dict:
58
+ def openai_get_model_list(url: str, api_key: Optional[str] = None, fix_url: bool = False, extra_params: Optional[dict] = None) -> dict:
56
59
  """https://platform.openai.com/docs/api-reference/models/list"""
57
60
  from letta.utils import printd
58
61
 
@@ -154,7 +157,10 @@ def build_openai_chat_completions_request(
154
157
  elif function_call not in ["none", "auto", "required"]:
155
158
  tool_choice = ToolFunctionChoice(type="function", function=ToolFunctionChoiceFunctionCall(name=function_call))
156
159
  else:
157
- tool_choice = function_call
160
+ if requires_auto_tool_choice(llm_config):
161
+ tool_choice = "auto"
162
+ else:
163
+ tool_choice = function_call
158
164
  data = ChatCompletionRequest(
159
165
  model=model,
160
166
  messages=openai_message_list,
@@ -197,12 +203,13 @@ def build_openai_chat_completions_request(
197
203
  if use_structured_output and data.tools is not None and len(data.tools) > 0:
198
204
  # Convert to structured output style (which has 'strict' and no optionals)
199
205
  for tool in data.tools:
200
- try:
201
- # tool["function"] = convert_to_structured_output(tool["function"])
202
- structured_output_version = convert_to_structured_output(tool.function.model_dump())
203
- tool.function = FunctionSchema(**structured_output_version)
204
- except ValueError as e:
205
- warnings.warn(f"Failed to convert tool function to structured output, tool={tool}, error={e}")
206
+ if supports_structured_output(llm_config):
207
+ try:
208
+ # tool["function"] = convert_to_structured_output(tool["function"])
209
+ structured_output_version = convert_to_structured_output(tool.function.model_dump())
210
+ tool.function = FunctionSchema(**structured_output_version)
211
+ except ValueError as e:
212
+ warnings.warn(f"Failed to convert tool function to structured output, tool={tool}, error={e}")
206
213
  return data
207
214
 
208
215
 
@@ -221,7 +228,7 @@ def openai_chat_completions_process_stream(
221
228
  expect_reasoning_content: bool = True,
222
229
  name: Optional[str] = None,
223
230
  ) -> ChatCompletionResponse:
224
- """Process a streaming completion response, and return a ChatCompletionRequest at the end.
231
+ """Process a streaming completion response, and return a ChatCompletionResponse at the end.
225
232
 
226
233
  To "stream" the response in Letta, we want to call a streaming-compatible interface function
227
234
  on the chunks received from the OpenAI-compatible server POST SSE response.
@@ -293,6 +300,9 @@ def openai_chat_completions_process_stream(
293
300
  url=url, api_key=api_key, chat_completion_request=chat_completion_request
294
301
  ):
295
302
  assert isinstance(chat_completion_chunk, ChatCompletionChunkResponse), type(chat_completion_chunk)
303
+ if chat_completion_chunk.choices is None or len(chat_completion_chunk.choices) == 0:
304
+ warnings.warn(f"No choices in chunk: {chat_completion_chunk}")
305
+ continue
296
306
 
297
307
  # NOTE: this assumes that the tool call ID will only appear in one of the chunks during the stream
298
308
  if override_tool_call_id:
@@ -429,6 +439,9 @@ def openai_chat_completions_process_stream(
429
439
  except Exception as e:
430
440
  if stream_interface:
431
441
  stream_interface.stream_end()
442
+ import traceback
443
+
444
+ traceback.print_exc()
432
445
  logger.error(f"Parsing ChatCompletion stream failed with error:\n{str(e)}")
433
446
  raise e
434
447
  finally:
@@ -463,14 +476,27 @@ def openai_chat_completions_request_stream(
463
476
  url: str,
464
477
  api_key: str,
465
478
  chat_completion_request: ChatCompletionRequest,
479
+ fix_url: bool = False,
466
480
  ) -> Generator[ChatCompletionChunkResponse, None, None]:
481
+
482
+ # In some cases we may want to double-check the URL and do basic correction, eg:
483
+ # In Letta config the address for vLLM is w/o a /v1 suffix for simplicity
484
+ # However if we're treating the server as an OpenAI proxy we want the /v1 suffix on our model hit
485
+ if fix_url:
486
+ if not url.endswith("/v1"):
487
+ url = smart_urljoin(url, "v1")
488
+
467
489
  data = prepare_openai_payload(chat_completion_request)
468
490
  data["stream"] = True
469
491
  client = OpenAI(api_key=api_key, base_url=url, max_retries=0)
470
- stream = client.chat.completions.create(**data)
471
- for chunk in stream:
472
- # TODO: Use the native OpenAI objects here?
473
- yield ChatCompletionChunkResponse(**chunk.model_dump(exclude_none=True))
492
+ try:
493
+ stream = client.chat.completions.create(**data)
494
+ for chunk in stream:
495
+ # TODO: Use the native OpenAI objects here?
496
+ yield ChatCompletionChunkResponse(**chunk.model_dump(exclude_none=True))
497
+ except Exception as e:
498
+ print(f"Error request stream from /v1/chat/completions, url={url}, data={data}:\n{e}")
499
+ raise e
474
500
 
475
501
 
476
502
  def openai_chat_completions_request(
@@ -75,6 +75,37 @@ def supports_parallel_tool_calling(model: str) -> bool:
75
75
  return True
76
76
 
77
77
 
78
+ # TODO move into LLMConfig as a field?
79
+ def supports_structured_output(llm_config: LLMConfig) -> bool:
80
+ """Certain providers don't support structured output."""
81
+
82
+ # FIXME pretty hacky - turn off for providers we know users will use,
83
+ # but also don't support structured output
84
+ if "nebius.com" in llm_config.model_endpoint:
85
+ return False
86
+ else:
87
+ return True
88
+
89
+
90
+ # TODO move into LLMConfig as a field?
91
+ def requires_auto_tool_choice(llm_config: LLMConfig) -> bool:
92
+ """Certain providers require the tool choice to be set to 'auto'."""
93
+
94
+ if "nebius.com" in llm_config.model_endpoint:
95
+ return True
96
+ if "together.ai" in llm_config.model_endpoint or "together.xyz" in llm_config.model_endpoint:
97
+ return True
98
+ # proxy also has this issue (FIXME check)
99
+ elif llm_config.model_endpoint == LETTA_MODEL_ENDPOINT:
100
+ return True
101
+ # same with vLLM (FIXME check)
102
+ elif llm_config.handle and "vllm" in llm_config.handle:
103
+ return True
104
+ else:
105
+ # will use "required" instead of "auto"
106
+ return False
107
+
108
+
78
109
  class OpenAIClient(LLMClientBase):
79
110
  def _prepare_client_kwargs(self, llm_config: LLMConfig) -> dict:
80
111
  api_key = None
@@ -136,7 +167,7 @@ class OpenAIClient(LLMClientBase):
136
167
  # TODO(matt) move into LLMConfig
137
168
  # TODO: This vllm checking is very brittle and is a patch at most
138
169
  tool_choice = None
139
- if llm_config.model_endpoint == LETTA_MODEL_ENDPOINT or (llm_config.handle and "vllm" in llm_config.handle):
170
+ if requires_auto_tool_choice(llm_config):
140
171
  tool_choice = "auto" # TODO change to "required" once proxy supports it
141
172
  elif tools:
142
173
  # only set if tools is non-Null
@@ -171,11 +202,12 @@ class OpenAIClient(LLMClientBase):
171
202
  if data.tools is not None and len(data.tools) > 0:
172
203
  # Convert to structured output style (which has 'strict' and no optionals)
173
204
  for tool in data.tools:
174
- try:
175
- structured_output_version = convert_to_structured_output(tool.function.model_dump())
176
- tool.function = FunctionSchema(**structured_output_version)
177
- except ValueError as e:
178
- logger.warning(f"Failed to convert tool function to structured output, tool={tool}, error={e}")
205
+ if supports_structured_output(llm_config):
206
+ try:
207
+ structured_output_version = convert_to_structured_output(tool.function.model_dump())
208
+ tool.function = FunctionSchema(**structured_output_version)
209
+ except ValueError as e:
210
+ logger.warning(f"Failed to convert tool function to structured output, tool={tool}, error={e}")
179
211
 
180
212
  return data.model_dump(exclude_unset=True)
181
213
 
letta/orm/source.py CHANGED
@@ -30,6 +30,7 @@ class Source(SqlalchemyBase, OrganizationMixin):
30
30
 
31
31
  name: Mapped[str] = mapped_column(doc="the name of the source, must be unique within the org", nullable=False)
32
32
  description: Mapped[str] = mapped_column(nullable=True, doc="a human-readable description of the source")
33
+ instructions: Mapped[str] = mapped_column(nullable=True, doc="instructions for how to use the source")
33
34
  embedding_config: Mapped[EmbeddingConfig] = mapped_column(EmbeddingConfigColumn, doc="Configuration settings for embedding.")
34
35
  metadata_: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True, doc="metadata for the source.")
35
36
 
@@ -24,7 +24,6 @@ class LLMConfig(BaseModel):
24
24
  max_tokens (int): The maximum number of tokens to generate.
25
25
  """
26
26
 
27
- # TODO: 🤮 don't default to a vendor! bug city!
28
27
  model: str = Field(..., description="LLM model name. ")
29
28
  model_endpoint_type: Literal[
30
29
  "openai",
@@ -1,5 +1,5 @@
1
1
  import datetime
2
- from typing import Dict, List, Literal, Optional, Union
2
+ from typing import List, Literal, Optional, Union
3
3
 
4
4
  from pydantic import BaseModel
5
5
 
@@ -27,6 +27,7 @@ class LogProbToken(BaseModel):
27
27
  bytes: Optional[List[int]]
28
28
 
29
29
 
30
+ # Legacy?
30
31
  class MessageContentLogProb(BaseModel):
31
32
  token: str
32
33
  logprob: float
@@ -34,6 +35,25 @@ class MessageContentLogProb(BaseModel):
34
35
  top_logprobs: Optional[List[LogProbToken]]
35
36
 
36
37
 
38
+ class TopLogprob(BaseModel):
39
+ token: str
40
+ bytes: Optional[List[int]] = None
41
+ logprob: float
42
+
43
+
44
+ class ChatCompletionTokenLogprob(BaseModel):
45
+ token: str
46
+ bytes: Optional[List[int]] = None
47
+ logprob: float
48
+ top_logprobs: List[TopLogprob]
49
+
50
+
51
+ class ChoiceLogprobs(BaseModel):
52
+ content: Optional[List[ChatCompletionTokenLogprob]] = None
53
+
54
+ refusal: Optional[List[ChatCompletionTokenLogprob]] = None
55
+
56
+
37
57
  class Message(BaseModel):
38
58
  content: Optional[str] = None
39
59
  tool_calls: Optional[List[ToolCall]] = None
@@ -49,7 +69,7 @@ class Choice(BaseModel):
49
69
  finish_reason: str
50
70
  index: int
51
71
  message: Message
52
- logprobs: Optional[Dict[str, Union[List[MessageContentLogProb], None]]] = None
72
+ logprobs: Optional[ChoiceLogprobs] = None
53
73
  seed: Optional[int] = None # found in TogetherAI
54
74
 
55
75
 
@@ -134,7 +154,7 @@ class ChatCompletionResponse(BaseModel):
134
154
  class FunctionCallDelta(BaseModel):
135
155
  # arguments: Optional[str] = None
136
156
  name: Optional[str] = None
137
- arguments: str
157
+ arguments: Optional[str] = None
138
158
  # name: str
139
159
 
140
160
 
@@ -179,7 +199,7 @@ class ChunkChoice(BaseModel):
179
199
  finish_reason: Optional[str] = None # NOTE: when streaming will be null
180
200
  index: int
181
201
  delta: MessageDelta
182
- logprobs: Optional[Dict[str, Union[List[MessageContentLogProb], None]]] = None
202
+ logprobs: Optional[ChoiceLogprobs] = None
183
203
 
184
204
 
185
205
  class ChatCompletionChunkResponse(BaseModel):
@@ -4,7 +4,7 @@ from typing import List, Literal, Optional
4
4
 
5
5
  from pydantic import BaseModel, Field, model_validator
6
6
 
7
- from letta.constants import LETTA_MODEL_ENDPOINT, LLM_MAX_TOKENS, MIN_CONTEXT_WINDOW
7
+ from letta.constants import DEFAULT_EMBEDDING_CHUNK_SIZE, LETTA_MODEL_ENDPOINT, LLM_MAX_TOKENS, MIN_CONTEXT_WINDOW
8
8
  from letta.llm_api.azure_openai import get_azure_chat_completions_endpoint, get_azure_embeddings_endpoint
9
9
  from letta.llm_api.azure_openai_constants import AZURE_MODEL_TO_CONTEXT_LENGTH
10
10
  from letta.schemas.embedding_config import EmbeddingConfig
@@ -57,7 +57,7 @@ class Provider(ProviderBase):
57
57
  """String representation of the provider for display purposes"""
58
58
  raise NotImplementedError
59
59
 
60
- def get_handle(self, model_name: str, is_embedding: bool = False) -> str:
60
+ def get_handle(self, model_name: str, is_embedding: bool = False, base_name: Optional[str] = None) -> str:
61
61
  """
62
62
  Get the handle for a model, with support for custom overrides.
63
63
 
@@ -68,11 +68,13 @@ class Provider(ProviderBase):
68
68
  Returns:
69
69
  str: The handle for the model.
70
70
  """
71
+ base_name = base_name if base_name else self.name
72
+
71
73
  overrides = EMBEDDING_HANDLE_OVERRIDES if is_embedding else LLM_HANDLE_OVERRIDES
72
- if self.name in overrides and model_name in overrides[self.name]:
73
- model_name = overrides[self.name][model_name]
74
+ if base_name in overrides and model_name in overrides[base_name]:
75
+ model_name = overrides[base_name][model_name]
74
76
 
75
- return f"{self.name}/{model_name}"
77
+ return f"{base_name}/{model_name}"
76
78
 
77
79
  def cast_to_subtype(self):
78
80
  match (self.provider_type):
@@ -162,21 +164,34 @@ class OpenAIProvider(Provider):
162
164
 
163
165
  openai_check_valid_api_key(self.base_url, self.api_key)
164
166
 
165
- def list_llm_models(self) -> List[LLMConfig]:
167
+ def _get_models(self) -> List[dict]:
166
168
  from letta.llm_api.openai import openai_get_model_list
167
169
 
168
170
  # Some hardcoded support for OpenRouter (so that we only get models with tool calling support)...
169
171
  # See: https://openrouter.ai/docs/requests
170
172
  extra_params = {"supported_parameters": "tools"} if "openrouter.ai" in self.base_url else None
171
- response = openai_get_model_list(self.base_url, api_key=self.api_key, extra_params=extra_params)
172
173
 
173
- # TogetherAI's response is missing the 'data' field
174
- # assert "data" in response, f"OpenAI model query response missing 'data' field: {response}"
174
+ # Similar to Nebius
175
+ extra_params = {"verbose": True} if "nebius.com" in self.base_url else None
176
+
177
+ response = openai_get_model_list(
178
+ self.base_url,
179
+ api_key=self.api_key,
180
+ extra_params=extra_params,
181
+ # fix_url=True, # NOTE: make sure together ends with /v1
182
+ )
183
+
175
184
  if "data" in response:
176
185
  data = response["data"]
177
186
  else:
187
+ # TogetherAI's response is missing the 'data' field
178
188
  data = response
179
189
 
190
+ return data
191
+
192
+ def list_llm_models(self) -> List[LLMConfig]:
193
+ data = self._get_models()
194
+
180
195
  configs = []
181
196
  for model in data:
182
197
  assert "id" in model, f"OpenAI model missing 'id' field: {model}"
@@ -192,8 +207,8 @@ class OpenAIProvider(Provider):
192
207
  continue
193
208
 
194
209
  # TogetherAI includes the type, which we can use to filter out embedding models
195
- if self.base_url == "https://api.together.ai/v1":
196
- if "type" in model and model["type"] != "chat":
210
+ if "api.together.ai" in self.base_url or "api.together.xyz" in self.base_url:
211
+ if "type" in model and model["type"] not in ["chat", "language"]:
197
212
  continue
198
213
 
199
214
  # for TogetherAI, we need to skip the models that don't support JSON mode / function calling
@@ -207,14 +222,17 @@ class OpenAIProvider(Provider):
207
222
  # }
208
223
  if "config" not in model:
209
224
  continue
210
- if "chat_template" not in model["config"]:
211
- continue
212
- if model["config"]["chat_template"] is None:
213
- continue
214
- if "tools" not in model["config"]["chat_template"]:
225
+
226
+ if "nebius.com" in self.base_url:
227
+ # Nebius includes the type, which we can use to filter for text models
228
+ try:
229
+ model_type = model["architecture"]["modality"]
230
+ if model_type not in ["text->text", "text+image->text"]:
231
+ # print(f"Skipping model w/ modality {model_type}:\n{model}")
232
+ continue
233
+ except KeyError:
234
+ print(f"Couldn't access architecture type field, skipping model:\n{model}")
215
235
  continue
216
- # if "config" in data and "chat_template" in data["config"] and "tools" not in data["config"]["chat_template"]:
217
- # continue
218
236
 
219
237
  # for openai, filter models
220
238
  if self.base_url == "https://api.openai.com/v1":
@@ -235,13 +253,19 @@ class OpenAIProvider(Provider):
235
253
  if skip:
236
254
  continue
237
255
 
256
+ # set the handle to openai-proxy if the base URL isn't OpenAI
257
+ if self.base_url != "https://api.openai.com/v1":
258
+ handle = self.get_handle(model_name, base_name="openai-proxy")
259
+ else:
260
+ handle = self.get_handle(model_name)
261
+
238
262
  configs.append(
239
263
  LLMConfig(
240
264
  model=model_name,
241
265
  model_endpoint_type="openai",
242
266
  model_endpoint=self.base_url,
243
267
  context_window=context_window_size,
244
- handle=self.get_handle(model_name),
268
+ handle=handle,
245
269
  provider_name=self.name,
246
270
  provider_category=self.provider_category,
247
271
  )
@@ -256,33 +280,87 @@ class OpenAIProvider(Provider):
256
280
 
257
281
  def list_embedding_models(self) -> List[EmbeddingConfig]:
258
282
 
259
- # TODO: actually automatically list models
260
- return [
261
- EmbeddingConfig(
262
- embedding_model="text-embedding-ada-002",
263
- embedding_endpoint_type="openai",
264
- embedding_endpoint=self.base_url,
265
- embedding_dim=1536,
266
- embedding_chunk_size=300,
267
- handle=self.get_handle("text-embedding-ada-002", is_embedding=True),
268
- ),
269
- EmbeddingConfig(
270
- embedding_model="text-embedding-3-small",
271
- embedding_endpoint_type="openai",
272
- embedding_endpoint=self.base_url,
273
- embedding_dim=2000,
274
- embedding_chunk_size=300,
275
- handle=self.get_handle("text-embedding-3-small", is_embedding=True),
276
- ),
277
- EmbeddingConfig(
278
- embedding_model="text-embedding-3-large",
279
- embedding_endpoint_type="openai",
280
- embedding_endpoint=self.base_url,
281
- embedding_dim=2000,
282
- embedding_chunk_size=300,
283
- handle=self.get_handle("text-embedding-3-large", is_embedding=True),
284
- ),
285
- ]
283
+ if self.base_url == "https://api.openai.com/v1":
284
+ # TODO: actually automatically list models for OpenAI
285
+ return [
286
+ EmbeddingConfig(
287
+ embedding_model="text-embedding-ada-002",
288
+ embedding_endpoint_type="openai",
289
+ embedding_endpoint=self.base_url,
290
+ embedding_dim=1536,
291
+ embedding_chunk_size=300,
292
+ handle=self.get_handle("text-embedding-ada-002", is_embedding=True),
293
+ ),
294
+ EmbeddingConfig(
295
+ embedding_model="text-embedding-3-small",
296
+ embedding_endpoint_type="openai",
297
+ embedding_endpoint=self.base_url,
298
+ embedding_dim=2000,
299
+ embedding_chunk_size=300,
300
+ handle=self.get_handle("text-embedding-3-small", is_embedding=True),
301
+ ),
302
+ EmbeddingConfig(
303
+ embedding_model="text-embedding-3-large",
304
+ embedding_endpoint_type="openai",
305
+ embedding_endpoint=self.base_url,
306
+ embedding_dim=2000,
307
+ embedding_chunk_size=300,
308
+ handle=self.get_handle("text-embedding-3-large", is_embedding=True),
309
+ ),
310
+ ]
311
+
312
+ else:
313
+ # Actually attempt to list
314
+ data = self._get_models()
315
+
316
+ configs = []
317
+ for model in data:
318
+ assert "id" in model, f"Model missing 'id' field: {model}"
319
+ model_name = model["id"]
320
+
321
+ if "context_length" in model:
322
+ # Context length is returned in Nebius as "context_length"
323
+ context_window_size = model["context_length"]
324
+ else:
325
+ context_window_size = self.get_model_context_window_size(model_name)
326
+
327
+ # We need the context length for embeddings too
328
+ if not context_window_size:
329
+ continue
330
+
331
+ if "nebius.com" in self.base_url:
332
+ # Nebius includes the type, which we can use to filter for embedidng models
333
+ try:
334
+ model_type = model["architecture"]["modality"]
335
+ if model_type not in ["text->embedding"]:
336
+ # print(f"Skipping model w/ modality {model_type}:\n{model}")
337
+ continue
338
+ except KeyError:
339
+ print(f"Couldn't access architecture type field, skipping model:\n{model}")
340
+ continue
341
+
342
+ elif "together.ai" in self.base_url or "together.xyz" in self.base_url:
343
+ # TogetherAI includes the type, which we can use to filter for embedding models
344
+ if "type" in model and model["type"] not in ["embedding"]:
345
+ # print(f"Skipping model w/ modality {model_type}:\n{model}")
346
+ continue
347
+
348
+ else:
349
+ # For other providers we should skip by default, since we don't want to assume embeddings are supported
350
+ continue
351
+
352
+ configs.append(
353
+ EmbeddingConfig(
354
+ embedding_model=model_name,
355
+ embedding_endpoint_type=self.provider_type,
356
+ embedding_endpoint=self.base_url,
357
+ embedding_dim=context_window_size,
358
+ embedding_chunk_size=DEFAULT_EMBEDDING_CHUNK_SIZE,
359
+ handle=self.get_handle(model, is_embedding=True),
360
+ )
361
+ )
362
+
363
+ return configs
286
364
 
287
365
  def get_model_context_window_size(self, model_name: str):
288
366
  if model_name in LLM_MAX_TOKENS:
letta/schemas/source.py CHANGED
@@ -31,6 +31,7 @@ class Source(BaseSource):
31
31
  id: str = BaseSource.generate_id_field()
32
32
  name: str = Field(..., description="The name of the source.")
33
33
  description: Optional[str] = Field(None, description="The description of the source.")
34
+ instructions: Optional[str] = Field(None, description="Instructions for how to use the source.")
34
35
  embedding_config: EmbeddingConfig = Field(..., description="The embedding configuration used by the source.")
35
36
  organization_id: Optional[str] = Field(None, description="The ID of the organization that created the source.")
36
37
  metadata: Optional[dict] = Field(None, validation_alias="metadata_", description="Metadata associated with the source.")
@@ -59,6 +60,7 @@ class SourceCreate(BaseSource):
59
60
 
60
61
  # optional
61
62
  description: Optional[str] = Field(None, description="The description of the source.")
63
+ instructions: Optional[str] = Field(None, description="Instructions for how to use the source.")
62
64
  metadata: Optional[dict] = Field(None, description="Metadata associated with the source.")
63
65
 
64
66
 
@@ -69,5 +71,6 @@ class SourceUpdate(BaseSource):
69
71
 
70
72
  name: Optional[str] = Field(None, description="The name of the source.")
71
73
  description: Optional[str] = Field(None, description="The description of the source.")
74
+ instructions: Optional[str] = Field(None, description="Instructions for how to use the source.")
72
75
  metadata: Optional[dict] = Field(None, description="Metadata associated with the source.")
73
76
  embedding_config: Optional[EmbeddingConfig] = Field(None, description="The embedding configuration used by the source.")
@@ -333,6 +333,7 @@ def start_server(
333
333
  port: Optional[int] = None,
334
334
  host: Optional[str] = None,
335
335
  debug: bool = False,
336
+ reload: bool = False,
336
337
  ):
337
338
  """Convenience method to start the server from within Python"""
338
339
  if debug:
@@ -356,7 +357,7 @@ def start_server(
356
357
  host=host or "localhost",
357
358
  port=port or REST_DEFAULT_PORT,
358
359
  workers=settings.uvicorn_workers,
359
- reload=settings.uvicorn_reload,
360
+ reload=reload or settings.uvicorn_reload,
360
361
  timeout_keep_alive=settings.uvicorn_timeout_keep_alive,
361
362
  ssl_keyfile="certs/localhost-key.pem",
362
363
  ssl_certfile="certs/localhost.pem",
@@ -375,6 +376,6 @@ def start_server(
375
376
  host=host or "localhost",
376
377
  port=port or REST_DEFAULT_PORT,
377
378
  workers=settings.uvicorn_workers,
378
- reload=settings.uvicorn_reload,
379
+ reload=reload or settings.uvicorn_reload,
379
380
  timeout_keep_alive=settings.uvicorn_timeout_keep_alive,
380
381
  )
@@ -482,6 +482,10 @@ class StreamingServerInterface(AgentChunkStreamingInterface):
482
482
 
483
483
  data: {"function_return": "None", "status": "success", "date": "2024-02-29T06:07:50.847262+00:00"}
484
484
  """
485
+ if not chunk.choices or len(chunk.choices) == 0:
486
+ warnings.warn(f"No choices in chunk: {chunk}")
487
+ return None
488
+
485
489
  choice = chunk.choices[0]
486
490
  message_delta = choice.delta
487
491
  otid = Message.generate_otid_from_id(message_id, message_index)
@@ -3,7 +3,7 @@ import traceback
3
3
  from datetime import datetime, timezone
4
4
  from typing import Annotated, Any, List, Optional
5
5
 
6
- from fastapi import APIRouter, BackgroundTasks, Body, Depends, File, Header, HTTPException, Query, UploadFile, status
6
+ from fastapi import APIRouter, BackgroundTasks, Body, Depends, File, Header, HTTPException, Query, Request, UploadFile, status
7
7
  from fastapi.responses import JSONResponse
8
8
  from marshmallow import ValidationError
9
9
  from orjson import orjson
@@ -619,6 +619,7 @@ def modify_message(
619
619
  )
620
620
  async def send_message(
621
621
  agent_id: str,
622
+ request_obj: Request, # FastAPI Request
622
623
  server: SyncServer = Depends(get_letta_server),
623
624
  request: LettaRequest = Body(...),
624
625
  actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
@@ -630,19 +631,12 @@ async def send_message(
630
631
  actor = server.user_manager.get_user_or_default(user_id=actor_id)
631
632
  # TODO: This is redundant, remove soon
632
633
  agent = server.agent_manager.get_agent_by_id(agent_id, actor)
634
+ agent_eligible = not agent.enable_sleeptime and not agent.multi_agent_group and agent.agent_type != AgentType.sleeptime_agent
635
+ experimental_header = request_obj.headers.get("x-experimental")
636
+ feature_enabled = settings.use_experimental or experimental_header
637
+ model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "google_vertex", "google_ai"]
633
638
 
634
- if all(
635
- (
636
- settings.use_experimental,
637
- not agent.enable_sleeptime,
638
- not agent.multi_agent_group,
639
- not agent.agent_type == AgentType.sleeptime_agent,
640
- )
641
- ) and (
642
- # LLM Model Check: (1) Anthropic or (2) Google Vertex + Flag
643
- agent.llm_config.model_endpoint_type == "anthropic"
644
- or (agent.llm_config.model_endpoint_type == "google_vertex" and settings.use_vertex_async_loop_experimental)
645
- ):
639
+ if agent_eligible and feature_enabled and model_compatible:
646
640
  experimental_agent = LettaAgent(
647
641
  agent_id=agent_id,
648
642
  message_manager=server.message_manager,
@@ -681,6 +675,7 @@ async def send_message(
681
675
  )
682
676
  async def send_message_streaming(
683
677
  agent_id: str,
678
+ request_obj: Request, # FastAPI Request
684
679
  server: SyncServer = Depends(get_letta_server),
685
680
  request: LettaStreamingRequest = Body(...),
686
681
  actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
@@ -694,14 +689,12 @@ async def send_message_streaming(
694
689
  actor = server.user_manager.get_user_or_default(user_id=actor_id)
695
690
  # TODO: This is redundant, remove soon
696
691
  agent = server.agent_manager.get_agent_by_id(agent_id, actor)
692
+ agent_eligible = not agent.enable_sleeptime and not agent.multi_agent_group and agent.agent_type != AgentType.sleeptime_agent
693
+ experimental_header = request_obj.headers.get("x-experimental")
694
+ feature_enabled = settings.use_experimental or experimental_header
695
+ model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai"]
697
696
 
698
- if (
699
- agent.llm_config.model_endpoint_type == "anthropic"
700
- and not agent.enable_sleeptime
701
- and not agent.multi_agent_group
702
- and not agent.agent_type == AgentType.sleeptime_agent
703
- and settings.use_experimental
704
- ):
697
+ if agent_eligible and feature_enabled and model_compatible:
705
698
  experimental_agent = LettaAgent(
706
699
  agent_id=agent_id,
707
700
  message_manager=server.message_manager,
@@ -72,7 +72,7 @@ async def sse_async_generator(
72
72
  ttft_span = None
73
73
  if request_start_timestamp_ns is not None:
74
74
  ttft_span = tracer.start_span("time_to_first_token", start_time=request_start_timestamp_ns)
75
- ttft_span.set_attributes({f"llm_config.{k}": v for k, v in llm_config.model_dump().items()})
75
+ ttft_span.set_attributes({f"llm_config.{k}": v for k, v in llm_config.model_dump().items() if v is not None})
76
76
 
77
77
  try:
78
78
  async for chunk in generator:
letta/server/server.py CHANGED
@@ -1219,6 +1219,9 @@ class SyncServer(Server):
1219
1219
  try:
1220
1220
  llm_models.extend(provider.list_llm_models())
1221
1221
  except Exception as e:
1222
+ import traceback
1223
+
1224
+ traceback.print_exc()
1222
1225
  warnings.warn(f"An error occurred while listing LLM models for provider {provider}: {e}")
1223
1226
 
1224
1227
  llm_models.extend(self.get_local_llm_configs())
@@ -866,9 +866,8 @@ class AgentManager:
866
866
  @enforce_types
867
867
  def trim_older_in_context_messages(self, num: int, agent_id: str, actor: PydanticUser) -> PydanticAgentState:
868
868
  message_ids = self.get_agent_by_id(agent_id=agent_id, actor=actor).message_ids
869
- newer_messages = self._trim_tool_response(agent_id=agent_id, actor=actor, message_ids=message_ids[num:])
870
- trimmed_messages = [message_ids[0]] + newer_messages # 0 is system message
871
- return self.set_in_context_messages(agent_id=agent_id, message_ids=trimmed_messages, actor=actor)
869
+ new_messages = [message_ids[0]] + message_ids[num:] # 0 is system message
870
+ return self.set_in_context_messages(agent_id=agent_id, message_ids=new_messages, actor=actor)
872
871
 
873
872
  @enforce_types
874
873
  def trim_all_in_context_messages_except_system(self, agent_id: str, actor: PydanticUser) -> PydanticAgentState:
@@ -877,16 +876,6 @@ class AgentManager:
877
876
  new_messages = [message_ids[0]] # 0 is system message
878
877
  return self.set_in_context_messages(agent_id=agent_id, message_ids=new_messages, actor=actor)
879
878
 
880
- def _trim_tool_response(self, agent_id: str, actor: PydanticUser, message_ids: list[str]) -> PydanticAgentState:
881
- """
882
- Trims the tool response from the in-context messages if there is no tool call present in trimmed messages.
883
- """
884
- if message_ids:
885
- messages = self.message_manager.get_messages_by_ids(message_ids=[message_ids[0]], actor=actor)
886
- if messages and messages[0].role == "tool":
887
- return message_ids[1:]
888
- return message_ids
889
-
890
879
  @enforce_types
891
880
  def prepend_to_in_context_messages(self, messages: List[PydanticMessage], agent_id: str, actor: PydanticUser) -> PydanticAgentState:
892
881
  message_ids = self.get_agent_by_id(agent_id=agent_id, actor=actor).message_ids
@@ -107,13 +107,9 @@ class Summarizer:
107
107
  self.summarizer_agent.update_message_transcript(message_transcripts=formatted_evicted_messages + formatted_in_context_messages)
108
108
 
109
109
  # Add line numbers to the formatted messages
110
- line_number = 0
111
- for i in range(len(formatted_evicted_messages)):
112
- formatted_evicted_messages[i] = f"{line_number}. " + formatted_evicted_messages[i]
113
- line_number += 1
114
- for i in range(len(formatted_in_context_messages)):
115
- formatted_in_context_messages[i] = f"{line_number}. " + formatted_in_context_messages[i]
116
- line_number += 1
110
+ offset = len(formatted_evicted_messages)
111
+ formatted_evicted_messages = [f"{i}. {msg}" for (i, msg) in enumerate(formatted_evicted_messages)]
112
+ formatted_in_context_messages = [f"{i + offset}. {msg}" for (i, msg) in enumerate(formatted_in_context_messages)]
117
113
 
118
114
  evicted_messages_str = "\n".join(formatted_evicted_messages)
119
115
  in_context_messages_str = "\n".join(formatted_in_context_messages)
letta/settings.py CHANGED
@@ -2,7 +2,7 @@ import os
2
2
  from pathlib import Path
3
3
  from typing import Optional
4
4
 
5
- from pydantic import Field
5
+ from pydantic import AliasChoices, Field
6
6
  from pydantic_settings import BaseSettings, SettingsConfigDict
7
7
 
8
8
  from letta.local_llm.constants import DEFAULT_WRAPPER_NAME
@@ -70,7 +70,13 @@ class ModelSettings(BaseSettings):
70
70
 
71
71
  # openai
72
72
  openai_api_key: Optional[str] = None
73
- openai_api_base: str = "https://api.openai.com/v1"
73
+ openai_api_base: str = Field(
74
+ default="https://api.openai.com/v1",
75
+ # NOTE: We previously used OPENAI_API_BASE, but this was deprecated in favor of OPENAI_BASE_URL
76
+ # preferred first, fallback second
77
+ # env=["OPENAI_BASE_URL", "OPENAI_API_BASE"], # pydantic-settings v2
78
+ validation_alias=AliasChoices("OPENAI_BASE_URL", "OPENAI_API_BASE"), # pydantic-settings v1
79
+ )
74
80
 
75
81
  # deepseek
76
82
  deepseek_api_key: Optional[str] = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: letta-nightly
3
- Version: 0.7.12.dev20250509104216
3
+ Version: 0.7.13.dev20250510172445
4
4
  Summary: Create LLM agents with long-term memory and custom tools
5
5
  License: Apache License
6
6
  Author: Letta Team
@@ -1,4 +1,4 @@
1
- letta/__init__.py,sha256=qYLzeNN1fIbaKf7rytyM_j256ng0pvDgtT9fPxM2bh4,916
1
+ letta/__init__.py,sha256=Y_QgodpFyn8eKt2ZRmuBOCwIS1xN_KmXBrjVm8KdcaQ,916
2
2
  letta/__main__.py,sha256=6Hs2PV7EYc5Tid4g4OtcLXhqVHiNYTGzSBdoOnW2HXA,29
3
3
  letta/agent.py,sha256=052xuXY4psY0BGDrtZ8XviOTpKhERSFrzKZnTsuIEvY,72125
4
4
  letta/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -12,7 +12,7 @@ letta/agents/voice_agent.py,sha256=YHaGCy3A35lB3bxXMXldIHUsXt76kC88Ti34EDs3zzA,2
12
12
  letta/agents/voice_sleeptime_agent.py,sha256=Joi3-8emTpV7v86OR_HGYXblkulrNaHhudCvPmMyXz0,7274
13
13
  letta/benchmark/benchmark.py,sha256=ebvnwfp3yezaXOQyGXkYCDYpsmre-b9hvNtnyx4xkG0,3701
14
14
  letta/benchmark/constants.py,sha256=aXc5gdpMGJT327VuxsT5FngbCK2J41PQYeICBO7g_RE,536
15
- letta/cli/cli.py,sha256=zJz78-qDUz-depb7VQWkg87RBKiETQU4h9DI6ukQBa8,16477
15
+ letta/cli/cli.py,sha256=Y3UC0mCvGR283sdegnqgq-6Fcwie59baXy3P0Cf0V-k,16569
16
16
  letta/cli/cli_config.py,sha256=MNMhIAAjXiAy2gX_gAtqiY0Ya6VNbzXJWjIcRVEZa-k,8597
17
17
  letta/cli/cli_load.py,sha256=vER0PwpHnsCZtCHcR2YjEXM-VVuO9jhfQibdo3gI3S0,2703
18
18
  letta/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -80,14 +80,14 @@ letta/llm_api/cohere.py,sha256=IZ6LXyOFMYjWHTeNG9lvFxCdV_NIl0hY2q9SPFYXNkQ,14849
80
80
  letta/llm_api/deepseek.py,sha256=b1mSW8gnBrpAI8d2GcBpDyLYDnuC-P1UP6xJPalfQS4,12456
81
81
  letta/llm_api/google_ai_client.py,sha256=9sKAuu3qGYt7IVGTb7krEUcVeSsPEE6x0V-jRacSgCg,24471
82
82
  letta/llm_api/google_constants.py,sha256=4PKWUNNbBHgHi4K5u9YaHr_8UC3fokfI6Qb6Dfpt4mU,693
83
- letta/llm_api/google_vertex_client.py,sha256=fXG-wSrooENdFxI6ahLAEbVrEBzHpNCYPtka7PMJ7_g,16032
84
- letta/llm_api/helpers.py,sha256=sLYv30UnKBRVPuhU_KDXfKFdbkUONiDAyVEwGr86l3A,16780
85
- letta/llm_api/llm_api_tools.py,sha256=Ytq50foJNiO69rC7KasZWfU1ZU2DP6xiR_Z76MEwVoU,28387
83
+ letta/llm_api/google_vertex_client.py,sha256=DAdqBUmHAYjhsBYGcL3HmFquSzII8Wwx4PWPMIw0Ru0,16044
84
+ letta/llm_api/helpers.py,sha256=rpZInutKVKgoywreclisNSi2zVxwFinAzJIuxF6ll4I,17041
85
+ letta/llm_api/llm_api_tools.py,sha256=YbslnPRFiJwaxRMhZ4T8K8SgByw73Ck61k3KNLScPr0,28995
86
86
  letta/llm_api/llm_client.py,sha256=qJ92biZVCXyRESzZX3ODeMd-kcgkYtJGdp6oVjV7PBc,2074
87
87
  letta/llm_api/llm_client_base.py,sha256=eID0J0l4VITZDL5765xEDvV8WLL4WzwQAzFEsC7ea6I,6067
88
88
  letta/llm_api/mistral.py,sha256=fHdfD9ug-rQIk2qn8tRKay1U6w9maF11ryhKi91FfXM,1593
89
- letta/llm_api/openai.py,sha256=KM70niO7i9hmr9agmhFvdk5D3GXWAmD9y82BqrHgijk,24583
90
- letta/llm_api/openai_client.py,sha256=8WpEyfGojIiFkNQVVXV6dVkq1bHWMx3aXIqQA38ovAI,14203
89
+ letta/llm_api/openai.py,sha256=KZ9UrL2aIC29o389f4yfF9_LP8UKYRyN4YUgBQuGhYU,25648
90
+ letta/llm_api/openai_client.py,sha256=NACYguMG_4nb5LqqdnR6XiD9RLqNLpn5TeHUzRL9VMc,15294
91
91
  letta/local_llm/README.md,sha256=hFJyw5B0TU2jrh9nb0zGZMgdH-Ei1dSRfhvPQG_NSoU,168
92
92
  letta/local_llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
93
93
  letta/local_llm/chat_completion_proxy.py,sha256=gc5gaKoHP8QaWRulDeEYPk7Onl8KdCBmpF2l9znXKeQ,13853
@@ -158,7 +158,7 @@ letta/orm/organization.py,sha256=STQ5x5zXoPhfagiRQX6j2lWgOqwznPp-K019MPjbY0s,359
158
158
  letta/orm/passage.py,sha256=luoQMBAm2DwWpcGtQziMDjDP5JZZNv1pnEmx-InNHFo,3090
159
159
  letta/orm/provider.py,sha256=KxIyUijtFapxXsgD86tWCRt1sG0TIETEyqlHEUWB7Fg,1312
160
160
  letta/orm/sandbox_config.py,sha256=DyOy_1_zCMlp13elCqPcuuA6OwUove6mrjhcpROTg50,4150
161
- letta/orm/source.py,sha256=z89VZUHV9K8Ew9JCYoZqUeRb1WEUKmrn0MMFkppaphE,2117
161
+ letta/orm/source.py,sha256=rtehzez80rRrJigXeRBgTlfTZEUy6cVqDizWEN2tvuY,2224
162
162
  letta/orm/sources_agents.py,sha256=Ik_PokCBrXRd9wXWomeNeb8EtLUwjb9VMZ8LWXqpK5A,473
163
163
  letta/orm/sqlalchemy_base.py,sha256=GAYUfniRNWkSVkhj6I1e5yzfy5H93aopBY72x8kO-cg,27238
164
164
  letta/orm/sqlite_functions.py,sha256=JCScKiRlYCKxy9hChQ8wsk4GMKknZE24MunnG3fM1Gw,4255
@@ -218,22 +218,22 @@ letta/schemas/letta_message_content.py,sha256=eF2UEDofQx7S_nS1jc9MZypP4EGVWj7zdi
218
218
  letta/schemas/letta_request.py,sha256=acGJDmrv804Xe0432Vnk6_yD8aUt4LZwK9lQKm56uss,1943
219
219
  letta/schemas/letta_response.py,sha256=lZ_uCwSmV7g7UCBxNyU_r-FFcXAf6_pBsFqgWJ-WkBs,7955
220
220
  letta/schemas/llm_batch_job.py,sha256=i8m58-EFF0xGD7cYfu-LRlbvYZwv5y2B14ckmuRQ_IM,2896
221
- letta/schemas/llm_config.py,sha256=mMHIuAwtQyvtOo4MfOvvva8cR90dyapyMGGXidSOxnE,8705
221
+ letta/schemas/llm_config.py,sha256=0sXsJm4fYHsggfXhkJMpeiiZ62xtGPOqr-nPlqmWN7c,8651
222
222
  letta/schemas/llm_config_overrides.py,sha256=E6qJuVA8TwAAy3VjGitJ5jSQo5PbN-6VPcZOF5qhP9A,1815
223
223
  letta/schemas/memory.py,sha256=GOYDfPKzbWftUWO9Hv4KW7xAi1EIQmC8zpP7qvEkVHw,10245
224
224
  letta/schemas/message.py,sha256=qHGNuhymW_qAbuPcb0CPRUYrrlBlWj5Pu4Oh2b7ag7Q,51143
225
225
  letta/schemas/openai/chat_completion_request.py,sha256=XARKB7717Crt3P2A53eeBZ6hlNJcb9TJHosWwK17tFw,4210
226
- letta/schemas/openai/chat_completion_response.py,sha256=7SsfVNsWq_EUajmckU5KjFClkK0iXZF2jHWZZ0nr6T4,6701
226
+ letta/schemas/openai/chat_completion_response.py,sha256=f2JZWh5mVz9pk19Iji8EnQD9NKq-sI2SVQkPeZHzzTQ,7067
227
227
  letta/schemas/openai/chat_completions.py,sha256=l0e9sT9boTD5VBU5YtJ0s7qUtCfFGB2K-gQLeEZ2LHU,3599
228
228
  letta/schemas/openai/embedding_response.py,sha256=WKIZpXab1Av7v6sxKG8feW3ZtpQUNosmLVSuhXYa_xU,357
229
229
  letta/schemas/openai/openai.py,sha256=Hilo5BiLAGabzxCwnwfzK5QrWqwYD8epaEKFa4Pwndk,7970
230
230
  letta/schemas/organization.py,sha256=TXrHN4IBQnX-mWvRuCOH57XZSLYCVOY0wWm2_UzDQIA,1279
231
231
  letta/schemas/passage.py,sha256=RG0vkaewEu4a_NAZM-FVyMammHjqpPP0RDYAdu27g6A,3723
232
- letta/schemas/providers.py,sha256=QhOhjBpgf356rTG57ht3hDE6jzJ4hXWAUGHul4kPvBA,53510
232
+ letta/schemas/providers.py,sha256=8lso7jhcnnqyzYspp1C3NZSqqJ9YF51qGsRKoetWJYQ,56895
233
233
  letta/schemas/response_format.py,sha256=pXNsjbtpA3Tf8HsDyIa40CSmoUbVR_7n2WOfQaX4aFs,2204
234
234
  letta/schemas/run.py,sha256=SRqPRziINIiPunjOhE_NlbnQYgxTvqmbauni_yfBQRA,2085
235
235
  letta/schemas/sandbox_config.py,sha256=Qfkzw422HCQUsE3GKry94oecQGziAzGXIyd6ke8W06M,5985
236
- letta/schemas/source.py,sha256=IuenIFs7B8uOuYJIHXqR1E28wVSa-pUX6NkLZH7cukg,3141
236
+ letta/schemas/source.py,sha256=ZDeTjkNp1rKamG7xZzoUHeCptjpW9WNLzAcJ9QQRxlM,3444
237
237
  letta/schemas/step.py,sha256=WkcVnruUUOWLKwiWPn2Gfal4EQZPNLqlsd9859xhgsw,2224
238
238
  letta/schemas/tool.py,sha256=k8dmXfYA_gcipAjBAwBYWsmhlkGvPnOBqrPG336QpBM,13111
239
239
  letta/schemas/tool_execution_result.py,sha256=O65Z-gwNuB2hrX0Vklw_A9uURu0sz67gxTDr1tNwvJM,804
@@ -255,18 +255,18 @@ letta/server/constants.py,sha256=yAdGbLkzlOU_dLTx0lKDmAnj0ZgRXCEaIcPJWO69eaE,92
255
255
  letta/server/db.py,sha256=Jt_lWUvqTWFHfgsWUuXcDlGD3yejNBjY1P4J2vI3kL0,4935
256
256
  letta/server/generate_openapi_schema.sh,sha256=0OtBhkC1g6CobVmNEd_m2B6sTdppjbJLXaM95icejvE,371
257
257
  letta/server/rest_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
258
- letta/server/rest_api/app.py,sha256=ZLtTm2HZwctfM69ujF433xK2Bx9ZILHyYSBnxdRQtcY,14669
258
+ letta/server/rest_api/app.py,sha256=WoAfqDrSkb86Wq0lAvTWJqoxys9e-kgKaS11sGb8Xzg,14715
259
259
  letta/server/rest_api/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
260
260
  letta/server/rest_api/auth/index.py,sha256=fQBGyVylGSRfEMLQ17cZzrHd5Y1xiVylvPqH5Rl-lXQ,1378
261
261
  letta/server/rest_api/auth_token.py,sha256=725EFEIiNj4dh70hrSd94UysmFD8vcJLrTRfNHkzxDo,774
262
262
  letta/server/rest_api/chat_completions_interface.py,sha256=90VOlJ2HTxZtQMDt3aCbrlbGLouj4qHkfEz6PwXtrkc,11089
263
- letta/server/rest_api/interface.py,sha256=SdRalftQawKjmZL8x2wRtZ1LJ9wLyE00isIfmNyDKQY,65788
263
+ letta/server/rest_api/interface.py,sha256=ypBXEySUQ0tR-CSKfn3RiK28uOggB5gjA_mq3uRMKW4,65929
264
264
  letta/server/rest_api/json_parser.py,sha256=IKG2xFAz_wkLfd3Z-18SKykSzCtUDjdYgTKSKaMqj1I,7813
265
265
  letta/server/rest_api/routers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
266
266
  letta/server/rest_api/routers/openai/chat_completions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
267
267
  letta/server/rest_api/routers/openai/chat_completions/chat_completions.py,sha256=QBWab1fn2LXVDMtc6li3gOzmrNzDiUw5WUJsMeeMZII,5076
268
268
  letta/server/rest_api/routers/v1/__init__.py,sha256=_skmAcDOK9ovHKfywRaBgigo3IvPmnUSQSR2hGVCOhY,1664
269
- letta/server/rest_api/routers/v1/agents.py,sha256=ugWHhjqH26qEvS8N1ihPTxvoGsKXLNzBy8cplxPpGgQ,35052
269
+ letta/server/rest_api/routers/v1/agents.py,sha256=SzRxDV9eXG0LieBZ0OSLse_Iu7LHlWNqPtuk_ri2-tU,35286
270
270
  letta/server/rest_api/routers/v1/blocks.py,sha256=jrDpSYrEgHaGvlnUCn6wczgWnCZa3ZyHVL5NQv2KJNE,4471
271
271
  letta/server/rest_api/routers/v1/embeddings.py,sha256=P-Dvt_HNKoTyjRwkScAMg1hlB3cNxMeAQwV7bSatsKI,957
272
272
  letta/server/rest_api/routers/v1/groups.py,sha256=JI9ShKewoE8lB58OP02NuAT7eUzPfqSG7y44a6tBh9s,10710
@@ -286,8 +286,8 @@ letta/server/rest_api/routers/v1/tools.py,sha256=FXFx8J4Zs-pZ1H8andFzI5Pyv-PJkY8
286
286
  letta/server/rest_api/routers/v1/users.py,sha256=G5DBHSkPfBgVHN2Wkm-rVYiLQAudwQczIq2Z3YLdbVo,2277
287
287
  letta/server/rest_api/routers/v1/voice.py,sha256=nSwjoW5Hi9EdScGyRWXpGVooAS0X2G-mOrpLUz0NqNs,1935
288
288
  letta/server/rest_api/static_files.py,sha256=NG8sN4Z5EJ8JVQdj19tkFa9iQ1kBPTab9f_CUxd_u4Q,3143
289
- letta/server/rest_api/utils.py,sha256=sUHtcmbs9yl7__jvtUHzfhiZV1mzU7r5SzrGTJhRIC4,16499
290
- letta/server/server.py,sha256=1PUlT22tT0ITfoZ2V66OgHKPSlIaydMJrWcMNXFftDU,86522
289
+ letta/server/rest_api/utils.py,sha256=n5ZwtCtF3Oa4b9NFQ8l9f13v4eOI4mWdWNQqFp5d3A0,16516
290
+ letta/server/server.py,sha256=fWg6a01RaaWkjguO6tzehlJWWycs5H7azLve5XJ0MiQ,86594
291
291
  letta/server/startup.sh,sha256=MRXh1RKbS5lyA7XAsk7O6Q4LEKOqnv5B-dwe0SnTHeQ,2514
292
292
  letta/server/static_files/assets/index-048c9598.js,sha256=mR16XppvselwKCcNgONs4L7kZEVa4OEERm4lNZYtLSk,146819
293
293
  letta/server/static_files/assets/index-0e31b727.css,sha256=SBbja96uiQVLDhDOroHgM6NSl7tS4lpJRCREgSS_hA8,7672
@@ -301,7 +301,7 @@ letta/server/ws_api/interface.py,sha256=TWl9vkcMCnLsUtgsuENZ-ku2oMDA-OUTzLh_yNRo
301
301
  letta/server/ws_api/protocol.py,sha256=5mDgpfNZn_kNwHnpt5Dsuw8gdNH298sgxTGed3etzYg,1836
302
302
  letta/server/ws_api/server.py,sha256=cBSzf-V4zT1bL_0i54OTI3cMXhTIIxqjSRF8pYjk7fg,5835
303
303
  letta/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
304
- letta/services/agent_manager.py,sha256=AB4yV70NNCzeKR7tR1CD4lqxlJUgPvKLBXOCdsZ6Rik,72665
304
+ letta/services/agent_manager.py,sha256=FfMRz0Ewebn0OGhb9adBxga06-da3YmQ2qlh1BHOXUI,72037
305
305
  letta/services/block_manager.py,sha256=rAwOX9MYGSWYlzQsUtS_UXJwoh7jD-5O6aL8VWnA7fw,17327
306
306
  letta/services/group_manager.py,sha256=EKYeD3MyJknpZBsH850pqIPZJmsphgEfI8hL-uOZbvU,15896
307
307
  letta/services/helpers/agent_manager_helper.py,sha256=2W9DpxGOx3rK2LnpGDtQmBJh9u9sKZ_xwAUAYzAMyS0,20350
@@ -324,7 +324,7 @@ letta/services/source_manager.py,sha256=yW88wAJoeAWtbg0FxifE352jhgOTKNiG7K-IPKXK
324
324
  letta/services/step_manager.py,sha256=B64iYn6Dt9yRKsSJ5vLxWQR2t-apvPLfUZyzrUsJTpI,5335
325
325
  letta/services/summarizer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
326
326
  letta/services/summarizer/enums.py,sha256=szzPX2OBRRJEZsBTGYQThrNz02ELFqhuLwvOR7ozi7A,208
327
- letta/services/summarizer/summarizer.py,sha256=cbBleCAOi4WFmU226xZ1oEiBeCGdLKQaIhYZBRnA3u0,7791
327
+ letta/services/summarizer/summarizer.py,sha256=LVFZ8NLtS1lz4Pdr7M_cixFYBYsllt2vXNzTOT3Wkxc,7658
328
328
  letta/services/tool_executor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
329
329
  letta/services/tool_executor/tool_execution_manager.py,sha256=NLkHLxxMYp0XKgu8j6YgqBVkhlG1cOioNRTd4muKJyU,4537
330
330
  letta/services/tool_executor/tool_execution_sandbox.py,sha256=9gcERJ15Y6u_6AGAVH3KGJP-85fpr2HLCV8SatQAG0w,24891
@@ -335,14 +335,14 @@ letta/services/tool_sandbox/base.py,sha256=pUnPFkEg9I5ktMuT4AOOxbTnTmZTGcTA2phLe
335
335
  letta/services/tool_sandbox/e2b_sandbox.py,sha256=umsXfolzM_j67izswECDdVfnlcm03wLpMoZtS6SZ0sc,6147
336
336
  letta/services/tool_sandbox/local_sandbox.py,sha256=ksbraC-zcMWt3vS7kSi98uWI9L73I0h73rMayhuTWsw,10474
337
337
  letta/services/user_manager.py,sha256=_aoiQy73B4Jm_uVDrfAUrg2TnOYa_tJLRUwa3fF5ASY,4429
338
- letta/settings.py,sha256=B7oKONj9H8isY-0C12aQNmdxHmv66YK2Dq8ZSZPK71Q,8295
338
+ letta/settings.py,sha256=2SCkE8SppUQmhHWrytR52bbKEJcQmJzkzHNFfGCdsK8,8665
339
339
  letta/streaming_interface.py,sha256=kDSc5bnodgGzAuLcnq4Zf7p-uS6cdyxSIZ5U_JA_8FU,16300
340
340
  letta/streaming_utils.py,sha256=jLqFTVhUL76FeOuYk8TaRQHmPTf3HSRc2EoJwxJNK6U,11946
341
341
  letta/system.py,sha256=mKxmvvekuP8mdgsebRINGBoFbUdJhxLJ260crPBNVyk,8386
342
342
  letta/tracing.py,sha256=j9uyBbx02erQZ307XmZmZSNyzQt-d7ZDB7vhFhjDlsU,8448
343
343
  letta/utils.py,sha256=IZFvtj9WYcrxUbkoUUYGDxMYQYdn5SgfqsvnARGsAzc,32245
344
- letta_nightly-0.7.12.dev20250509104216.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
345
- letta_nightly-0.7.12.dev20250509104216.dist-info/METADATA,sha256=7wpN1QJCb_XwnNKRsa60DkRz2wCwZeszgqiklDg3HA4,22232
346
- letta_nightly-0.7.12.dev20250509104216.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
347
- letta_nightly-0.7.12.dev20250509104216.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
348
- letta_nightly-0.7.12.dev20250509104216.dist-info/RECORD,,
344
+ letta_nightly-0.7.13.dev20250510172445.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
345
+ letta_nightly-0.7.13.dev20250510172445.dist-info/METADATA,sha256=lKByluI4-V-R3o79HRcpep7MK7-Y_b8lyiGth_U8rNk,22232
346
+ letta_nightly-0.7.13.dev20250510172445.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
347
+ letta_nightly-0.7.13.dev20250510172445.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
348
+ letta_nightly-0.7.13.dev20250510172445.dist-info/RECORD,,