letta-nightly 0.6.12.dev20250122104013__py3-none-any.whl → 0.6.14.dev20250123041709__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 (61) hide show
  1. letta/__init__.py +2 -2
  2. letta/agent.py +69 -100
  3. letta/chat_only_agent.py +1 -1
  4. letta/client/client.py +169 -149
  5. letta/constants.py +1 -8
  6. letta/data_sources/connectors.py +1 -1
  7. letta/functions/helpers.py +29 -4
  8. letta/functions/schema_generator.py +55 -0
  9. letta/llm_api/helpers.py +51 -1
  10. letta/memory.py +9 -7
  11. letta/orm/agent.py +2 -2
  12. letta/orm/block.py +3 -1
  13. letta/orm/custom_columns.py +5 -4
  14. letta/orm/enums.py +1 -0
  15. letta/orm/message.py +2 -2
  16. letta/orm/sqlalchemy_base.py +5 -0
  17. letta/schemas/agent.py +13 -13
  18. letta/schemas/block.py +2 -2
  19. letta/schemas/environment_variables.py +1 -1
  20. letta/schemas/job.py +1 -1
  21. letta/schemas/letta_base.py +6 -0
  22. letta/schemas/letta_message.py +6 -6
  23. letta/schemas/memory.py +3 -2
  24. letta/schemas/message.py +21 -13
  25. letta/schemas/passage.py +1 -1
  26. letta/schemas/source.py +4 -4
  27. letta/schemas/tool.py +38 -43
  28. letta/server/rest_api/app.py +1 -16
  29. letta/server/rest_api/routers/v1/agents.py +95 -118
  30. letta/server/rest_api/routers/v1/blocks.py +8 -46
  31. letta/server/rest_api/routers/v1/jobs.py +4 -4
  32. letta/server/rest_api/routers/v1/providers.py +2 -2
  33. letta/server/rest_api/routers/v1/runs.py +6 -6
  34. letta/server/rest_api/routers/v1/sources.py +8 -38
  35. letta/server/rest_api/routers/v1/tags.py +1 -1
  36. letta/server/rest_api/routers/v1/tools.py +6 -24
  37. letta/server/server.py +6 -6
  38. letta/services/agent_manager.py +43 -9
  39. letta/services/block_manager.py +3 -3
  40. letta/services/job_manager.py +5 -3
  41. letta/services/organization_manager.py +1 -1
  42. letta/services/passage_manager.py +3 -3
  43. letta/services/provider_manager.py +2 -2
  44. letta/services/sandbox_config_manager.py +2 -2
  45. letta/services/source_manager.py +3 -3
  46. letta/services/tool_execution_sandbox.py +3 -1
  47. letta/services/tool_manager.py +8 -3
  48. letta/services/user_manager.py +2 -2
  49. letta/settings.py +29 -0
  50. letta/system.py +2 -2
  51. {letta_nightly-0.6.12.dev20250122104013.dist-info → letta_nightly-0.6.14.dev20250123041709.dist-info}/METADATA +1 -1
  52. {letta_nightly-0.6.12.dev20250122104013.dist-info → letta_nightly-0.6.14.dev20250123041709.dist-info}/RECORD +55 -61
  53. letta/server/rest_api/routers/openai/__init__.py +0 -0
  54. letta/server/rest_api/routers/openai/assistants/__init__.py +0 -0
  55. letta/server/rest_api/routers/openai/assistants/assistants.py +0 -115
  56. letta/server/rest_api/routers/openai/assistants/schemas.py +0 -115
  57. letta/server/rest_api/routers/openai/chat_completions/__init__.py +0 -0
  58. letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +0 -120
  59. {letta_nightly-0.6.12.dev20250122104013.dist-info → letta_nightly-0.6.14.dev20250123041709.dist-info}/LICENSE +0 -0
  60. {letta_nightly-0.6.12.dev20250122104013.dist-info → letta_nightly-0.6.14.dev20250123041709.dist-info}/WHEEL +0 -0
  61. {letta_nightly-0.6.12.dev20250122104013.dist-info → letta_nightly-0.6.14.dev20250123041709.dist-info}/entry_points.txt +0 -0
letta/schemas/passage.py CHANGED
@@ -23,7 +23,7 @@ class PassageBase(OrmMetadataBase):
23
23
 
24
24
  # file association
25
25
  file_id: Optional[str] = Field(None, description="The unique identifier of the file associated with the passage.")
26
- metadata_: Optional[Dict] = Field({}, description="The metadata of the passage.")
26
+ metadata: Optional[Dict] = Field({}, description="The metadata of the passage.")
27
27
 
28
28
 
29
29
  class Passage(PassageBase):
letta/schemas/source.py CHANGED
@@ -24,7 +24,7 @@ class Source(BaseSource):
24
24
  name (str): The name of the source.
