letta-nightly 0.6.39.dev20250314104053__py3-none-any.whl → 0.6.40.dev20250314173529__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.
Potentially problematic release.
This version of letta-nightly might be problematic. Click here for more details.
- letta/agent.py +13 -3
- letta/agents/ephemeral_agent.py +2 -1
- letta/agents/low_latency_agent.py +8 -0
- letta/dynamic_multi_agent.py +274 -0
- letta/functions/function_sets/base.py +1 -0
- letta/functions/function_sets/extras.py +2 -1
- letta/functions/function_sets/multi_agent.py +17 -0
- letta/functions/helpers.py +41 -0
- letta/helpers/converters.py +67 -0
- letta/helpers/mcp_helpers.py +26 -5
- letta/llm_api/openai.py +1 -1
- letta/memory.py +2 -1
- letta/orm/__init__.py +2 -0
- letta/orm/agent.py +69 -20
- letta/orm/custom_columns.py +15 -0
- letta/orm/group.py +33 -0
- letta/orm/groups_agents.py +13 -0
- letta/orm/message.py +7 -4
- letta/orm/organization.py +1 -0
- letta/orm/sqlalchemy_base.py +3 -3
- letta/round_robin_multi_agent.py +152 -0
- letta/schemas/agent.py +3 -0
- letta/schemas/enums.py +0 -4
- letta/schemas/group.py +65 -0
- letta/schemas/letta_message.py +167 -106
- letta/schemas/letta_message_content.py +192 -0
- letta/schemas/message.py +28 -36
- letta/serialize_schemas/__init__.py +1 -1
- letta/serialize_schemas/marshmallow_agent.py +108 -0
- letta/serialize_schemas/{agent_environment_variable.py → marshmallow_agent_environment_variable.py} +1 -1
- letta/serialize_schemas/marshmallow_base.py +52 -0
- letta/serialize_schemas/{block.py → marshmallow_block.py} +1 -1
- letta/serialize_schemas/{custom_fields.py → marshmallow_custom_fields.py} +12 -0
- letta/serialize_schemas/marshmallow_message.py +42 -0
- letta/serialize_schemas/{tag.py → marshmallow_tag.py} +12 -2
- letta/serialize_schemas/{tool.py → marshmallow_tool.py} +1 -1
- letta/serialize_schemas/pydantic_agent_schema.py +111 -0
- letta/server/rest_api/app.py +15 -0
- letta/server/rest_api/routers/v1/__init__.py +2 -0
- letta/server/rest_api/routers/v1/agents.py +46 -40
- letta/server/rest_api/routers/v1/groups.py +233 -0
- letta/server/rest_api/routers/v1/tools.py +31 -3
- letta/server/rest_api/utils.py +1 -1
- letta/server/server.py +267 -12
- letta/services/agent_manager.py +65 -28
- letta/services/group_manager.py +147 -0
- letta/services/helpers/agent_manager_helper.py +151 -1
- letta/services/message_manager.py +11 -3
- letta/services/passage_manager.py +15 -0
- letta/settings.py +5 -0
- letta/supervisor_multi_agent.py +103 -0
- {letta_nightly-0.6.39.dev20250314104053.dist-info → letta_nightly-0.6.40.dev20250314173529.dist-info}/METADATA +1 -2
- {letta_nightly-0.6.39.dev20250314104053.dist-info → letta_nightly-0.6.40.dev20250314173529.dist-info}/RECORD +56 -46
- letta/serialize_schemas/agent.py +0 -80
- letta/serialize_schemas/base.py +0 -64
- letta/serialize_schemas/message.py +0 -29
- {letta_nightly-0.6.39.dev20250314104053.dist-info → letta_nightly-0.6.40.dev20250314173529.dist-info}/LICENSE +0 -0
- {letta_nightly-0.6.39.dev20250314104053.dist-info → letta_nightly-0.6.40.dev20250314173529.dist-info}/WHEEL +0 -0
- {letta_nightly-0.6.39.dev20250314104053.dist-info → letta_nightly-0.6.40.dev20250314173529.dist-info}/entry_points.txt +0 -0
|
@@ -24,6 +24,7 @@ from letta.schemas.run import Run
|
|
|
24
24
|
from letta.schemas.source import Source
|
|
25
25
|
from letta.schemas.tool import Tool
|
|
26
26
|
from letta.schemas.user import User
|
|
27
|
+
from letta.serialize_schemas.pydantic_agent_schema import AgentSchema
|
|
27
28
|
from letta.server.rest_api.utils import get_letta_server
|
|
28
29
|
from letta.server.server import SyncServer
|
|
29
30
|
|
|
@@ -35,81 +36,82 @@ router = APIRouter(prefix="/agents", tags=["agents"])
|
|
|
35
36
|
logger = get_logger(__name__)
|
|
36
37
|
|
|
37
38
|
|
|
38
|
-
# TODO: This should be paginated
|
|
39
39
|
@router.get("/", response_model=List[AgentState], operation_id="list_agents")
|
|
40
40
|
def list_agents(
|
|
41
41
|
name: Optional[str] = Query(None, description="Name of the agent"),
|
|
42
42
|
tags: Optional[List[str]] = Query(None, description="List of tags to filter agents by"),
|
|
43
43
|
match_all_tags: bool = Query(
|
|
44
44
|
False,
|
|
45
|
-
description="If True, only returns agents that match ALL given tags. Otherwise, return agents that have ANY of the passed
|
|
45
|
+
description="If True, only returns agents that match ALL given tags. Otherwise, return agents that have ANY of the passed-in tags.",
|
|
46
46
|
),
|
|
47
|
-
server:
|
|
47
|
+
server: SyncServer = Depends(get_letta_server),
|
|
48
48
|
actor_id: Optional[str] = Header(None, alias="user_id"),
|
|
49
49
|
before: Optional[str] = Query(None, description="Cursor for pagination"),
|
|
50
50
|
after: Optional[str] = Query(None, description="Cursor for pagination"),
|
|
51
|
-
limit: Optional[int] = Query(
|
|
51
|
+
limit: Optional[int] = Query(50, description="Limit for pagination"),
|
|
52
52
|
query_text: Optional[str] = Query(None, description="Search agents by name"),
|
|
53
|
-
project_id: Optional[str] = Query(None, description="Search agents by project
|
|
54
|
-
template_id: Optional[str] = Query(None, description="Search agents by template
|
|
55
|
-
base_template_id: Optional[str] = Query(None, description="Search agents by base template
|
|
56
|
-
identity_id: Optional[str] = Query(None, description="Search agents by
|
|
53
|
+
project_id: Optional[str] = Query(None, description="Search agents by project ID"),
|
|
54
|
+
template_id: Optional[str] = Query(None, description="Search agents by template ID"),
|
|
55
|
+
base_template_id: Optional[str] = Query(None, description="Search agents by base template ID"),
|
|
56
|
+
identity_id: Optional[str] = Query(None, description="Search agents by identity ID"),
|
|
57
57
|
identifier_keys: Optional[List[str]] = Query(None, description="Search agents by identifier keys"),
|
|
58
|
+
include_relationships: Optional[List[str]] = Query(
|
|
59
|
+
None,
|
|
60
|
+
description=(
|
|
61
|
+
"Specify which relational fields (e.g., 'tools', 'sources', 'memory') to include in the response. "
|
|
62
|
+
"If not provided, all relationships are loaded by default. "
|
|
63
|
+
"Using this can optimize performance by reducing unnecessary joins."
|
|
64
|
+
),
|
|
65
|
+
),
|
|
58
66
|
):
|
|
59
67
|
"""
|
|
60
68
|
List all agents associated with a given user.
|
|
61
|
-
|
|
69
|
+
|
|
70
|
+
This endpoint retrieves a list of all agents and their configurations
|
|
71
|
+
associated with the specified user ID.
|
|
62
72
|
"""
|
|
73
|
+
|
|
74
|
+
# Retrieve the actor (user) details
|
|
63
75
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
|
64
76
|
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
key: value
|
|
68
|
-
for key, value in {
|
|
69
|
-
"name": name,
|
|
70
|
-
"project_id": project_id,
|
|
71
|
-
"template_id": template_id,
|
|
72
|
-
"base_template_id": base_template_id,
|
|
73
|
-
}.items()
|
|
74
|
-
if value is not None
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
# Call list_agents with the dynamic kwargs
|
|
78
|
-
agents = server.agent_manager.list_agents(
|
|
77
|
+
# Call list_agents directly without unnecessary dict handling
|
|
78
|
+
return server.agent_manager.list_agents(
|
|
79
79
|
actor=actor,
|
|
80
|
+
name=name,
|
|
80
81
|
before=before,
|
|
81
82
|
after=after,
|
|
82
83
|
limit=limit,
|
|
83
84
|
query_text=query_text,
|
|
84
85
|
tags=tags,
|
|
85
86
|
match_all_tags=match_all_tags,
|
|
86
|
-
|
|
87
|
+
project_id=project_id,
|
|
88
|
+
template_id=template_id,
|
|
89
|
+
base_template_id=base_template_id,
|
|
87
90
|
identity_id=identity_id,
|
|
88
|
-
|
|
91
|
+
identifier_keys=identifier_keys,
|
|
92
|
+
include_relationships=include_relationships,
|
|
89
93
|
)
|
|
90
|
-
return agents
|
|
91
94
|
|
|
92
95
|
|
|
93
|
-
@router.get("/{agent_id}/
|
|
94
|
-
def
|
|
96
|
+
@router.get("/{agent_id}/export", operation_id="export_agent_serialized", response_model=AgentSchema)
|
|
97
|
+
def export_agent_serialized(
|
|
95
98
|
agent_id: str,
|
|
96
99
|
server: "SyncServer" = Depends(get_letta_server),
|
|
97
100
|
actor_id: Optional[str] = Header(None, alias="user_id"),
|
|
98
|
-
):
|
|
101
|
+
) -> AgentSchema:
|
|
99
102
|
"""
|
|
100
|
-
|
|
103
|
+
Export the serialized JSON representation of an agent.
|
|
101
104
|
"""
|
|
102
105
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
|
103
106
|
|
|
104
107
|
try:
|
|
105
|
-
|
|
106
|
-
return JSONResponse(content=serialized_agent, media_type="application/json")
|
|
108
|
+
return server.agent_manager.serialize(agent_id=agent_id, actor=actor)
|
|
107
109
|
except NoResultFound:
|
|
108
110
|
raise HTTPException(status_code=404, detail=f"Agent with id={agent_id} not found for user_id={actor.id}.")
|
|
109
111
|
|
|
110
112
|
|
|
111
|
-
@router.post("/
|
|
112
|
-
async def
|
|
113
|
+
@router.post("/import", response_model=AgentState, operation_id="import_agent_serialized")
|
|
114
|
+
async def import_agent_serialized(
|
|
113
115
|
file: UploadFile = File(...),
|
|
114
116
|
server: "SyncServer" = Depends(get_letta_server),
|
|
115
117
|
actor_id: Optional[str] = Header(None, alias="user_id"),
|
|
@@ -121,15 +123,19 @@ async def upload_agent_serialized(
|
|
|
121
123
|
project_id: Optional[str] = Query(None, description="The project ID to associate the uploaded agent with."),
|
|
122
124
|
):
|
|
123
125
|
"""
|
|
124
|
-
|
|
126
|
+
Import a serialized agent file and recreate the agent in the system.
|
|
125
127
|
"""
|
|
126
128
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
|
127
129
|
|
|
128
130
|
try:
|
|
129
131
|
serialized_data = await file.read()
|
|
130
132
|
agent_json = json.loads(serialized_data)
|
|
133
|
+
|
|
134
|
+
# Validate the JSON against AgentSchema before passing it to deserialize
|
|
135
|
+
agent_schema = AgentSchema.model_validate(agent_json)
|
|
136
|
+
|
|
131
137
|
new_agent = server.agent_manager.deserialize(
|
|
132
|
-
serialized_agent=
|
|
138
|
+
serialized_agent=agent_schema, # Ensure we're passing a validated AgentSchema
|
|
133
139
|
actor=actor,
|
|
134
140
|
append_copy_suffix=append_copy_suffix,
|
|
135
141
|
override_existing_tools=override_existing_tools,
|
|
@@ -141,7 +147,7 @@ async def upload_agent_serialized(
|
|
|
141
147
|
raise HTTPException(status_code=400, detail="Corrupted agent file format.")
|
|
142
148
|
|
|
143
149
|
except ValidationError as e:
|
|
144
|
-
raise HTTPException(status_code=422, detail=f"Invalid agent schema: {
|
|
150
|
+
raise HTTPException(status_code=422, detail=f"Invalid agent schema: {e.errors()}")
|
|
145
151
|
|
|
146
152
|
except IntegrityError as e:
|
|
147
153
|
raise HTTPException(status_code=409, detail=f"Database integrity error: {str(e)}")
|
|
@@ -149,9 +155,9 @@ async def upload_agent_serialized(
|
|
|
149
155
|
except OperationalError as e:
|
|
150
156
|
raise HTTPException(status_code=503, detail=f"Database connection error. Please try again later: {str(e)}")
|
|
151
157
|
|
|
152
|
-
except Exception:
|
|
158
|
+
except Exception as e:
|
|
153
159
|
traceback.print_exc()
|
|
154
|
-
raise HTTPException(status_code=500, detail="An unexpected error occurred while uploading the agent
|
|
160
|
+
raise HTTPException(status_code=500, detail=f"An unexpected error occurred while uploading the agent: {str(e)}")
|
|
155
161
|
|
|
156
162
|
|
|
157
163
|
@router.get("/{agent_id}/context", response_model=ContextWindowOverview, operation_id="retrieve_agent_context_window")
|
|
@@ -530,7 +536,7 @@ def list_messages(
|
|
|
530
536
|
)
|
|
531
537
|
|
|
532
538
|
|
|
533
|
-
@router.patch("/{agent_id}/messages/{message_id}", response_model=
|
|
539
|
+
@router.patch("/{agent_id}/messages/{message_id}", response_model=LettaMessageUnion, operation_id="modify_message")
|
|
534
540
|
def modify_message(
|
|
535
541
|
agent_id: str,
|
|
536
542
|
message_id: str,
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
from typing import Annotated, List, Optional
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Body, Depends, Header, Query
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
|
|
6
|
+
from letta.constants import DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG
|
|
7
|
+
from letta.schemas.group import Group, GroupCreate, ManagerType
|
|
8
|
+
from letta.schemas.letta_message import LettaMessageUnion
|
|
9
|
+
from letta.schemas.letta_request import LettaRequest, LettaStreamingRequest
|
|
10
|
+
from letta.schemas.letta_response import LettaResponse
|
|
11
|
+
from letta.server.rest_api.utils import get_letta_server
|
|
12
|
+
from letta.server.server import SyncServer
|
|
13
|
+
|
|
14
|
+
router = APIRouter(prefix="/groups", tags=["groups"])
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@router.post("/", response_model=Group, operation_id="create_group")
|
|
18
|
+
async def create_group(
|
|
19
|
+
server: SyncServer = Depends(get_letta_server),
|
|
20
|
+
request: GroupCreate = Body(...),
|
|
21
|
+
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
|
22
|
+
):
|
|
23
|
+
"""
|
|
24
|
+
Create a multi-agent group with a specified management pattern. When no
|
|
25
|
+
management config is specified, this endpoint will use round robin for
|
|
26
|
+
speaker selection.
|
|
27
|
+
"""
|
|
28
|
+
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
|
29
|
+
return server.group_manager.create_group(request, actor=actor)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@router.get("/", response_model=List[Group], operation_id="list_groups")
|
|
33
|
+
def list_groups(
|
|
34
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
35
|
+
actor_id: Optional[str] = Header(None, alias="user_id"),
|
|
36
|
+
manager_type: Optional[ManagerType] = Query(None, description="Search groups by manager type"),
|
|
37
|
+
before: Optional[str] = Query(None, description="Cursor for pagination"),
|
|
38
|
+
after: Optional[str] = Query(None, description="Cursor for pagination"),
|
|
39
|
+
limit: Optional[int] = Query(None, description="Limit for pagination"),
|
|
40
|
+
project_id: Optional[str] = Query(None, description="Search groups by project id"),
|
|
41
|
+
):
|
|
42
|
+
"""
|
|
43
|
+
Fetch all multi-agent groups matching query.
|
|
44
|
+
"""
|
|
45
|
+
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
|
46
|
+
return server.group_manager.list_groups(
|
|
47
|
+
project_id=project_id,
|
|
48
|
+
manager_type=manager_type,
|
|
49
|
+
before=before,
|
|
50
|
+
after=after,
|
|
51
|
+
limit=limit,
|
|
52
|
+
actor=actor,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@router.post("/", response_model=Group, operation_id="create_group")
|
|
57
|
+
def create_group(
|
|
58
|
+
group: GroupCreate = Body(...),
|
|
59
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
60
|
+
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
|
61
|
+
x_project: Optional[str] = Header(None, alias="X-Project"), # Only handled by next js middleware
|
|
62
|
+
):
|
|
63
|
+
"""
|
|
64
|
+
Create a new multi-agent group with the specified configuration.
|
|
65
|
+
"""
|
|
66
|
+
try:
|
|
67
|
+
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
|
68
|
+
return server.group_manager.create_group(group, actor=actor)
|
|
69
|
+
except Exception as e:
|
|
70
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@router.put("/", response_model=Group, operation_id="upsert_group")
|
|
74
|
+
def upsert_group(
|
|
75
|
+
group: GroupCreate = Body(...),
|
|
76
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
77
|
+
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
|
78
|
+
x_project: Optional[str] = Header(None, alias="X-Project"), # Only handled by next js middleware
|
|
79
|
+
):
|
|
80
|
+
"""
|
|
81
|
+
Create a new multi-agent group with the specified configuration.
|
|
82
|
+
"""
|
|
83
|
+
try:
|
|
84
|
+
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
|
85
|
+
return server.group_manager.create_group(group, actor=actor)
|
|
86
|
+
except Exception as e:
|
|
87
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@router.delete("/{group_id}", response_model=None, operation_id="delete_group")
|
|
91
|
+
def delete_group(
|
|
92
|
+
group_id: str,
|
|
93
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
94
|
+
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
|
95
|
+
):
|
|
96
|
+
"""
|
|
97
|
+
Delete a multi-agent group.
|
|
98
|
+
"""
|
|
99
|
+
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
|
100
|
+
try:
|
|
101
|
+
server.group_manager.delete_group(group_id=group_id, actor=actor)
|
|
102
|
+
return JSONResponse(status_code=status.HTTP_200_OK, content={"message": f"Group id={group_id} successfully deleted"})
|
|
103
|
+
except NoResultFound:
|
|
104
|
+
raise HTTPException(status_code=404, detail=f"Group id={group_id} not found for user_id={actor.id}.")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@router.post(
|
|
108
|
+
"/{group_id}/messages",
|
|
109
|
+
response_model=LettaResponse,
|
|
110
|
+
operation_id="send_group_message",
|
|
111
|
+
)
|
|
112
|
+
async def send_group_message(
|
|
113
|
+
agent_id: str,
|
|
114
|
+
server: SyncServer = Depends(get_letta_server),
|
|
115
|
+
request: LettaRequest = Body(...),
|
|
116
|
+
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
|
117
|
+
):
|
|
118
|
+
"""
|
|
119
|
+
Process a user message and return the group's response.
|
|
120
|
+
This endpoint accepts a message from a user and processes it through through agents in the group based on the specified pattern
|
|
121
|
+
"""
|
|
122
|
+
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
|
123
|
+
result = await server.send_group_message_to_agent(
|
|
124
|
+
group_id=group_id,
|
|
125
|
+
actor=actor,
|
|
126
|
+
messages=request.messages,
|
|
127
|
+
stream_steps=False,
|
|
128
|
+
stream_tokens=False,
|
|
129
|
+
# Support for AssistantMessage
|
|
130
|
+
use_assistant_message=request.use_assistant_message,
|
|
131
|
+
assistant_message_tool_name=request.assistant_message_tool_name,
|
|
132
|
+
assistant_message_tool_kwarg=request.assistant_message_tool_kwarg,
|
|
133
|
+
)
|
|
134
|
+
return result
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@router.post(
|
|
138
|
+
"/{group_id}/messages/stream",
|
|
139
|
+
response_model=None,
|
|
140
|
+
operation_id="send_group_message_streaming",
|
|
141
|
+
responses={
|
|
142
|
+
200: {
|
|
143
|
+
"description": "Successful response",
|
|
144
|
+
"content": {
|
|
145
|
+
"text/event-stream": {"description": "Server-Sent Events stream"},
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
)
|
|
150
|
+
async def send_group_message_streaming(
|
|
151
|
+
group_id: str,
|
|
152
|
+
server: SyncServer = Depends(get_letta_server),
|
|
153
|
+
request: LettaStreamingRequest = Body(...),
|
|
154
|
+
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
|
155
|
+
):
|
|
156
|
+
"""
|
|
157
|
+
Process a user message and return the group's responses.
|
|
158
|
+
This endpoint accepts a message from a user and processes it through agents in the group based on the specified pattern.
|
|
159
|
+
It will stream the steps of the response always, and stream the tokens if 'stream_tokens' is set to True.
|
|
160
|
+
"""
|
|
161
|
+
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
|
162
|
+
result = await server.send_group_message_to_agent(
|
|
163
|
+
group_id=group_id,
|
|
164
|
+
actor=actor,
|
|
165
|
+
messages=request.messages,
|
|
166
|
+
stream_steps=True,
|
|
167
|
+
stream_tokens=request.stream_tokens,
|
|
168
|
+
# Support for AssistantMessage
|
|
169
|
+
use_assistant_message=request.use_assistant_message,
|
|
170
|
+
assistant_message_tool_name=request.assistant_message_tool_name,
|
|
171
|
+
assistant_message_tool_kwarg=request.assistant_message_tool_kwarg,
|
|
172
|
+
)
|
|
173
|
+
return result
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
GroupMessagesResponse = Annotated[
|
|
177
|
+
List[LettaMessageUnion], Field(json_schema_extra={"type": "array", "items": {"$ref": "#/components/schemas/LettaMessageUnion"}})
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@router.get("/{group_id}/messages", response_model=GroupMessagesResponse, operation_id="list_group_messages")
|
|
182
|
+
def list_group_messages(
|
|
183
|
+
group_id: str,
|
|
184
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
185
|
+
after: Optional[str] = Query(None, description="Message after which to retrieve the returned messages."),
|
|
186
|
+
before: Optional[str] = Query(None, description="Message before which to retrieve the returned messages."),
|
|
187
|
+
limit: int = Query(10, description="Maximum number of messages to retrieve."),
|
|
188
|
+
use_assistant_message: bool = Query(True, description="Whether to use assistant messages"),
|
|
189
|
+
assistant_message_tool_name: str = Query(DEFAULT_MESSAGE_TOOL, description="The name of the designated message tool."),
|
|
190
|
+
assistant_message_tool_kwarg: str = Query(DEFAULT_MESSAGE_TOOL_KWARG, description="The name of the message argument."),
|
|
191
|
+
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
|
192
|
+
):
|
|
193
|
+
"""
|
|
194
|
+
Retrieve message history for an agent.
|
|
195
|
+
"""
|
|
196
|
+
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
|
197
|
+
|
|
198
|
+
return server.group_manager.list_group_messages(
|
|
199
|
+
group_id=group_id,
|
|
200
|
+
before=before,
|
|
201
|
+
after=after,
|
|
202
|
+
limit=limit,
|
|
203
|
+
actor=actor,
|
|
204
|
+
use_assistant_message=use_assistant_message,
|
|
205
|
+
assistant_message_tool_name=assistant_message_tool_name,
|
|
206
|
+
assistant_message_tool_kwarg=assistant_message_tool_kwarg,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
'''
|
|
211
|
+
@router.patch("/{group_id}/reset-messages", response_model=None, operation_id="reset_group_messages")
|
|
212
|
+
def reset_group_messages(
|
|
213
|
+
group_id: str,
|
|
214
|
+
add_default_initial_messages: bool = Query(default=False, description="If true, adds the default initial messages after resetting."),
|
|
215
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
216
|
+
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
|
217
|
+
):
|
|
218
|
+
"""
|
|
219
|
+
Resets the messages for all agents that are part of the multi-agent group.
|
|
220
|
+
TODO: only delete group messages not all messages!
|
|
221
|
+
"""
|
|
222
|
+
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
|
223
|
+
group = server.group_manager.retrieve_group(group_id=group_id, actor=actor)
|
|
224
|
+
agent_ids = group.agent_ids
|
|
225
|
+
if group.manager_agent_id:
|
|
226
|
+
agent_ids.append(group.manager_agent_id)
|
|
227
|
+
for agent_id in agent_ids:
|
|
228
|
+
server.agent_manager.reset_messages(
|
|
229
|
+
agent_id=agent_id,
|
|
230
|
+
actor=actor,
|
|
231
|
+
add_default_initial_messages=add_default_initial_messages,
|
|
232
|
+
)
|
|
233
|
+
'''
|
|
@@ -13,7 +13,7 @@ from fastapi import APIRouter, Body, Depends, Header, HTTPException
|
|
|
13
13
|
|
|
14
14
|
from letta.errors import LettaToolCreateError
|
|
15
15
|
from letta.helpers.composio_helpers import get_composio_api_key
|
|
16
|
-
from letta.helpers.mcp_helpers import
|
|
16
|
+
from letta.helpers.mcp_helpers import MCPTool, SSEServerConfig, StdioServerConfig
|
|
17
17
|
from letta.log import get_logger
|
|
18
18
|
from letta.orm.errors import UniqueConstraintViolationError
|
|
19
19
|
from letta.schemas.letta_message import ToolReturnMessage
|
|
@@ -333,7 +333,7 @@ def add_composio_tool(
|
|
|
333
333
|
|
|
334
334
|
|
|
335
335
|
# Specific routes for MCP
|
|
336
|
-
@router.get("/mcp/servers", response_model=dict[str, Union[SSEServerConfig,
|
|
336
|
+
@router.get("/mcp/servers", response_model=dict[str, Union[SSEServerConfig, StdioServerConfig]], operation_id="list_mcp_servers")
|
|
337
337
|
def list_mcp_servers(server: SyncServer = Depends(get_letta_server), user_id: Optional[str] = Header(None, alias="user_id")):
|
|
338
338
|
"""
|
|
339
339
|
Get a list of all configured MCP servers
|
|
@@ -376,7 +376,7 @@ def add_mcp_tool(
|
|
|
376
376
|
actor_id: Optional[str] = Header(None, alias="user_id"),
|
|
377
377
|
):
|
|
378
378
|
"""
|
|
379
|
-
|
|
379
|
+
Register a new MCP tool as a Letta server by MCP server + tool name
|
|
380
380
|
"""
|
|
381
381
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
|
382
382
|
|
|
@@ -399,3 +399,31 @@ def add_mcp_tool(
|
|
|
399
399
|
|
|
400
400
|
tool_create = ToolCreate.from_mcp(mcp_server_name=mcp_server_name, mcp_tool=mcp_tool)
|
|
401
401
|
return server.tool_manager.create_or_update_mcp_tool(tool_create=tool_create, actor=actor)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
@router.put("/mcp/servers", response_model=List[Union[StdioServerConfig, SSEServerConfig]], operation_id="add_mcp_server")
|
|
405
|
+
def add_mcp_server_to_config(
|
|
406
|
+
request: Union[StdioServerConfig, SSEServerConfig] = Body(...),
|
|
407
|
+
server: SyncServer = Depends(get_letta_server),
|
|
408
|
+
actor_id: Optional[str] = Header(None, alias="user_id"),
|
|
409
|
+
):
|
|
410
|
+
"""
|
|
411
|
+
Add a new MCP server to the Letta MCP server config
|
|
412
|
+
"""
|
|
413
|
+
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
|
414
|
+
return server.add_mcp_server_to_config(server_config=request, allow_upsert=True)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
@router.delete(
|
|
418
|
+
"/mcp/servers/{mcp_server_name}", response_model=List[Union[StdioServerConfig, SSEServerConfig]], operation_id="delete_mcp_server"
|
|
419
|
+
)
|
|
420
|
+
def delete_mcp_server_from_config(
|
|
421
|
+
mcp_server_name: str,
|
|
422
|
+
server: SyncServer = Depends(get_letta_server),
|
|
423
|
+
actor_id: Optional[str] = Header(None, alias="user_id"),
|
|
424
|
+
):
|
|
425
|
+
"""
|
|
426
|
+
Add a new MCP server to the Letta MCP server config
|
|
427
|
+
"""
|
|
428
|
+
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
|
429
|
+
return server.delete_mcp_server_from_config(server_name=mcp_server_name)
|
letta/server/rest_api/utils.py
CHANGED
|
@@ -18,7 +18,7 @@ from letta.errors import ContextWindowExceededError, RateLimitExceededError
|
|
|
18
18
|
from letta.helpers.datetime_helpers import get_utc_time
|
|
19
19
|
from letta.log import get_logger
|
|
20
20
|
from letta.schemas.enums import MessageRole
|
|
21
|
-
from letta.schemas.
|
|
21
|
+
from letta.schemas.letta_message_content import TextContent
|
|
22
22
|
from letta.schemas.message import Message
|
|
23
23
|
from letta.schemas.usage import LettaUsageStatistics
|
|
24
24
|
from letta.schemas.user import User
|