letta-nightly 0.11.7.dev20250915104130__py3-none-any.whl → 0.11.7.dev20250917104122__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 +10 -2
- letta/adapters/letta_llm_request_adapter.py +0 -1
- letta/adapters/letta_llm_stream_adapter.py +0 -1
- letta/agent.py +1 -1
- letta/agents/letta_agent.py +1 -4
- letta/agents/letta_agent_v2.py +2 -1
- letta/agents/voice_agent.py +1 -1
- letta/functions/function_sets/multi_agent.py +1 -1
- letta/functions/helpers.py +1 -1
- letta/helpers/converters.py +8 -2
- letta/helpers/crypto_utils.py +144 -0
- letta/llm_api/llm_api_tools.py +0 -1
- letta/llm_api/llm_client_base.py +0 -2
- letta/orm/__init__.py +1 -0
- letta/orm/agent.py +5 -1
- letta/orm/job.py +3 -1
- letta/orm/mcp_oauth.py +6 -0
- letta/orm/mcp_server.py +7 -1
- letta/orm/sqlalchemy_base.py +2 -1
- letta/prompts/gpt_system.py +13 -15
- letta/prompts/system_prompts/__init__.py +27 -0
- letta/prompts/{system/memgpt_chat.txt → system_prompts/memgpt_chat.py} +2 -0
- letta/prompts/{system/memgpt_generate_tool.txt → system_prompts/memgpt_generate_tool.py} +4 -2
- letta/prompts/{system/memgpt_v2_chat.txt → system_prompts/memgpt_v2_chat.py} +2 -0
- letta/prompts/{system/react.txt → system_prompts/react.py} +2 -0
- letta/prompts/{system/sleeptime_doc_ingest.txt → system_prompts/sleeptime_doc_ingest.py} +2 -0
- letta/prompts/{system/sleeptime_v2.txt → system_prompts/sleeptime_v2.py} +2 -0
- letta/prompts/{system/summary_system_prompt.txt → system_prompts/summary_system_prompt.py} +2 -0
- letta/prompts/{system/voice_chat.txt → system_prompts/voice_chat.py} +2 -0
- letta/prompts/{system/voice_sleeptime.txt → system_prompts/voice_sleeptime.py} +2 -0
- letta/prompts/{system/workflow.txt → system_prompts/workflow.py} +2 -0
- letta/schemas/agent.py +10 -7
- letta/schemas/job.py +10 -0
- letta/schemas/mcp.py +146 -6
- letta/schemas/provider_trace.py +0 -2
- letta/schemas/run.py +2 -0
- letta/schemas/secret.py +378 -0
- letta/serialize_schemas/marshmallow_agent.py +4 -0
- letta/server/rest_api/dependencies.py +37 -0
- letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +4 -3
- letta/server/rest_api/routers/v1/__init__.py +2 -0
- letta/server/rest_api/routers/v1/agents.py +115 -107
- letta/server/rest_api/routers/v1/archives.py +113 -0
- letta/server/rest_api/routers/v1/blocks.py +44 -20
- letta/server/rest_api/routers/v1/embeddings.py +3 -3
- letta/server/rest_api/routers/v1/folders.py +107 -47
- letta/server/rest_api/routers/v1/groups.py +52 -32
- letta/server/rest_api/routers/v1/identities.py +110 -21
- letta/server/rest_api/routers/v1/internal_templates.py +28 -13
- letta/server/rest_api/routers/v1/jobs.py +19 -14
- letta/server/rest_api/routers/v1/llms.py +6 -8
- letta/server/rest_api/routers/v1/messages.py +14 -14
- letta/server/rest_api/routers/v1/organizations.py +1 -1
- letta/server/rest_api/routers/v1/providers.py +40 -16
- letta/server/rest_api/routers/v1/runs.py +28 -20
- letta/server/rest_api/routers/v1/sandbox_configs.py +25 -25
- letta/server/rest_api/routers/v1/sources.py +44 -45
- letta/server/rest_api/routers/v1/steps.py +27 -25
- letta/server/rest_api/routers/v1/tags.py +11 -7
- letta/server/rest_api/routers/v1/telemetry.py +11 -6
- letta/server/rest_api/routers/v1/tools.py +78 -80
- letta/server/rest_api/routers/v1/users.py +1 -1
- letta/server/rest_api/routers/v1/voice.py +6 -5
- letta/server/rest_api/utils.py +1 -18
- letta/services/agent_manager.py +17 -9
- letta/services/agent_serialization_manager.py +11 -3
- letta/services/archive_manager.py +73 -0
- letta/services/file_manager.py +6 -0
- letta/services/group_manager.py +2 -1
- letta/services/helpers/agent_manager_helper.py +6 -1
- letta/services/identity_manager.py +67 -0
- letta/services/job_manager.py +18 -2
- letta/services/mcp_manager.py +198 -82
- letta/services/provider_manager.py +14 -1
- letta/services/source_manager.py +11 -1
- letta/services/telemetry_manager.py +2 -0
- letta/services/tool_executor/composio_tool_executor.py +1 -1
- letta/services/tool_manager.py +46 -9
- letta/services/tool_sandbox/base.py +2 -3
- letta/utils.py +4 -2
- {letta_nightly-0.11.7.dev20250915104130.dist-info → letta_nightly-0.11.7.dev20250917104122.dist-info}/METADATA +5 -2
- {letta_nightly-0.11.7.dev20250915104130.dist-info → letta_nightly-0.11.7.dev20250917104122.dist-info}/RECORD +85 -94
- letta/prompts/system/memgpt_base.txt +0 -54
- letta/prompts/system/memgpt_chat_compressed.txt +0 -13
- letta/prompts/system/memgpt_chat_fstring.txt +0 -51
- letta/prompts/system/memgpt_convo_only.txt +0 -12
- letta/prompts/system/memgpt_doc.txt +0 -50
- letta/prompts/system/memgpt_gpt35_extralong.txt +0 -53
- letta/prompts/system/memgpt_intuitive_knowledge.txt +0 -31
- letta/prompts/system/memgpt_memory_only.txt +0 -29
- letta/prompts/system/memgpt_modified_chat.txt +0 -23
- letta/prompts/system/memgpt_modified_o1.txt +0 -31
- letta/prompts/system/memgpt_offline_memory.txt +0 -23
- letta/prompts/system/memgpt_offline_memory_chat.txt +0 -35
- letta/prompts/system/memgpt_sleeptime_chat.txt +0 -52
- letta/prompts/system/sleeptime.txt +0 -37
- {letta_nightly-0.11.7.dev20250915104130.dist-info → letta_nightly-0.11.7.dev20250917104122.dist-info}/WHEEL +0 -0
- {letta_nightly-0.11.7.dev20250915104130.dist-info → letta_nightly-0.11.7.dev20250917104122.dist-info}/entry_points.txt +0 -0
- {letta_nightly-0.11.7.dev20250915104130.dist-info → letta_nightly-0.11.7.dev20250917104122.dist-info}/licenses/LICENSE +0 -0
@@ -1,26 +1,31 @@
|
|
1
1
|
from typing import Optional
|
2
2
|
|
3
|
-
from fastapi import APIRouter, Depends
|
3
|
+
from fastapi import APIRouter, Depends
|
4
4
|
|
5
5
|
from letta.schemas.provider_trace import ProviderTrace
|
6
|
-
from letta.server.rest_api.
|
6
|
+
from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
|
7
7
|
from letta.server.server import SyncServer
|
8
8
|
from letta.settings import settings
|
9
9
|
|
10
10
|
router = APIRouter(prefix="/telemetry", tags=["telemetry"])
|
11
11
|
|
12
12
|
|
13
|
-
@router.get("/{step_id}", response_model=Optional[ProviderTrace], operation_id="retrieve_provider_trace")
|
14
|
-
async def
|
13
|
+
@router.get("/{step_id}", response_model=Optional[ProviderTrace], operation_id="retrieve_provider_trace", deprecated=True)
|
14
|
+
async def retrieve_provider_trace(
|
15
15
|
step_id: str,
|
16
16
|
server: SyncServer = Depends(get_letta_server),
|
17
|
-
|
17
|
+
headers: HeaderParams = Depends(get_headers),
|
18
18
|
):
|
19
|
+
"""
|
20
|
+
**DEPRECATED**: Use `GET /steps/{step_id}/trace` instead.
|
21
|
+
|
22
|
+
Retrieve provider trace by step ID.
|
23
|
+
"""
|
19
24
|
provider_trace = None
|
20
25
|
if settings.track_provider_trace:
|
21
26
|
try:
|
22
27
|
provider_trace = await server.telemetry_manager.get_provider_trace_by_step_id_async(
|
23
|
-
step_id=step_id, actor=await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
28
|
+
step_id=step_id, actor=await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
24
29
|
)
|
25
30
|
except:
|
26
31
|
pass
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import json
|
2
2
|
from collections.abc import AsyncGenerator
|
3
|
-
from typing import Any, Dict, List, Optional, Union
|
3
|
+
from typing import Any, Dict, List, Literal, Optional, Union
|
4
4
|
|
5
5
|
from composio.client import ComposioClientError, HTTPError, NoItemsFound
|
6
6
|
from composio.client.collections import ActionModel, AppModel
|
@@ -11,7 +11,7 @@ from composio.exceptions import (
|
|
11
11
|
EnumMetadataNotFound,
|
12
12
|
EnumStringNotFound,
|
13
13
|
)
|
14
|
-
from fastapi import APIRouter, Body, Depends,
|
14
|
+
from fastapi import APIRouter, Body, Depends, HTTPException, Query, Request
|
15
15
|
from httpx import ConnectError, HTTPStatusError
|
16
16
|
from pydantic import BaseModel, Field
|
17
17
|
from starlette.responses import StreamingResponse
|
@@ -34,8 +34,8 @@ from letta.schemas.mcp import UpdateSSEMCPServer, UpdateStdioMCPServer, UpdateSt
|
|
34
34
|
from letta.schemas.message import Message
|
35
35
|
from letta.schemas.pip_requirement import PipRequirement
|
36
36
|
from letta.schemas.tool import Tool, ToolCreate, ToolRunFromSource, ToolUpdate
|
37
|
+
from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
|
37
38
|
from letta.server.rest_api.streaming_response import StreamingResponseWithStatusCode
|
38
|
-
from letta.server.rest_api.utils import get_letta_server
|
39
39
|
from letta.server.server import SyncServer
|
40
40
|
from letta.services.mcp.oauth_utils import MCPOAuthSession, drill_down_exception, oauth_stream_event
|
41
41
|
from letta.services.mcp.stdio_client import AsyncStdioMCPClient
|
@@ -51,12 +51,12 @@ logger = get_logger(__name__)
|
|
51
51
|
async def delete_tool(
|
52
52
|
tool_id: str,
|
53
53
|
server: SyncServer = Depends(get_letta_server),
|
54
|
-
|
54
|
+
headers: HeaderParams = Depends(get_headers),
|
55
55
|
):
|
56
56
|
"""
|
57
57
|
Delete a tool by name
|
58
58
|
"""
|
59
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
59
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
60
60
|
await server.tool_manager.delete_tool_by_id_async(tool_id=tool_id, actor=actor)
|
61
61
|
|
62
62
|
|
@@ -75,7 +75,7 @@ async def count_tools(
|
|
75
75
|
return_only_letta_tools: Optional[bool] = Query(False, description="Count only tools with tool_type starting with 'letta_'"),
|
76
76
|
exclude_letta_tools: Optional[bool] = Query(False, description="Exclude built-in Letta tools from the count"),
|
77
77
|
server: SyncServer = Depends(get_letta_server),
|
78
|
-
|
78
|
+
headers: HeaderParams = Depends(get_headers),
|
79
79
|
):
|
80
80
|
"""
|
81
81
|
Get a count of all tools available to agents belonging to the org of the user.
|
@@ -110,7 +110,7 @@ async def count_tools(
|
|
110
110
|
tool_types_str = parse_tool_types(tool_types)
|
111
111
|
exclude_tool_types_str = parse_tool_types(exclude_tool_types)
|
112
112
|
|
113
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
113
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
114
114
|
|
115
115
|
# Combine single name with names list for unified processing (same logic as list_tools)
|
116
116
|
combined_names = []
|
@@ -158,12 +158,12 @@ async def count_tools(
|
|
158
158
|
async def retrieve_tool(
|
159
159
|
tool_id: str,
|
160
160
|
server: SyncServer = Depends(get_letta_server),
|
161
|
-
|
161
|
+
headers: HeaderParams = Depends(get_headers),
|
162
162
|
):
|
163
163
|
"""
|
164
164
|
Get a tool by ID
|
165
165
|
"""
|
166
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
166
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
167
167
|
tool = await server.tool_manager.get_tool_by_id_async(tool_id=tool_id, actor=actor)
|
168
168
|
if tool is None:
|
169
169
|
# return 404 error
|
@@ -173,9 +173,18 @@ async def retrieve_tool(
|
|
173
173
|
|
174
174
|
@router.get("/", response_model=List[Tool], operation_id="list_tools")
|
175
175
|
async def list_tools(
|
176
|
-
|
177
|
-
|
178
|
-
|
176
|
+
before: Optional[str] = Query(
|
177
|
+
None, description="Tool ID cursor for pagination. Returns tools that come before this tool ID in the specified sort order"
|
178
|
+
),
|
179
|
+
after: Optional[str] = Query(
|
180
|
+
None, description="Tool ID cursor for pagination. Returns tools that come after this tool ID in the specified sort order"
|
181
|
+
),
|
182
|
+
limit: Optional[int] = Query(50, description="Maximum number of tools to return"),
|
183
|
+
order: Literal["asc", "desc"] = Query(
|
184
|
+
"desc", description="Sort order for tools by creation time. 'asc' for oldest first, 'desc' for newest first"
|
185
|
+
),
|
186
|
+
order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
|
187
|
+
name: Optional[str] = Query(None, description="Filter by single tool name"),
|
179
188
|
names: Optional[List[str]] = Query(None, description="Filter by specific tool names"),
|
180
189
|
tool_ids: Optional[List[str]] = Query(
|
181
190
|
None, description="Filter by specific tool IDs - accepts repeated params or comma-separated values"
|
@@ -187,10 +196,10 @@ async def list_tools(
|
|
187
196
|
),
|
188
197
|
return_only_letta_tools: Optional[bool] = Query(False, description="Return only tools with tool_type starting with 'letta_'"),
|
189
198
|
server: SyncServer = Depends(get_letta_server),
|
190
|
-
|
199
|
+
headers: HeaderParams = Depends(get_headers),
|
191
200
|
):
|
192
201
|
"""
|
193
|
-
Get a list of all tools available to agents
|
202
|
+
Get a list of all tools available to agents.
|
194
203
|
"""
|
195
204
|
try:
|
196
205
|
# Helper function to parse tool types - supports both repeated params and comma-separated values
|
@@ -222,7 +231,7 @@ async def list_tools(
|
|
222
231
|
tool_types_str = parse_tool_types(tool_types)
|
223
232
|
exclude_tool_types_str = parse_tool_types(exclude_tool_types)
|
224
233
|
|
225
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
234
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
226
235
|
|
227
236
|
# Combine single name with names list for unified processing
|
228
237
|
combined_names = []
|
@@ -254,8 +263,10 @@ async def list_tools(
|
|
254
263
|
# Get the list of tools using unified query
|
255
264
|
return await server.tool_manager.list_tools_async(
|
256
265
|
actor=actor,
|
266
|
+
before=before,
|
257
267
|
after=after,
|
258
268
|
limit=limit,
|
269
|
+
ascending=(order == "asc"),
|
259
270
|
tool_types=tool_types_str,
|
260
271
|
exclude_tool_types=exclude_tool_types_str,
|
261
272
|
names=final_names,
|
@@ -271,13 +282,13 @@ async def list_tools(
|
|
271
282
|
async def create_tool(
|
272
283
|
request: ToolCreate = Body(...),
|
273
284
|
server: SyncServer = Depends(get_letta_server),
|
274
|
-
|
285
|
+
headers: HeaderParams = Depends(get_headers),
|
275
286
|
):
|
276
287
|
"""
|
277
288
|
Create a new tool
|
278
289
|
"""
|
279
290
|
try:
|
280
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
291
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
281
292
|
tool = Tool(**request.model_dump(exclude_unset=True))
|
282
293
|
return await server.tool_manager.create_tool_async(pydantic_tool=tool, actor=actor)
|
283
294
|
except UniqueConstraintViolationError as e:
|
@@ -295,13 +306,13 @@ async def create_tool(
|
|
295
306
|
async def upsert_tool(
|
296
307
|
request: ToolCreate = Body(...),
|
297
308
|
server: SyncServer = Depends(get_letta_server),
|
298
|
-
|
309
|
+
headers: HeaderParams = Depends(get_headers),
|
299
310
|
):
|
300
311
|
"""
|
301
312
|
Create or update a tool
|
302
313
|
"""
|
303
314
|
try:
|
304
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
315
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
305
316
|
tool = await server.tool_manager.create_or_update_tool_async(
|
306
317
|
pydantic_tool=Tool(**request.model_dump(exclude_unset=True)), actor=actor
|
307
318
|
)
|
@@ -322,13 +333,13 @@ async def modify_tool(
|
|
322
333
|
tool_id: str,
|
323
334
|
request: ToolUpdate = Body(...),
|
324
335
|
server: SyncServer = Depends(get_letta_server),
|
325
|
-
|
336
|
+
headers: HeaderParams = Depends(get_headers),
|
326
337
|
):
|
327
338
|
"""
|
328
339
|
Update an existing tool
|
329
340
|
"""
|
330
341
|
try:
|
331
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
342
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
332
343
|
tool = await server.tool_manager.update_tool_by_id_async(tool_id=tool_id, tool_update=request, actor=actor)
|
333
344
|
return tool
|
334
345
|
except LettaToolNameConflictError as e:
|
@@ -345,12 +356,12 @@ async def modify_tool(
|
|
345
356
|
@router.post("/add-base-tools", response_model=List[Tool], operation_id="add_base_tools")
|
346
357
|
async def upsert_base_tools(
|
347
358
|
server: SyncServer = Depends(get_letta_server),
|
348
|
-
|
359
|
+
headers: HeaderParams = Depends(get_headers),
|
349
360
|
):
|
350
361
|
"""
|
351
362
|
Upsert base tools
|
352
363
|
"""
|
353
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
364
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
354
365
|
return await server.tool_manager.upsert_base_tools_async(actor=actor)
|
355
366
|
|
356
367
|
|
@@ -358,12 +369,12 @@ async def upsert_base_tools(
|
|
358
369
|
async def run_tool_from_source(
|
359
370
|
server: SyncServer = Depends(get_letta_server),
|
360
371
|
request: ToolRunFromSource = Body(...),
|
361
|
-
|
372
|
+
headers: HeaderParams = Depends(get_headers),
|
362
373
|
):
|
363
374
|
"""
|
364
375
|
Attempt to build a tool from source, then run it on the provided arguments
|
365
376
|
"""
|
366
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
377
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
367
378
|
|
368
379
|
try:
|
369
380
|
return await server.run_tool_from_source(
|
@@ -388,11 +399,14 @@ async def run_tool_from_source(
|
|
388
399
|
|
389
400
|
# Specific routes for Composio
|
390
401
|
@router.get("/composio/apps", response_model=List[AppModel], operation_id="list_composio_apps")
|
391
|
-
def list_composio_apps(
|
402
|
+
def list_composio_apps(
|
403
|
+
server: SyncServer = Depends(get_letta_server),
|
404
|
+
headers: HeaderParams = Depends(get_headers),
|
405
|
+
):
|
392
406
|
"""
|
393
407
|
Get a list of all Composio apps
|
394
408
|
"""
|
395
|
-
actor = server.user_manager.get_user_or_default(user_id=
|
409
|
+
actor = server.user_manager.get_user_or_default(user_id=headers.actor_id)
|
396
410
|
composio_api_key = get_composio_api_key(actor=actor, logger=logger)
|
397
411
|
if not composio_api_key:
|
398
412
|
raise HTTPException(
|
@@ -406,12 +420,12 @@ def list_composio_apps(server: SyncServer = Depends(get_letta_server), user_id:
|
|
406
420
|
def list_composio_actions_by_app(
|
407
421
|
composio_app_name: str,
|
408
422
|
server: SyncServer = Depends(get_letta_server),
|
409
|
-
|
423
|
+
headers: HeaderParams = Depends(get_headers),
|
410
424
|
):
|
411
425
|
"""
|
412
426
|
Get a list of all Composio actions for a specific app
|
413
427
|
"""
|
414
|
-
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
428
|
+
actor = server.user_manager.get_user_or_default(user_id=headers.actor_id)
|
415
429
|
composio_api_key = get_composio_api_key(actor=actor, logger=logger)
|
416
430
|
if not composio_api_key:
|
417
431
|
raise HTTPException(
|
@@ -425,12 +439,12 @@ def list_composio_actions_by_app(
|
|
425
439
|
async def add_composio_tool(
|
426
440
|
composio_action_name: str,
|
427
441
|
server: SyncServer = Depends(get_letta_server),
|
428
|
-
|
442
|
+
headers: HeaderParams = Depends(get_headers),
|
429
443
|
):
|
430
444
|
"""
|
431
445
|
Add a new Composio tool by action name (Composio refers to each tool as an `Action`)
|
432
446
|
"""
|
433
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
447
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
434
448
|
|
435
449
|
try:
|
436
450
|
tool_create = ToolCreate.from_composio(action_name=composio_action_name)
|
@@ -515,14 +529,17 @@ async def add_composio_tool(
|
|
515
529
|
response_model=dict[str, Union[SSEServerConfig, StdioServerConfig, StreamableHTTPServerConfig]],
|
516
530
|
operation_id="list_mcp_servers",
|
517
531
|
)
|
518
|
-
async def list_mcp_servers(
|
532
|
+
async def list_mcp_servers(
|
533
|
+
server: SyncServer = Depends(get_letta_server),
|
534
|
+
headers: HeaderParams = Depends(get_headers),
|
535
|
+
):
|
519
536
|
"""
|
520
537
|
Get a list of all configured MCP servers
|
521
538
|
"""
|
522
539
|
if tool_settings.mcp_read_from_config:
|
523
540
|
return server.get_mcp_servers()
|
524
541
|
else:
|
525
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=
|
542
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
526
543
|
mcp_servers = await server.mcp_manager.list_mcp_servers(actor=actor)
|
527
544
|
return {server.server_name: server.to_config(resolve_variables=False) for server in mcp_servers}
|
528
545
|
|
@@ -533,13 +550,13 @@ async def list_mcp_servers(server: SyncServer = Depends(get_letta_server), user_
|
|
533
550
|
async def list_mcp_tools_by_server(
|
534
551
|
mcp_server_name: str,
|
535
552
|
server: SyncServer = Depends(get_letta_server),
|
536
|
-
|
553
|
+
headers: HeaderParams = Depends(get_headers),
|
537
554
|
):
|
538
555
|
"""
|
539
556
|
Get a list of all tools for a specific MCP server
|
540
557
|
"""
|
541
558
|
try:
|
542
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
559
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
543
560
|
mcp_tools = await server.mcp_manager.list_mcp_server_tools(mcp_server_name=mcp_server_name, actor=actor)
|
544
561
|
return mcp_tools
|
545
562
|
except Exception as e:
|
@@ -576,7 +593,7 @@ async def list_mcp_tools_by_server(
|
|
576
593
|
async def resync_mcp_server_tools(
|
577
594
|
mcp_server_name: str,
|
578
595
|
server: SyncServer = Depends(get_letta_server),
|
579
|
-
|
596
|
+
headers: HeaderParams = Depends(get_headers),
|
580
597
|
agent_id: Optional[str] = None,
|
581
598
|
):
|
582
599
|
"""
|
@@ -588,7 +605,7 @@ async def resync_mcp_server_tools(
|
|
588
605
|
|
589
606
|
Returns a summary of changes made.
|
590
607
|
"""
|
591
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
608
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
592
609
|
|
593
610
|
try:
|
594
611
|
result = await server.mcp_manager.resync_mcp_server_tools(mcp_server_name=mcp_server_name, actor=actor, agent_id=agent_id)
|
@@ -619,12 +636,12 @@ async def add_mcp_tool(
|
|
619
636
|
mcp_server_name: str,
|
620
637
|
mcp_tool_name: str,
|
621
638
|
server: SyncServer = Depends(get_letta_server),
|
622
|
-
|
639
|
+
headers: HeaderParams = Depends(get_headers),
|
623
640
|
):
|
624
641
|
"""
|
625
642
|
Register a new MCP tool as a Letta server by MCP server + tool name
|
626
643
|
"""
|
627
|
-
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
644
|
+
actor = server.user_manager.get_user_or_default(user_id=headers.actor_id)
|
628
645
|
|
629
646
|
if tool_settings.mcp_read_from_config:
|
630
647
|
try:
|
@@ -698,48 +715,29 @@ async def add_mcp_tool(
|
|
698
715
|
async def add_mcp_server_to_config(
|
699
716
|
request: Union[StdioServerConfig, SSEServerConfig, StreamableHTTPServerConfig] = Body(...),
|
700
717
|
server: SyncServer = Depends(get_letta_server),
|
701
|
-
|
718
|
+
headers: HeaderParams = Depends(get_headers),
|
702
719
|
):
|
703
720
|
"""
|
704
721
|
Add a new MCP server to the Letta MCP server config
|
705
722
|
"""
|
706
723
|
try:
|
707
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
724
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
708
725
|
|
709
726
|
if tool_settings.mcp_read_from_config:
|
710
727
|
# write to config file
|
711
728
|
return await server.add_mcp_server_to_config(server_config=request, allow_upsert=True)
|
712
729
|
else:
|
713
730
|
# log to DB
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
if tool_settings.mcp_disable_stdio: # protected server
|
720
|
-
raise HTTPException(
|
721
|
-
status_code=400,
|
722
|
-
detail="stdio is not supported in the current environment, please use a self-hosted Letta server in order to add a stdio MCP server",
|
723
|
-
)
|
724
|
-
elif isinstance(request, SSEServerConfig):
|
725
|
-
mapped_request = MCPServer(
|
726
|
-
server_name=request.server_name,
|
727
|
-
server_type=request.type,
|
728
|
-
server_url=request.server_url,
|
729
|
-
token=request.resolve_token(),
|
730
|
-
custom_headers=request.custom_headers,
|
731
|
-
)
|
732
|
-
elif isinstance(request, StreamableHTTPServerConfig):
|
733
|
-
mapped_request = MCPServer(
|
734
|
-
server_name=request.server_name,
|
735
|
-
server_type=request.type,
|
736
|
-
server_url=request.server_url,
|
737
|
-
token=request.resolve_token(),
|
738
|
-
custom_headers=request.custom_headers,
|
731
|
+
# Check if stdio servers are disabled
|
732
|
+
if isinstance(request, StdioServerConfig) and tool_settings.mcp_disable_stdio:
|
733
|
+
raise HTTPException(
|
734
|
+
status_code=400,
|
735
|
+
detail="stdio is not supported in the current environment, please use a self-hosted Letta server in order to add a stdio MCP server",
|
739
736
|
)
|
740
737
|
|
741
738
|
# Create MCP server and optimistically sync tools
|
742
|
-
|
739
|
+
# The mcp_manager will handle encryption of sensitive fields
|
740
|
+
await server.mcp_manager.create_mcp_server_from_config_with_tools(request, actor=actor)
|
743
741
|
|
744
742
|
# TODO: don't do this in the future (just return MCPServer)
|
745
743
|
all_servers = await server.mcp_manager.list_mcp_servers(actor=actor)
|
@@ -767,13 +765,13 @@ async def update_mcp_server(
|
|
767
765
|
mcp_server_name: str,
|
768
766
|
request: Union[UpdateStdioMCPServer, UpdateSSEMCPServer, UpdateStreamableHTTPMCPServer] = Body(...),
|
769
767
|
server: SyncServer = Depends(get_letta_server),
|
770
|
-
|
768
|
+
headers: HeaderParams = Depends(get_headers),
|
771
769
|
):
|
772
770
|
"""
|
773
771
|
Update an existing MCP server configuration
|
774
772
|
"""
|
775
773
|
try:
|
776
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
774
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
777
775
|
|
778
776
|
if tool_settings.mcp_read_from_config:
|
779
777
|
raise HTTPException(status_code=501, detail="Update not implemented for config file mode, config files to be deprecated.")
|
@@ -797,7 +795,7 @@ async def update_mcp_server(
|
|
797
795
|
async def delete_mcp_server_from_config(
|
798
796
|
mcp_server_name: str,
|
799
797
|
server: SyncServer = Depends(get_letta_server),
|
800
|
-
|
798
|
+
headers: HeaderParams = Depends(get_headers),
|
801
799
|
):
|
802
800
|
"""
|
803
801
|
Delete a MCP server configuration
|
@@ -807,7 +805,7 @@ async def delete_mcp_server_from_config(
|
|
807
805
|
return server.delete_mcp_server_from_config(server_name=mcp_server_name)
|
808
806
|
else:
|
809
807
|
# log to DB
|
810
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
808
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
811
809
|
mcp_server_id = await server.mcp_manager.get_mcp_server_id_by_name(mcp_server_name, actor)
|
812
810
|
await server.mcp_manager.delete_mcp_server_by_id(mcp_server_id, actor=actor)
|
813
811
|
|
@@ -821,7 +819,7 @@ async def delete_mcp_server_from_config(
|
|
821
819
|
async def test_mcp_server(
|
822
820
|
request: Union[StdioServerConfig, SSEServerConfig, StreamableHTTPServerConfig] = Body(...),
|
823
821
|
server: SyncServer = Depends(get_letta_server),
|
824
|
-
|
822
|
+
headers: HeaderParams = Depends(get_headers),
|
825
823
|
):
|
826
824
|
"""
|
827
825
|
Test connection to an MCP server without adding it.
|
@@ -829,7 +827,7 @@ async def test_mcp_server(
|
|
829
827
|
"""
|
830
828
|
client = None
|
831
829
|
try:
|
832
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
830
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
833
831
|
request.resolve_environment_variables()
|
834
832
|
client = await server.mcp_manager.get_mcp_client(request, actor)
|
835
833
|
|
@@ -888,7 +886,7 @@ async def test_mcp_server(
|
|
888
886
|
async def connect_mcp_server(
|
889
887
|
request: Union[StdioServerConfig, SSEServerConfig, StreamableHTTPServerConfig] = Body(...),
|
890
888
|
server: SyncServer = Depends(get_letta_server),
|
891
|
-
|
889
|
+
headers: HeaderParams = Depends(get_headers),
|
892
890
|
http_request: Request = None,
|
893
891
|
) -> StreamingResponse:
|
894
892
|
"""
|
@@ -907,7 +905,7 @@ async def connect_mcp_server(
|
|
907
905
|
# Acknolwedge connection attempt
|
908
906
|
yield oauth_stream_event(OauthStreamEvent.CONNECTION_ATTEMPT, server_name=request.server_name)
|
909
907
|
|
910
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
908
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
911
909
|
|
912
910
|
# Create MCP client with respective transport type
|
913
911
|
try:
|
@@ -974,7 +972,7 @@ class CodeInput(BaseModel):
|
|
974
972
|
async def generate_json_schema(
|
975
973
|
request: CodeInput = Body(...),
|
976
974
|
server: SyncServer = Depends(get_letta_server),
|
977
|
-
|
975
|
+
headers: HeaderParams = Depends(get_headers),
|
978
976
|
):
|
979
977
|
"""
|
980
978
|
Generate a JSON schema from the given source code defining a function or class.
|
@@ -1005,7 +1003,7 @@ async def execute_mcp_tool(
|
|
1005
1003
|
tool_name: str,
|
1006
1004
|
request: MCPToolExecuteRequest = Body(...),
|
1007
1005
|
server: SyncServer = Depends(get_letta_server),
|
1008
|
-
|
1006
|
+
headers: HeaderParams = Depends(get_headers),
|
1009
1007
|
):
|
1010
1008
|
"""
|
1011
1009
|
Execute a specific MCP tool from a configured server.
|
@@ -1013,7 +1011,7 @@ async def execute_mcp_tool(
|
|
1013
1011
|
"""
|
1014
1012
|
client = None
|
1015
1013
|
try:
|
1016
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
1014
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
1017
1015
|
|
1018
1016
|
# Get the MCP server by name
|
1019
1017
|
mcp_server = await server.mcp_manager.get_mcp_server(mcp_server_name, actor)
|
@@ -1117,7 +1115,7 @@ class GenerateToolOutput(BaseModel):
|
|
1117
1115
|
async def generate_tool_from_prompt(
|
1118
1116
|
request: GenerateToolInput = Body(...),
|
1119
1117
|
server: SyncServer = Depends(get_letta_server),
|
1120
|
-
|
1118
|
+
headers: HeaderParams = Depends(get_headers),
|
1121
1119
|
):
|
1122
1120
|
"""
|
1123
1121
|
Generate a tool from the given user prompt.
|
@@ -1125,7 +1123,7 @@ async def generate_tool_from_prompt(
|
|
1125
1123
|
response_data = None
|
1126
1124
|
|
1127
1125
|
try:
|
1128
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
1126
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
1129
1127
|
llm_config = await server.get_cached_llm_config_async(actor=actor, handle=request.handle or "anthropic/claude-3-5-sonnet-20240620")
|
1130
1128
|
formatted_prompt = (
|
1131
1129
|
f"Generate a python function named {request.tool_name} using the instructions below "
|
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, List, Optional
|
|
3
3
|
from fastapi import APIRouter, Body, Depends, HTTPException, Query
|
4
4
|
|
5
5
|
from letta.schemas.user import User, UserCreate, UserUpdate
|
6
|
-
from letta.server.rest_api.
|
6
|
+
from letta.server.rest_api.dependencies import get_letta_server
|
7
7
|
|
8
8
|
if TYPE_CHECKING:
|
9
9
|
from letta.schemas.user import User
|
@@ -1,12 +1,13 @@
|
|
1
|
-
from typing import TYPE_CHECKING, Any, Dict
|
1
|
+
from typing import TYPE_CHECKING, Any, Dict
|
2
2
|
|
3
3
|
import openai
|
4
|
-
from fastapi import APIRouter, Body, Depends
|
4
|
+
from fastapi import APIRouter, Body, Depends
|
5
5
|
from fastapi.responses import StreamingResponse
|
6
6
|
|
7
7
|
from letta.agents.voice_agent import VoiceAgent
|
8
8
|
from letta.log import get_logger
|
9
|
-
from letta.server.rest_api.
|
9
|
+
from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
|
10
|
+
from letta.server.rest_api.utils import get_user_message_from_chat_completions_request
|
10
11
|
from letta.settings import model_settings
|
11
12
|
|
12
13
|
if TYPE_CHECKING:
|
@@ -33,9 +34,9 @@ async def create_voice_chat_completions(
|
|
33
34
|
agent_id: str,
|
34
35
|
completion_request: Dict[str, Any] = Body(...), # The validation is soft in case providers like VAPI send extra params
|
35
36
|
server: "SyncServer" = Depends(get_letta_server),
|
36
|
-
|
37
|
+
headers: HeaderParams = Depends(get_headers),
|
37
38
|
):
|
38
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=
|
39
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
39
40
|
|
40
41
|
# Create OpenAI async client
|
41
42
|
client = openai.AsyncClient(
|
letta/server/rest_api/utils.py
CHANGED
@@ -3,7 +3,7 @@ import json
|
|
3
3
|
import os
|
4
4
|
import uuid
|
5
5
|
from enum import Enum
|
6
|
-
from typing import
|
6
|
+
from typing import AsyncGenerator, Dict, Iterable, List, Optional, Union, cast
|
7
7
|
|
8
8
|
from fastapi import Header, HTTPException
|
9
9
|
from openai.types.chat import ChatCompletionMessageParam
|
@@ -35,9 +35,6 @@ from letta.schemas.usage import LettaUsageStatistics
|
|
35
35
|
from letta.schemas.user import User
|
36
36
|
from letta.system import get_heartbeat, package_function_response
|
37
37
|
|
38
|
-
if TYPE_CHECKING:
|
39
|
-
from letta.server.server import SyncServer
|
40
|
-
|
41
38
|
SENTRY_ENABLED = bool(os.getenv("SENTRY_DSN"))
|
42
39
|
|
43
40
|
if SENTRY_ENABLED:
|
@@ -145,20 +142,6 @@ async def sse_async_generator(
|
|
145
142
|
yield sse_formatter(SSE_FINISH_MSG)
|
146
143
|
|
147
144
|
|
148
|
-
# TODO: why does this double up the interface?
|
149
|
-
def get_letta_server() -> "SyncServer":
|
150
|
-
# Check if a global server is already instantiated
|
151
|
-
from letta.server.rest_api.app import server
|
152
|
-
|
153
|
-
# assert isinstance(server, SyncServer)
|
154
|
-
return server
|
155
|
-
|
156
|
-
|
157
|
-
# Dependency to get user_id from headers
|
158
|
-
def get_user_id(user_id: Optional[str] = Header(None, alias="user_id")) -> Optional[str]:
|
159
|
-
return user_id
|
160
|
-
|
161
|
-
|
162
145
|
def capture_sentry_exception(e: BaseException):
|
163
146
|
"""This will capture the exception in sentry, since the exception handler upstack (in FastAPI) won't catch it, because this may be a 200 response"""
|
164
147
|
if SENTRY_ENABLED:
|