25
25
  embedding_config (EmbeddingConfig): The embedding configuration used by the source.
26
26
  user_id (str): The ID of the user that created the source.
27
- metadata_ (dict): Metadata associated with the source.
27
+ metadata (dict): Metadata associated with the source.
28
28
  description (str): The description of the source.
29
29
  """
30
30
 
@@ -33,7 +33,7 @@ class Source(BaseSource):
33
33
  description: Optional[str] = Field(None, description="The description of the source.")
34
34
  embedding_config: EmbeddingConfig = Field(..., description="The embedding configuration used by the source.")
35
35
  organization_id: Optional[str] = Field(None, description="The ID of the organization that created the source.")
36
- metadata_: Optional[dict] = Field(None, description="Metadata associated with the source.")
36
+ metadata: Optional[dict] = Field(None, description="Metadata associated with the source.")
37
37
 
38
38
  # metadata fields
39
39
  created_by_id: Optional[str] = Field(None, description="The id of the user that made this Tool.")
@@ -54,7 +54,7 @@ class SourceCreate(BaseSource):
54
54
 
55
55
  # optional
56
56
  description: Optional[str] = Field(None, description="The description of the source.")
57
- metadata_: Optional[dict] = Field(None, description="Metadata associated with the source.")
57
+ metadata: Optional[dict] = Field(None, description="Metadata associated with the source.")
58
58
 
59
59
 
60
60
  class SourceUpdate(BaseSource):
@@ -64,5 +64,5 @@ class SourceUpdate(BaseSource):
64
64
 
65
65
  name: Optional[str] = Field(None, description="The name of the source.")
66
66
  description: Optional[str] = Field(None, description="The description of the source.")
67
- metadata_: Optional[dict] = Field(None, description="Metadata associated with the source.")
67
+ metadata: Optional[dict] = Field(None, description="Metadata associated with the source.")
68
68
  embedding_config: Optional[EmbeddingConfig] = Field(None, description="The embedding configuration used by the source.")
letta/schemas/tool.py CHANGED
@@ -9,11 +9,14 @@ from letta.constants import (
9
9
  LETTA_MULTI_AGENT_TOOL_MODULE_NAME,
10
10
  )
11
11
  from letta.functions.functions import derive_openai_json_schema, get_json_schema_from_module
12
- from letta.functions.helpers import generate_composio_tool_wrapper, generate_langchain_tool_wrapper
13
- from letta.functions.schema_generator import generate_schema_from_args_schema_v2
12
+ from letta.functions.helpers import generate_composio_action_from_func_name, generate_composio_tool_wrapper, generate_langchain_tool_wrapper
13
+ from letta.functions.schema_generator import generate_schema_from_args_schema_v2, generate_tool_schema_for_composio
14
+ from letta.log import get_logger
14
15
  from letta.orm.enums import ToolType
15
16
  from letta.schemas.letta_base import LettaBase
16
17
 
18
+ logger = get_logger(__name__)
19
+
17
20
 
18
21
  class BaseTool(LettaBase):
19
22
  __id_prefix__ = "tool"
@@ -52,14 +55,16 @@ class Tool(BaseTool):
52
55
  last_updated_by_id: Optional[str] = Field(None, description="The id of the user that made this Tool.")
53
56
 
54
57
  @model_validator(mode="after")
55
- def populate_missing_fields(self):
58
+ def refresh_source_code_and_json_schema(self):
56
59
  """
