letta-nightly 0.13.0.dev20251031104146__py3-none-any.whl → 0.13.1.dev20251031234110__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.

Files changed (101) hide show
  1. letta/__init__.py +1 -1
  2. letta/adapters/simple_llm_stream_adapter.py +1 -0
  3. letta/agents/letta_agent_v2.py +8 -0
  4. letta/agents/letta_agent_v3.py +120 -27
  5. letta/agents/temporal/activities/__init__.py +25 -0
  6. letta/agents/temporal/activities/create_messages.py +26 -0
  7. letta/agents/temporal/activities/create_step.py +57 -0
  8. letta/agents/temporal/activities/example_activity.py +9 -0
  9. letta/agents/temporal/activities/execute_tool.py +130 -0
  10. letta/agents/temporal/activities/llm_request.py +114 -0
  11. letta/agents/temporal/activities/prepare_messages.py +27 -0
  12. letta/agents/temporal/activities/refresh_context.py +160 -0
  13. letta/agents/temporal/activities/summarize_conversation_history.py +77 -0
  14. letta/agents/temporal/activities/update_message_ids.py +25 -0
  15. letta/agents/temporal/activities/update_run.py +43 -0
  16. letta/agents/temporal/constants.py +59 -0
  17. letta/agents/temporal/temporal_agent_workflow.py +704 -0
  18. letta/agents/temporal/types.py +275 -0
  19. letta/constants.py +8 -0
  20. letta/errors.py +4 -0
  21. letta/functions/function_sets/base.py +0 -11
  22. letta/groups/helpers.py +7 -1
  23. letta/groups/sleeptime_multi_agent_v4.py +4 -3
  24. letta/interfaces/anthropic_streaming_interface.py +0 -1
  25. letta/interfaces/openai_streaming_interface.py +103 -100
  26. letta/llm_api/anthropic_client.py +57 -12
  27. letta/llm_api/bedrock_client.py +1 -0
  28. letta/llm_api/deepseek_client.py +3 -2
  29. letta/llm_api/google_vertex_client.py +1 -0
  30. letta/llm_api/groq_client.py +1 -0
  31. letta/llm_api/llm_client_base.py +15 -1
  32. letta/llm_api/openai.py +2 -2
  33. letta/llm_api/openai_client.py +17 -3
  34. letta/llm_api/xai_client.py +1 -0
  35. letta/orm/organization.py +4 -0
  36. letta/orm/sqlalchemy_base.py +7 -0
  37. letta/otel/tracing.py +131 -4
  38. letta/schemas/agent_file.py +10 -10
  39. letta/schemas/block.py +22 -3
  40. letta/schemas/enums.py +21 -0
  41. letta/schemas/environment_variables.py +3 -2
  42. letta/schemas/group.py +3 -3
  43. letta/schemas/letta_response.py +36 -4
  44. letta/schemas/llm_batch_job.py +3 -3
  45. letta/schemas/llm_config.py +27 -3
  46. letta/schemas/mcp.py +3 -2
  47. letta/schemas/mcp_server.py +3 -2
  48. letta/schemas/message.py +167 -49
  49. letta/schemas/organization.py +2 -1
  50. letta/schemas/passage.py +2 -1
  51. letta/schemas/provider_trace.py +2 -1
  52. letta/schemas/providers/openrouter.py +1 -2
  53. letta/schemas/run_metrics.py +2 -1
  54. letta/schemas/sandbox_config.py +3 -1
  55. letta/schemas/step_metrics.py +2 -1
  56. letta/schemas/tool_rule.py +2 -2
  57. letta/schemas/user.py +2 -1
  58. letta/server/rest_api/app.py +5 -1
  59. letta/server/rest_api/routers/v1/__init__.py +4 -0
  60. letta/server/rest_api/routers/v1/agents.py +71 -9
  61. letta/server/rest_api/routers/v1/blocks.py +7 -7
  62. letta/server/rest_api/routers/v1/groups.py +40 -0
  63. letta/server/rest_api/routers/v1/identities.py +2 -2
  64. letta/server/rest_api/routers/v1/internal_agents.py +31 -0
  65. letta/server/rest_api/routers/v1/internal_blocks.py +177 -0
  66. letta/server/rest_api/routers/v1/internal_runs.py +25 -1
  67. letta/server/rest_api/routers/v1/runs.py +2 -22
  68. letta/server/rest_api/routers/v1/tools.py +10 -0
  69. letta/server/server.py +5 -2
  70. letta/services/agent_manager.py +4 -4
  71. letta/services/archive_manager.py +16 -0
  72. letta/services/group_manager.py +44 -0
  73. letta/services/helpers/run_manager_helper.py +2 -2
  74. letta/services/lettuce/lettuce_client.py +148 -0
  75. letta/services/mcp/base_client.py +9 -3
  76. letta/services/run_manager.py +148 -37
  77. letta/services/source_manager.py +91 -3
  78. letta/services/step_manager.py +2 -3
  79. letta/services/streaming_service.py +52 -13
  80. letta/services/summarizer/summarizer.py +28 -2
  81. letta/services/tool_executor/builtin_tool_executor.py +1 -1
  82. letta/services/tool_executor/core_tool_executor.py +2 -117
  83. letta/services/tool_schema_generator.py +2 -2
  84. letta/validators.py +21 -0
  85. {letta_nightly-0.13.0.dev20251031104146.dist-info → letta_nightly-0.13.1.dev20251031234110.dist-info}/METADATA +1 -1
  86. {letta_nightly-0.13.0.dev20251031104146.dist-info → letta_nightly-0.13.1.dev20251031234110.dist-info}/RECORD +89 -84
  87. letta/agent.py +0 -1758
  88. letta/cli/cli_load.py +0 -16
  89. letta/client/__init__.py +0 -0
  90. letta/client/streaming.py +0 -95
  91. letta/client/utils.py +0 -78
  92. letta/functions/async_composio_toolset.py +0 -109
  93. letta/functions/composio_helpers.py +0 -96
  94. letta/helpers/composio_helpers.py +0 -38
  95. letta/orm/job_messages.py +0 -33
  96. letta/schemas/providers.py +0 -1617
  97. letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +0 -132
  98. letta/services/tool_executor/composio_tool_executor.py +0 -57
  99. {letta_nightly-0.13.0.dev20251031104146.dist-info → letta_nightly-0.13.1.dev20251031234110.dist-info}/WHEEL +0 -0
  100. {letta_nightly-0.13.0.dev20251031104146.dist-info → letta_nightly-0.13.1.dev20251031234110.dist-info}/entry_points.txt +0 -0
  101. {letta_nightly-0.13.0.dev20251031104146.dist-info → letta_nightly-0.13.1.dev20251031234110.dist-info}/licenses/LICENSE +0 -0
