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.
- letta/__init__.py +2 -2
- letta/agent.py +69 -100
- letta/chat_only_agent.py +1 -1
- letta/client/client.py +169 -149
- letta/constants.py +1 -8
- letta/data_sources/connectors.py +1 -1
- letta/functions/helpers.py +29 -4
- letta/functions/schema_generator.py +55 -0
- letta/llm_api/helpers.py +51 -1
- letta/memory.py +9 -7
- letta/orm/agent.py +2 -2
- letta/orm/block.py +3 -1
- letta/orm/custom_columns.py +5 -4
- letta/orm/enums.py +1 -0
- letta/orm/message.py +2 -2
- letta/orm/sqlalchemy_base.py +5 -0
- letta/schemas/agent.py +13 -13
- letta/schemas/block.py +2 -2
- letta/schemas/environment_variables.py +1 -1
- letta/schemas/job.py +1 -1
- letta/schemas/letta_base.py +6 -0
- letta/schemas/letta_message.py +6 -6
- letta/schemas/memory.py +3 -2
- letta/schemas/message.py +21 -13
- letta/schemas/passage.py +1 -1
- letta/schemas/source.py +4 -4
- letta/schemas/tool.py +38 -43
- letta/server/rest_api/app.py +1 -16
- letta/server/rest_api/routers/v1/agents.py +95 -118
- letta/server/rest_api/routers/v1/blocks.py +8 -46
- letta/server/rest_api/routers/v1/jobs.py +4 -4
- letta/server/rest_api/routers/v1/providers.py +2 -2
- letta/server/rest_api/routers/v1/runs.py +6 -6
- letta/server/rest_api/routers/v1/sources.py +8 -38
- letta/server/rest_api/routers/v1/tags.py +1 -1
- letta/server/rest_api/routers/v1/tools.py +6 -24
- letta/server/server.py +6 -6
- letta/services/agent_manager.py +43 -9
- letta/services/block_manager.py +3 -3
- letta/services/job_manager.py +5 -3
- letta/services/organization_manager.py +1 -1
- letta/services/passage_manager.py +3 -3
- letta/services/provider_manager.py +2 -2
- letta/services/sandbox_config_manager.py +2 -2
- letta/services/source_manager.py +3 -3
- letta/services/tool_execution_sandbox.py +3 -1
- letta/services/tool_manager.py +8 -3
- letta/services/user_manager.py +2 -2
- letta/settings.py +29 -0
- letta/system.py +2 -2
- {letta_nightly-0.6.12.dev20250122104013.dist-info → letta_nightly-0.6.14.dev20250123041709.dist-info}/METADATA +1 -1
- {letta_nightly-0.6.12.dev20250122104013.dist-info → letta_nightly-0.6.14.dev20250123041709.dist-info}/RECORD +55 -61
- letta/server/rest_api/routers/openai/__init__.py +0 -0
- letta/server/rest_api/routers/openai/assistants/__init__.py +0 -0
- letta/server/rest_api/routers/openai/assistants/assistants.py +0 -115
- letta/server/rest_api/routers/openai/assistants/schemas.py +0 -115
- letta/server/rest_api/routers/openai/chat_completions/__init__.py +0 -0
- letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +0 -120
- {letta_nightly-0.6.12.dev20250122104013.dist-info → letta_nightly-0.6.14.dev20250123041709.dist-info}/LICENSE +0 -0
- {letta_nightly-0.6.12.dev20250122104013.dist-info → letta_nightly-0.6.14.dev20250123041709.dist-info}/WHEEL +0 -0
- {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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
58
|
+
def refresh_source_code_and_json_schema(self):
|
|
56
59
|
"""
|
|
57
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
119
|
-
|
|
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(
|
|
127
|
-
assert
|
|
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
|
-
|
|
149
|
+
composio_action_schema = composio_action_schemas[0]
|
|
130
150
|
|
|
131
|
-
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 =
|
|
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.")
|
letta/server/rest_api/app.py
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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="
|
|
74
|
-
def
|
|
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="
|
|
110
|
-
def
|
|
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
|
|
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="
|
|
122
|
-
def
|
|
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.
|
|
129
|
+
return server.agent_manager.list_attached_tools(agent_id=agent_id, actor=actor)
|
|
130
130
|
|
|
131
131
|
|
|
132
|
-
@router.patch("/{agent_id}/
|
|
133
|
-
def
|
|
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"),
|
|
137
|
+
user_id: Optional[str] = Header(None, alias="user_id"),
|
|
138
138
|
):
|
|
139
|
-
"""
|
|
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}/
|
|
145
|
-
def
|
|
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"),
|
|
151
|
+
user_id: Optional[str] = Header(None, alias="user_id"),
|
|
150
152
|
):
|
|
151
|
-
"""
|
|
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}/
|
|
157
|
-
def
|
|
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
|
-
|
|
163
|
+
source_id: str,
|
|
160
164
|
server: "SyncServer" = Depends(get_letta_server),
|
|
161
|
-
user_id: Optional[str] = Header(None, alias="user_id"),
|
|
165
|
+
user_id: Optional[str] = Header(None, alias="user_id"),
|
|
162
166
|
):
|
|
163
|
-
"""
|
|
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.
|
|
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="
|
|
169
|
-
def
|
|
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="
|
|
203
|
-
def
|
|
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="
|
|
230
|
-
def
|
|
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/
|
|
245
|
-
def
|
|
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/
|
|
263
|
-
def
|
|
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.
|
|
280
|
-
def
|
|
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
|
-
|
|
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.
|
|
345
|
-
def
|
|
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"),
|
|
313
|
+
user_id: Optional[str] = Header(None, alias="user_id"),
|
|
349
314
|
):
|
|
350
315
|
"""
|
|
351
|
-
|
|
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
|
-
|
|
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"),
|
|
327
|
+
user_id: Optional[str] = Header(None, alias="user_id"),
|
|
363
328
|
):
|
|
364
329
|
"""
|
|
365
|
-
|
|
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.
|
|
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="
|
|
372
|
-
def
|
|
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="
|
|
398
|
-
def
|
|
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="
|
|
415
|
-
def
|
|
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/
|
|
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="
|
|
445
|
-
def
|
|
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="
|
|
480
|
-
def
|
|
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="
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|