57
- Populate missing fields: name, description, and json_schema.
60
+ Refresh name, description, source_code, and json_schema.
58
61
  """
59
62
  if self.tool_type == ToolType.CUSTOM:
60
63
  # If it's a custom tool, we need to ensure source_code is present
61
64
  if not self.source_code:
62
- raise ValueError(f"Custom tool with id={self.id} is missing source_code field.")
65
+ error_msg = f"Custom tool with id={self.id} is missing source_code field."
66
+ logger.error(error_msg)
67
+ raise ValueError(error_msg)
63
68
 
64
69
  # Always derive json_schema for freshest possible json_schema
65
70
  # TODO: Instead of checking the tag, we should having `COMPOSIO` as a specific ToolType
@@ -72,6 +77,24 @@ class Tool(BaseTool):
72
77
  elif self.tool_type in {ToolType.LETTA_MULTI_AGENT_CORE}:
73
78
  # If it's letta multi-agent tool, we also generate the json_schema on the fly here
74
79
  self.json_schema = get_json_schema_from_module(module_name=LETTA_MULTI_AGENT_TOOL_MODULE_NAME, function_name=self.name)
80
+ elif self.tool_type == ToolType.EXTERNAL_COMPOSIO:
81
+ # If it is a composio tool, we generate both the source code and json schema on the fly here
82
+ # TODO: This is brittle, need to think long term about how to improve this
83
+ try:
84
+ composio_action = generate_composio_action_from_func_name(self.name)
85
+ tool_create = ToolCreate.from_composio(composio_action)
86
+ self.source_code = tool_create.source_code
87
+ self.json_schema = tool_create.json_schema
88
+ self.description = tool_create.description
89
+ self.tags = tool_create.tags
90
+ except Exception as e:
91
+ logger.error(f"Encountered exception while attempting to refresh source_code and json_schema for composio_tool: {e}")
92
+
93
+ # At this point, we need to validate that at least json_schema is populated
94
+ if not self.json_schema:
95
+ error_msg = f"Tool with id={self.id} name={self.name} tool_type={self.tool_type} is missing a json_schema."
96
+ logger.error(error_msg)
97
+ raise ValueError(error_msg)
75
98
 
76
99
  # Derive name from the JSON schema if not provided
77
100
  if not self.name:
@@ -100,7 +123,7 @@ class ToolCreate(LettaBase):
100
123
  return_char_limit: int = Field(FUNCTION_RETURN_CHAR_LIMIT, description="The maximum number of characters in the response.")
101
124
 
102
125
  @classmethod
103
- def from_composio(cls, action_name: str, api_key: Optional[str] = None) -> "ToolCreate":
126
+ def from_composio(cls, action_name: str) -> "ToolCreate":
104
127
  """
105
128
  Class method to create an instance of Letta-compatible Composio Tool.
106
129
  Check https://docs.composio.dev/introduction/intro/overview to look at options for from_composio
@@ -115,24 +138,21 @@ class ToolCreate(LettaBase):
115
138
  from composio import LogLevel
116
139
  from composio_langchain import ComposioToolSet
117
140
 
118
- if api_key:
119
- # Pass in an external API key
120
- composio_toolset = ComposioToolSet(logging_level=LogLevel.ERROR, api_key=api_key)
121
- else:
122
- # Use environmental variable
123
- composio_toolset = ComposioToolSet(logging_level=LogLevel.ERROR)
124
- composio_tools = composio_toolset.get_tools(actions=[action_name])
141
+ composio_toolset = ComposioToolSet(logging_level=LogLevel.ERROR)
142
+ composio_action_schemas = composio_toolset.get_action_schemas(actions=[action_name], check_connected_accounts=False)
125
143
 
126
- assert len(composio_tools) > 0, "User supplied parameters do not match any Composio tools"
127
- assert len(composio_tools) == 1, f"User supplied parameters match too many Composio tools; {len(composio_tools)} > 1"
144
+ assert len(composio_action_schemas) > 0, "User supplied parameters do not match any Composio tools"
145
+ assert (
146
+ len(composio_action_schemas) == 1
147
+ ), f"User supplied parameters match too many Composio tools; {len(composio_action_schemas)} > 1"
128
148
 
129
- composio_tool = composio_tools[0]
149
+ composio_action_schema = composio_action_schemas[0]
130
150
 
131
- description = composio_tool.description
151
+ description = composio_action_schema.description
132
152
  source_type = "python"
133
153
  tags = [COMPOSIO_TOOL_TAG_NAME]
134
154
  wrapper_func_name, wrapper_function_str = generate_composio_tool_wrapper(action_name)
135
- json_schema = generate_schema_from_args_schema_v2(composio_tool.args_schema, name=wrapper_func_name, description=description)
155
+ json_schema = generate_tool_schema_for_composio(composio_action_schema.parameters, name=wrapper_func_name, description=description)
136
156
 
137
157
  return cls(
138
158
  name=wrapper_func_name,
@@ -175,31 +195,6 @@ class ToolCreate(LettaBase):
175
195
  json_schema=json_schema,
176
196
  )
177
197
 
178
- @classmethod
179
- def load_default_langchain_tools(cls) -> List["ToolCreate"]:
180
- # For now, we only support wikipedia tool
181
- from langchain_community.tools import WikipediaQueryRun
182
- from langchain_community.utilities import WikipediaAPIWrapper
183
-
184
- wikipedia_tool = ToolCreate.from_langchain(
185
- WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper()), {"langchain_community.utilities": "WikipediaAPIWrapper"}
186
- )
187
-
188
- return [wikipedia_tool]
189
-
190
- @classmethod
191
- def load_default_composio_tools(cls) -> List["ToolCreate"]:
192
- pass
193
-
194
- # TODO: Disable composio tools for now
195
- # TODO: Naming is causing issues
196
- # calculator = ToolCreate.from_composio(action_name=Action.MATHEMATICAL_CALCULATOR.name)
197
- # serp_news = ToolCreate.from_composio(action_name=Action.SERPAPI_NEWS_SEARCH.name)
198
- # serp_google_search = ToolCreate.from_composio(action_name=Action.SERPAPI_SEARCH.name)
199
- # serp_google_maps = ToolCreate.from_composio(action_name=Action.SERPAPI_GOOGLE_MAPS_SEARCH.name)
200
-
201
- return []
202
-
203
198
 