@@ -21,7 +21,6 @@ class OpenRouterProvider(OpenAIProvider):
21
21
  provider_category: ProviderCategory = Field(ProviderCategory.base, description="The category of the provider (base or byok)")
22
22
  api_key: str = Field(..., description="API key for the OpenRouter API.")
23
23
  base_url: str = Field("https://openrouter.ai/api/v1", description="Base URL for the OpenRouter API.")
24
- handle_base: str | None = Field(None, description="Custom handle base name for model handles (e.g., 'custom' instead of 'openrouter').")
25
24
 
26
25
  def _list_llm_models(self, data: list[dict]) -> list[LLMConfig]:
27
26
  """
@@ -34,7 +33,7 @@ class OpenRouterProvider(OpenAIProvider):
34
33
  continue
35
34
  model_name, context_window_size = check
36
35
 
37
- handle = self.get_handle(model_name, base_name=self.handle_base) if self.handle_base else self.get_handle(model_name)
36
+ handle = self.get_handle(model_name)
38
37
 
39
38
  config = LLMConfig(
40
39
  model=model_name,
@@ -2,11 +2,12 @@ from typing import List, Optional
2
2
 
3
3
  from pydantic import Field
4
4
 
5
+ from letta.schemas.enums import PrimitiveType
5
6
  from letta.schemas.letta_base import LettaBase
6
7
 
7
8
 
8
9
  class RunMetricsBase(LettaBase):
9
- __id_prefix__ = "run"
10
+ __id_prefix__ = PrimitiveType.RUN.value
10
11
 
11
12
 
12
13
  class RunMetrics(RunMetricsBase):
@@ -102,7 +102,9 @@ class SandboxConfig(SandboxConfigBase):
102
102
  config: Dict = Field(default_factory=lambda: {}, description="The JSON sandbox settings data.")
103
103
 
104
104
  def get_e2b_config(self) -> E2BSandboxConfig:
105
- return E2BSandboxConfig(**self.config)
105
+ config_dict = self.config.copy()
106
+ config_dict["template"] = tool_settings.e2b_sandbox_template_id
107
+ return E2BSandboxConfig(**config_dict)
106
108
 
107
109
  def get_local_config(self) -> LocalSandboxConfig:
108
110
  return LocalSandboxConfig(**self.config)
@@ -2,11 +2,12 @@ from typing import Optional
2
2
 
3
3
  from pydantic import Field
4
4
 
5
+ from letta.schemas.enums import PrimitiveType
5
6
  from letta.schemas.letta_base import LettaBase
6
7
 
7
8
 
8
9
  class StepMetricsBase(LettaBase):
9
- __id_prefix__ = "step"
10
+ __id_prefix__ = PrimitiveType.STEP.value
10
11
 
11
12
 
12
13
  class StepMetrics(StepMetricsBase):
@@ -4,14 +4,14 @@ from typing import Annotated, Any, Dict, List, Literal, Optional, Set, Union
4
4
 
5
5
  from pydantic import BaseModel, Field, field_validator, model_validator
6
6
 
7
- from letta.schemas.enums import ToolRuleType
7
+ from letta.schemas.enums import PrimitiveType, ToolRuleType
8
8
  from letta.schemas.letta_base import LettaBase
9
9
 
10
10
  logger = logging.getLogger(__name__)
11
11
 
12
12
 
13
13
  class BaseToolRule(LettaBase):
14
- __id_prefix__ = "tool_rule"
14
+ __id_prefix__ = PrimitiveType.TOOL_RULE.value
15
15
  tool_name: str = Field(..., description="The name of the tool. Must exist in the database for the user's organization.")
16
16
  type: ToolRuleType = Field(..., description="The type of the message.")
17
17
  prompt_template: Optional[str] = Field(
letta/schemas/user.py CHANGED
@@ -4,11 +4,12 @@ from typing import Optional
4
4
  from pydantic import Field
5
5
 
6
6
  from letta.constants import DEFAULT_ORG_ID
7
+ from letta.schemas.enums import PrimitiveType
7
8
  from letta.schemas.letta_base import LettaBase
8
9
 
9
10
 
10
11
  class UserBase(LettaBase):
11
- __id_prefix__ = "user"
12
+ __id_prefix__ = PrimitiveType.USER.value
12
13
 
13
14
 
14
15
  class User(UserBase):
@@ -26,6 +26,7 @@ from letta.errors import (
26
26
  AgentFileImportError,
27
27
  AgentNotFoundForExportError,
28
28
  BedrockPermissionError,
29
+ HandleNotFoundError,
29
30
  LettaAgentNotFoundError,
30
31
  LettaExpiredError,
31
32
  LettaInvalidArgumentError,
@@ -39,6 +40,7 @@ from letta.errors import (
39
40
  LettaUserNotFoundError,
40
41
  LLMAuthenticationError,
41
42
  LLMError,
43
+ LLMProviderOverloaded,
42
44
  LLMRateLimitError,
43
45
  LLMTimeoutError,
44
46
  PendingApprovalError,
@@ -270,7 +272,7 @@ def create_application() -> "FastAPI":
270
272
  return JSONResponse(
271
273
  status_code=500,
272
274
  content={
273
- "detail": "An internal server error occurred",
275
+ "detail": "An unknown error occurred",
274
276
  # Only include error details in debug/development mode
275
277
  # "debug_info": str(exc) if settings.debug else None
276
278
  },
@@ -369,6 +371,7 @@ def create_application() -> "FastAPI":
369
371
  app.add_exception_handler(LettaAgentNotFoundError, _error_handler_404_agent)
370
372
  app.add_exception_handler(LettaUserNotFoundError, _error_handler_404_user)
371
373
  app.add_exception_handler(AgentNotFoundForExportError, _error_handler_404)
374
+ app.add_exception_handler(HandleNotFoundError, _error_handler_404)
372
375
 
373
376
  # 410 Expired errors
374
377
  app.add_exception_handler(LettaExpiredError, _error_handler_410)
@@ -396,6 +399,7 @@ def create_application() -> "FastAPI":
396
399
  # 503 Service Unavailable errors
397
400
  app.add_exception_handler(OperationalError, _error_handler_503)
398
401
  app.add_exception_handler(LettaServiceUnavailableError, _error_handler_503)
402
+ app.add_exception_handler(LLMProviderOverloaded, _error_handler_503)
399
403
 
400
404
  @app.exception_handler(IncompatibleAgentType)
401
405
  async def handle_incompatible_agent_type(request: Request, exc: IncompatibleAgentType):
@@ -7,6 +7,8 @@ from letta.server.rest_api.routers.v1.folders import router as folders_router
7
7
  from letta.server.rest_api.routers.v1.groups import router as groups_router
8
8
  from letta.server.rest_api.routers.v1.health import router as health_router
9
9
  from letta.server.rest_api.routers.v1.identities import router as identities_router
10
+ from letta.server.rest_api.routers.v1.internal_agents import router as internal_agents_router
11
+ from letta.server.rest_api.routers.v1.internal_blocks import router as internal_blocks_router
10
12
  from letta.server.rest_api.routers.v1.internal_runs import router as internal_runs_router
11
13
  from letta.server.rest_api.routers.v1.internal_templates import router as internal_templates_router
12
14
  from letta.server.rest_api.routers.v1.jobs import router as jobs_router
@@ -32,6 +34,8 @@ ROUTERS = [
32
34
  chat_completions_router,
33
35
  groups_router,
34
36
  identities_router,
37
+ internal_agents_router,
38
+ internal_blocks_router,
35
39
  internal_runs_router,
36
40
  internal_templates_router,
37
41
  llm_router,
@@ -25,21 +25,23 @@ from letta.errors import (
25
25
  AgentNotFoundForExportError,
26
26
  PendingApprovalError,
27
27
  )
28
- from letta.helpers.datetime_helpers import get_utc_timestamp_ns
28
+ from letta.groups.sleeptime_multi_agent_v4 import SleeptimeMultiAgentV4
29
+ from letta.helpers.datetime_helpers import get_utc_time, get_utc_timestamp_ns
29
30
  from letta.log import get_logger
30
31
  from letta.orm.errors import NoResultFound
31
32
  from letta.otel.context import get_ctx_attributes
32
33
  from letta.otel.metric_registry import MetricRegistry
33
34
  from letta.schemas.agent import AgentRelationships, AgentState, CreateAgent, UpdateAgent
34
35
  from letta.schemas.agent_file import AgentFileSchema
35
- from letta.schemas.block import BaseBlock, Block, BlockUpdate
36
- from letta.schemas.enums import AgentType, RunStatus
36
+ from letta.schemas.block import BaseBlock, Block, BlockResponse, BlockUpdate
37
+ from letta.schemas.enums import AgentType, MessageRole, RunStatus
37
38
  from letta.schemas.file import AgentFileAttachment, FileMetadataBase, PaginatedAgentFiles
38
39
  from letta.schemas.group import Group
39
40
  from letta.schemas.job import LettaRequestConfig
40
41
  from letta.schemas.letta_message import LettaMessageUnion, LettaMessageUpdateUnion, MessageType
42
+ from letta.schemas.letta_message_content import TextContent
41
43
  from letta.schemas.letta_request import LettaAsyncRequest, LettaRequest, LettaStreamingRequest
42
- from letta.schemas.letta_response import LettaResponse
44
+ from letta.schemas.letta_response import LettaResponse, LettaStreamingResponse
43
45
  from letta.schemas.letta_stop_reason import StopReasonType
44
46
  from letta.schemas.memory import (
45
47
  ArchivalMemorySearchResponse,
@@ -48,7 +50,7 @@ from letta.schemas.memory import (
48
50
  CreateArchivalMemory,
49
51
  Memory,
50
52
  )
51
- from letta.schemas.message import BaseMessage, MessageCreate, MessageCreateType, MessageSearchRequest, MessageSearchResult
53
+ from letta.schemas.message import Message, MessageCreate, MessageCreateType, MessageSearchRequest, MessageSearchResult
52
54
  from letta.schemas.passage import Passage
53
55
  from letta.schemas.run import Run as PydanticRun, RunUpdate
54
56
  from letta.schemas.source import BaseSource, Source
@@ -915,7 +917,7 @@ async def retrieve_agent_memory(
915
917
  return await server.get_agent_memory_async(agent_id=agent_id, actor=actor)
916
918
 
917
919
 
918
- @router.get("/{agent_id}/core-memory/blocks/{block_label}", response_model=Block, operation_id="retrieve_core_memory_block")
920
+ @router.get("/{agent_id}/core-memory/blocks/{block_label}", response_model=BlockResponse, operation_id="retrieve_core_memory_block")
919
921
  async def retrieve_block_for_agent(
920
922
  block_label: str,
921
923
  agent_id: AgentId,
@@ -930,7 +932,7 @@ async def retrieve_block_for_agent(
930
932
  return await server.agent_manager.get_block_with_label_async(agent_id=agent_id, block_label=block_label, actor=actor)
931
933
 
932
934
 
933
- @router.get("/{agent_id}/core-memory/blocks", response_model=list[Block], operation_id="list_core_memory_blocks")
935
+ @router.get("/{agent_id}/core-memory/blocks", response_model=list[BlockResponse], operation_id="list_core_memory_blocks")
934
936
  async def list_blocks_for_agent(
935
937
  agent_id: AgentId,
936
938
  server: "SyncServer" = Depends(get_letta_server),
@@ -962,7 +964,7 @@ async def list_blocks_for_agent(
962
964
  )
963
965
 
964
966
 
965
- @router.patch("/{agent_id}/core-memory/blocks/{block_label}", response_model=Block, operation_id="modify_core_memory_block")
967
+ @router.patch("/{agent_id}/core-memory/blocks/{block_label}", response_model=BlockResponse, operation_id="modify_core_memory_block")
966
968
  async def modify_block_for_agent(
967
969
  block_label: str,
968
970
  agent_id: AgentId,
@@ -1394,7 +1396,7 @@ async def send_message(
1394
1396
  # noinspection PyInconsistentReturns
1395
1397
  @router.post(
1396
1398
  "/{agent_id}/messages/stream",
1397
- response_model=None,
1399
+ response_model=LettaStreamingResponse,
1398
1400
  operation_id="create_agent_message_stream",
1399
1401
  responses={
1400
1402
  200: {
@@ -1902,3 +1904,63 @@ async def summarize_messages(
1902
1904
  status_code=status.HTTP_403_FORBIDDEN,
1903
1905
  detail="Summarization is not currently supported for this agent configuration. Please contact Letta support.",
1904
1906
  )
1907
+
1908
+
1909
+ class CaptureMessagesRequest(BaseModel):
1910
+ provider: str
1911
+ model: str
1912
+ request_messages: list[dict[str, Any]]
1913
+ response_dict: dict[str, Any]
1914
+
1915
+
1916
+ @router.post("/{agent_id}/messages/capture", response_model=str, operation_id="capture_messages", include_in_schema=False)
1917
+ async def capture_messages(
1918
+ agent_id: AgentId,
1919
+ request: CaptureMessagesRequest = Body(...),
1920
+ server: "SyncServer" = Depends(get_letta_server),
1921
+ headers: HeaderParams = Depends(get_headers),
1922
+ ):
1923
+ """
1924
+ Capture a list of messages for an agent.
1925
+ """
1926
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
1927
+ agent = await server.agent_manager.get_agent_by_id_async(agent_id, actor, include_relationships=["multi_agent_group"])
1928
+
1929
+ messages_to_persist = []
1930
+
1931
+ # Input user messages
1932
+ for message in request.request_messages:
1933
+ if message["role"] == "user":
1934
+ messages_to_persist.append(
1935
+ Message(
1936
+ role=MessageRole.user,
1937
+ content=[(TextContent(text=message["content"]))],
1938
+ agent_id=agent_id,
1939
+ tool_calls=None,
1940
+ tool_call_id=None,
1941
+ created_at=get_utc_time(),
1942
+ )
1943
+ )
1944
+
1945
+ # Assistant response
1946
+ messages_to_persist.append(
1947
+ Message(
1948
+ role=MessageRole.assistant,
1949
+ content=[(TextContent(text=request.response_dict["content"]))],
1950
+ agent_id=agent_id,
1951
+ model=request.model,
1952
+ tool_calls=None,
1953
+ tool_call_id=None,
1954
+ created_at=get_utc_time(),
1955
+ )
1956
+ )
1957
+
1958
+ response_messages = await server.message_manager.create_many_messages_async(messages_to_persist, actor=actor)
1959
+
1960
+ sleeptime_group = agent.multi_agent_group if agent.multi_agent_group and agent.multi_agent_group.manager_type == "sleeptime" else None
1961
+ if sleeptime_group:
1962
+ sleeptime_agent_loop = SleeptimeMultiAgentV4(agent_state=agent, actor=actor, group=sleeptime_group)
1963
+ sleeptime_agent_loop.response_messages = response_messages
1964
+ run_ids = await sleeptime_agent_loop.run_sleeptime_agents()
1965
+
1966
+ return JSONResponse({"success": True, "messages_created": len(response_messages), "run_ids": run_ids})
@@ -4,7 +4,7 @@ from fastapi import APIRouter, Body, Depends, HTTPException, Query
4
4
 
5
5
  from letta.orm.errors import NoResultFound
6
6
  from letta.schemas.agent import AgentRelationships, AgentState
7
- from letta.schemas.block import BaseBlock, Block, BlockUpdate, CreateBlock
7
+ from letta.schemas.block import BaseBlock, Block, BlockResponse, BlockUpdate, CreateBlock
8
8
  from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
9
9
  from letta.server.server import SyncServer
10
10
  from letta.utils import is_1_0_sdk_version
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
16
16
  router = APIRouter(prefix="/blocks", tags=["blocks"])
17
17
 
18
18
 
19
- @router.get("/", response_model=List[Block], operation_id="list_blocks")
19
+ @router.get("/", response_model=List[BlockResponse], operation_id="list_blocks")
20
20
  async def list_blocks(
21
21
  # query parameters
22
22
  label: Optional[str] = Query(None, description="Labels to include (e.g. human, persona)"),
@@ -117,7 +117,7 @@ async def count_blocks(
117
117
  return await server.block_manager.size_async(actor=actor)
118
118
 
119
119
 
120
- @router.post("/", response_model=Block, operation_id="create_block")
120
+ @router.post("/", response_model=BlockResponse, operation_id="create_block")
121
121
  async def create_block(
122
122
  create_block: CreateBlock = Body(...),
123
123
  server: SyncServer = Depends(get_letta_server),
@@ -128,7 +128,7 @@ async def create_block(
128
128
  return await server.block_manager.create_or_update_block_async(actor=actor, block=block)
129
129
 
130
130
 
131
- @router.patch("/{block_id}", response_model=Block, operation_id="modify_block")
131
+ @router.patch("/{block_id}", response_model=BlockResponse, operation_id="modify_block")
132
132
  async def modify_block(
133
133
  block_id: BlockId,
134
134
  block_update: BlockUpdate = Body(...),
@@ -149,7 +149,7 @@ async def delete_block(
149
149
  await server.block_manager.delete_block_async(block_id=block_id, actor=actor)
150
150
 
151
151
 
152
- @router.get("/{block_id}", response_model=Block, operation_id="retrieve_block")
152
+ @router.get("/{block_id}", response_model=BlockResponse, operation_id="retrieve_block")
153
153
  async def retrieve_block(
154
154
  block_id: BlockId,
155
155
  server: SyncServer = Depends(get_letta_server),
@@ -214,7 +214,7 @@ async def list_agents_for_block(
214
214
  return agents
215
215
 
216
216
 
217
- @router.patch("/{block_id}/identities/attach/{identity_id}", response_model=Block, operation_id="attach_identity_to_block")
217
+ @router.patch("/{block_id}/identities/attach/{identity_id}", response_model=BlockResponse, operation_id="attach_identity_to_block")
218
218
  async def attach_identity_to_block(
219
219
  identity_id: str,
220
220
  block_id: BlockId,
@@ -233,7 +233,7 @@ async def attach_identity_to_block(
233
233
  return await server.block_manager.get_block_by_id_async(block_id=block_id, actor=actor)
234
234
 
235
235
 
236
- @router.patch("/{block_id}/identities/detach/{identity_id}", response_model=Block, operation_id="detach_identity_from_block")
236
+ @router.patch("/{block_id}/identities/detach/{identity_id}", response_model=BlockResponse, operation_id="detach_identity_from_block")
237
237
  async def detach_identity_from_block(
238
238
  identity_id: str,
239
239
  block_id: BlockId,
@@ -284,3 +284,43 @@ async def reset_group_messages(
284
284
  """
