letta-nightly 0.8.17.dev20250722104501__py3-none-any.whl → 0.9.0.dev20250724081419__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 +5 -3
- letta/agent.py +3 -2
- letta/agents/base_agent.py +4 -1
- letta/agents/voice_agent.py +1 -0
- letta/constants.py +4 -2
- letta/functions/schema_generator.py +2 -1
- letta/groups/dynamic_multi_agent.py +1 -0
- letta/helpers/converters.py +13 -5
- letta/helpers/json_helpers.py +6 -1
- letta/llm_api/anthropic.py +2 -2
- letta/llm_api/aws_bedrock.py +24 -94
- letta/llm_api/deepseek.py +1 -1
- letta/llm_api/google_ai_client.py +0 -38
- letta/llm_api/google_constants.py +6 -3
- letta/llm_api/helpers.py +1 -1
- letta/llm_api/llm_api_tools.py +4 -7
- letta/llm_api/mistral.py +12 -37
- letta/llm_api/openai.py +17 -17
- letta/llm_api/sample_response_jsons/aws_bedrock.json +38 -0
- letta/llm_api/sample_response_jsons/lmstudio_embedding_list.json +15 -0
- letta/llm_api/sample_response_jsons/lmstudio_model_list.json +15 -0
- letta/local_llm/constants.py +2 -23
- letta/local_llm/json_parser.py +11 -1
- letta/local_llm/llm_chat_completion_wrappers/airoboros.py +9 -9
- letta/local_llm/llm_chat_completion_wrappers/chatml.py +7 -8
- letta/local_llm/llm_chat_completion_wrappers/configurable_wrapper.py +6 -6
- letta/local_llm/llm_chat_completion_wrappers/dolphin.py +3 -3
- letta/local_llm/llm_chat_completion_wrappers/simple_summary_wrapper.py +1 -1
- letta/local_llm/ollama/api.py +2 -2
- letta/orm/__init__.py +1 -0
- letta/orm/agent.py +33 -2
- letta/orm/files_agents.py +13 -10
- letta/orm/mixins.py +8 -0
- letta/orm/prompt.py +13 -0
- letta/orm/sqlite_functions.py +61 -17
- letta/otel/db_pool_monitoring.py +13 -12
- letta/schemas/agent.py +69 -4
- letta/schemas/agent_file.py +2 -0
- letta/schemas/block.py +11 -0
- letta/schemas/embedding_config.py +15 -3
- letta/schemas/enums.py +2 -0
- letta/schemas/file.py +1 -1
- letta/schemas/folder.py +74 -0
- letta/schemas/memory.py +12 -6
- letta/schemas/prompt.py +9 -0
- letta/schemas/providers/__init__.py +47 -0
- letta/schemas/providers/anthropic.py +78 -0
- letta/schemas/providers/azure.py +80 -0
- letta/schemas/providers/base.py +201 -0
- letta/schemas/providers/bedrock.py +78 -0
- letta/schemas/providers/cerebras.py +79 -0
- letta/schemas/providers/cohere.py +18 -0
- letta/schemas/providers/deepseek.py +63 -0
- letta/schemas/providers/google_gemini.py +102 -0
- letta/schemas/providers/google_vertex.py +54 -0
- letta/schemas/providers/groq.py +35 -0
- letta/schemas/providers/letta.py +39 -0
- letta/schemas/providers/lmstudio.py +97 -0
- letta/schemas/providers/mistral.py +41 -0
- letta/schemas/providers/ollama.py +151 -0
- letta/schemas/providers/openai.py +241 -0
- letta/schemas/providers/together.py +85 -0
- letta/schemas/providers/vllm.py +57 -0
- letta/schemas/providers/xai.py +66 -0
- letta/server/db.py +0 -5
- letta/server/rest_api/app.py +4 -3
- letta/server/rest_api/routers/v1/__init__.py +2 -0
- letta/server/rest_api/routers/v1/agents.py +152 -4
- letta/server/rest_api/routers/v1/folders.py +490 -0
- letta/server/rest_api/routers/v1/providers.py +2 -2
- letta/server/rest_api/routers/v1/sources.py +21 -26
- letta/server/rest_api/routers/v1/tools.py +90 -15
- letta/server/server.py +50 -95
- letta/services/agent_manager.py +420 -81
- letta/services/agent_serialization_manager.py +707 -0
- letta/services/block_manager.py +132 -11
- letta/services/file_manager.py +104 -29
- letta/services/file_processor/embedder/pinecone_embedder.py +8 -2
- letta/services/file_processor/file_processor.py +75 -24
- letta/services/file_processor/parser/markitdown_parser.py +95 -0
- letta/services/files_agents_manager.py +57 -17
- letta/services/group_manager.py +7 -0
- letta/services/helpers/agent_manager_helper.py +25 -15
- letta/services/provider_manager.py +2 -2
- letta/services/source_manager.py +35 -16
- letta/services/tool_executor/files_tool_executor.py +12 -5
- letta/services/tool_manager.py +12 -0
- letta/services/tool_sandbox/e2b_sandbox.py +52 -48
- letta/settings.py +9 -6
- letta/streaming_utils.py +2 -1
- letta/utils.py +34 -1
- {letta_nightly-0.8.17.dev20250722104501.dist-info → letta_nightly-0.9.0.dev20250724081419.dist-info}/METADATA +9 -8
- {letta_nightly-0.8.17.dev20250722104501.dist-info → letta_nightly-0.9.0.dev20250724081419.dist-info}/RECORD +96 -68
- {letta_nightly-0.8.17.dev20250722104501.dist-info → letta_nightly-0.9.0.dev20250724081419.dist-info}/LICENSE +0 -0
- {letta_nightly-0.8.17.dev20250722104501.dist-info → letta_nightly-0.9.0.dev20250724081419.dist-info}/WHEEL +0 -0
- {letta_nightly-0.8.17.dev20250722104501.dist-info → letta_nightly-0.9.0.dev20250724081419.dist-info}/entry_points.txt +0 -0
@@ -1,3 +1,4 @@
|
|
1
|
+
import json
|
1
2
|
from typing import Any, Dict, List, Optional, Union
|
2
3
|
|
3
4
|
from composio.client import ComposioClientError, HTTPError, NoItemsFound
|
@@ -17,10 +18,14 @@ from letta.functions.functions import derive_openai_json_schema
|
|
17
18
|
from letta.functions.mcp_client.exceptions import MCPTimeoutError
|
18
19
|
from letta.functions.mcp_client.types import MCPServerType, MCPTool, SSEServerConfig, StdioServerConfig, StreamableHTTPServerConfig
|
19
20
|
from letta.helpers.composio_helpers import get_composio_api_key
|
21
|
+
from letta.llm_api.llm_client import LLMClient
|
20
22
|
from letta.log import get_logger
|
21
23
|
from letta.orm.errors import UniqueConstraintViolationError
|
24
|
+
from letta.schemas.enums import MessageRole
|
22
25
|
from letta.schemas.letta_message import ToolReturnMessage
|
26
|
+
from letta.schemas.letta_message_content import TextContent
|
23
27
|
from letta.schemas.mcp import UpdateSSEMCPServer, UpdateStdioMCPServer, UpdateStreamableHTTPMCPServer
|
28
|
+
from letta.schemas.message import Message
|
24
29
|
from letta.schemas.tool import Tool, ToolCreate, ToolRunFromSource, ToolUpdate
|
25
30
|
from letta.server.rest_api.utils import get_letta_server
|
26
31
|
from letta.server.server import SyncServer
|
@@ -106,21 +111,6 @@ async def list_tools(
|
|
106
111
|
raise HTTPException(status_code=500, detail=str(e))
|
107
112
|
|
108
113
|
|
109
|
-
@router.get("/count", response_model=int, operation_id="count_tools")
|
110
|
-
def count_tools(
|
111
|
-
server: SyncServer = Depends(get_letta_server),
|
112
|
-
actor_id: Optional[str] = Header(None, alias="user_id"),
|
113
|
-
):
|
114
|
-
"""
|
115
|
-
Get a count of all tools available to agents belonging to the org of the user
|
116
|
-
"""
|
117
|
-
try:
|
118
|
-
return server.tool_manager.size(actor=server.user_manager.get_user_or_default(user_id=actor_id))
|
119
|
-
except Exception as e:
|
120
|
-
print(f"Error occurred: {e}")
|
121
|
-
raise HTTPException(status_code=500, detail=str(e))
|
122
|
-
|
123
|
-
|
124
114
|
@router.post("/", response_model=Tool, operation_id="create_tool")
|
125
115
|
async def create_tool(
|
126
116
|
request: ToolCreate = Body(...),
|
@@ -701,3 +691,88 @@ async def generate_json_schema(
|
|
701
691
|
|
702
692
|
except Exception as e:
|
703
693
|
raise HTTPException(status_code=400, detail=f"Failed to generate schema: {str(e)}")
|
694
|
+
|
695
|
+
|
696
|
+
class GenerateToolInput(BaseModel):
|
697
|
+
tool_name: str = Field(..., description="Name of the tool to generate code for")
|
698
|
+
prompt: str = Field(..., description="User prompt to generate code")
|
699
|
+
handle: Optional[str] = Field(None, description="Handle of the tool to generate code for")
|
700
|
+
starter_code: Optional[str] = Field(None, description="Python source code to parse for JSON schema")
|
701
|
+
validation_errors: List[str] = Field(..., description="List of validation errors")
|
702
|
+
|
703
|
+
|
704
|
+
class GenerateToolOutput(BaseModel):
|
705
|
+
tool: Tool = Field(..., description="Generated tool")
|
706
|
+
sample_args: Dict[str, Any] = Field(..., description="Sample arguments for the tool")
|
707
|
+
response: str = Field(..., description="Response from the assistant")
|
708
|
+
|
709
|
+
|
710
|
+
@router.post("/generate-tool", response_model=GenerateToolOutput, operation_id="generate_tool")
|
711
|
+
async def generate_tool_from_prompt(
|
712
|
+
request: GenerateToolInput = Body(...),
|
713
|
+
server: SyncServer = Depends(get_letta_server),
|
714
|
+
actor_id: Optional[str] = Header(None, alias="user_id"),
|
715
|
+
):
|
716
|
+
"""
|
717
|
+
Generate a tool from the given user prompt.
|
718
|
+
"""
|
719
|
+
try:
|
720
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
721
|
+
llm_config = await server.get_cached_llm_config_async(actor=actor, handle=request.handle or "anthropic/claude-3-5-sonnet-20240620")
|
722
|
+
formatted_prompt = (
|
723
|
+
f"Generate a python function named {request.tool_name} using the instructions below "
|
724
|
+
+ (f"based on this starter code: \n\n```\n{request.starter_code}\n```\n\n" if request.starter_code else "\n")
|
725
|
+
+ (f"Note the following validation errors: \n{' '.join(request.validation_errors)}\n\n" if request.validation_errors else "\n")
|
726
|
+
+ f"Instructions: {request.prompt}"
|
727
|
+
)
|
728
|
+
llm_client = LLMClient.create(
|
729
|
+
provider_type=llm_config.model_endpoint_type,
|
730
|
+
actor=actor,
|
731
|
+
)
|
732
|
+
assert llm_client is not None
|
733
|
+
|
734
|
+
input_messages = [
|
735
|
+
Message(role=MessageRole.system, content=[TextContent(text="Placeholder system message")]),
|
736
|
+
Message(role=MessageRole.assistant, content=[TextContent(text="Placeholder assistant message")]),
|
737
|
+
Message(role=MessageRole.user, content=[TextContent(text=formatted_prompt)]),
|
738
|
+
]
|
739
|
+
|
740
|
+
tool = {
|
741
|
+
"name": "generate_tool",
|
742
|
+
"description": "This method generates the raw source code for a custom tool that can be attached to and agent for llm invocation.",
|
743
|
+
"parameters": {
|
744
|
+
"type": "object",
|
745
|
+
"properties": {
|
746
|
+
"raw_source_code": {"type": "string", "description": "The raw python source code of the custom tool."},
|
747
|
+
"sample_args_json": {
|
748
|
+
"type": "string",
|
749
|
+
"description": "The JSON dict that contains sample args for a test run of the python function. Key is the name of the function parameter and value is an example argument that is passed in.",
|
750
|
+
},
|
751
|
+
"pip_requirements_json": {
|
752
|
+
"type": "string",
|
753
|
+
"description": "Optional JSON dict that contains pip packages to be installed if needed by the source code. Key is the name of the pip package and value is the version number.",
|
754
|
+
},
|
755
|
+
},
|
756
|
+
"required": ["raw_source_code", "sample_args_json", "pip_requirements_json"],
|
757
|
+
},
|
758
|
+
}
|
759
|
+
request_data = llm_client.build_request_data(
|
760
|
+
input_messages,
|
761
|
+
llm_config,
|
762
|
+
tools=[tool],
|
763
|
+
)
|
764
|
+
response_data = await llm_client.request_async(request_data, llm_config)
|
765
|
+
response = llm_client.convert_response_to_chat_completion(response_data, input_messages, llm_config)
|
766
|
+
output = json.loads(response.choices[0].message.tool_calls[0].function.arguments)
|
767
|
+
return GenerateToolOutput(
|
768
|
+
tool=Tool(
|
769
|
+
name=request.tool_name,
|
770
|
+
source_type="python",
|
771
|
+
source_code=output["raw_source_code"],
|
772
|
+
),
|
773
|
+
sample_args=json.loads(output["sample_args_json"]),
|
774
|
+
response=response.choices[0].message.content,
|
775
|
+
)
|
776
|
+
except Exception as e:
|
777
|
+
logger.error(f"Failed to generate tool: {str(e)}")
|
778
|
+
raise HTTPException(status_code=500, detail=f"Failed to generate tool: {str(e)}")
|
letta/server/server.py
CHANGED
@@ -42,7 +42,6 @@ from letta.schemas.embedding_config import EmbeddingConfig
|
|
42
42
|
# openai schemas
|
43
43
|
from letta.schemas.enums import JobStatus, MessageStreamStatus, ProviderCategory, ProviderType
|
44
44
|
from letta.schemas.environment_variables import SandboxEnvironmentVariableCreate
|
45
|
-
from letta.schemas.file import FileMetadata
|
46
45
|
from letta.schemas.group import GroupCreate, ManagerType, SleeptimeManager, VoiceSleeptimeManager
|
47
46
|
from letta.schemas.job import Job, JobUpdate
|
48
47
|
from letta.schemas.letta_message import LegacyLettaMessage, LettaMessage, MessageType, ToolReturnMessage
|
@@ -68,8 +67,6 @@ from letta.schemas.providers import (
|
|
68
67
|
OpenAIProvider,
|
69
68
|
Provider,
|
70
69
|
TogetherProvider,
|
71
|
-
VLLMChatCompletionsProvider,
|
72
|
-
VLLMCompletionsProvider,
|
73
70
|
XAIProvider,
|
74
71
|
)
|
75
72
|
from letta.schemas.sandbox_config import LocalSandboxConfig, SandboxConfigCreate, SandboxType
|
@@ -81,9 +78,9 @@ from letta.server.rest_api.chat_completions_interface import ChatCompletionsStre
|
|
81
78
|
from letta.server.rest_api.interface import StreamingServerInterface
|
82
79
|
from letta.server.rest_api.utils import sse_async_generator
|
83
80
|
from letta.services.agent_manager import AgentManager
|
81
|
+
from letta.services.agent_serialization_manager import AgentSerializationManager
|
84
82
|
from letta.services.block_manager import BlockManager
|
85
83
|
from letta.services.file_manager import FileManager
|
86
|
-
from letta.services.file_processor.chunker.line_chunker import LineChunker
|
87
84
|
from letta.services.files_agents_manager import FileAgentManager
|
88
85
|
from letta.services.group_manager import GroupManager
|
89
86
|
from letta.services.helpers.tool_execution_helper import prepare_local_sandbox
|
@@ -226,6 +223,18 @@ class SyncServer(Server):
|
|
226
223
|
self.file_agent_manager = FileAgentManager()
|
227
224
|
self.file_manager = FileManager()
|
228
225
|
|
226
|
+
self.agent_serialization_manager = AgentSerializationManager(
|
227
|
+
agent_manager=self.agent_manager,
|
228
|
+
tool_manager=self.tool_manager,
|
229
|
+
source_manager=self.source_manager,
|
230
|
+
block_manager=self.block_manager,
|
231
|
+
group_manager=self.group_manager,
|
232
|
+
mcp_manager=self.mcp_manager,
|
233
|
+
file_manager=self.file_manager,
|
234
|
+
file_agent_manager=self.file_agent_manager,
|
235
|
+
message_manager=self.message_manager,
|
236
|
+
)
|
237
|
+
|
229
238
|
# A resusable httpx client
|
230
239
|
timeout = httpx.Timeout(connect=10.0, read=20.0, write=10.0, pool=10.0)
|
231
240
|
limits = httpx.Limits(max_connections=100, max_keepalive_connections=80, keepalive_expiry=300)
|
@@ -360,7 +369,7 @@ class SyncServer(Server):
|
|
360
369
|
)
|
361
370
|
)
|
362
371
|
# NOTE: to use the /chat/completions endpoint, you need to specify extra flags on vLLM startup
|
363
|
-
# see: https://docs.vllm.ai/en/
|
372
|
+
# see: https://docs.vllm.ai/en/stable/features/tool_calling.html
|
364
373
|
# e.g. "... --enable-auto-tool-choice --tool-call-parser hermes"
|
365
374
|
self._enabled_providers.append(
|
366
375
|
VLLMChatCompletionsProvider(
|
@@ -460,7 +469,7 @@ class SyncServer(Server):
|
|
460
469
|
# Determine whether or not to token stream based on the capability of the interface
|
461
470
|
token_streaming = letta_agent.interface.streaming_mode if hasattr(letta_agent.interface, "streaming_mode") else False
|
462
471
|
|
463
|
-
logger.debug(
|
472
|
+
logger.debug("Starting agent step")
|
464
473
|
if interface:
|
465
474
|
metadata = interface.metadata if hasattr(interface, "metadata") else None
|
466
475
|
else:
|
@@ -534,7 +543,7 @@ class SyncServer(Server):
|
|
534
543
|
letta_agent.interface.print_messages_raw(letta_agent.messages)
|
535
544
|
|
536
545
|
elif command.lower() == "memory":
|
537
|
-
ret_str =
|
546
|
+
ret_str = "\nDumping memory contents:\n" + f"\n{str(letta_agent.agent_state.memory)}" + f"\n{str(letta_agent.passage_manager)}"
|
538
547
|
return ret_str
|
539
548
|
|
540
549
|
elif command.lower() == "pop" or command.lower().startswith("pop "):
|
@@ -554,7 +563,7 @@ class SyncServer(Server):
|
|
554
563
|
|
555
564
|
elif command.lower() == "retry":
|
556
565
|
# TODO this needs to also modify the persistence manager
|
557
|
-
logger.debug(
|
566
|
+
logger.debug("Retrying for another answer")
|
558
567
|
while len(letta_agent.messages) > 0:
|
559
568
|
if letta_agent.messages[-1].get("role") == "user":
|
560
569
|
# we want to pop up to the last user message and send it again
|
@@ -770,6 +779,7 @@ class SyncServer(Server):
|
|
770
779
|
self._embedding_config_cache[key] = self.get_embedding_config_from_handle(actor=actor, **kwargs)
|
771
780
|
return self._embedding_config_cache[key]
|
772
781
|
|
782
|
+
# @async_redis_cache(key_func=lambda (actor, **kwargs): actor.id + hash(kwargs))
|
773
783
|
@trace_method
|
774
784
|
async def get_cached_embedding_config_async(self, actor: User, **kwargs):
|
775
785
|
key = make_key(**kwargs)
|
@@ -782,9 +792,9 @@ class SyncServer(Server):
|
|
782
792
|
self,
|
783
793
|
request: CreateAgent,
|
784
794
|
actor: User,
|
785
|
-
|
786
|
-
interface: Union[AgentInterface, None] = None,
|
795
|
+
interface: AgentInterface | None = None,
|
787
796
|
) -> AgentState:
|
797
|
+
warnings.warn("This method is deprecated, use create_agent_async where possible.", DeprecationWarning, stacklevel=2)
|
788
798
|
if request.llm_config is None:
|
789
799
|
if request.model is None:
|
790
800
|
raise ValueError("Must specify either model or llm_config in request")
|
@@ -873,7 +883,9 @@ class SyncServer(Server):
|
|
873
883
|
if request.source_ids:
|
874
884
|
for source_id in request.source_ids:
|
875
885
|
files = await self.file_manager.list_files(source_id, actor, include_content=True)
|
876
|
-
await self.insert_files_into_context_window(
|
886
|
+
await self.agent_manager.insert_files_into_context_window(
|
887
|
+
agent_state=main_agent, file_metadata_with_content=files, actor=actor
|
888
|
+
)
|
877
889
|
|
878
890
|
main_agent = await self.agent_manager.refresh_file_blocks(agent_state=main_agent, actor=actor)
|
879
891
|
main_agent = await self.agent_manager.attach_missing_files_tools_async(agent_state=main_agent, actor=actor)
|
@@ -1320,7 +1332,6 @@ class SyncServer(Server):
|
|
1320
1332
|
# TODO: delete data from agent passage stores (?)
|
1321
1333
|
|
1322
1334
|
async def load_file_to_source(self, source_id: str, file_path: str, job_id: str, actor: User) -> Job:
|
1323
|
-
|
1324
1335
|
# update job
|
1325
1336
|
job = await self.job_manager.get_job_by_id_async(job_id, actor=actor)
|
1326
1337
|
job.status = JobStatus.running
|
@@ -1397,90 +1408,28 @@ class SyncServer(Server):
|
|
1397
1408
|
except NoResultFound:
|
1398
1409
|
logger.info(f"File {file_id} already removed from agent {agent_id}, skipping...")
|
1399
1410
|
|
1400
|
-
async def insert_file_into_context_windows(
|
1401
|
-
self, source_id: str, file_metadata_with_content: FileMetadata, actor: User, agent_states: Optional[List[AgentState]] = None
|
1402
|
-
) -> List[AgentState]:
|
1403
|
-
"""
|
1404
|
-
Insert the uploaded document into the context window of all agents
|
1405
|
-
attached to the given source.
|
1406
|
-
"""
|
1407
|
-
agent_states = agent_states or await self.source_manager.list_attached_agents(source_id=source_id, actor=actor)
|
1408
|
-
|
1409
|
-
# Return early
|
1410
|
-
if not agent_states:
|
1411
|
-
return []
|
1412
|
-
|
1413
|
-
logger.info(f"Inserting document into context window for source: {source_id}")
|
1414
|
-
logger.info(f"Attached agents: {[a.id for a in agent_states]}")
|
1415
|
-
|
1416
|
-
# Generate visible content for the file
|
1417
|
-
line_chunker = LineChunker()
|
1418
|
-
content_lines = line_chunker.chunk_text(file_metadata=file_metadata_with_content)
|
1419
|
-
visible_content = "\n".join(content_lines)
|
1420
|
-
visible_content_map = {file_metadata_with_content.file_name: visible_content}
|
1421
|
-
|
1422
|
-
# Attach file to each agent using bulk method (one file per agent, but atomic per agent)
|
1423
|
-
all_closed_files = await asyncio.gather(
|
1424
|
-
*(
|
1425
|
-
self.file_agent_manager.attach_files_bulk(
|
1426
|
-
agent_id=agent_state.id,
|
1427
|
-
files_metadata=[file_metadata_with_content],
|
1428
|
-
visible_content_map=visible_content_map,
|
1429
|
-
actor=actor,
|
1430
|
-
)
|
1431
|
-
for agent_state in agent_states
|
1432
|
-
)
|
1433
|
-
)
|
1434
|
-
# Flatten and log if any files were closed
|
1435
|
-
closed_files = [file for closed_list in all_closed_files for file in closed_list]
|
1436
|
-
if closed_files:
|
1437
|
-
logger.info(f"LRU eviction closed {len(closed_files)} files during bulk attach: {closed_files}")
|
1438
|
-
|
1439
|
-
return agent_states
|
1440
|
-
|
1441
|
-
async def insert_files_into_context_window(
|
1442
|
-
self, agent_state: AgentState, file_metadata_with_content: List[FileMetadata], actor: User
|
1443
|
-
) -> None:
|
1444
|
-
"""
|
1445
|
-
Insert the uploaded documents into the context window of an agent
|
1446
|
-
attached to the given source.
|
1447
|
-
"""
|
1448
|
-
logger.info(f"Inserting {len(file_metadata_with_content)} documents into context window for agent_state: {agent_state.id}")
|
1449
|
-
|
1450
|
-
# Generate visible content for each file
|
1451
|
-
line_chunker = LineChunker()
|
1452
|
-
visible_content_map = {}
|
1453
|
-
for file_metadata in file_metadata_with_content:
|
1454
|
-
content_lines = line_chunker.chunk_text(file_metadata=file_metadata)
|
1455
|
-
visible_content_map[file_metadata.file_name] = "\n".join(content_lines)
|
1456
|
-
|
1457
|
-
# Use bulk attach to avoid race conditions and duplicate LRU eviction decisions
|
1458
|
-
closed_files = await self.file_agent_manager.attach_files_bulk(
|
1459
|
-
agent_id=agent_state.id,
|
1460
|
-
files_metadata=file_metadata_with_content,
|
1461
|
-
visible_content_map=visible_content_map,
|
1462
|
-
actor=actor,
|
1463
|
-
)
|
1464
|
-
|
1465
|
-
if closed_files:
|
1466
|
-
logger.info(f"LRU eviction closed {len(closed_files)} files during bulk insert: {closed_files}")
|
1467
|
-
|
1468
1411
|
async def remove_file_from_context_windows(self, source_id: str, file_id: str, actor: User) -> None:
|
1469
1412
|
"""
|
1470
1413
|
Remove the document from the context window of all agents
|
1471
1414
|
attached to the given source.
|
1472
1415
|
"""
|
1473
|
-
#
|
1474
|
-
|
1416
|
+
# Use the optimized ids_only parameter
|
1417
|
+
agent_ids = await self.source_manager.list_attached_agents(source_id=source_id, actor=actor, ids_only=True)
|
1475
1418
|
|
1476
|
-
# Return early
|
1477
|
-
if not
|
1419
|
+
# Return early if no agents
|
1420
|
+
if not agent_ids:
|
1478
1421
|
return
|
1479
1422
|
|
1480
1423
|
logger.info(f"Removing file from context window for source: {source_id}")
|
1481
|
-
logger.info(f"Attached agents: {
|
1424
|
+
logger.info(f"Attached agents: {agent_ids}")
|
1425
|
+
|
1426
|
+
# Create agent-file pairs for bulk deletion
|
1427
|
+
agent_file_pairs = [(agent_id, file_id) for agent_id in agent_ids]
|
1428
|
+
|
1429
|
+
# Bulk delete in a single query
|
1430
|
+
deleted_count = await self.file_agent_manager.detach_file_bulk(agent_file_pairs=agent_file_pairs, actor=actor)
|
1482
1431
|
|
1483
|
-
|
1432
|
+
logger.info(f"Removed file {file_id} from {deleted_count} agent context windows")
|
1484
1433
|
|
1485
1434
|
async def remove_files_from_context_window(self, agent_state: AgentState, file_ids: List[str], actor: User) -> None:
|
1486
1435
|
"""
|
@@ -1490,7 +1439,13 @@ class SyncServer(Server):
|
|
1490
1439
|
logger.info(f"Removing files from context window for agent_state: {agent_state.id}")
|
1491
1440
|
logger.info(f"Files to remove: {file_ids}")
|
1492
1441
|
|
1493
|
-
|
1442
|
+
# Create agent-file pairs for bulk deletion
|
1443
|
+
agent_file_pairs = [(agent_state.id, file_id) for file_id in file_ids]
|
1444
|
+
|
1445
|
+
# Bulk delete in a single query
|
1446
|
+
deleted_count = await self.file_agent_manager.detach_file_bulk(agent_file_pairs=agent_file_pairs, actor=actor)
|
1447
|
+
|
1448
|
+
logger.info(f"Removed {deleted_count} files from agent {agent_state.id}")
|
1494
1449
|
|
1495
1450
|
async def create_document_sleeptime_agent_async(
|
1496
1451
|
self, main_agent: AgentState, source: Source, actor: User, clear_history: bool = False
|
@@ -1562,7 +1517,6 @@ class SyncServer(Server):
|
|
1562
1517
|
# Add extra metadata to the sources
|
1563
1518
|
sources_with_metadata = []
|
1564
1519
|
for source in sources:
|
1565
|
-
|
1566
1520
|
# count number of passages
|
1567
1521
|
num_passages = self.agent_manager.passage_size(actor=actor, source_id=source.id)
|
1568
1522
|
|
@@ -1932,7 +1886,9 @@ class SyncServer(Server):
|
|
1932
1886
|
def get_provider_from_name(self, provider_name: str, actor: User) -> Provider:
|
1933
1887
|
providers = [provider for provider in self.get_enabled_providers(actor) if provider.name == provider_name]
|
1934
1888
|
if not providers:
|
1935
|
-
raise ValueError(
|
1889
|
+
raise ValueError(
|
1890
|
+
f"Provider {provider_name} is not supported (supported providers: {', '.join([provider.name for provider in self._enabled_providers])})"
|
1891
|
+
)
|
1936
1892
|
elif len(providers) > 1:
|
1937
1893
|
raise ValueError(f"Multiple providers with name {provider_name} supported")
|
1938
1894
|
else:
|
@@ -1944,7 +1900,9 @@ class SyncServer(Server):
|
|
1944
1900
|
all_providers = await self.get_enabled_providers_async(actor)
|
1945
1901
|
providers = [provider for provider in all_providers if provider.name == provider_name]
|
1946
1902
|
if not providers:
|
1947
|
-
raise ValueError(
|
1903
|
+
raise ValueError(
|
1904
|
+
f"Provider {provider_name} is not supported (supported providers: {', '.join([provider.name for provider in self._enabled_providers])})"
|
1905
|
+
)
|
1948
1906
|
elif len(providers) > 1:
|
1949
1907
|
raise ValueError(f"Multiple providers with name {provider_name} supported")
|
1950
1908
|
else:
|
@@ -2112,7 +2070,6 @@ class SyncServer(Server):
|
|
2112
2070
|
mcp_config_path = os.path.join(constants.LETTA_DIR, constants.MCP_CONFIG_NAME)
|
2113
2071
|
if os.path.exists(mcp_config_path):
|
2114
2072
|
with open(mcp_config_path, "r") as f:
|
2115
|
-
|
2116
2073
|
try:
|
2117
2074
|
mcp_config = json.load(f)
|
2118
2075
|
except Exception as e:
|
@@ -2124,7 +2081,6 @@ class SyncServer(Server):
|
|
2124
2081
|
# with the value being the schema from StdioServerParameters
|
2125
2082
|
if MCP_CONFIG_TOPLEVEL_KEY in mcp_config:
|
2126
2083
|
for server_name, server_params_raw in mcp_config[MCP_CONFIG_TOPLEVEL_KEY].items():
|
2127
|
-
|
2128
2084
|
# No support for duplicate server names
|
2129
2085
|
if server_name in mcp_server_list:
|
2130
2086
|
logger.error(f"Duplicate MCP server name found (skipping): {server_name}")
|
@@ -2295,7 +2251,6 @@ class SyncServer(Server):
|
|
2295
2251
|
|
2296
2252
|
# For streaming response
|
2297
2253
|
try:
|
2298
|
-
|
2299
2254
|
# TODO: move this logic into server.py
|
2300
2255
|
|
2301
2256
|
# Get the generator object off of the agent's streaming interface
|
@@ -2435,9 +2390,9 @@ class SyncServer(Server):
|
|
2435
2390
|
if not stream_steps and stream_tokens:
|
2436
2391
|
raise ValueError("stream_steps must be 'true' if stream_tokens is 'true'")
|
2437
2392
|
|
2438
|
-
group = self.group_manager.
|
2393
|
+
group = await self.group_manager.retrieve_group_async(group_id=group_id, actor=actor)
|
2439
2394
|
agent_state_id = group.manager_agent_id or (group.agent_ids[0] if len(group.agent_ids) > 0 else None)
|
2440
|
-
agent_state = self.agent_manager.
|
2395
|
+
agent_state = await self.agent_manager.get_agent_by_id_async(agent_id=agent_state_id, actor=actor) if agent_state_id else None
|
2441
2396
|
letta_multi_agent = load_multi_agent(group=group, agent_state=agent_state, actor=actor)
|
2442
2397
|
|
2443
2398
|
llm_config = letta_multi_agent.agent_state.llm_config
|