204
199
  class ToolUpdate(LettaBase):
205
200
  description: Optional[str] = Field(None, description="The description of the tool.")
@@ -22,8 +22,6 @@ from letta.server.constants import REST_DEFAULT_PORT
22
22
  # NOTE(charles): these are extra routes that are not part of v1 but we still need to mount to pass tests
23
23
  from letta.server.rest_api.auth.index import setup_auth_router # TODO: probably remove right?
24
24
  from letta.server.rest_api.interface import StreamingServerInterface
25
- from letta.server.rest_api.routers.openai.assistants.assistants import router as openai_assistants_router
26
- from letta.server.rest_api.routers.openai.chat_completions.chat_completions import router as openai_chat_completions_router
27
25
 
28
26
  # from letta.orm.utilities import get_db_session # TODO(ethan) reenable once we merge ORM
29
27
  from letta.server.rest_api.routers.v1 import ROUTERS as v1_routes
@@ -62,20 +60,11 @@ def generate_openapi_schema(app: FastAPI):
62
60
  if not app.openapi_schema:
63
61
  app.openapi_schema = app.openapi()
64
62
 
65
- openai_docs, letta_docs = [app.openapi_schema.copy() for _ in range(2)]
66
-
67
- openai_docs["paths"] = {k: v for k, v in openai_docs["paths"].items() if k.startswith("/openai")}
68
- openai_docs["info"]["title"] = "OpenAI Assistants API"
63
+ letta_docs = app.openapi_schema.copy()
69
64
  letta_docs["paths"] = {k: v for k, v in letta_docs["paths"].items() if not k.startswith("/openai")}
70
65
  letta_docs["info"]["title"] = "Letta API"
71
66
  letta_docs["components"]["schemas"]["LettaMessageUnion"] = create_letta_message_union_schema()