285
285
  actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
286
286
  await server.group_manager.reset_messages_async(group_id=group_id, actor=actor)
287
+
288
+
289
+ @router.patch("/{group_id}/blocks/attach/{block_id}", response_model=None, operation_id="attach_block_to_group")
290
+ async def attach_block_to_group(
291
+ block_id: str,
292
+ group_id: GroupId,
293
+ server: "SyncServer" = Depends(get_letta_server),
294
+ headers: HeaderParams = Depends(get_headers),
295
+ ):
296
+ """
297
+ Attach a block to a group.
298
+ This will add the block to the group and all agents within the group.
299
+ """
300
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
301
+ await server.group_manager.attach_block_async(
302
+ group_id=group_id,
303
+ block_id=block_id,
304
+ actor=actor,
305
+ )
306
+ return None
307
+
308
+
309
+ @router.patch("/{group_id}/blocks/detach/{block_id}", response_model=None, operation_id="detach_block_from_group")
310
+ async def detach_block_from_group(
311
+ block_id: str,
312
+ group_id: GroupId,
313
+ server: "SyncServer" = Depends(get_letta_server),
314
+ headers: HeaderParams = Depends(get_headers),
315
+ ):
316
+ """
317
+ Detach a block from a group.
318
+ This will remove the block from the group and all agents within the group.
319
+ """
320
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
321
+ await server.group_manager.detach_block_async(
322
+ group_id=group_id,
323
+ block_id=block_id,
324
+ actor=actor,
325
+ )
326
+ return None
@@ -4,7 +4,7 @@ from fastapi import APIRouter, Body, Depends, Header, Query
4
4
 
