letta-nightly 0.11.7.dev20250911104039__py3-none-any.whl → 0.11.7.dev20250912104045__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/agents/letta_agent_v2.py +29 -8
- letta/helpers/tpuf_client.py +41 -9
- letta/interfaces/openai_streaming_interface.py +9 -74
- letta/llm_api/google_vertex_client.py +5 -0
- letta/llm_api/openai_client.py +9 -8
- letta/orm/job.py +5 -1
- letta/orm/organization.py +2 -0
- letta/server/rest_api/redis_stream_manager.py +15 -2
- letta/server/rest_api/routers/v1/agents.py +2 -0
- letta/server/rest_api/routers/v1/tools.py +23 -39
- letta/services/job_manager.py +5 -2
- letta/services/mcp_manager.py +64 -3
- letta/services/tool_executor/files_tool_executor.py +2 -2
- {letta_nightly-0.11.7.dev20250911104039.dist-info → letta_nightly-0.11.7.dev20250912104045.dist-info}/METADATA +3 -3
- {letta_nightly-0.11.7.dev20250911104039.dist-info → letta_nightly-0.11.7.dev20250912104045.dist-info}/RECORD +18 -18
- {letta_nightly-0.11.7.dev20250911104039.dist-info → letta_nightly-0.11.7.dev20250912104045.dist-info}/WHEEL +0 -0
- {letta_nightly-0.11.7.dev20250911104039.dist-info → letta_nightly-0.11.7.dev20250912104045.dist-info}/entry_points.txt +0 -0
- {letta_nightly-0.11.7.dev20250911104039.dist-info → letta_nightly-0.11.7.dev20250912104045.dist-info}/licenses/LICENSE +0 -0
letta/agents/letta_agent_v2.py
CHANGED
@@ -213,8 +213,17 @@ class LettaAgentV2(BaseAgentV2):
|
|
213
213
|
|
214
214
|
if self.stop_reason is None:
|
215
215
|
self.stop_reason = LettaStopReason(stop_reason=StopReasonType.end_turn.value)
|
216
|
-
|
217
|
-
|
216
|
+
|
217
|
+
result = LettaResponse(messages=response_letta_messages, stop_reason=self.stop_reason, usage=self.usage)
|
218
|
+
if run_id:
|
219
|
+
if self.job_update_metadata is None:
|
220
|
+
self.job_update_metadata = {}
|
221
|
+
self.job_update_metadata["result"] = result.model_dump(mode="json")
|
222
|
+
|
223
|
+
await self._request_checkpoint_finish(
|
224
|
+
request_span=request_span, request_start_timestamp_ns=request_start_timestamp_ns, run_id=run_id
|
225
|
+
)
|
226
|
+
return result
|
218
227
|
|
219
228
|
@trace_method
|
220
229
|
async def stream(
|
@@ -301,7 +310,20 @@ class LettaAgentV2(BaseAgentV2):
|
|
301
310
|
yield f"data: {self.stop_reason.model_dump_json()}\n\n"
|
302
311
|
raise
|
303
312
|
|
304
|
-
|
313
|
+
if run_id:
|
314
|
+
letta_messages = Message.to_letta_messages_from_list(
|
315
|
+
self.response_messages,
|
316
|
+
use_assistant_message=use_assistant_message,
|
317
|
+
reverse=False,
|
318
|
+
)
|
319
|
+
result = LettaResponse(messages=letta_messages, stop_reason=self.stop_reason, usage=self.usage)
|
320
|
+
if self.job_update_metadata is None:
|
321
|
+
self.job_update_metadata = {}
|
322
|
+
self.job_update_metadata["result"] = result.model_dump(mode="json")
|
323
|
+
|
324
|
+
await self._request_checkpoint_finish(
|
325
|
+
request_span=request_span, request_start_timestamp_ns=request_start_timestamp_ns, run_id=run_id
|
326
|
+
)
|
305
327
|
for finish_chunk in self.get_finish_chunks_for_stream(self.usage, self.stop_reason):
|
306
328
|
yield f"data: {finish_chunk}\n\n"
|
307
329
|
|
@@ -736,11 +758,10 @@ class LettaAgentV2(BaseAgentV2):
|
|
736
758
|
return None
|
737
759
|
|
738
760
|
@trace_method
|
739
|
-
def _request_checkpoint_finish(
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
request_span.end()
|
761
|
+
async def _request_checkpoint_finish(
|
762
|
+
self, request_span: Span | None, request_start_timestamp_ns: int | None, run_id: str | None
|
763
|
+
) -> None:
|
764
|
+
await self._log_request(request_start_timestamp_ns, request_span, self.job_update_metadata, is_error=False, run_id=run_id)
|
744
765
|
return None
|
745
766
|
|
746
767
|
@trace_method
|
letta/helpers/tpuf_client.py
CHANGED
@@ -62,11 +62,18 @@ class TurbopufferClient:
|
|
62
62
|
"""
|
63
63
|
from letta.llm_api.llm_client import LLMClient
|
64
64
|
|
65
|
+
# filter out empty strings after stripping
|
66
|
+
filtered_texts = [text for text in texts if text.strip()]
|
67
|
+
|
68
|
+
# skip embedding if no valid texts
|
69
|
+
if not filtered_texts:
|
70
|
+
return []
|
71
|
+
|
65
72
|
embedding_client = LLMClient.create(
|
66
73
|
provider_type=self.default_embedding_config.embedding_endpoint_type,
|
67
74
|
actor=actor,
|
68
75
|
)
|
69
|
-
embeddings = await embedding_client.request_embeddings(
|
76
|
+
embeddings = await embedding_client.request_embeddings(filtered_texts, self.default_embedding_config)
|
70
77
|
return embeddings
|
71
78
|
|
72
79
|
@trace_method
|
@@ -119,8 +126,16 @@ class TurbopufferClient:
|
|
119
126
|
"""
|
120
127
|
from turbopuffer import AsyncTurbopuffer
|
121
128
|
|
129
|
+
# filter out empty text chunks
|
130
|
+
filtered_chunks = [(i, text) for i, text in enumerate(text_chunks) if text.strip()]
|
131
|
+
|
132
|
+
if not filtered_chunks:
|
133
|
+
logger.warning("All text chunks were empty, skipping insertion")
|
134
|
+
return []
|
135
|
+
|
122
136
|
# generate embeddings using the default config
|
123
|
-
|
137
|
+
filtered_texts = [text for _, text in filtered_chunks]
|
138
|
+
embeddings = await self._generate_embeddings(filtered_texts, actor)
|
124
139
|
|
125
140
|
namespace_name = await self._get_archive_namespace_name(archive_id)
|
126
141
|
|
@@ -152,8 +167,8 @@ class TurbopufferClient:
|
|
152
167
|
tags_arrays = [] # Store tags as arrays
|
153
168
|
passages = []
|
154
169
|
|
155
|
-
for
|
156
|
-
passage_id = passage_ids[
|
170
|
+
for (original_idx, text), embedding in zip(filtered_chunks, embeddings):
|
171
|
+
passage_id = passage_ids[original_idx]
|
157
172
|
|
158
173
|
# append to columns
|
159
174
|
ids.append(passage_id)
|
@@ -240,8 +255,16 @@ class TurbopufferClient:
|
|
240
255
|
"""
|
241
256
|
from turbopuffer import AsyncTurbopuffer
|
242
257
|
|
258
|
+
# filter out empty message texts
|
259
|
+
filtered_messages = [(i, text) for i, text in enumerate(message_texts) if text.strip()]
|
260
|
+
|
261
|
+
if not filtered_messages:
|
262
|
+
logger.warning("All message texts were empty, skipping insertion")
|
263
|
+
return True
|
264
|
+
|
243
265
|
# generate embeddings using the default config
|
244
|
-
|
266
|
+
filtered_texts = [text for _, text in filtered_messages]
|
267
|
+
embeddings = await self._generate_embeddings(filtered_texts, actor)
|
245
268
|
|
246
269
|
namespace_name = await self._get_message_namespace_name(organization_id)
|
247
270
|
|
@@ -266,8 +289,10 @@ class TurbopufferClient:
|
|
266
289
|
project_ids = []
|
267
290
|
template_ids = []
|
268
291
|
|
269
|
-
for
|
270
|
-
message_id = message_ids[
|
292
|
+
for (original_idx, text), embedding in zip(filtered_messages, embeddings):
|
293
|
+
message_id = message_ids[original_idx]
|
294
|
+
role = roles[original_idx]
|
295
|
+
created_at = created_ats[original_idx]
|
271
296
|
|
272
297
|
# ensure the provided timestamp is timezone-aware and in UTC
|
273
298
|
if created_at.tzinfo is None:
|
@@ -1162,8 +1187,15 @@ class TurbopufferClient:
|
|
1162
1187
|
if not text_chunks:
|
1163
1188
|
return []
|
1164
1189
|
|
1190
|
+
# filter out empty text chunks
|
1191
|
+
filtered_chunks = [text for text in text_chunks if text.strip()]
|
1192
|
+
|
1193
|
+
if not filtered_chunks:
|
1194
|
+
logger.warning("All text chunks were empty, skipping file passage insertion")
|
1195
|
+
return []
|
1196
|
+
|
1165
1197
|
# generate embeddings using the default config
|
1166
|
-
embeddings = await self._generate_embeddings(
|
1198
|
+
embeddings = await self._generate_embeddings(filtered_chunks, actor)
|
1167
1199
|
|
1168
1200
|
namespace_name = await self._get_file_passages_namespace_name(organization_id)
|
1169
1201
|
|
@@ -1189,7 +1221,7 @@ class TurbopufferClient:
|
|
1189
1221
|
created_ats = []
|
1190
1222
|
passages = []
|
1191
1223
|
|
1192
|
-
for
|
1224
|
+
for text, embedding in zip(filtered_chunks, embeddings):
|
1193
1225
|
passage = PydanticPassage(
|
1194
1226
|
text=text,
|
1195
1227
|
file_id=file_id,
|
@@ -24,7 +24,7 @@ from letta.schemas.letta_stop_reason import LettaStopReason, StopReasonType
|
|
24
24
|
from letta.schemas.message import Message
|
25
25
|
from letta.schemas.openai.chat_completion_response import FunctionCall, ToolCall
|
26
26
|
from letta.server.rest_api.json_parser import OptimisticJSONParser
|
27
|
-
from letta.streaming_utils import JSONInnerThoughtsExtractor
|
27
|
+
from letta.streaming_utils import FunctionArgumentsStreamHandler, JSONInnerThoughtsExtractor
|
28
28
|
from letta.utils import count_tokens
|
29
29
|
|
30
30
|
logger = get_logger(__name__)
|
@@ -53,6 +53,8 @@ class OpenAIStreamingInterface:
|
|
53
53
|
|
54
54
|
self.optimistic_json_parser: OptimisticJSONParser = OptimisticJSONParser()
|
55
55
|
self.function_args_reader = JSONInnerThoughtsExtractor(wait_for_first_key=put_inner_thoughts_in_kwarg)
|
56
|
+
# Reader that extracts only the assistant message value from send_message args
|
57
|
+
self.assistant_message_json_reader = FunctionArgumentsStreamHandler(json_key=self.assistant_message_tool_kwarg)
|
56
58
|
self.function_name_buffer = None
|
57
59
|
self.function_args_buffer = None
|
58
60
|
self.function_id_buffer = None
|
@@ -274,6 +276,8 @@ class OpenAIStreamingInterface:
|
|
274
276
|
# Store the ID of the tool call so allow skipping the corresponding response
|
275
277
|
if self.function_id_buffer:
|
276
278
|
self.prev_assistant_message_id = self.function_id_buffer
|
279
|
+
# Reset message reader at the start of a new send_message stream
|
280
|
+
self.assistant_message_json_reader.reset()
|
277
281
|
|
278
282
|
else:
|
279
283
|
if prev_message_type and prev_message_type != "tool_call_message":
|
@@ -328,39 +332,15 @@ class OpenAIStreamingInterface:
|
|
328
332
|
self.last_flushed_function_name is not None
|
329
333
|
and self.last_flushed_function_name == self.assistant_message_tool_name
|
330
334
|
):
|
331
|
-
#
|
332
|
-
|
333
|
-
|
334
|
-
self.function_args_buffer = None
|
335
|
-
|
336
|
-
# Pretty gross hardcoding that assumes that if we're toggling into the keywords, we have the full prefix
|
337
|
-
match_str = '{"' + self.assistant_message_tool_kwarg + '":"'
|
338
|
-
if updates_main_json == match_str:
|
339
|
-
updates_main_json = None
|
340
|
-
|
341
|
-
else:
|
342
|
-
# Some hardcoding to strip off the trailing "}"
|
343
|
-
if updates_main_json in ["}", '"}']:
|
344
|
-
updates_main_json = None
|
345
|
-
if updates_main_json and len(updates_main_json) > 0 and updates_main_json[-1:] == '"':
|
346
|
-
updates_main_json = updates_main_json[:-1]
|
347
|
-
|
348
|
-
if not updates_main_json:
|
349
|
-
# early exit to turn into content mode
|
350
|
-
pass
|
351
|
-
|
352
|
-
# There may be a buffer from a previous chunk, for example
|
353
|
-
# if the previous chunk had arguments but we needed to flush name
|
354
|
-
if self.function_args_buffer:
|
355
|
-
# In this case, we should release the buffer + new data at once
|
356
|
-
combined_chunk = self.function_args_buffer + updates_main_json
|
357
|
-
|
335
|
+
# Minimal, robust extraction: only emit the value of "message"
|
336
|
+
extracted = self.assistant_message_json_reader.process_json_chunk(tool_call.function.arguments)
|
337
|
+
if extracted:
|
358
338
|
if prev_message_type and prev_message_type != "assistant_message":
|
359
339
|
message_index += 1
|
360
340
|
assistant_message = AssistantMessage(
|
361
341
|
id=self.letta_message_id,
|
362
342
|
date=datetime.now(timezone.utc),
|
363
|
-
content=
|
343
|
+
content=extracted,
|
364
344
|
otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
|
365
345
|
)
|
366
346
|
prev_message_type = assistant_message.message_type
|
@@ -368,51 +348,6 @@ class OpenAIStreamingInterface:
|
|
368
348
|
# Store the ID of the tool call so allow skipping the corresponding response
|
369
349
|
if self.function_id_buffer:
|
370
350
|
self.prev_assistant_message_id = self.function_id_buffer
|
371
|
-
# clear buffer
|
372
|
-
self.function_args_buffer = None
|
373
|
-
self.function_id_buffer = None
|
374
|
-
|
375
|
-
else:
|
376
|
-
# If there's no buffer to clear, just output a new chunk with new data
|
377
|
-
# TODO: THIS IS HORRIBLE
|
378
|
-
# TODO: WE USE THE OLD JSON PARSER EARLIER (WHICH DOES NOTHING) AND NOW THE NEW JSON PARSER
|
379
|
-
# TODO: THIS IS TOTALLY WRONG AND BAD, BUT SAVING FOR A LARGER REWRITE IN THE NEAR FUTURE
|
380
|
-
parsed_args = self.optimistic_json_parser.parse(self.current_function_arguments)
|
381
|
-
|
382
|
-
if parsed_args.get(self.assistant_message_tool_kwarg) and parsed_args.get(
|
383
|
-
self.assistant_message_tool_kwarg
|
384
|
-
) != self.current_json_parse_result.get(self.assistant_message_tool_kwarg):
|
385
|
-
new_content = parsed_args.get(self.assistant_message_tool_kwarg)
|
386
|
-
prev_content = self.current_json_parse_result.get(self.assistant_message_tool_kwarg, "")
|
387
|
-
# TODO: Assumes consistent state and that prev_content is subset of new_content
|
388
|
-
diff = new_content.replace(prev_content, "", 1)
|
389
|
-
|
390
|
-
# quick patch to mitigate double message streaming error
|
391
|
-
# TODO: root cause this issue and remove patch
|
392
|
-
if diff != "" and "\\n" not in new_content:
|
393
|
-
converted_new_content = new_content.replace("\n", "\\n")
|
394
|
-
converted_content_diff = converted_new_content.replace(prev_content, "", 1)
|
395
|
-
if converted_content_diff == "":
|
396
|
-
diff = converted_content_diff
|
397
|
-
|
398
|
-
self.current_json_parse_result = parsed_args
|
399
|
-
if prev_message_type and prev_message_type != "assistant_message":
|
400
|
-
message_index += 1
|
401
|
-
assistant_message = AssistantMessage(
|
402
|
-
id=self.letta_message_id,
|
403
|
-
date=datetime.now(timezone.utc),
|
404
|
-
content=diff,
|
405
|
-
# name=name,
|
406
|
-
otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
|
407
|
-
)
|
408
|
-
prev_message_type = assistant_message.message_type
|
409
|
-
yield assistant_message
|
410
|
-
|
411
|
-
# Store the ID of the tool call so allow skipping the corresponding response
|
412
|
-
if self.function_id_buffer:
|
413
|
-
self.prev_assistant_message_id = self.function_id_buffer
|
414
|
-
# clear buffers
|
415
|
-
self.function_id_buffer = None
|
416
351
|
else:
|
417
352
|
# There may be a buffer from a previous chunk, for example
|
418
353
|
# if the previous chunk had arguments but we needed to flush name
|
@@ -67,6 +67,7 @@ class GoogleVertexClient(LLMClientBase):
|
|
67
67
|
# https://github.com/googleapis/python-aiplatform/issues/4472
|
68
68
|
retry_count = 1
|
69
69
|
should_retry = True
|
70
|
+
response_data = None
|
70
71
|
while should_retry and retry_count <= self.MAX_RETRIES:
|
71
72
|
try:
|
72
73
|
response = await client.aio.models.generate_content(
|
@@ -79,6 +80,8 @@ class GoogleVertexClient(LLMClientBase):
|
|
79
80
|
if e.code == 503 or e.code == 500:
|
80
81
|
logger.warning(f"Received {e}, retrying {retry_count}/{self.MAX_RETRIES}")
|
81
82
|
retry_count += 1
|
83
|
+
if retry_count > self.MAX_RETRIES:
|
84
|
+
raise e
|
82
85
|
continue
|
83
86
|
raise e
|
84
87
|
except Exception as e:
|
@@ -114,6 +117,8 @@ class GoogleVertexClient(LLMClientBase):
|
|
114
117
|
should_retry = is_malformed_function_call
|
115
118
|
retry_count += 1
|
116
119
|
|
120
|
+
if response_data is None:
|
121
|
+
raise RuntimeError("Failed to get response data after all retries")
|
117
122
|
return response_data
|
118
123
|
|
119
124
|
@staticmethod
|
letta/llm_api/openai_client.py
CHANGED
@@ -198,14 +198,15 @@ class OpenAIClient(LLMClientBase):
|
|
198
198
|
# TODO(matt) move into LLMConfig
|
199
199
|
# TODO: This vllm checking is very brittle and is a patch at most
|
200
200
|
tool_choice = None
|
201
|
-
if
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
201
|
+
if tools: # only set tool_choice if tools exist
|
202
|
+
if self.requires_auto_tool_choice(llm_config):
|
203
|
+
tool_choice = "auto"
|
204
|
+
else:
|
205
|
+
# only set if tools is non-Null
|
206
|
+
tool_choice = "required"
|
207
|
+
|
208
|
+
if force_tool_call is not None:
|
209
|
+
tool_choice = ToolFunctionChoice(type="function", function=ToolFunctionChoiceFunctionCall(name=force_tool_call))
|
209
210
|
|
210
211
|
data = ChatCompletionRequest(
|
211
212
|
model=model,
|
letta/orm/job.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
from datetime import datetime
|
2
2
|
from typing import TYPE_CHECKING, List, Optional
|
3
3
|
|
4
|
-
from sqlalchemy import JSON, BigInteger, Index, String
|
4
|
+
from sqlalchemy import JSON, BigInteger, ForeignKey, Index, String
|
5
5
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
6
6
|
|
7
7
|
from letta.orm.mixins import UserMixin
|
@@ -12,6 +12,7 @@ from letta.schemas.job import Job as PydanticJob, LettaRequestConfig
|
|
12
12
|
if TYPE_CHECKING:
|
13
13
|
from letta.orm.job_messages import JobMessage
|
14
14
|
from letta.orm.message import Message
|
15
|
+
from letta.orm.organization import Organization
|
15
16
|
from letta.orm.step import Step
|
16
17
|
from letta.orm.user import User
|
17
18
|
|
@@ -36,6 +37,7 @@ class Job(SqlalchemyBase, UserMixin):
|
|
36
37
|
request_config: Mapped[Optional[LettaRequestConfig]] = mapped_column(
|
37
38
|
JSON, nullable=True, doc="The request configuration for the job, stored as JSON."
|
38
39
|
)
|
40
|
+
organization_id: Mapped[Optional[str]] = mapped_column(String, ForeignKey("organizations.id"))
|
39
41
|
|
40
42
|
# callback related columns
|
41
43
|
callback_url: Mapped[Optional[str]] = mapped_column(String, nullable=True, doc="When set, POST to this URL after job completion.")
|
@@ -53,6 +55,8 @@ class Job(SqlalchemyBase, UserMixin):
|
|
53
55
|
user: Mapped["User"] = relationship("User", back_populates="jobs")
|
54
56
|
job_messages: Mapped[List["JobMessage"]] = relationship("JobMessage", back_populates="job", cascade="all, delete-orphan")
|
55
57
|
steps: Mapped[List["Step"]] = relationship("Step", back_populates="job", cascade="save-update")
|
58
|
+
# organization relationship (nullable for backward compatibility)
|
59
|
+
organization: Mapped[Optional["Organization"]] = relationship("Organization", back_populates="jobs")
|
56
60
|
|
57
61
|
@property
|
58
62
|
def messages(self) -> List["Message"]:
|
letta/orm/organization.py
CHANGED
@@ -12,6 +12,7 @@ if TYPE_CHECKING:
|
|
12
12
|
from letta.orm.block import Block
|
13
13
|
from letta.orm.group import Group
|
14
14
|
from letta.orm.identity import Identity
|
15
|
+
from letta.orm.job import Job
|
15
16
|
from letta.orm.llm_batch_items import LLMBatchItem
|
16
17
|
from letta.orm.llm_batch_job import LLMBatchJob
|
17
18
|
from letta.orm.message import Message
|
@@ -66,3 +67,4 @@ class Organization(SqlalchemyBase):
|
|
66
67
|
llm_batch_items: Mapped[List["LLMBatchItem"]] = relationship(
|
67
68
|
"LLMBatchItem", back_populates="organization", cascade="all, delete-orphan"
|
68
69
|
)
|
70
|
+
jobs: Mapped[List["Job"]] = relationship("Job", back_populates="organization", cascade="all, delete-orphan")
|
@@ -8,6 +8,9 @@ from typing import AsyncIterator, Dict, List, Optional
|
|
8
8
|
|
9
9
|
from letta.data_sources.redis_client import AsyncRedisClient
|
10
10
|
from letta.log import get_logger
|
11
|
+
from letta.schemas.enums import JobStatus
|
12
|
+
from letta.schemas.user import User
|
13
|
+
from letta.services.job_manager import JobManager
|
11
14
|
from letta.utils import safe_create_task
|
12
15
|
|
13
16
|
logger = get_logger(__name__)
|
@@ -133,9 +136,9 @@ class RedisSSEStreamWriter:
|
|
133
136
|
|
134
137
|
async with client.pipeline(transaction=False) as pipe:
|
135
138
|
for chunk in chunks:
|
136
|
-
pipe.xadd(stream_key, chunk, maxlen=self.max_stream_length, approximate=True)
|
139
|
+
await pipe.xadd(stream_key, chunk, maxlen=self.max_stream_length, approximate=True)
|
137
140
|
|
138
|
-
pipe.expire(stream_key, self.stream_ttl)
|
141
|
+
await pipe.expire(stream_key, self.stream_ttl)
|
139
142
|
|
140
143
|
await pipe.execute()
|
141
144
|
|
@@ -191,6 +194,8 @@ async def create_background_stream_processor(
|
|
191
194
|
redis_client: AsyncRedisClient,
|
192
195
|
run_id: str,
|
193
196
|
writer: Optional[RedisSSEStreamWriter] = None,
|
197
|
+
job_manager: Optional[JobManager] = None,
|
198
|
+
actor: Optional[User] = None,
|
194
199
|
) -> None:
|
195
200
|
"""
|
196
201
|
Process a stream in the background and store chunks to Redis.
|
@@ -203,6 +208,8 @@ async def create_background_stream_processor(
|
|
203
208
|
redis_client: Redis client instance
|
204
209
|
run_id: The run ID to store chunks under
|
205
210
|
writer: Optional pre-configured writer (creates new if not provided)
|
211
|
+
job_manager: Optional job manager for updating job status
|
212
|
+
actor: Optional actor for job status updates
|
206
213
|
"""
|
207
214
|
if writer is None:
|
208
215
|
writer = RedisSSEStreamWriter(redis_client)
|
@@ -227,6 +234,12 @@ async def create_background_stream_processor(
|
|
227
234
|
logger.error(f"Error processing stream for run {run_id}: {e}")
|
228
235
|
# Write error chunk
|
229
236
|
# error_chunk = {"error": {"message": str(e)}}
|
237
|
+
# Mark run_id terminal state
|
238
|
+
if job_manager and actor:
|
239
|
+
await job_manager.safe_update_job_status_async(
|
240
|
+
job_id=run_id, new_status=JobStatus.failed, actor=actor, metadata={"error": str(e)}
|
241
|
+
)
|
242
|
+
|
230
243
|
error_chunk = {"error": str(e), "code": "INTERNAL_SERVER_ERROR"}
|
231
244
|
await writer.write_chunk(run_id=run_id, data=f"event: error\ndata: {json.dumps(error_chunk)}\n\n", is_complete=True)
|
232
245
|
finally:
|
@@ -12,7 +12,7 @@ from composio.exceptions import (
|
|
12
12
|
EnumStringNotFound,
|
13
13
|
)
|
14
14
|
from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query, Request
|
15
|
-
from httpx import HTTPStatusError
|
15
|
+
from httpx import ConnectError, HTTPStatusError
|
16
16
|
from pydantic import BaseModel, Field
|
17
17
|
from starlette.responses import StreamingResponse
|
18
18
|
|
@@ -151,7 +151,6 @@ async def count_tools(
|
|
151
151
|
exclude_letta_tools=exclude_letta_tools,
|
152
152
|
)
|
153
153
|
except Exception as e:
|
154
|
-
print(f"Error occurred: {e}")
|
155
154
|
raise HTTPException(status_code=500, detail=str(e))
|
156
155
|
|
157
156
|
|
@@ -265,8 +264,6 @@ async def list_tools(
|
|
265
264
|
return_only_letta_tools=return_only_letta_tools,
|
266
265
|
)
|
267
266
|
except Exception as e:
|
268
|
-
# Log or print the full exception here for debugging
|
269
|
-
print(f"Error occurred: {e}")
|
270
267
|
raise HTTPException(status_code=500, detail=str(e))
|
271
268
|
|
272
269
|
|
@@ -284,21 +281,13 @@ async def create_tool(
|
|
284
281
|
tool = Tool(**request.model_dump(exclude_unset=True))
|
285
282
|
return await server.tool_manager.create_tool_async(pydantic_tool=tool, actor=actor)
|
286
283
|
except UniqueConstraintViolationError as e:
|
287
|
-
# Log or print the full exception here for debugging
|
288
|
-
print(f"Error occurred: {e}")
|
289
284
|
clean_error_message = "Tool with this name already exists."
|
290
285
|
raise HTTPException(status_code=409, detail=clean_error_message)
|
291
286
|
except LettaToolCreateError as e:
|
292
287
|
# HTTP 400 == Bad Request
|
293
|
-
print(f"Error occurred during tool creation: {e}")
|
294
|
-
# print the full stack trace
|
295
|
-
import traceback
|
296
|
-
|
297
|
-
print(traceback.format_exc())
|
298
288
|
raise HTTPException(status_code=400, detail=str(e))
|
299
289
|
except Exception as e:
|
300
290
|
# Catch other unexpected errors and raise an internal server error
|
301
|
-
print(f"Unexpected error occurred: {e}")
|
302
291
|
raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {str(e)}")
|
303
292
|
|
304
293
|
|
@@ -319,15 +308,12 @@ async def upsert_tool(
|
|
319
308
|
return tool
|
320
309
|
except UniqueConstraintViolationError as e:
|
321
310
|
# Log the error and raise a conflict exception
|
322
|
-
print(f"Unique constraint violation occurred: {e}")
|
323
311
|
raise HTTPException(status_code=409, detail=str(e))
|
324
312
|
except LettaToolCreateError as e:
|
325
313
|
# HTTP 400 == Bad Request
|
326
|
-
print(f"Error occurred during tool upsert: {e}")
|
327
314
|
raise HTTPException(status_code=400, detail=str(e))
|
328
315
|
except Exception as e:
|
329
316
|
# Catch other unexpected errors and raise an internal server error
|
330
|
-
print(f"Unexpected error occurred: {e}")
|
331
317
|
raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {str(e)}")
|
332
318
|
|
333
319
|
|
@@ -344,7 +330,6 @@ async def modify_tool(
|
|
344
330
|
try:
|
345
331
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
346
332
|
tool = await server.tool_manager.update_tool_by_id_async(tool_id=tool_id, tool_update=request, actor=actor)
|
347
|
-
print("FINAL TOOL", tool)
|
348
333
|
return tool
|
349
334
|
except LettaToolNameConflictError as e:
|
350
335
|
# HTTP 409 == Conflict
|
@@ -394,16 +379,10 @@ async def run_tool_from_source(
|
|
394
379
|
)
|
395
380
|
except LettaToolCreateError as e:
|
396
381
|
# HTTP 400 == Bad Request
|
397
|
-
print(f"Error occurred during tool creation: {e}")
|
398
|
-
# print the full stack trace
|
399
|
-
import traceback
|
400
|
-
|
401
|
-
print(traceback.format_exc())
|
402
382
|
raise HTTPException(status_code=400, detail=str(e))
|
403
383
|
|
404
384
|
except Exception as e:
|
405
385
|
# Catch other unexpected errors and raise an internal server error
|
406
|
-
print(f"Unexpected error occurred: {e}")
|
407
386
|
raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {str(e)}")
|
408
387
|
|
409
388
|
|
@@ -559,32 +538,38 @@ async def list_mcp_tools_by_server(
|
|
559
538
|
"""
|
560
539
|
Get a list of all tools for a specific MCP server
|
561
540
|
"""
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
541
|
+
try:
|
542
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
543
|
+
mcp_tools = await server.mcp_manager.list_mcp_server_tools(mcp_server_name=mcp_server_name, actor=actor)
|
544
|
+
return mcp_tools
|
545
|
+
except Exception as e:
|
546
|
+
if isinstance(e, ConnectError) or isinstance(e, ConnectionError):
|
567
547
|
raise HTTPException(
|
568
|
-
status_code=
|
548
|
+
status_code=404,
|
569
549
|
detail={
|
570
|
-
"code": "
|
550
|
+
"code": "MCPListToolsError",
|
571
551
|
"message": str(e),
|
572
552
|
"mcp_server_name": mcp_server_name,
|
573
553
|
},
|
574
554
|
)
|
575
|
-
|
555
|
+
if isinstance(e, HTTPStatusError):
|
576
556
|
raise HTTPException(
|
577
|
-
status_code=
|
557
|
+
status_code=401,
|
578
558
|
detail={
|
579
|
-
"code": "
|
559
|
+
"code": "MCPListToolsError",
|
560
|
+
"message": str(e),
|
561
|
+
"mcp_server_name": mcp_server_name,
|
562
|
+
},
|
563
|
+
)
|
564
|
+
else:
|
565
|
+
raise HTTPException(
|
566
|
+
status_code=500,
|
567
|
+
detail={
|
568
|
+
"code": "MCPListToolsError",
|
580
569
|
"message": str(e),
|
581
570
|
"mcp_server_name": mcp_server_name,
|
582
571
|
},
|
583
572
|
)
|
584
|
-
else:
|
585
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
586
|
-
mcp_tools = await server.mcp_manager.list_mcp_server_tools(mcp_server_name=mcp_server_name, actor=actor)
|
587
|
-
return mcp_tools
|
588
573
|
|
589
574
|
|
590
575
|
@router.post("/mcp/servers/{mcp_server_name}/resync", operation_id="resync_mcp_server_tools")
|
@@ -753,7 +738,8 @@ async def add_mcp_server_to_config(
|
|
753
738
|
custom_headers=request.custom_headers,
|
754
739
|
)
|
755
740
|
|
756
|
-
|
741
|
+
# Create MCP server and optimistically sync tools
|
742
|
+
await server.mcp_manager.create_mcp_server_with_tools(mapped_request, actor=actor)
|
757
743
|
|
758
744
|
# TODO: don't do this in the future (just return MCPServer)
|
759
745
|
all_servers = await server.mcp_manager.list_mcp_servers(actor=actor)
|
@@ -769,7 +755,6 @@ async def add_mcp_server_to_config(
|
|
769
755
|
},
|
770
756
|
)
|
771
757
|
except Exception as e:
|
772
|
-
print(f"Unexpected error occurred while adding MCP server: {e}")
|
773
758
|
raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {str(e)}")
|
774
759
|
|
775
760
|
|
@@ -801,7 +786,6 @@ async def update_mcp_server(
|
|
801
786
|
# Re-raise HTTP exceptions (like 404)
|
802
787
|
raise
|
803
788
|
except Exception as e:
|
804
|
-
print(f"Unexpected error occurred while updating MCP server: {e}")
|
805
789
|
raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {str(e)}")
|
806
790
|
|
807
791
|
|
letta/services/job_manager.py
CHANGED
@@ -43,6 +43,7 @@ class JobManager:
|
|
43
43
|
pydantic_job.user_id = actor.id
|
44
44
|
job_data = pydantic_job.model_dump(to_orm=True)
|
45
45
|
job = JobModel(**job_data)
|
46
|
+
job.organization_id = actor.organization_id
|
46
47
|
job.create(session, actor=actor) # Save job in the database
|
47
48
|
return job.to_pydantic()
|
48
49
|
|
@@ -57,6 +58,7 @@ class JobManager:
|
|
57
58
|
pydantic_job.user_id = actor.id
|
58
59
|
job_data = pydantic_job.model_dump(to_orm=True)
|
59
60
|
job = JobModel(**job_data)
|
61
|
+
job.organization_id = actor.organization_id
|
60
62
|
job = await job.create_async(session, actor=actor, no_commit=True, no_refresh=True) # Save job in the database
|
61
63
|
result = job.to_pydantic()
|
62
64
|
await session.commit()
|
@@ -150,8 +152,9 @@ class JobManager:
|
|
150
152
|
logger.error(f"Invalid job status transition from {current_status} to {job_update.status} for job {job_id}")
|
151
153
|
raise ValueError(f"Invalid job status transition from {current_status} to {job_update.status}")
|
152
154
|
|
153
|
-
# Check if we'll need to dispatch callback
|
154
|
-
|
155
|
+
# Check if we'll need to dispatch callback (only if not already completed)
|
156
|
+
not_completed_before = not bool(job.completed_at)
|
157
|
+
if job_update.status in {JobStatus.completed, JobStatus.failed} and not_completed_before and job.callback_url:
|
155
158
|
needs_callback = True
|
156
159
|
callback_url = job.callback_url
|
157
160
|
|
letta/services/mcp_manager.py
CHANGED
@@ -79,11 +79,16 @@ class MCPManager:
|
|
79
79
|
except Exception as e:
|
80
80
|
# MCP tool listing errors are often due to connection/configuration issues, not system errors
|
81
81
|
# Log at info level to avoid triggering Sentry alerts for expected failures
|
82
|
-
logger.
|
83
|
-
|
82
|
+
logger.warning(f"Error listing tools for MCP server {mcp_server_name}: {e}")
|
83
|
+
raise e
|
84
84
|
finally:
|
85
85
|
if mcp_client:
|
86
|
-
|
86
|
+
try:
|
87
|
+
await mcp_client.cleanup()
|
88
|
+
except* Exception as eg:
|
89
|
+
for e in eg.exceptions:
|
90
|
+
logger.warning(f"Error listing tools for MCP server {mcp_server_name}: {e}")
|
91
|
+
raise e
|
87
92
|
|
88
93
|
@enforce_types
|
89
94
|
async def execute_mcp_server_tool(
|
@@ -349,6 +354,62 @@ class MCPManager:
|
|
349
354
|
logger.error(f"Failed to create MCP server: {e}")
|
350
355
|
raise
|
351
356
|
|
357
|
+
@enforce_types
|
358
|
+
async def create_mcp_server_with_tools(self, pydantic_mcp_server: MCPServer, actor: PydanticUser) -> MCPServer:
|
359
|
+
"""
|
360
|
+
Create a new MCP server and optimistically sync its tools.
|
361
|
+
|
362
|
+
This method:
|
363
|
+
1. Creates the MCP server record
|
364
|
+
2. Attempts to connect and fetch tools
|
365
|
+
3. Persists valid tools in parallel (best-effort)
|
366
|
+
"""
|
367
|
+
import asyncio
|
368
|
+
|
369
|
+
# First, create the MCP server
|
370
|
+
created_server = await self.create_mcp_server(pydantic_mcp_server, actor)
|
371
|
+
|
372
|
+
# Optimistically try to sync tools
|
373
|
+
try:
|
374
|
+
logger.info(f"Attempting to auto-sync tools from MCP server: {created_server.server_name}")
|
375
|
+
|
376
|
+
# List all tools from the MCP server
|
377
|
+
mcp_tools = await self.list_mcp_server_tools(mcp_server_name=created_server.server_name, actor=actor)
|
378
|
+
|
379
|
+
# Filter out invalid tools
|
380
|
+
valid_tools = [tool for tool in mcp_tools if not (tool.health and tool.health.status == "INVALID")]
|
381
|
+
|
382
|
+
# Register in parallel
|
383
|
+
if valid_tools:
|
384
|
+
tool_tasks = []
|
385
|
+
for mcp_tool in valid_tools:
|
386
|
+
tool_create = ToolCreate.from_mcp(mcp_server_name=created_server.server_name, mcp_tool=mcp_tool)
|
387
|
+
task = self.tool_manager.create_mcp_tool_async(
|
388
|
+
tool_create=tool_create, mcp_server_name=created_server.server_name, mcp_server_id=created_server.id, actor=actor
|
389
|
+
)
|
390
|
+
tool_tasks.append(task)
|
391
|
+
|
392
|
+
results = await asyncio.gather(*tool_tasks, return_exceptions=True)
|
393
|
+
|
394
|
+
successful = sum(1 for r in results if not isinstance(r, Exception))
|
395
|
+
failed = len(results) - successful
|
396
|
+
logger.info(
|
397
|
+
f"Auto-sync completed for MCP server {created_server.server_name}: "
|
398
|
+
f"{successful} tools persisted, {failed} failed, "
|
399
|
+
f"{len(mcp_tools) - len(valid_tools)} invalid tools skipped"
|
400
|
+
)
|
401
|
+
else:
|
402
|
+
logger.info(f"No valid tools found to sync from MCP server {created_server.server_name}")
|
403
|
+
|
404
|
+
except Exception as e:
|
405
|
+
# Log the error but don't fail the server creation
|
406
|
+
logger.warning(
|
407
|
+
f"Failed to auto-sync tools from MCP server {created_server.server_name}: {e}. "
|
408
|
+
f"Server was created successfully but tools were not persisted."
|
409
|
+
)
|
410
|
+
|
411
|
+
return created_server
|
412
|
+
|
352
413
|
@enforce_types
|
353
414
|
async def update_mcp_server_by_id(self, mcp_server_id: str, mcp_server_update: UpdateMCPServer, actor: PydanticUser) -> MCPServer:
|
354
415
|
"""Update a tool by its ID with the given ToolUpdate object."""
|
@@ -645,7 +645,7 @@ class LettaFileToolExecutor(ToolExecutor):
|
|
645
645
|
raise e
|
646
646
|
|
647
647
|
if not files_with_matches:
|
648
|
-
return f"No semantic matches found
|
648
|
+
return f"No semantic matches found for query: '{query}'"
|
649
649
|
|
650
650
|
# Format results
|
651
651
|
passage_num = 0
|
@@ -678,7 +678,7 @@ class LettaFileToolExecutor(ToolExecutor):
|
|
678
678
|
|
679
679
|
# create summary header
|
680
680
|
file_count = len(files_with_matches)
|
681
|
-
summary = f"Found {total_hits}
|
681
|
+
summary = f"Found {total_hits} matches in {file_count} file{'s' if file_count != 1 else ''} for query: '{query}'"
|
682
682
|
|
683
683
|
# combine all results
|
684
684
|
formatted_results = [summary, "=" * len(summary)] + results
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: letta-nightly
|
3
|
-
Version: 0.11.7.
|
3
|
+
Version: 0.11.7.dev20250912104045
|
4
4
|
Summary: Create LLM agents with long-term memory and custom tools
|
5
5
|
Author-email: Letta Team <contact@letta.com>
|
6
6
|
License: Apache License
|
@@ -84,7 +84,7 @@ Requires-Dist: magika>=0.6.2; extra == 'desktop'
|
|
84
84
|
Requires-Dist: pgvector>=0.2.3; extra == 'desktop'
|
85
85
|
Requires-Dist: sqlite-vec>=0.1.7a2; extra == 'desktop'
|
86
86
|
Requires-Dist: tiktoken>=0.11.0; extra == 'desktop'
|
87
|
-
Requires-Dist: uvicorn
|
87
|
+
Requires-Dist: uvicorn==0.29.0; extra == 'desktop'
|
88
88
|
Requires-Dist: websockets; extra == 'desktop'
|
89
89
|
Requires-Dist: wikipedia>=1.4.0; extra == 'desktop'
|
90
90
|
Provides-Extra: dev
|
@@ -125,7 +125,7 @@ Provides-Extra: redis
|
|
125
125
|
Requires-Dist: redis>=6.2.0; extra == 'redis'
|
126
126
|
Provides-Extra: server
|
127
127
|
Requires-Dist: fastapi>=0.115.6; extra == 'server'
|
128
|
-
Requires-Dist: uvicorn
|
128
|
+
Requires-Dist: uvicorn==0.29.0; extra == 'server'
|
129
129
|
Requires-Dist: websockets; extra == 'server'
|
130
130
|
Provides-Extra: sqlite
|
131
131
|
Requires-Dist: aiosqlite>=0.21.0; extra == 'sqlite'
|
@@ -27,7 +27,7 @@ letta/agents/exceptions.py,sha256=BQY4D4w32OYHM63CM19ko7dPwZiAzUs3NbKvzmCTcJg,31
|
|
27
27
|
letta/agents/helpers.py,sha256=eCHsvZEkTe0L_uZHYkfNAztsEJW0FTnKZMgVbqlI0Yg,11618
|
28
28
|
letta/agents/letta_agent.py,sha256=6nRTh5kzUpqK7eNMk4DlcgEoPmDxFmRb5ysoVHa-vh8,99488
|
29
29
|
letta/agents/letta_agent_batch.py,sha256=17RpYVXpGh9dlKxdMOLMCOHWFsi6N5S9FJHxooxkJCI,27998
|
30
|
-
letta/agents/letta_agent_v2.py,sha256=
|
30
|
+
letta/agents/letta_agent_v2.py,sha256=Xs54mewx9SgHHFAz8uLJ_6OHv9RHU1PtkwAB_Pu0XMk,58992
|
31
31
|
letta/agents/voice_agent.py,sha256=y-n6qadfKsswvGODzXH02pLIQQ44wnaDSE6oUgKHVkA,23381
|
32
32
|
letta/agents/voice_sleeptime_agent.py,sha256=_JzCbWBOKrmo1cTaqZFTrQudpJEapwAyrXYtAHUILGo,8675
|
33
33
|
letta/cli/cli.py,sha256=tKtghlX36Rp0_HbkMosvlAapL07JXhA0vKLGTNKnxSQ,1615
|
@@ -78,14 +78,14 @@ letta/helpers/reasoning_helper.py,sha256=8P5AJo-UFsYhZC3yNx4N-pIkVUsjfz7ZPmydsVK
|
|
78
78
|
letta/helpers/singleton.py,sha256=Y4dG_ZBCcrogvl9iZ69bSLq-QltrdP8wHqKkhef8OBI,370
|
79
79
|
letta/helpers/tool_execution_helper.py,sha256=Oz9xNDrSFUIrYhEhLaw8yoXdHbfijuxwtS1tJv-lH2A,5149
|
80
80
|
letta/helpers/tool_rule_solver.py,sha256=dd5gvj67_8FgXrC0_Px5TWXE8A9CMFvH_E26idPrkKY,9848
|
81
|
-
letta/helpers/tpuf_client.py,sha256=
|
81
|
+
letta/helpers/tpuf_client.py,sha256=7x6dP7MeHfINs_aVxQAmEXYYM3bJcw3fiyrJYG1ZpAk,62211
|
82
82
|
letta/humans/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
83
83
|
letta/humans/examples/basic.txt,sha256=Lcp8YESTWvOJgO4Yf_yyQmgo5bKakeB1nIVrwEGG6PA,17
|
84
84
|
letta/humans/examples/cs_phd.txt,sha256=9C9ZAV_VuG7GB31ksy3-_NAyk8rjE6YtVOkhp08k1xw,297
|
85
85
|
letta/interfaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
86
86
|
letta/interfaces/anthropic_streaming_interface.py,sha256=0VyK8kTRgCLNDLQN6vX1gJ0dfJhqguL_NL1GYgFr6fU,25614
|
87
87
|
letta/interfaces/openai_chat_completions_streaming_interface.py,sha256=3xHXh8cW79EkiMUTYfvcH_s92nkLjxXfvtVOVC3bfLo,5050
|
88
|
-
letta/interfaces/openai_streaming_interface.py,sha256=
|
88
|
+
letta/interfaces/openai_streaming_interface.py,sha256=t_TKcZSH0Bv_ajOh2mTd4RetrCr-rahkjmGIZIIGDXQ,23593
|
89
89
|
letta/interfaces/utils.py,sha256=c6jvO0dBYHh8DQnlN-B0qeNC64d3CSunhfqlFA4pJTY,278
|
90
90
|
letta/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
91
91
|
letta/jobs/helpers.py,sha256=kO4aj954xsQ1RAmkjY6LQQ7JEIGuhaxB1e9pzrYKHAY,914
|
@@ -99,7 +99,7 @@ letta/llm_api/bedrock_client.py,sha256=gNKSFGCbrrLMPvtBItAOz1nme4K_opgkZdFa3cUzp
|
|
99
99
|
letta/llm_api/deepseek_client.py,sha256=di6ApSQu1DewXw0_JIP7AK4IHvXQHd0e32tQfFf5F34,16975
|
100
100
|
letta/llm_api/google_ai_client.py,sha256=JweTUHZXvK6kcZBGXA7XEU53KP4vM7_zdD7AorCtsdI,8166
|
101
101
|
letta/llm_api/google_constants.py,sha256=eOjOv-FImyJ4b4QGIaod-mEROMtrBFz0yhuYHqOEkwY,797
|
102
|
-
letta/llm_api/google_vertex_client.py,sha256=
|
102
|
+
letta/llm_api/google_vertex_client.py,sha256=57qrBe5dY-ERB9xI9_tWRwW_uSxGbHqR02mvnWmCLGY,28910
|
103
103
|
letta/llm_api/groq_client.py,sha256=nNeWSgDVOLn3iFiicDKyhHj7f73JxrB9-7_M2Pv2e1I,3192
|
104
104
|
letta/llm_api/helpers.py,sha256=GXV_SuaU7uSCDj6bxDcCCF7CUjuZQCVWd5qZ3OsHVNk,17587
|
105
105
|
letta/llm_api/llm_api_tools.py,sha256=lsZ6OeIHesyOfbNQi5CVw5hn1lTQP5gJyforp-D0nk8,12294
|
@@ -107,7 +107,7 @@ letta/llm_api/llm_client.py,sha256=iXiPbrhluP2DBczv9nkFlAXdwWGOkg0lNDA9LzLrG4o,3
|
|
107
107
|
letta/llm_api/llm_client_base.py,sha256=RFo8H4ILxVyzB3DeF4rJoJJYjRF8ScVO4yyDrhuN0DY,10052
|
108
108
|
letta/llm_api/mistral.py,sha256=ruOTBt07Uzx7S30_eXhedVWngtpjtlzG6Ox1Iw0_mQs,662
|
109
109
|
letta/llm_api/openai.py,sha256=56cwdS9l-75cMTtY9df6Dbb1M9crH8YQsSdF3Pm3Rpg,27393
|
110
|
-
letta/llm_api/openai_client.py,sha256=
|
110
|
+
letta/llm_api/openai_client.py,sha256=Ww68D103uQolsALOzfPD5-CTuEaIFBbkdnrtMBIaZlc,22475
|
111
111
|
letta/llm_api/together_client.py,sha256=HeDMDDa525yfDTKciODDfX_t93QBfFmX0n2P-FT1QTU,2284
|
112
112
|
letta/llm_api/xai_client.py,sha256=3mpSQ9OoWyjqo2VhNM_m0EPBzS69r4p-OEwL7UWc9oY,3772
|
113
113
|
letta/llm_api/sample_response_jsons/aws_bedrock.json,sha256=RS3VqyxPB9hQQCPm42hWoga0bisKv_0e8ZF-c3Ag1FA,930
|
@@ -171,7 +171,7 @@ letta/orm/groups_blocks.py,sha256=ou18XqI9tkb0fcecUd4eHTVmmndGuby1DIdmHM5lHF4,48
|
|
171
171
|
letta/orm/identities_agents.py,sha256=cfIQ6UsbwmjxtGVmFt1ArK2zbKr9k6VWoELuISDnLSc,502
|
172
172
|
letta/orm/identities_blocks.py,sha256=oS0DnDXKzcWtlH2dDFREkNF1JHJ3Kyg8fN9GI8DKtcg,501
|
173
173
|
letta/orm/identity.py,sha256=NRgC6ArQZCrs3LYhGHoBMHNJzi_uoYFvJAr0HoRhqLg,2924
|
174
|
-
letta/orm/job.py,sha256=
|
174
|
+
letta/orm/job.py,sha256=nlDw6Y7zT96jFW-OOjf-vY66Py7S6_3jTRmhOmNnIUw,3357
|
175
175
|
letta/orm/job_messages.py,sha256=SgwaYPYwwAC3dBtl9Xye_TWUl9H_-m95S95TTcfPyOg,1245
|
176
176
|
letta/orm/llm_batch_items.py,sha256=LZI9vjOrswiGXPVLvLOT5uObDtyTXX1p7yljSiiMH7g,2725
|
177
177
|
letta/orm/llm_batch_job.py,sha256=LaeOrnNf6FMm6ff2kOCEAjtbSuz4C5KYU5OmM90F1PY,2368
|
@@ -179,7 +179,7 @@ letta/orm/mcp_oauth.py,sha256=lr0XIcj9zwQpWlqQHIWAaT73YnunsrkEnLzBLP-8L2k,3095
|
|
179
179
|
letta/orm/mcp_server.py,sha256=PT3Edqn0Er1ZDExsXdjhC--iGL4Vi3XjPPgxNfbNRDA,2155
|
180
180
|
letta/orm/message.py,sha256=sGMH7XJRErtIEngFV08b9A_zk5veyLkpQsU3zKN0AhM,9335
|
181
181
|
letta/orm/mixins.py,sha256=moZyS4e9gXGULKNsOqQuZrBn55IlrFF7MQOAVl68Aq0,2688
|
182
|
-
letta/orm/organization.py,sha256=
|
182
|
+
letta/orm/organization.py,sha256=wZ3yvPa6Vy-c_74S_XGjzjWND1oR7U7xzrdrdipZM8A,4205
|
183
183
|
letta/orm/passage.py,sha256=qjBZdyZV05ZGe6Dprn4GIwdc3wjYRuchZO2Ja9I9bT4,4404
|
184
184
|
letta/orm/passage_tag.py,sha256=TtT00DjXh9n0BshehBBcTjo3lnR1d-wNBi-ePJTZ_0M,2121
|
185
185
|
letta/orm/prompt.py,sha256=NpFPTm3jD8Aewxhlnq8s4eIzANJ3bAEtbq6UmFqyc3U,489
|
@@ -342,7 +342,7 @@ letta/server/rest_api/auth_token.py,sha256=725EFEIiNj4dh70hrSd94UysmFD8vcJLrTRfN
|
|
342
342
|
letta/server/rest_api/chat_completions_interface.py,sha256=-7wO7pNBWXMqblVkJpuZ8JPJ-LjudLTtT6BJu-q_XAM,11138
|
343
343
|
letta/server/rest_api/interface.py,sha256=X5NZ8oerDcipG9y1AfD92zJ_2TgVMO4eJ42RP82GFF8,70952
|
344
344
|
letta/server/rest_api/json_parser.py,sha256=yoakaCkSMdf0Y_pyILoFKZlvzXeqF-E1KNeHzatLMDc,9157
|
345
|
-
letta/server/rest_api/redis_stream_manager.py,sha256=
|
345
|
+
letta/server/rest_api/redis_stream_manager.py,sha256=hz85CigFWdLkK1FWUmF-i6ObgoKkuoEgkiwshZ6QPKI,10764
|
346
346
|
letta/server/rest_api/static_files.py,sha256=NG8sN4Z5EJ8JVQdj19tkFa9iQ1kBPTab9f_CUxd_u4Q,3143
|
347
347
|
letta/server/rest_api/streaming_response.py,sha256=wfhby6skucjGtw9d9pcfa856lI1r6JKaosCYbutKD2k,14458
|
348
348
|
letta/server/rest_api/utils.py,sha256=IT3RnZJWNIaTdFEEyveZb47o9PIzDT_2pIgpPBVN7iU,19326
|
@@ -355,7 +355,7 @@ letta/server/rest_api/routers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5N
|
|
355
355
|
letta/server/rest_api/routers/openai/chat_completions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
356
356
|
letta/server/rest_api/routers/openai/chat_completions/chat_completions.py,sha256=ohM1i8BsNxTiw8duuRT5X_0tSUzBwctQM4fJ5DXURic,5157
|
357
357
|
letta/server/rest_api/routers/v1/__init__.py,sha256=9MnEA7CgtIxyU_dDNG0jm-Ziqu1somBml-e5gKjgd9I,1997
|
358
|
-
letta/server/rest_api/routers/v1/agents.py,sha256=
|
358
|
+
letta/server/rest_api/routers/v1/agents.py,sha256=2eo7EDXTpybTPfOvgEGhm81LenIJcXNxv-bf5CcqjkU,75673
|
359
359
|
letta/server/rest_api/routers/v1/blocks.py,sha256=ykI77xnmIxPLqdAy5kzGyGw0w0ZRyVXn-O5Xcdj6-70,7690
|
360
360
|
letta/server/rest_api/routers/v1/embeddings.py,sha256=PRaQlrmEXPiIdWsTbadrFsv3Afyv5oEFUdhgHA8FTi8,989
|
361
361
|
letta/server/rest_api/routers/v1/folders.py,sha256=8Yb-bw2JdXBxMfrJNIZQk9_FKN2fet9Ccp8T83_c2sc,23539
|
@@ -374,7 +374,7 @@ letta/server/rest_api/routers/v1/sources.py,sha256=nXZxtHi40281VltWmx1RwGBbau_00
|
|
374
374
|
letta/server/rest_api/routers/v1/steps.py,sha256=bTzfz1GR3VEZdJRYUGiSr6ZLd12i5faPsf3oAqu1eMk,5570
|
375
375
|
letta/server/rest_api/routers/v1/tags.py,sha256=ef94QitUSJ3NQVffWF1ZqANUZ2b2jRyGHp_I3UUjhno,912
|
376
376
|
letta/server/rest_api/routers/v1/telemetry.py,sha256=eSTg7mWbuwPb2OTHQxwRM0EUEl49wHzNB6i1xJtH8BQ,1036
|
377
|
-
letta/server/rest_api/routers/v1/tools.py,sha256=
|
377
|
+
letta/server/rest_api/routers/v1/tools.py,sha256=UMtJj3bX8fVe0VuuU5JS0TeaFimEzZ4YRyphSO2tQMU,51085
|
378
378
|
letta/server/rest_api/routers/v1/users.py,sha256=J1vaTbS1UrBMgnPya7GdZ2wr3L9XHmkm6qdGY6pWaOI,2366
|
379
379
|
letta/server/rest_api/routers/v1/voice.py,sha256=ghMBp5Uovbf0-3nN6d9P5kpl1hHACLRMhIDGQp96G9Q,1986
|
380
380
|
letta/server/static_files/favicon.ico,sha256=DezhLdFSbM8o81wCOZcV3riq7tFUOGQD4h6-vr-HuU0,342
|
@@ -397,9 +397,9 @@ letta/services/file_manager.py,sha256=d4uX8RblmqNGk1MsfeGzQ5uDWKVFP-AH63Jz5xOkj2
|
|
397
397
|
letta/services/files_agents_manager.py,sha256=QJrJTgDn3RXUjZIGiIw4GQ5k2iKj-Wvzs-WQetpQ154,30059
|
398
398
|
letta/services/group_manager.py,sha256=dD4DDHjOptMrtbWqw1ErlhpBqChw2ubLJdILjeLTY8I,29183
|
399
399
|
letta/services/identity_manager.py,sha256=JI9Xc7EsBagSwDS2na4rFNhoO_LuaxlkVO_1oIK_ITQ,11841
|
400
|
-
letta/services/job_manager.py,sha256=
|
400
|
+
letta/services/job_manager.py,sha256=nDrnr_r8ELwf8KMKyRRrWHsysrTGldgCTplJdaSiNiQ,35543
|
401
401
|
letta/services/llm_batch_manager.py,sha256=iDzLFfmgpQooGY4zpN_w8q1SZ27fr2Cv6Ks3ltZErL8,20929
|
402
|
-
letta/services/mcp_manager.py,sha256=
|
402
|
+
letta/services/mcp_manager.py,sha256=QuvKQnwxMXrhiCaYlF50GZwXmbSU7PxmcOZ85sQ3t7I,47848
|
403
403
|
letta/services/message_manager.py,sha256=tomsZidPT-I95sJsEsls-vj3qglehV7XNTs-m2zF8Bg,60629
|
404
404
|
letta/services/organization_manager.py,sha256=JMW5oS_sr6vQQ27OgRV3BR1JL0MqyoGUDcpEOs3SLRY,5800
|
405
405
|
letta/services/passage_manager.py,sha256=kOQjlJFz7Dy6e0NEECoFHhcH8hPIMNeEHxZ1JJ-R2Cs,52372
|
@@ -447,7 +447,7 @@ letta/services/tool_executor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
|
|
447
447
|
letta/services/tool_executor/builtin_tool_executor.py,sha256=QKPFXbgzYQobqWRuFs9RhVcQ1d53q8-yV_GFdR2IX88,9718
|
448
448
|
letta/services/tool_executor/composio_tool_executor.py,sha256=ia2AA_WDOseR8Ylam-HEayR7OiyfNSb1sSUrjwqlmFM,2308
|
449
449
|
letta/services/tool_executor/core_tool_executor.py,sha256=bd9x0M9Ttj3FqoIUU66k2FHa2PVY0OlUHqs1aLZYxFI,22624
|
450
|
-
letta/services/tool_executor/files_tool_executor.py,sha256=
|
450
|
+
letta/services/tool_executor/files_tool_executor.py,sha256=nlXk0w-t_dLiN7fpUrnBrHGIwWIEmSkcXa7b_YJs2ME,37781
|
451
451
|
letta/services/tool_executor/mcp_tool_executor.py,sha256=2zEXmkKsH-IGbMG2Xw1S9op4eL21g25RA4vGImB29KY,2000
|
452
452
|
letta/services/tool_executor/multi_agent_tool_executor.py,sha256=LIg9yh8BlfYokP_29WryLZPPSMqEsJUwc0mZGMlhc00,5724
|
453
453
|
letta/services/tool_executor/sandbox_tool_executor.py,sha256=LamEZUVqsiBfBL9S1uQMXFGQOXqJlXK9ZrfpZBk5jsA,6258
|
@@ -470,8 +470,8 @@ letta/templates/sandbox_code_file_async.py.j2,sha256=lb7nh_P2W9VZHzU_9TxSCEMUod7
|
|
470
470
|
letta/templates/summary_request_text.j2,sha256=ZttQwXonW2lk4pJLYzLK0pmo4EO4EtUUIXjgXKiizuc,842
|
471
471
|
letta/templates/template_helper.py,sha256=HkG3zwRc5NVGmSTQu5PUTpz7LevK43bzXVaQuN8urf0,1634
|
472
472
|
letta/types/__init__.py,sha256=hokKjCVFGEfR7SLMrtZsRsBfsC7yTIbgKPLdGg4K1eY,147
|
473
|
-
letta_nightly-0.11.7.
|
474
|
-
letta_nightly-0.11.7.
|
475
|
-
letta_nightly-0.11.7.
|
476
|
-
letta_nightly-0.11.7.
|
477
|
-
letta_nightly-0.11.7.
|
473
|
+
letta_nightly-0.11.7.dev20250912104045.dist-info/METADATA,sha256=tqJlpOfovWrr9Go7iI1cwIOkAgFx0Qwf7JYX11vg2JI,24424
|
474
|
+
letta_nightly-0.11.7.dev20250912104045.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
475
|
+
letta_nightly-0.11.7.dev20250912104045.dist-info/entry_points.txt,sha256=m-94Paj-kxiR6Ktu0us0_2qfhn29DzF2oVzqBE6cu8w,41
|
476
|
+
letta_nightly-0.11.7.dev20250912104045.dist-info/licenses/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
|
477
|
+
letta_nightly-0.11.7.dev20250912104045.dist-info/RECORD,,
|
File without changes
|
File without changes
|