letta-nightly 0.8.5.dev20250624104340__py3-none-any.whl → 0.8.6.dev20250625222533__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/agent.py +16 -12
- letta/agents/base_agent.py +4 -1
- letta/agents/helpers.py +35 -3
- letta/agents/letta_agent.py +132 -106
- letta/agents/letta_agent_batch.py +4 -3
- letta/agents/voice_agent.py +12 -2
- letta/agents/voice_sleeptime_agent.py +12 -2
- letta/constants.py +24 -3
- letta/data_sources/redis_client.py +6 -0
- letta/errors.py +5 -0
- letta/functions/function_sets/files.py +10 -3
- letta/functions/function_sets/multi_agent.py +0 -32
- letta/groups/sleeptime_multi_agent_v2.py +6 -0
- letta/helpers/converters.py +4 -1
- letta/helpers/datetime_helpers.py +16 -23
- letta/helpers/message_helper.py +5 -2
- letta/helpers/tool_rule_solver.py +29 -2
- letta/interfaces/openai_streaming_interface.py +9 -2
- letta/llm_api/anthropic.py +11 -1
- letta/llm_api/anthropic_client.py +14 -3
- letta/llm_api/aws_bedrock.py +29 -15
- letta/llm_api/bedrock_client.py +74 -0
- letta/llm_api/google_ai_client.py +7 -3
- letta/llm_api/google_vertex_client.py +18 -4
- letta/llm_api/llm_client.py +7 -0
- letta/llm_api/openai_client.py +13 -0
- letta/orm/agent.py +5 -0
- letta/orm/block_history.py +1 -1
- letta/orm/enums.py +6 -25
- letta/orm/job.py +1 -2
- letta/orm/llm_batch_items.py +1 -1
- letta/orm/mcp_server.py +1 -1
- letta/orm/passage.py +7 -1
- letta/orm/sqlalchemy_base.py +7 -5
- letta/orm/tool.py +2 -1
- letta/schemas/agent.py +34 -10
- letta/schemas/enums.py +42 -1
- letta/schemas/job.py +6 -3
- letta/schemas/letta_request.py +4 -0
- letta/schemas/llm_batch_job.py +7 -2
- letta/schemas/memory.py +2 -2
- letta/schemas/providers.py +32 -6
- letta/schemas/run.py +1 -1
- letta/schemas/tool_rule.py +40 -12
- letta/serialize_schemas/pydantic_agent_schema.py +9 -2
- letta/server/rest_api/app.py +3 -2
- letta/server/rest_api/routers/v1/agents.py +25 -22
- letta/server/rest_api/routers/v1/runs.py +2 -3
- letta/server/rest_api/routers/v1/sources.py +31 -0
- letta/server/rest_api/routers/v1/voice.py +1 -0
- letta/server/rest_api/utils.py +38 -13
- letta/server/server.py +52 -21
- letta/services/agent_manager.py +58 -7
- letta/services/block_manager.py +1 -1
- letta/services/file_processor/chunker/line_chunker.py +2 -1
- letta/services/file_processor/file_processor.py +2 -9
- letta/services/files_agents_manager.py +177 -37
- letta/services/helpers/agent_manager_helper.py +77 -48
- letta/services/helpers/tool_parser_helper.py +2 -1
- letta/services/job_manager.py +33 -2
- letta/services/llm_batch_manager.py +1 -1
- letta/services/provider_manager.py +6 -4
- letta/services/tool_executor/core_tool_executor.py +1 -1
- letta/services/tool_executor/files_tool_executor.py +99 -30
- letta/services/tool_executor/multi_agent_tool_executor.py +1 -17
- letta/services/tool_executor/tool_execution_manager.py +6 -0
- letta/services/tool_executor/tool_executor_base.py +3 -0
- letta/services/tool_sandbox/base.py +39 -1
- letta/services/tool_sandbox/e2b_sandbox.py +7 -0
- letta/services/user_manager.py +3 -2
- letta/settings.py +8 -14
- letta/system.py +17 -17
- letta/templates/sandbox_code_file_async.py.j2 +59 -0
- {letta_nightly-0.8.5.dev20250624104340.dist-info → letta_nightly-0.8.6.dev20250625222533.dist-info}/METADATA +3 -2
- {letta_nightly-0.8.5.dev20250624104340.dist-info → letta_nightly-0.8.6.dev20250625222533.dist-info}/RECORD +78 -76
- {letta_nightly-0.8.5.dev20250624104340.dist-info → letta_nightly-0.8.6.dev20250625222533.dist-info}/LICENSE +0 -0
- {letta_nightly-0.8.5.dev20250624104340.dist-info → letta_nightly-0.8.6.dev20250625222533.dist-info}/WHEEL +0 -0
- {letta_nightly-0.8.5.dev20250624104340.dist-info → letta_nightly-0.8.6.dev20250625222533.dist-info}/entry_points.txt +0 -0
@@ -13,7 +13,7 @@ from sqlalchemy.exc import IntegrityError, OperationalError
|
|
13
13
|
from starlette.responses import Response, StreamingResponse
|
14
14
|
|
15
15
|
from letta.agents.letta_agent import LettaAgent
|
16
|
-
from letta.constants import DEFAULT_MAX_STEPS, DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG
|
16
|
+
from letta.constants import DEFAULT_MAX_STEPS, DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG, LETTA_MODEL_ENDPOINT
|
17
17
|
from letta.groups.sleeptime_multi_agent_v2 import SleeptimeMultiAgentV2
|
18
18
|
from letta.helpers.datetime_helpers import get_utc_timestamp_ns
|
19
19
|
from letta.log import get_logger
|
@@ -25,7 +25,7 @@ from letta.schemas.block import Block, BlockUpdate
|
|
25
25
|
from letta.schemas.group import Group
|
26
26
|
from letta.schemas.job import JobStatus, JobUpdate, LettaRequestConfig
|
27
27
|
from letta.schemas.letta_message import LettaMessageUnion, LettaMessageUpdateUnion, MessageType
|
28
|
-
from letta.schemas.letta_request import LettaRequest, LettaStreamingRequest
|
28
|
+
from letta.schemas.letta_request import LettaAsyncRequest, LettaRequest, LettaStreamingRequest
|
29
29
|
from letta.schemas.letta_response import LettaResponse
|
30
30
|
from letta.schemas.memory import ContextWindowOverview, CreateArchivalMemory, Memory
|
31
31
|
from letta.schemas.message import MessageCreate
|
@@ -323,17 +323,7 @@ async def attach_source(
|
|
323
323
|
agent_state = await server.agent_manager.attach_missing_files_tools_async(agent_state=agent_state, actor=actor)
|
324
324
|
|
325
325
|
files = await server.file_manager.list_files(source_id, actor, include_content=True)
|
326
|
-
|
327
|
-
file_ids = []
|
328
|
-
file_names = []
|
329
|
-
for f in files:
|
330
|
-
texts.append(f.content if f.content else "")
|
331
|
-
file_ids.append(f.id)
|
332
|
-
file_names.append(f.file_name)
|
333
|
-
|
334
|
-
await server.insert_files_into_context_window(
|
335
|
-
agent_state=agent_state, texts=texts, file_ids=file_ids, file_names=file_names, actor=actor
|
336
|
-
)
|
326
|
+
await server.insert_files_into_context_window(agent_state=agent_state, file_metadata_with_content=files, actor=actor)
|
337
327
|
|
338
328
|
if agent_state.enable_sleeptime:
|
339
329
|
source = await server.source_manager.get_source_by_id(source_id=source_id)
|
@@ -686,7 +676,7 @@ async def send_message(
|
|
686
676
|
# TODO: This is redundant, remove soon
|
687
677
|
agent = await server.agent_manager.get_agent_by_id_async(agent_id, actor, include_relationships=["multi_agent_group"])
|
688
678
|
agent_eligible = agent.multi_agent_group is None or agent.multi_agent_group.manager_type in ["sleeptime", "voice_sleeptime"]
|
689
|
-
model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "together", "google_ai", "google_vertex"]
|
679
|
+
model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "together", "google_ai", "google_vertex", "bedrock"]
|
690
680
|
|
691
681
|
if agent_eligible and model_compatible:
|
692
682
|
if agent.enable_sleeptime and agent.agent_type != AgentType.voice_convo_agent:
|
@@ -707,6 +697,7 @@ async def send_message(
|
|
707
697
|
message_manager=server.message_manager,
|
708
698
|
agent_manager=server.agent_manager,
|
709
699
|
block_manager=server.block_manager,
|
700
|
+
job_manager=server.job_manager,
|
710
701
|
passage_manager=server.passage_manager,
|
711
702
|
actor=actor,
|
712
703
|
step_manager=server.step_manager,
|
@@ -768,9 +759,9 @@ async def send_message_streaming(
|
|
768
759
|
# TODO: This is redundant, remove soon
|
769
760
|
agent = await server.agent_manager.get_agent_by_id_async(agent_id, actor, include_relationships=["multi_agent_group"])
|
770
761
|
agent_eligible = agent.multi_agent_group is None or agent.multi_agent_group.manager_type in ["sleeptime", "voice_sleeptime"]
|
771
|
-
model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "together", "google_ai", "google_vertex"]
|
772
|
-
model_compatible_token_streaming = agent.llm_config.model_endpoint_type in ["anthropic", "openai"]
|
773
|
-
not_letta_endpoint =
|
762
|
+
model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "together", "google_ai", "google_vertex", "bedrock"]
|
763
|
+
model_compatible_token_streaming = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "bedrock"]
|
764
|
+
not_letta_endpoint = LETTA_MODEL_ENDPOINT != agent.llm_config.model_endpoint
|
774
765
|
|
775
766
|
if agent_eligible and model_compatible:
|
776
767
|
if agent.enable_sleeptime and agent.agent_type != AgentType.voice_convo_agent:
|
@@ -793,6 +784,7 @@ async def send_message_streaming(
|
|
793
784
|
message_manager=server.message_manager,
|
794
785
|
agent_manager=server.agent_manager,
|
795
786
|
block_manager=server.block_manager,
|
787
|
+
job_manager=server.job_manager,
|
796
788
|
passage_manager=server.passage_manager,
|
797
789
|
actor=actor,
|
798
790
|
step_manager=server.step_manager,
|
@@ -857,7 +849,14 @@ async def process_message_background(
|
|
857
849
|
try:
|
858
850
|
agent = await server.agent_manager.get_agent_by_id_async(agent_id, actor, include_relationships=["multi_agent_group"])
|
859
851
|
agent_eligible = agent.multi_agent_group is None or agent.multi_agent_group.manager_type in ["sleeptime", "voice_sleeptime"]
|
860
|
-
model_compatible = agent.llm_config.model_endpoint_type in [
|
852
|
+
model_compatible = agent.llm_config.model_endpoint_type in [
|
853
|
+
"anthropic",
|
854
|
+
"openai",
|
855
|
+
"together",
|
856
|
+
"google_ai",
|
857
|
+
"google_vertex",
|
858
|
+
"bedrock",
|
859
|
+
]
|
861
860
|
if agent_eligible and model_compatible:
|
862
861
|
if agent.enable_sleeptime and agent.agent_type != AgentType.voice_convo_agent:
|
863
862
|
agent_loop = SleeptimeMultiAgentV2(
|
@@ -877,6 +876,7 @@ async def process_message_background(
|
|
877
876
|
message_manager=server.message_manager,
|
878
877
|
agent_manager=server.agent_manager,
|
879
878
|
block_manager=server.block_manager,
|
879
|
+
job_manager=server.job_manager,
|
880
880
|
passage_manager=server.passage_manager,
|
881
881
|
actor=actor,
|
882
882
|
step_manager=server.step_manager,
|
@@ -886,6 +886,7 @@ async def process_message_background(
|
|
886
886
|
result = await agent_loop.step(
|
887
887
|
messages,
|
888
888
|
max_steps=max_steps,
|
889
|
+
run_id=job_id,
|
889
890
|
use_assistant_message=use_assistant_message,
|
890
891
|
request_start_timestamp_ns=request_start_timestamp_ns,
|
891
892
|
include_return_message_types=include_return_message_types,
|
@@ -897,6 +898,7 @@ async def process_message_background(
|
|
897
898
|
input_messages=messages,
|
898
899
|
stream_steps=False,
|
899
900
|
stream_tokens=False,
|
901
|
+
metadata={"job_id": job_id},
|
900
902
|
# Support for AssistantMessage
|
901
903
|
use_assistant_message=use_assistant_message,
|
902
904
|
assistant_message_tool_name=assistant_message_tool_name,
|
@@ -929,9 +931,8 @@ async def process_message_background(
|
|
929
931
|
async def send_message_async(
|
930
932
|
agent_id: str,
|
931
933
|
server: SyncServer = Depends(get_letta_server),
|
932
|
-
request:
|
934
|
+
request: LettaAsyncRequest = Body(...),
|
933
935
|
actor_id: Optional[str] = Header(None, alias="user_id"),
|
934
|
-
callback_url: Optional[str] = Query(None, description="Optional callback URL to POST to when the job completes"),
|
935
936
|
):
|
936
937
|
"""
|
937
938
|
Asynchronously process a user message and return a run object.
|
@@ -944,7 +945,7 @@ async def send_message_async(
|
|
944
945
|
run = Run(
|
945
946
|
user_id=actor.id,
|
946
947
|
status=JobStatus.created,
|
947
|
-
callback_url=callback_url,
|
948
|
+
callback_url=request.callback_url,
|
948
949
|
metadata={
|
949
950
|
"job_type": "send_message_async",
|
950
951
|
"agent_id": agent_id,
|
@@ -953,6 +954,7 @@ async def send_message_async(
|
|
953
954
|
use_assistant_message=request.use_assistant_message,
|
954
955
|
assistant_message_tool_name=request.assistant_message_tool_name,
|
955
956
|
assistant_message_tool_kwarg=request.assistant_message_tool_kwarg,
|
957
|
+
include_return_message_types=request.include_return_message_types,
|
956
958
|
),
|
957
959
|
)
|
958
960
|
run = await server.job_manager.create_job_async(pydantic_job=run, actor=actor)
|
@@ -1021,7 +1023,7 @@ async def summarize_agent_conversation(
|
|
1021
1023
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
1022
1024
|
agent = await server.agent_manager.get_agent_by_id_async(agent_id, actor, include_relationships=["multi_agent_group"])
|
1023
1025
|
agent_eligible = agent.multi_agent_group is None or agent.multi_agent_group.manager_type in ["sleeptime", "voice_sleeptime"]
|
1024
|
-
model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "together", "google_ai", "google_vertex"]
|
1026
|
+
model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "together", "google_ai", "google_vertex", "bedrock"]
|
1025
1027
|
|
1026
1028
|
if agent_eligible and model_compatible:
|
1027
1029
|
agent = LettaAgent(
|
@@ -1029,6 +1031,7 @@ async def summarize_agent_conversation(
|
|
1029
1031
|
message_manager=server.message_manager,
|
1030
1032
|
agent_manager=server.agent_manager,
|
1031
1033
|
block_manager=server.block_manager,
|
1034
|
+
job_manager=server.job_manager,
|
1032
1035
|
passage_manager=server.passage_manager,
|
1033
1036
|
actor=actor,
|
1034
1037
|
step_manager=server.step_manager,
|
@@ -3,9 +3,8 @@ from typing import Annotated, List, Optional
|
|
3
3
|
from fastapi import APIRouter, Depends, Header, HTTPException, Query
|
4
4
|
from pydantic import Field
|
5
5
|
|
6
|
-
from letta.orm.enums import JobType
|
7
6
|
from letta.orm.errors import NoResultFound
|
8
|
-
from letta.schemas.enums import JobStatus, MessageRole
|
7
|
+
from letta.schemas.enums import JobStatus, JobType, MessageRole
|
9
8
|
from letta.schemas.letta_message import LettaMessageUnion
|
10
9
|
from letta.schemas.openai.chat_completion_response import UsageStatistics
|
11
10
|
from letta.schemas.run import Run
|
@@ -92,7 +91,7 @@ async def list_run_messages(
|
|
92
91
|
after: Optional[str] = Query(None, description="Cursor for pagination"),
|
93
92
|
limit: Optional[int] = Query(100, description="Maximum number of messages to return"),
|
94
93
|
order: str = Query(
|
95
|
-
"
|
94
|
+
"asc", description="Sort order by the created_at timestamp of the objects. asc for ascending order and desc for descending order."
|
96
95
|
),
|
97
96
|
role: Optional[MessageRole] = Query(None, description="Filter by role"),
|
98
97
|
):
|
@@ -304,6 +304,37 @@ async def list_source_files(
|
|
304
304
|
)
|
305
305
|
|
306
306
|
|
307
|
+
@router.get("/{source_id}/files/{file_id}", response_model=FileMetadata, operation_id="get_file_metadata")
|
308
|
+
async def get_file_metadata(
|
309
|
+
source_id: str,
|
310
|
+
file_id: str,
|
311
|
+
include_content: bool = Query(False, description="Whether to include full file content"),
|
312
|
+
server: "SyncServer" = Depends(get_letta_server),
|
313
|
+
actor_id: Optional[str] = Header(None, alias="user_id"),
|
314
|
+
):
|
315
|
+
"""
|
316
|
+
Retrieve metadata for a specific file by its ID.
|
317
|
+
"""
|
318
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
319
|
+
|
320
|
+
# Verify the source exists and user has access
|
321
|
+
source = await server.source_manager.get_source_by_id(source_id=source_id, actor=actor)
|
322
|
+
if not source:
|
323
|
+
raise HTTPException(status_code=404, detail=f"Source with id={source_id} not found.")
|
324
|
+
|
325
|
+
# Get file metadata using the file manager
|
326
|
+
file_metadata = await server.file_manager.get_file_by_id(file_id=file_id, actor=actor, include_content=include_content)
|
327
|
+
|
328
|
+
if not file_metadata:
|
329
|
+
raise HTTPException(status_code=404, detail=f"File with id={file_id} not found.")
|
330
|
+
|
331
|
+
# Verify the file belongs to the specified source
|
332
|
+
if file_metadata.source_id != source_id:
|
333
|
+
raise HTTPException(status_code=404, detail=f"File with id={file_id} not found in source {source_id}.")
|
334
|
+
|
335
|
+
return file_metadata
|
336
|
+
|
337
|
+
|
307
338
|
# it's redundant to include /delete in the URL path. The HTTP verb DELETE already implies that action.
|
308
339
|
# it's still good practice to return a status indicating the success or failure of the deletion
|
309
340
|
@router.delete("/{source_id}/{file_id}", status_code=204, operation_id="delete_file_from_source")
|
letta/server/rest_api/utils.py
CHANGED
@@ -13,7 +13,13 @@ from openai.types.chat.chat_completion_message_tool_call import Function as Open
|
|
13
13
|
from openai.types.chat.completion_create_params import CompletionCreateParams
|
14
14
|
from pydantic import BaseModel
|
15
15
|
|
16
|
-
from letta.constants import
|
16
|
+
from letta.constants import (
|
17
|
+
DEFAULT_MESSAGE_TOOL,
|
18
|
+
DEFAULT_MESSAGE_TOOL_KWARG,
|
19
|
+
FUNC_FAILED_HEARTBEAT_MESSAGE,
|
20
|
+
REQ_HEARTBEAT_MESSAGE,
|
21
|
+
REQUEST_HEARTBEAT_PARAM,
|
22
|
+
)
|
17
23
|
from letta.errors import ContextWindowExceededError, RateLimitExceededError
|
18
24
|
from letta.helpers.datetime_helpers import get_utc_time, get_utc_timestamp_ns, ns_to_ms
|
19
25
|
from letta.helpers.message_helper import convert_message_creates_to_messages
|
@@ -169,7 +175,7 @@ def log_error_to_sentry(e):
|
|
169
175
|
sentry_sdk.capture_exception(e)
|
170
176
|
|
171
177
|
|
172
|
-
def create_input_messages(input_messages: List[MessageCreate], agent_id: str, actor: User) -> List[Message]:
|
178
|
+
def create_input_messages(input_messages: List[MessageCreate], agent_id: str, timezone: str, actor: User) -> List[Message]:
|
173
179
|
"""
|
174
180
|
Converts a user input message into the internal structured format.
|
175
181
|
|
@@ -177,7 +183,7 @@ def create_input_messages(input_messages: List[MessageCreate], agent_id: str, ac
|
|
177
183
|
we should unify this when it's clear what message attributes we need.
|
178
184
|
"""
|
179
185
|
|
180
|
-
messages = convert_message_creates_to_messages(input_messages, agent_id, wrap_user_message=False, wrap_system_message=False)
|
186
|
+
messages = convert_message_creates_to_messages(input_messages, agent_id, timezone, wrap_user_message=False, wrap_system_message=False)
|
181
187
|
for message in messages:
|
182
188
|
message.organization_id = actor.organization_id
|
183
189
|
return messages
|
@@ -192,17 +198,19 @@ def create_letta_messages_from_llm_response(
|
|
192
198
|
tool_call_id: str,
|
193
199
|
function_call_success: bool,
|
194
200
|
function_response: Optional[str],
|
201
|
+
timezone: str,
|
195
202
|
actor: User,
|
196
|
-
|
203
|
+
continue_stepping: bool = False,
|
204
|
+
heartbeat_reason: Optional[str] = None,
|
197
205
|
reasoning_content: Optional[List[Union[TextContent, ReasoningContent, RedactedReasoningContent, OmittedReasoningContent]]] = None,
|
198
206
|
pre_computed_assistant_message_id: Optional[str] = None,
|
199
207
|
llm_batch_item_id: Optional[str] = None,
|
200
208
|
step_id: str | None = None,
|
201
209
|
) -> List[Message]:
|
202
210
|
messages = []
|
203
|
-
|
204
211
|
# Construct the tool call with the assistant's message
|
205
|
-
|
212
|
+
# Force set request_heartbeat in tool_args to calculated continue_stepping
|
213
|
+
function_arguments[REQUEST_HEARTBEAT_PARAM] = continue_stepping
|
206
214
|
tool_call = OpenAIToolCall(
|
207
215
|
id=tool_call_id,
|
208
216
|
function=OpenAIFunction(
|
@@ -232,7 +240,7 @@ def create_letta_messages_from_llm_response(
|
|
232
240
|
# TODO: This helps preserve ordering
|
233
241
|
tool_message = Message(
|
234
242
|
role=MessageRole.tool,
|
235
|
-
content=[TextContent(text=package_function_response(function_call_success, function_response))],
|
243
|
+
content=[TextContent(text=package_function_response(function_call_success, function_response, timezone))],
|
236
244
|
organization_id=actor.organization_id,
|
237
245
|
agent_id=agent_id,
|
238
246
|
model=model,
|
@@ -252,9 +260,14 @@ def create_letta_messages_from_llm_response(
|
|
252
260
|
)
|
253
261
|
messages.append(tool_message)
|
254
262
|
|
255
|
-
if
|
263
|
+
if continue_stepping:
|
256
264
|
heartbeat_system_message = create_heartbeat_system_message(
|
257
|
-
agent_id=agent_id,
|
265
|
+
agent_id=agent_id,
|
266
|
+
model=model,
|
267
|
+
function_call_success=function_call_success,
|
268
|
+
actor=actor,
|
269
|
+
timezone=timezone,
|
270
|
+
heartbeat_reason=heartbeat_reason,
|
258
271
|
)
|
259
272
|
messages.append(heartbeat_system_message)
|
260
273
|
|
@@ -265,12 +278,22 @@ def create_letta_messages_from_llm_response(
|
|
265
278
|
|
266
279
|
|
267
280
|
def create_heartbeat_system_message(
|
268
|
-
agent_id: str,
|
281
|
+
agent_id: str,
|
282
|
+
model: str,
|
283
|
+
function_call_success: bool,
|
284
|
+
timezone: str,
|
285
|
+
actor: User,
|
286
|
+
llm_batch_item_id: Optional[str] = None,
|
287
|
+
heartbeat_reason: Optional[str] = None,
|
269
288
|
) -> Message:
|
270
|
-
|
289
|
+
if heartbeat_reason:
|
290
|
+
text_content = heartbeat_reason
|
291
|
+
else:
|
292
|
+
text_content = REQ_HEARTBEAT_MESSAGE if function_call_success else FUNC_FAILED_HEARTBEAT_MESSAGE
|
293
|
+
|
271
294
|
heartbeat_system_message = Message(
|
272
295
|
role=MessageRole.user,
|
273
|
-
content=[TextContent(text=get_heartbeat(text_content))],
|
296
|
+
content=[TextContent(text=get_heartbeat(timezone, text_content))],
|
274
297
|
organization_id=actor.organization_id,
|
275
298
|
agent_id=agent_id,
|
276
299
|
model=model,
|
@@ -287,6 +310,7 @@ def create_assistant_messages_from_openai_response(
|
|
287
310
|
agent_id: str,
|
288
311
|
model: str,
|
289
312
|
actor: User,
|
313
|
+
timezone: str,
|
290
314
|
) -> List[Message]:
|
291
315
|
"""
|
292
316
|
Converts an OpenAI response into Messages that follow the internal
|
@@ -303,8 +327,9 @@ def create_assistant_messages_from_openai_response(
|
|
303
327
|
tool_call_id=tool_call_id,
|
304
328
|
function_call_success=True,
|
305
329
|
function_response=None,
|
330
|
+
timezone=timezone,
|
306
331
|
actor=actor,
|
307
|
-
|
332
|
+
continue_stepping=False,
|
308
333
|
)
|
309
334
|
|
310
335
|
|
letta/server/server.py
CHANGED
@@ -43,6 +43,7 @@ from letta.schemas.embedding_config import EmbeddingConfig
|
|
43
43
|
# openai schemas
|
44
44
|
from letta.schemas.enums import JobStatus, MessageStreamStatus, ProviderCategory, ProviderType
|
45
45
|
from letta.schemas.environment_variables import SandboxEnvironmentVariableCreate
|
46
|
+
from letta.schemas.file import FileMetadata
|
46
47
|
from letta.schemas.group import GroupCreate, ManagerType, SleeptimeManager, VoiceSleeptimeManager
|
47
48
|
from letta.schemas.job import Job, JobUpdate
|
48
49
|
from letta.schemas.letta_message import LegacyLettaMessage, LettaMessage, MessageType, ToolReturnMessage
|
@@ -54,9 +55,9 @@ from letta.schemas.memory import ArchivalMemorySummary, Memory, RecallMemorySumm
|
|
54
55
|
from letta.schemas.message import Message, MessageCreate, MessageUpdate
|
55
56
|
from letta.schemas.passage import Passage, PassageUpdate
|
56
57
|
from letta.schemas.providers import (
|
57
|
-
AnthropicBedrockProvider,
|
58
58
|
AnthropicProvider,
|
59
59
|
AzureProvider,
|
60
|
+
BedrockProvider,
|
60
61
|
DeepSeekProvider,
|
61
62
|
GoogleAIProvider,
|
62
63
|
GoogleVertexProvider,
|
@@ -82,6 +83,7 @@ from letta.server.rest_api.utils import sse_async_generator
|
|
82
83
|
from letta.services.agent_manager import AgentManager
|
83
84
|
from letta.services.block_manager import BlockManager
|
84
85
|
from letta.services.file_manager import FileManager
|
86
|
+
from letta.services.file_processor.chunker.line_chunker import LineChunker
|
85
87
|
from letta.services.files_agents_manager import FileAgentManager
|
86
88
|
from letta.services.group_manager import GroupManager
|
87
89
|
from letta.services.helpers.tool_execution_helper import prepare_local_sandbox
|
@@ -365,11 +367,11 @@ class SyncServer(Server):
|
|
365
367
|
base_url=model_settings.vllm_api_base,
|
366
368
|
)
|
367
369
|
)
|
368
|
-
if model_settings.
|
370
|
+
if model_settings.aws_access_key_id and model_settings.aws_secret_access_key and model_settings.aws_default_region:
|
369
371
|
self._enabled_providers.append(
|
370
|
-
|
372
|
+
BedrockProvider(
|
371
373
|
name="bedrock",
|
372
|
-
|
374
|
+
region=model_settings.aws_default_region,
|
373
375
|
)
|
374
376
|
)
|
375
377
|
# Attempt to enable LM Studio by default
|
@@ -631,7 +633,7 @@ class SyncServer(Server):
|
|
631
633
|
|
632
634
|
packaged_user_message = system.package_user_message(
|
633
635
|
user_message=message,
|
634
|
-
|
636
|
+
timezone=agent.timezone,
|
635
637
|
)
|
636
638
|
|
637
639
|
# NOTE: eventually deprecate and only allow passing Message types
|
@@ -710,6 +712,7 @@ class SyncServer(Server):
|
|
710
712
|
# Run the agent state forward
|
711
713
|
return self._step(actor=actor, agent_id=agent_id, input_messages=message)
|
712
714
|
|
715
|
+
# TODO: Deprecate this
|
713
716
|
def send_messages(
|
714
717
|
self,
|
715
718
|
actor: User,
|
@@ -826,8 +829,6 @@ class SyncServer(Server):
|
|
826
829
|
self,
|
827
830
|
request: CreateAgent,
|
828
831
|
actor: User,
|
829
|
-
# interface
|
830
|
-
interface: Union[AgentInterface, None] = None,
|
831
832
|
) -> AgentState:
|
832
833
|
if request.llm_config is None:
|
833
834
|
if request.model is None:
|
@@ -867,6 +868,16 @@ class SyncServer(Server):
|
|
867
868
|
)
|
868
869
|
log_event(name="end create_agent db")
|
869
870
|
|
871
|
+
log_event(name="start insert_files_into_context_window db")
|
872
|
+
if request.source_ids:
|
873
|
+
for source_id in request.source_ids:
|
874
|
+
files = await self.file_manager.list_files(source_id, actor, include_content=True)
|
875
|
+
await self.insert_files_into_context_window(agent_state=main_agent, file_metadata_with_content=files, actor=actor)
|
876
|
+
|
877
|
+
main_agent = await self.agent_manager.refresh_file_blocks(agent_state=main_agent, actor=actor)
|
878
|
+
main_agent = await self.agent_manager.attach_missing_files_tools_async(agent_state=main_agent, actor=actor)
|
879
|
+
log_event(name="end insert_files_into_context_window db")
|
880
|
+
|
870
881
|
if request.enable_sleeptime:
|
871
882
|
if request.agent_type == AgentType.voice_convo_agent:
|
872
883
|
main_agent = await self.create_voice_sleeptime_agent_async(main_agent=main_agent, actor=actor)
|
@@ -1355,6 +1366,7 @@ class SyncServer(Server):
|
|
1355
1366
|
message_manager=self.message_manager,
|
1356
1367
|
agent_manager=self.agent_manager,
|
1357
1368
|
block_manager=self.block_manager,
|
1369
|
+
job_manager=self.job_manager,
|
1358
1370
|
passage_manager=self.passage_manager,
|
1359
1371
|
actor=actor,
|
1360
1372
|
step_manager=self.step_manager,
|
@@ -1369,13 +1381,25 @@ class SyncServer(Server):
|
|
1369
1381
|
)
|
1370
1382
|
await self.agent_manager.delete_agent_async(agent_id=sleeptime_agent_state.id, actor=actor)
|
1371
1383
|
|
1372
|
-
async def _upsert_file_to_agent(self, agent_id: str,
|
1384
|
+
async def _upsert_file_to_agent(self, agent_id: str, file_metadata_with_content: FileMetadata, actor: User) -> List[str]:
|
1373
1385
|
"""
|
1374
1386
|
Internal method to create or update a file <-> agent association
|
1387
|
+
|
1388
|
+
Returns:
|
1389
|
+
List of file names that were closed due to LRU eviction
|
1375
1390
|
"""
|
1376
|
-
|
1377
|
-
|
1391
|
+
# TODO: Maybe have LineChunker object be on the server level?
|
1392
|
+
content_lines = LineChunker().chunk_text(file_metadata=file_metadata_with_content)
|
1393
|
+
visible_content = "\n".join(content_lines)
|
1394
|
+
|
1395
|
+
file_agent, closed_files = await self.file_agent_manager.attach_file(
|
1396
|
+
agent_id=agent_id,
|
1397
|
+
file_id=file_metadata_with_content.id,
|
1398
|
+
file_name=file_metadata_with_content.file_name,
|
1399
|
+
actor=actor,
|
1400
|
+
visible_content=visible_content,
|
1378
1401
|
)
|
1402
|
+
return closed_files
|
1379
1403
|
|
1380
1404
|
async def _remove_file_from_agent(self, agent_id: str, file_id: str, actor: User) -> None:
|
1381
1405
|
"""
|
@@ -1391,7 +1415,7 @@ class SyncServer(Server):
|
|
1391
1415
|
logger.info(f"File {file_id} already removed from agent {agent_id}, skipping...")
|
1392
1416
|
|
1393
1417
|
async def insert_file_into_context_windows(
|
1394
|
-
self, source_id: str,
|
1418
|
+
self, source_id: str, file_metadata_with_content: FileMetadata, actor: User, agent_states: Optional[List[AgentState]] = None
|
1395
1419
|
) -> List[AgentState]:
|
1396
1420
|
"""
|
1397
1421
|
Insert the uploaded document into the context window of all agents
|
@@ -1406,12 +1430,19 @@ class SyncServer(Server):
|
|
1406
1430
|
logger.info(f"Inserting document into context window for source: {source_id}")
|
1407
1431
|
logger.info(f"Attached agents: {[a.id for a in agent_states]}")
|
1408
1432
|
|
1409
|
-
|
1433
|
+
# Collect any files that were closed due to LRU eviction during bulk attach
|
1434
|
+
all_closed_files = await asyncio.gather(
|
1435
|
+
*(self._upsert_file_to_agent(agent_state.id, file_metadata_with_content, actor) for agent_state in agent_states)
|
1436
|
+
)
|
1437
|
+
# Flatten and log if any files were closed
|
1438
|
+
closed_files = [file for closed_list in all_closed_files for file in closed_list]
|
1439
|
+
if closed_files:
|
1440
|
+
logger.info(f"LRU eviction closed {len(closed_files)} files during bulk attach: {closed_files}")
|
1410
1441
|
|
1411
1442
|
return agent_states
|
1412
1443
|
|
1413
1444
|
async def insert_files_into_context_window(
|
1414
|
-
self, agent_state: AgentState,
|
1445
|
+
self, agent_state: AgentState, file_metadata_with_content: List[FileMetadata], actor: User
|
1415
1446
|
) -> None:
|
1416
1447
|
"""
|
1417
1448
|
Insert the uploaded documents into the context window of an agent
|
@@ -1419,15 +1450,14 @@ class SyncServer(Server):
|
|
1419
1450
|
"""
|
1420
1451
|
logger.info(f"Inserting documents into context window for agent_state: {agent_state.id}")
|
1421
1452
|
|
1422
|
-
|
1423
|
-
|
1424
|
-
|
1425
|
-
await asyncio.gather(
|
1426
|
-
*(
|
1427
|
-
self._upsert_file_to_agent(agent_state.id, text, file_id, file_name, actor)
|
1428
|
-
for text, file_id, file_name in zip(texts, file_ids, file_names)
|
1429
|
-
)
|
1453
|
+
# Collect any files that were closed due to LRU eviction during bulk insert
|
1454
|
+
all_closed_files = await asyncio.gather(
|
1455
|
+
*(self._upsert_file_to_agent(agent_state.id, file_metadata, actor) for file_metadata in file_metadata_with_content)
|
1430
1456
|
)
|
1457
|
+
# Flatten and log if any files were closed
|
1458
|
+
closed_files = [file for closed_list in all_closed_files for file in closed_list]
|
1459
|
+
if closed_files:
|
1460
|
+
logger.info(f"LRU eviction closed {len(closed_files)} files during bulk insert: {closed_files}")
|
1431
1461
|
|
1432
1462
|
async def remove_file_from_context_windows(self, source_id: str, file_id: str, actor: User) -> None:
|
1433
1463
|
"""
|
@@ -1996,6 +2026,7 @@ class SyncServer(Server):
|
|
1996
2026
|
message_manager=self.message_manager,
|
1997
2027
|
agent_manager=self.agent_manager,
|
1998
2028
|
block_manager=self.block_manager,
|
2029
|
+
job_manager=self.job_manager,
|
1999
2030
|
passage_manager=self.passage_manager,
|
2000
2031
|
actor=actor,
|
2001
2032
|
sandbox_env_vars=tool_env_vars,
|