5
5
  from letta.orm.errors import NoResultFound, UniqueConstraintViolationError
6
6
  from letta.schemas.agent import AgentRelationships, AgentState
7
- from letta.schemas.block import Block
7
+ from letta.schemas.block import Block, BlockResponse
8
8
  from letta.schemas.identity import (
9
9
  Identity,
10
10
  IdentityCreate,
@@ -188,7 +188,7 @@ async def list_agents_for_identity(
188
188
  )
189
189
 
190
190
 
191
- @router.get("/{identity_id}/blocks", response_model=List[Block], operation_id="list_blocks_for_identity")
191
+ @router.get("/{identity_id}/blocks", response_model=List[BlockResponse], operation_id="list_blocks_for_identity")
192
192
  async def list_blocks_for_identity(
193
193
  identity_id: IdentityId,
194
194
  before: Optional[str] = Query(
@@ -0,0 +1,31 @@
1
+ from fastapi import APIRouter, Body, Depends
2
+
3
+ from letta.schemas.block import Block, BlockUpdate
4
+ from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
5
+ from letta.server.server import SyncServer
6
+ from letta.validators import AgentId
7
+
8
+ router = APIRouter(prefix="/_internal_agents", tags=["_internal_agents"])
9
+
10
+
11
+ @router.patch("/{agent_id}/core-memory/blocks/{block_label}", response_model=Block, operation_id="modify_internal_core_memory_block")
12
+ async def modify_block_for_agent(
13
+ block_label: str,
14
+ agent_id: AgentId,
15
+ block_update: BlockUpdate = Body(...),
16
+ server: "SyncServer" = Depends(get_letta_server),
17
+ headers: HeaderParams = Depends(get_headers),
18
+ ):
19
+ """
20
+ Updates a core memory block of an agent.
21
+ """
22
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
23
+
24
+ block = await server.agent_manager.modify_block_by_label_async(
25
+ agent_id=agent_id, block_label=block_label, block_update=block_update, actor=actor
26
+ )
27
+
28
+ # This should also trigger a system prompt change in the agent
29
+ await server.agent_manager.rebuild_system_prompt_async(agent_id=agent_id, actor=actor, force=True, update_timestamp=False)
30
+
31
+ return block
@@ -0,0 +1,177 @@
1
+ from typing import TYPE_CHECKING, List, Literal, Optional
2
+
3
+ from fastapi import APIRouter, Body, Depends, Query
4
+
5
+ from letta.schemas.agent import AgentState
6
+ from letta.schemas.block import Block, CreateBlock
7
+ from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
8
+ from letta.server.server import SyncServer
9
+ from letta.utils import is_1_0_sdk_version
10
+ from letta.validators import BlockId
11
+
12
+ if TYPE_CHECKING:
13
+ pass
14
+
15
+ router = APIRouter(prefix="/_internal_blocks", tags=["_internal_blocks"])
16
+
17
+
18
+ @router.get("/", response_model=List[Block], operation_id="list_internal_blocks")
19
+ async def list_blocks(
20
+ # query parameters
21
+ label: Optional[str] = Query(None, description="Labels to include (e.g. human, persona)"),
22
+ templates_only: bool = Query(False, description="Whether to include only templates"),
23
+ name: Optional[str] = Query(None, description="Name of the block"),
24
+ identity_id: Optional[str] = Query(None, description="Search agents by identifier id"),
25
+ identifier_keys: Optional[List[str]] = Query(None, description="Search agents by identifier keys"),
26
+ project_id: Optional[str] = Query(None, description="Search blocks by project id"),
27
+ limit: Optional[int] = Query(50, description="Number of blocks to return"),
28
+ before: Optional[str] = Query(
29
+ None,
30
+ description="Block ID cursor for pagination. Returns blocks that come before this block ID in the specified sort order",
31
+ ),
32
+ after: Optional[str] = Query(
33
+ None,
34
+ description="Block ID cursor for pagination. Returns blocks that come after this block ID in the specified sort order",
35
+ ),
36
+ order: Literal["asc", "desc"] = Query(
37
+ "asc", description="Sort order for blocks by creation time. 'asc' for oldest first, 'desc' for newest first"
38
+ ),
39
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
40
+ label_search: Optional[str] = Query(
41
+ None,
42
+ description=("Search blocks by label. If provided, returns blocks that match this label. This is a full-text search on labels."),
43
+ ),
44
+ description_search: Optional[str] = Query(
45
+ None,
46
+ description=(
47
+ "Search blocks by description. If provided, returns blocks that match this description. "
48
+ "This is a full-text search on block descriptions."
49
+ ),
50
+ ),
51
+ value_search: Optional[str] = Query(
52
+ None,
53
+ description=("Search blocks by value. If provided, returns blocks that match this value."),
54
+ ),
55
+ connected_to_agents_count_gt: Optional[int] = Query(
56
+ None,
57
+ description=(
58
+ "Filter blocks by the number of connected agents. "
59
+ "If provided, returns blocks that have more than this number of connected agents."
60
+ ),
61
+ ),
62
+ connected_to_agents_count_lt: Optional[int] = Query(
63
+ None,
64
+ description=(
65
+ "Filter blocks by the number of connected agents. "
66
+ "If provided, returns blocks that have less than this number of connected agents."
67
+ ),
68
+ ),
69
+ connected_to_agents_count_eq: Optional[List[int]] = Query(
70
+ None,
71
+ description=(
72
+ "Filter blocks by the exact number of connected agents. "
73
+ "If provided, returns blocks that have exactly this number of connected agents."
74
+ ),
75
+ ),
76
+ show_hidden_blocks: bool | None = Query(
77
+ False,
78
+ include_in_schema=False,
79
+ description="If set to True, include blocks marked as hidden in the results.",
80
+ ),
81
+ server: SyncServer = Depends(get_letta_server),
82
+ headers: HeaderParams = Depends(get_headers),
83
+ ):
84
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
85
+ return await server.block_manager.get_blocks_async(
86
+ actor=actor,
87
+ label=label,
88
+ is_template=templates_only,
89
+ value_search=value_search,
90
+ label_search=label_search,
91
+ description_search=description_search,
92
+ template_name=name,
93
+ identity_id=identity_id,
94
+ identifier_keys=identifier_keys,
95
+ project_id=project_id,
96
+ before=before,
97
+ connected_to_agents_count_gt=connected_to_agents_count_gt,
98
+ connected_to_agents_count_lt=connected_to_agents_count_lt,
99
+ connected_to_agents_count_eq=connected_to_agents_count_eq,
100
+ limit=limit,
101
+ after=after,
102
+ ascending=(order == "asc"),
103
+ show_hidden_blocks=show_hidden_blocks,
104
+ )
105
+
106
+
107
+ @router.post("/", response_model=Block, operation_id="create_internal_block")
108
+ async def create_block(
109
+ create_block: CreateBlock = Body(...),
110
+ server: SyncServer = Depends(get_letta_server),
111
+ headers: HeaderParams = Depends(get_headers),
112
+ ):
113
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
114
+ block = Block(**create_block.model_dump())
115
+ return await server.block_manager.create_or_update_block_async(actor=actor, block=block)
116
+
117
+
118
+ @router.delete("/{block_id}", operation_id="delete_internal_block")
119
+ async def delete_block(
120
+ block_id: BlockId,
121
+ server: SyncServer = Depends(get_letta_server),
122
+ headers: HeaderParams = Depends(get_headers),
123
+ ):
124
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
125
+ await server.block_manager.delete_block_async(block_id=block_id, actor=actor)
126
+
127
+
128
+ @router.get("/{block_id}/agents", response_model=List[AgentState], operation_id="list_agents_for_internal_block")
129
+ async def list_agents_for_block(
130
+ block_id: BlockId,
131
+ before: Optional[str] = Query(
132
+ None,
133
+ description="Agent ID cursor for pagination. Returns agents that come before this agent ID in the specified sort order",
134
+ ),
135
+ after: Optional[str] = Query(
136
+ None,
137
+ description="Agent ID cursor for pagination. Returns agents that come after this agent ID in the specified sort order",
138
+ ),
139
+ limit: Optional[int] = Query(50, description="Maximum number of agents to return"),
140
+ order: Literal["asc", "desc"] = Query(
141
+ "desc", description="Sort order for agents by creation time. 'asc' for oldest first, 'desc' for newest first"
142
+ ),
143
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
144
+ include_relationships: list[str] | None = Query(
145
+ None,
146
+ description=(
147
+ "Specify which relational fields (e.g., 'tools', 'sources', 'memory') to include in the response. "
148
+ "If not provided, all relationships are loaded by default. "
149
+ "Using this can optimize performance by reducing unnecessary joins."
150
+ "This is a legacy parameter, and no longer supported after 1.0.0 SDK versions."
151
+ ),
152
+ ),
153
+ include: List[str] = Query(
154
+ [],
155
+ description=("Specify which relational fields to include in the response. No relationships are included by default."),
156
+ ),
157
+ server: SyncServer = Depends(get_letta_server),
158
+ headers: HeaderParams = Depends(get_headers),
159
+ ):
160
+ """
161
+ Retrieves all agents associated with the specified block.
162
+ Raises a 404 if the block does not exist.
163
+ """
164
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
165
+ if include_relationships is None and is_1_0_sdk_version(headers):
166
+ include_relationships = [] # don't default include all if using new SDK version
167
+ agents = await server.block_manager.get_agents_for_block_async(
168
+ block_id=block_id,
169
+ before=before,
170
+ after=after,
171
+ limit=limit,
172
+ ascending=(order == "asc"),
173
+ include_relationships=include_relationships,
174
+ include=include,
175
+ actor=actor,
176
+ )
177
+ return agents