72
-
73
- # Split the API docs into Letta API, and OpenAI Assistants compatible API
74
67
  for name, docs in [
75
- (
76
- "openai",
77
- openai_docs,
78
- ),
79
68
  (
80
69
  "letta",
81
70
  letta_docs,
@@ -249,10 +238,6 @@ def create_application() -> "FastAPI":
249
238
  app.include_router(users_router, prefix=ADMIN_PREFIX)
250
239
  app.include_router(organizations_router, prefix=ADMIN_PREFIX)
251
240
 
252
- # openai
253
- app.include_router(openai_assistants_router, prefix=OPENAI_API_PREFIX)
254
- app.include_router(openai_chat_completions_router, prefix=OPENAI_API_PREFIX)
255
-
256
241
  # /api/auth endpoints
257
242
  app.include_router(setup_auth_router(server, interface, password), prefix=API_PREFIX)
258
243
 
@@ -14,7 +14,7 @@ from letta.schemas.job import JobStatus, JobUpdate
14
14
  from letta.schemas.letta_message import LettaMessageUnion
15
15
  from letta.schemas.letta_request import LettaRequest, LettaStreamingRequest
16
16
  from letta.schemas.letta_response import LettaResponse
17
- from letta.schemas.memory import ArchivalMemorySummary, ContextWindowOverview, CreateArchivalMemory, Memory, RecallMemorySummary
17
+ from letta.schemas.memory import ContextWindowOverview, CreateArchivalMemory, Memory
18
18
  from letta.schemas.message import Message, MessageUpdate
19
19
  from letta.schemas.passage import Passage
20
20
  from letta.schemas.run import Run
@@ -70,8 +70,8 @@ def list_agents(
70
70
  return agents
71
71
 
72
72
 
73
- @router.get("/{agent_id}/context", response_model=ContextWindowOverview, operation_id="get_agent_context_window")
74
- def get_agent_context_window(
73
+ @router.get("/{agent_id}/context", response_model=ContextWindowOverview, operation_id="retrieve_agent_context_window")
74
+ def retrieve_agent_context_window(
75
75
  agent_id: str,
76
76
  server: "SyncServer" = Depends(get_letta_server),
77
77
  user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
@@ -106,67 +106,87 @@ def create_agent(
106
106
  return server.create_agent(agent, actor=actor)
107
107
 
108
108
 
109
- @router.patch("/{agent_id}", response_model=AgentState, operation_id="update_agent")
110
- def update_agent(
109
+ @router.patch("/{agent_id}", response_model=AgentState, operation_id="modify_agent")
110
+ def modify_agent(
111
111
  agent_id: str,
112
112
  update_agent: UpdateAgent = Body(...),
113
113
  server: "SyncServer" = Depends(get_letta_server),
114
114
  user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
115
115
  ):
116
- """Update an exsiting agent"""
116
+ """Update an existing agent"""
117
117
  actor = server.user_manager.get_user_or_default(user_id=user_id)
118
118
  return server.agent_manager.update_agent(agent_id=agent_id, agent_update=update_agent, actor=actor)
119
119
 
120
120
 
121
- @router.get("/{agent_id}/tools", response_model=List[Tool], operation_id="get_tools_from_agent")
122
- def get_tools_from_agent(
121
+ @router.get("/{agent_id}/tools", response_model=List[Tool], operation_id="list_agent_tools")
122
+ def list_agent_tools(
123
123
  agent_id: str,
124
124
  server: "SyncServer" = Depends(get_letta_server),
125
125
  user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
126
126
  ):
127
127
  """Get tools from an existing agent"""
128
128
  actor = server.user_manager.get_user_or_default(user_id=user_id)
129
- return server.agent_manager.get_agent_by_id(agent_id=agent_id, actor=actor).tools
129
+ return server.agent_manager.list_attached_tools(agent_id=agent_id, actor=actor)
130
130
 
131
131
 
132
- @router.patch("/{agent_id}/add-tool/{tool_id}", response_model=AgentState, operation_id="add_tool_to_agent")
133
- def add_tool_to_agent(
132
+ @router.patch("/{agent_id}/tools/attach/{tool_id}", response_model=AgentState, operation_id="attach_tool")
133
+ def attach_tool(
134
134
  agent_id: str,
135
135
  tool_id: str,
136
136
  server: "SyncServer" = Depends(get_letta_server),
137
- user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
137
+ user_id: Optional[str] = Header(None, alias="user_id"),
138
138
  ):
139
- """Add tools to an existing agent"""
139
+ """
140
+ Attach a tool to an agent.
141
+ """
140
142
  actor = server.user_manager.get_user_or_default(user_id=user_id)
141
143
  return server.agent_manager.attach_tool(agent_id=agent_id, tool_id=tool_id, actor=actor)
142
144
 
143
145
 
144
- @router.patch("/{agent_id}/remove-tool/{tool_id}", response_model=AgentState, operation_id="remove_tool_from_agent")
145
- def remove_tool_from_agent(
146
+ @router.patch("/{agent_id}/tools/detach/{tool_id}", response_model=AgentState, operation_id="detach_tool")
147
+ def detach_tool(
146
148
  agent_id: str,
147
149
  tool_id: str,
148
150
  server: "SyncServer" = Depends(get_letta_server),
149
- user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
151
+ user_id: Optional[str] = Header(None, alias="user_id"),
150
152
  ):
151
- """Add tools to an existing agent"""
153
+ """
154
+ Detach a tool from an agent.
155
+ """
152
156
  actor = server.user_manager.get_user_or_default(user_id=user_id)
153
157
  return server.agent_manager.detach_tool(agent_id=agent_id, tool_id=tool_id, actor=actor)
154
158
 
155
159
 
156
- @router.patch("/{agent_id}/reset-messages", response_model=AgentState, operation_id="reset_messages")
157
- def reset_messages(
160
+ @router.patch("/{agent_id}/sources/attach/{source_id}", response_model=AgentState, operation_id="attach_source_to_agent")
161
+ def attach_source(
158
162
  agent_id: str,
159
- add_default_initial_messages: bool = Query(default=False, description="If true, adds the default initial messages after resetting."),
163
+ source_id: str,
160
164
  server: "SyncServer" = Depends(get_letta_server),
161
- user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
165
+ user_id: Optional[str] = Header(None, alias="user_id"),
162
166
  ):
163
- """Resets the messages for an agent"""
167
+ """
168
+ Attach a source to an agent.
169
+ """
164
170
  actor = server.user_manager.get_user_or_default(user_id=user_id)
165
- return server.agent_manager.reset_messages(agent_id=agent_id, actor=actor, add_default_initial_messages=add_default_initial_messages)
171
+ return server.agent_manager.attach_source(agent_id=agent_id, source_id=source_id, actor=actor)
172
+
173
+
174
+ @router.patch("/{agent_id}/sources/detach/{source_id}", response_model=AgentState, operation_id="detach_source_from_agent")
175
+ def detach_source(
176
+ agent_id: str,
177
+ source_id: str,
178
+ server: "SyncServer" = Depends(get_letta_server),
179
+ user_id: Optional[str] = Header(None, alias="user_id"),
180
+ ):
181
+ """
182
+ Detach a source from an agent.
183
+ """
184
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
185
+ return server.agent_manager.detach_source(agent_id=agent_id, source_id=source_id, actor=actor)
166
186
 
167
187
 
168
- @router.get("/{agent_id}", response_model=AgentState, operation_id="get_agent")
169
- def get_agent_state(
188
+ @router.get("/{agent_id}", response_model=AgentState, operation_id="retrieve_agent")
189
+ def retrieve_agent(
170
190
  agent_id: str,
171
191
  server: "SyncServer" = Depends(get_letta_server),
172
192
  user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
@@ -199,8 +219,8 @@ def delete_agent(
199
219
  raise HTTPException(status_code=404, detail=f"Agent agent_id={agent_id} not found for user_id={actor.id}.")
200
220
 
201
221
 
202
- @router.get("/{agent_id}/sources", response_model=List[Source], operation_id="get_agent_sources")
203
- def get_agent_sources(
222
+ @router.get("/{agent_id}/sources", response_model=List[Source], operation_id="list_agent_sources")
223
+ def list_agent_sources(
204
224
  agent_id: str,
205
225
  server: "SyncServer" = Depends(get_letta_server),
206
226
  user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
@@ -212,22 +232,9 @@ def get_agent_sources(
212
232
  return server.agent_manager.list_attached_sources(agent_id=agent_id, actor=actor)
213
233
 
214
234
 
215
- @router.get("/{agent_id}/memory/messages", response_model=List[Message], operation_id="list_agent_in_context_messages")
216
- def get_agent_in_context_messages(
217
- agent_id: str,
218
- server: "SyncServer" = Depends(get_letta_server),
219
- user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
220
- ):
221
- """
222
- Retrieve the messages in the context of a specific agent.
223
- """
224
- actor = server.user_manager.get_user_or_default(user_id=user_id)
225
- return server.agent_manager.get_in_context_messages(agent_id=agent_id, actor=actor)
226
-
227
-
228
235
  # TODO: remove? can also get with agent blocks
229
- @router.get("/{agent_id}/memory", response_model=Memory, operation_id="get_agent_memory")
230
- def get_agent_memory(
236
+ @router.get("/{agent_id}/core-memory", response_model=Memory, operation_id="retrieve_agent_memory")
237
+ def retrieve_agent_memory(
231
238
  agent_id: str,
232
239
  server: "SyncServer" = Depends(get_letta_server),
233
240
  user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
@@ -241,8 +248,8 @@ def get_agent_memory(
241
248
  return server.get_agent_memory(agent_id=agent_id, actor=actor)
242
249
 
243
250
 
244
- @router.get("/{agent_id}/memory/block/{block_label}", response_model=Block, operation_id="get_agent_memory_block")
245
- def get_agent_memory_block(
251
+ @router.get("/{agent_id}/core-memory/blocks/{block_label}", response_model=Block, operation_id="retrieve_core_memory_block")
252
+ def retrieve_core_memory_block(
246
253
  agent_id: str,
247
254
  block_label: str,
248
255
  server: "SyncServer" = Depends(get_letta_server),
@@ -259,8 +266,8 @@ def get_agent_memory_block(
259
266
  raise HTTPException(status_code=404, detail=str(e))
260
267
 
261
268
 
262
- @router.get("/{agent_id}/memory/block", response_model=List[Block], operation_id="get_agent_memory_blocks")
263
- def get_agent_memory_blocks(
269
+ @router.get("/{agent_id}/core-memory/blocks", response_model=List[Block], operation_id="list_core_memory_blocks")
270
+ def list_core_memory_blocks(
264
271
  agent_id: str,
265
272
  server: "SyncServer" = Depends(get_letta_server),
266
273
  user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
@@ -276,51 +283,8 @@ def get_agent_memory_blocks(
276
283
  raise HTTPException(status_code=404, detail=str(e))
277
284
 
278
285
 
279
- @router.post("/{agent_id}/memory/block", response_model=Memory, operation_id="add_agent_memory_block")
280
- def add_agent_memory_block(
281
- agent_id: str,
282
- create_block: CreateBlock = Body(...),
283
- server: "SyncServer" = Depends(get_letta_server),
284
- user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
285
- ):
286
- """
287
- Creates a memory block and links it to the agent.
288
- """
289
- actor = server.user_manager.get_user_or_default(user_id=user_id)
290
-
291
- # Copied from POST /blocks
292
- # TODO: Should have block_manager accept only CreateBlock
293
- # TODO: This will be possible once we move ID creation to the ORM
294
- block_req = Block(**create_block.model_dump())
295
- block = server.block_manager.create_or_update_block(actor=actor, block=block_req)
296
-
297
- # Link the block to the agent
298
- agent = server.agent_manager.attach_block(agent_id=agent_id, block_id=block.id, actor=actor)
299
- return agent.memory
300
-
301
-
302
- @router.delete("/{agent_id}/memory/block/{block_label}", response_model=Memory, operation_id="remove_agent_memory_block_by_label")
303
- def remove_agent_memory_block(
304
- agent_id: str,
305
- # TODO should this be block_id, or the label?
306
- # I think label is OK since it's user-friendly + guaranteed to be unique within a Memory object
307
- block_label: str,
308
- server: "SyncServer" = Depends(get_letta_server),
309
- user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
310
- ):
311
- """
312
- Removes a memory block from an agent by unlnking it. If the block is not linked to any other agent, it is deleted.
313
- """
314
- actor = server.user_manager.get_user_or_default(user_id=user_id)
315
-
316
- # Unlink the block from the agent
317
- agent = server.agent_manager.detach_block_with_label(agent_id=agent_id, block_label=block_label, actor=actor)
318
-
319
- return agent.memory
320
-
321
-
322
- @router.patch("/{agent_id}/memory/block/{block_label}", response_model=Block, operation_id="update_agent_memory_block_by_label")
323
- def update_agent_memory_block(
286
+ @router.patch("/{agent_id}/core-memory/blocks/{block_label}", response_model=Block, operation_id="modify_core_memory_block")
287
+ def modify_core_memory_block(
324
288
  agent_id: str,
325
289
  block_label: str,
326
290
  block_update: BlockUpdate = Body(...),
@@ -328,7 +292,7 @@ def update_agent_memory_block(
328
292
  user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
329
293
  ):
330
294
  """
331
- Removes a memory block from an agent by unlnking it. If the block is not linked to any other agent, it is deleted.
295
+ Updates a memory block of an agent.
332
296
  """
333
297
  actor = server.user_manager.get_user_or_default(user_id=user_id)
334
298
 
@@ -341,35 +305,36 @@ def update_agent_memory_block(
341
305
  return block
342
306
 
343
307
 
344
- @router.get("/{agent_id}/memory/recall", response_model=RecallMemorySummary, operation_id="get_agent_recall_memory_summary")
345
- def get_agent_recall_memory_summary(
308
+ @router.patch("/{agent_id}/core-memory/blocks/attach/{block_id}", response_model=AgentState, operation_id="attach_core_memory_block")
309
+ def attach_core_memory_block(
346
310
  agent_id: str,
311
+ block_id: str,
347
312
  server: "SyncServer" = Depends(get_letta_server),
348
- user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
313
+ user_id: Optional[str] = Header(None, alias="user_id"),
349
314
  ):
350
315
  """
351
- Retrieve the summary of the recall memory of a specific agent.
316
+ Attach a block to an agent.
352
317
  """
353
318
  actor = server.user_manager.get_user_or_default(user_id=user_id)
319
+ return server.agent_manager.attach_block(agent_id=agent_id, block_id=block_id, actor=actor)
354
320
 
355
- return server.get_recall_memory_summary(agent_id=agent_id, actor=actor)
356
321
 
357
-
358
- @router.get("/{agent_id}/memory/archival", response_model=ArchivalMemorySummary, operation_id="get_agent_archival_memory_summary")
359
- def get_agent_archival_memory_summary(
322
+ @router.patch("/{agent_id}/core-memory/blocks/detach/{block_id}", response_model=AgentState, operation_id="detach_core_memory_block")
323
+ def detach_core_memory_block(
360
324
  agent_id: str,
325
+ block_id: str,
361
326
  server: "SyncServer" = Depends(get_letta_server),
362
- user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
327
+ user_id: Optional[str] = Header(None, alias="user_id"),
363
328
  ):
364
329
  """
365
- Retrieve the summary of the archival memory of a specific agent.
330
+ Detach a block from an agent.
366
331
  """
367
332
  actor = server.user_manager.get_user_or_default(user_id=user_id)
368
- return server.get_archival_memory_summary(agent_id=agent_id, actor=actor)
333
+ return server.agent_manager.detach_block(agent_id=agent_id, block_id=block_id, actor=actor)
369
334
 
370
335
 
371
- @router.get("/{agent_id}/archival", response_model=List[Passage], operation_id="list_agent_archival_memory")
372
- def get_agent_archival_memory(
336
+ @router.get("/{agent_id}/archival-memory", response_model=List[Passage], operation_id="list_archival_memory")
337
+ def list_archival_memory(
373
338
  agent_id: str,
374
339
  server: "SyncServer" = Depends(get_letta_server),
375
340
  after: Optional[int] = Query(None, description="Unique ID of the memory to start the query range at."),
@@ -394,8 +359,8 @@ def get_agent_archival_memory(
394
359
  )
395
360
 
396
361
 
397
- @router.post("/{agent_id}/archival", response_model=List[Passage], operation_id="create_agent_archival_memory")
398
- def insert_agent_archival_memory(
362
+ @router.post("/{agent_id}/archival-memory", response_model=List[Passage], operation_id="create_archival_memory")
363
+ def create_archival_memory(
399
364
  agent_id: str,
400
365
  request: CreateArchivalMemory = Body(...),
401
366
  server: "SyncServer" = Depends(get_letta_server),
@@ -411,8 +376,8 @@ def insert_agent_archival_memory(
411
376
 
412
377
  # TODO(ethan): query or path parameter for memory_id?
413
378
  # @router.delete("/{agent_id}/archival")
414
- @router.delete("/{agent_id}/archival/{memory_id}", response_model=None, operation_id="delete_agent_archival_memory")
415
- def delete_agent_archival_memory(
379
+ @router.delete("/{agent_id}/archival-memory/{memory_id}", response_model=None, operation_id="delete_archival_memory")
380
+ def delete_archival_memory(
416
381
  agent_id: str,
417
382
  memory_id: str,
418
383
  # memory_id: str = Query(..., description="Unique ID of the memory to be deleted."),
@@ -433,7 +398,7 @@ AgentMessagesResponse = Annotated[
433
398
  Field(
434
399
  json_schema_extra={
435
400
  "anyOf": [
436
- {"type": "array", "items": {"$ref": "#/components/schemas/letta__schemas__message__Message"}},
401
+ {"type": "array", "items": {"$ref": "#/components/schemas/Message"}},
437
402
  {"type": "array", "items": {"$ref": "#/components/schemas/LettaMessageUnion"}},
438
403
  ]
439
404
  }
@@ -441,8 +406,8 @@ AgentMessagesResponse = Annotated[
441
406
  ]
442
407
 
443
408
 
444
- @router.get("/{agent_id}/messages", response_model=AgentMessagesResponse, operation_id="list_agent_messages")
445
- def get_agent_messages(
409
+ @router.get("/{agent_id}/messages", response_model=AgentMessagesResponse, operation_id="list_messages")
410
+ def list_messages(
446
411
  agent_id: str,
447
412
  server: "SyncServer" = Depends(get_letta_server),
448
413
  before: Optional[str] = Query(None, description="Message before which to retrieve the returned messages."),
@@ -476,8 +441,8 @@ def get_agent_messages(
476
441
  )
477
442
 
478
443
 
479
- @router.patch("/{agent_id}/messages/{message_id}", response_model=Message, operation_id="update_agent_message")
480
- def update_message(
444
+ @router.patch("/{agent_id}/messages/{message_id}", response_model=Message, operation_id="modify_message")
445
+ def modify_message(
481
446
  agent_id: str,
482
447
  message_id: str,
483
448
  request: MessageUpdate = Body(...),
@@ -495,7 +460,7 @@ def update_message(
495
460
  @router.post(
496
461
  "/{agent_id}/messages",
497
462
  response_model=LettaResponse,
498
- operation_id="create_agent_message",
463
+ operation_id="send_message",
499
464
  )
500
465
  async def send_message(
501
466
  agent_id: str,
@@ -591,7 +556,7 @@ async def process_message_background(
591
556
  job_update = JobUpdate(
592
557
  status=JobStatus.completed,
593
558
  completed_at=datetime.utcnow(),
594
- metadata_={"result": result.model_dump()}, # Store the result in metadata
559
+ metadata={"result": result.model_dump()}, # Store the result in metadata
595
560
  )
596
561
  server.job_manager.update_job_by_id(job_id=job_id, job_update=job_update, actor=actor)
597
562
 
@@ -603,7 +568,7 @@ async def process_message_background(
603
568
  job_update = JobUpdate(
604
569
  status=JobStatus.failed,
605
570
  completed_at=datetime.utcnow(),
606
- metadata_={"error": str(e)},
571
+ metadata={"error": str(e)},
607
572
  )
608
573
  server.job_manager.update_job_by_id(job_id=job_id, job_update=job_update, actor=actor)
609
574
  raise
@@ -631,7 +596,7 @@ async def send_message_async(
631
596
  run = Run(
632
597
  user_id=actor.id,
633
598
  status=JobStatus.created,
634
- metadata_={
599
+ metadata={
635
600
  "job_type": "send_message_async",
636
601
  "agent_id": agent_id,
637
602
  },
@@ -653,3 +618,15 @@ async def send_message_async(
653
618
  )
654
619
 
655
620
  return run
621
+
622
+
623
+ @router.patch("/{agent_id}/reset-messages", response_model=AgentState, operation_id="reset_messages")
624
+ def reset_messages(
625
+ agent_id: str,
626
+ add_default_initial_messages: bool = Query(default=False, description="If true, adds the default initial messages after resetting."),
627
+ server: "SyncServer" = Depends(get_letta_server),
628
+ user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
629
+ ):
630
+ """Resets the messages for an agent"""
631
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
632
+ return server.agent_manager.reset_messages(agent_id=agent_id, actor=actor, add_default_initial_messages=add_default_initial_messages)