letta-nightly 0.6.4.dev20241213193437__py3-none-any.whl → 0.6.4.dev20241215104129__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 +1 -1
- letta/agent.py +54 -45
- letta/chat_only_agent.py +6 -8
- letta/cli/cli.py +2 -10
- letta/client/client.py +121 -138
- letta/config.py +0 -161
- letta/main.py +3 -8
- letta/memory.py +3 -14
- letta/o1_agent.py +1 -5
- letta/offline_memory_agent.py +2 -6
- letta/orm/__init__.py +2 -0
- letta/orm/agent.py +109 -0
- letta/orm/agents_tags.py +10 -18
- letta/orm/block.py +29 -4
- letta/orm/blocks_agents.py +5 -11
- letta/orm/custom_columns.py +152 -0
- letta/orm/message.py +3 -38
- letta/orm/organization.py +2 -7
- letta/orm/passage.py +10 -32
- letta/orm/source.py +5 -25
- letta/orm/sources_agents.py +13 -0
- letta/orm/sqlalchemy_base.py +54 -30
- letta/orm/tool.py +1 -19
- letta/orm/tools_agents.py +7 -24
- letta/orm/user.py +3 -4
- letta/schemas/agent.py +48 -65
- letta/schemas/memory.py +2 -1
- letta/schemas/sandbox_config.py +12 -1
- letta/server/rest_api/app.py +0 -5
- letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +1 -1
- letta/server/rest_api/routers/v1/agents.py +99 -78
- letta/server/rest_api/routers/v1/blocks.py +22 -25
- letta/server/rest_api/routers/v1/jobs.py +4 -4
- letta/server/rest_api/routers/v1/sandbox_configs.py +10 -10
- letta/server/rest_api/routers/v1/sources.py +12 -12
- letta/server/rest_api/routers/v1/tools.py +35 -15
- letta/server/rest_api/routers/v1/users.py +0 -46
- letta/server/server.py +172 -716
- letta/server/ws_api/server.py +0 -5
- letta/services/agent_manager.py +405 -0
- letta/services/block_manager.py +13 -21
- letta/services/helpers/agent_manager_helper.py +90 -0
- letta/services/organization_manager.py +0 -1
- letta/services/passage_manager.py +62 -62
- letta/services/sandbox_config_manager.py +3 -3
- letta/services/source_manager.py +22 -1
- letta/services/user_manager.py +11 -6
- letta/utils.py +2 -2
- {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241215104129.dist-info}/METADATA +1 -1
- {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241215104129.dist-info}/RECORD +53 -57
- letta/metadata.py +0 -407
- letta/schemas/agents_tags.py +0 -33
- letta/schemas/api_key.py +0 -21
- letta/schemas/blocks_agents.py +0 -32
- letta/schemas/tools_agents.py +0 -32
- letta/server/rest_api/routers/openai/assistants/threads.py +0 -338
- letta/services/agents_tags_manager.py +0 -64
- letta/services/blocks_agents_manager.py +0 -106
- letta/services/tools_agents_manager.py +0 -94
- {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241215104129.dist-info}/LICENSE +0 -0
- {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241215104129.dist-info}/WHEEL +0 -0
- {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241215104129.dist-info}/entry_points.txt +0 -0
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
import uuid
|
|
2
|
-
from typing import TYPE_CHECKING, List, Optional
|
|
3
|
-
|
|
4
|
-
from fastapi import APIRouter, Body, Depends, Header, HTTPException, Path, Query
|
|
5
|
-
|
|
6
|
-
from letta.constants import DEFAULT_PRESET
|
|
7
|
-
from letta.schemas.agent import CreateAgent
|
|
8
|
-
from letta.schemas.enums import MessageRole
|
|
9
|
-
from letta.schemas.message import Message
|
|
10
|
-
from letta.schemas.openai.openai import (
|
|
11
|
-
MessageFile,
|
|
12
|
-
OpenAIMessage,
|
|
13
|
-
OpenAIRun,
|
|
14
|
-
OpenAIRunStep,
|
|
15
|
-
OpenAIThread,
|
|
16
|
-
Text,
|
|
17
|
-
)
|
|
18
|
-
from letta.server.rest_api.routers.openai.assistants.schemas import (
|
|
19
|
-
CreateMessageRequest,
|
|
20
|
-
CreateRunRequest,
|
|
21
|
-
CreateThreadRequest,
|
|
22
|
-
CreateThreadRunRequest,
|
|
23
|
-
DeleteThreadResponse,
|
|
24
|
-
ListMessagesResponse,
|
|
25
|
-
ModifyMessageRequest,
|
|
26
|
-
ModifyRunRequest,
|
|
27
|
-
ModifyThreadRequest,
|
|
28
|
-
OpenAIThread,
|
|
29
|
-
SubmitToolOutputsToRunRequest,
|
|
30
|
-
)
|
|
31
|
-
from letta.server.rest_api.utils import get_letta_server
|
|
32
|
-
from letta.server.server import SyncServer
|
|
33
|
-
|
|
34
|
-
if TYPE_CHECKING:
|
|
35
|
-
from letta.utils import get_utc_time
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
# TODO: implement mechanism for creating/authenticating users associated with a bearer token
|
|
39
|
-
router = APIRouter(prefix="/v1/threads", tags=["threads"])
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@router.post("/", response_model=OpenAIThread)
|
|
43
|
-
def create_thread(
|
|
44
|
-
request: CreateThreadRequest = Body(...),
|
|
45
|
-
server: SyncServer = Depends(get_letta_server),
|
|
46
|
-
user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
|
47
|
-
):
|
|
48
|
-
# TODO: use requests.description and requests.metadata fields
|
|
49
|
-
# TODO: handle requests.file_ids and requests.tools
|
|
50
|
-
# TODO: eventually allow request to override embedding/llm model
|
|
51
|
-
actor = server.get_user_or_default(user_id=user_id)
|
|
52
|
-
|
|
53
|
-
print("Create thread/agent", request)
|
|
54
|
-
# create a letta agent
|
|
55
|
-
agent_state = server.create_agent(
|
|
56
|
-
request=CreateAgent(),
|
|
57
|
-
user_id=actor.id,
|
|
58
|
-
)
|
|
59
|
-
# TODO: insert messages into recall memory
|
|
60
|
-
return OpenAIThread(
|
|
61
|
-
id=str(agent_state.id),
|
|
62
|
-
created_at=int(agent_state.created_at.timestamp()),
|
|
63
|
-
metadata={}, # TODO add metadata?
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@router.get("/{thread_id}", response_model=OpenAIThread)
|
|
68
|
-
def retrieve_thread(
|
|
69
|
-
thread_id: str = Path(..., description="The unique identifier of the thread."),
|
|
70
|
-
server: SyncServer = Depends(get_letta_server),
|
|
71
|
-
user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
|
72
|
-
):
|
|
73
|
-
actor = server.get_user_or_default(user_id=user_id)
|
|
74
|
-
agent = server.get_agent(user_id=actor.id, agent_id=thread_id)
|
|
75
|
-
assert agent is not None
|
|
76
|
-
return OpenAIThread(
|
|
77
|
-
id=str(agent.id),
|
|
78
|
-
created_at=int(agent.created_at.timestamp()),
|
|
79
|
-
metadata={}, # TODO add metadata?
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
@router.get("/{thread_id}", response_model=OpenAIThread)
|
|
84
|
-
def modify_thread(
|
|
85
|
-
thread_id: str = Path(..., description="The unique identifier of the thread."),
|
|
86
|
-
request: ModifyThreadRequest = Body(...),
|
|
87
|
-
):
|
|
88
|
-
# TODO: add agent metadata so this can be modified
|
|
89
|
-
raise HTTPException(status_code=404, detail="Not yet implemented (coming soon)")
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
@router.delete("/{thread_id}", response_model=DeleteThreadResponse)
|
|
93
|
-
def delete_thread(
|
|
94
|
-
thread_id: str = Path(..., description="The unique identifier of the thread."),
|
|
95
|
-
):
|
|
96
|
-
# TODO: delete agent
|
|
97
|
-
raise HTTPException(status_code=404, detail="Not yet implemented (coming soon)")
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
@router.post("/{thread_id}/messages", tags=["messages"], response_model=OpenAIMessage)
|
|
101
|
-
def create_message(
|
|
102
|
-
thread_id: str = Path(..., description="The unique identifier of the thread."),
|
|
103
|
-
request: CreateMessageRequest = Body(...),
|
|
104
|
-
server: SyncServer = Depends(get_letta_server),
|
|
105
|
-
user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
|
106
|
-
):
|
|
107
|
-
actor = server.get_user_or_default(user_id=user_id)
|
|
108
|
-
agent_id = thread_id
|
|
109
|
-
# create message object
|
|
110
|
-
message = Message(
|
|
111
|
-
user_id=actor.id,
|
|
112
|
-
agent_id=agent_id,
|
|
113
|
-
role=MessageRole(request.role),
|
|
114
|
-
text=request.content,
|
|
115
|
-
model=None,
|
|
116
|
-
tool_calls=None,
|
|
117
|
-
tool_call_id=None,
|
|
118
|
-
name=None,
|
|
119
|
-
)
|
|
120
|
-
agent = server.load_agent(agent_id=agent_id)
|
|
121
|
-
# add message to agent
|
|
122
|
-
agent._append_to_messages([message])
|
|
123
|
-
|
|
124
|
-
openai_message = OpenAIMessage(
|
|
125
|
-
id=str(message.id),
|
|
126
|
-
created_at=int(message.created_at.timestamp()),
|
|
127
|
-
content=[Text(text=(message.text if message.text else ""))],
|
|
128
|
-
role=message.role,
|
|
129
|
-
thread_id=str(message.agent_id),
|
|
130
|
-
assistant_id=DEFAULT_PRESET, # TODO: update this
|
|
131
|
-
# TODO(sarah) fill in?
|
|
132
|
-
run_id=None,
|
|
133
|
-
file_ids=None,
|
|
134
|
-
metadata=None,
|
|
135
|
-
# file_ids=message.file_ids,
|
|
136
|
-
# metadata=message.metadata,
|
|
137
|
-
)
|
|
138
|
-
return openai_message
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
@router.get("/{thread_id}/messages", tags=["messages"], response_model=ListMessagesResponse)
|
|
142
|
-
def list_messages(
|
|
143
|
-
thread_id: str = Path(..., description="The unique identifier of the thread."),
|
|
144
|
-
limit: int = Query(1000, description="How many messages to retrieve."),
|
|
145
|
-
order: str = Query("asc", description="Order of messages to retrieve (either 'asc' or 'desc')."),
|
|
146
|
-
after: str = Query(None, description="A cursor for use in pagination. `after` is an object ID that defines your place in the list."),
|
|
147
|
-
before: str = Query(None, description="A cursor for use in pagination. `after` is an object ID that defines your place in the list."),
|
|
148
|
-
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
|
|
150
|
-
):
|
|
151
|
-
actor = server.get_user_or_default(user_id)
|
|
152
|
-
after_uuid = after if before else None
|
|
153
|
-
before_uuid = before if before else None
|
|
154
|
-
agent_id = thread_id
|
|
155
|
-
reverse = True if (order == "desc") else False
|
|
156
|
-
json_messages = server.get_agent_recall_cursor(
|
|
157
|
-
user_id=actor.id,
|
|
158
|
-
agent_id=agent_id,
|
|
159
|
-
limit=limit,
|
|
160
|
-
after=after_uuid,
|
|
161
|
-
before=before_uuid,
|
|
162
|
-
order_by="created_at",
|
|
163
|
-
reverse=reverse,
|
|
164
|
-
)
|
|
165
|
-
assert isinstance(json_messages, List)
|
|
166
|
-
assert all([isinstance(message, Message) for message in json_messages])
|
|
167
|
-
assert isinstance(json_messages[0], Message)
|
|
168
|
-
print(json_messages[0].text)
|
|
169
|
-
# convert to openai style messages
|
|
170
|
-
openai_messages = []
|
|
171
|
-
for message in json_messages:
|
|
172
|
-
assert isinstance(message, Message)
|
|
173
|
-
openai_messages.append(
|
|
174
|
-
OpenAIMessage(
|
|
175
|
-
id=str(message.id),
|
|
176
|
-
created_at=int(message.created_at.timestamp()),
|
|
177
|
-
content=[Text(text=(message.text if message.text else ""))],
|
|
178
|
-
role=str(message.role),
|
|
179
|
-
thread_id=str(message.agent_id),
|
|
180
|
-
assistant_id=DEFAULT_PRESET, # TODO: update this
|
|
181
|
-
# TODO(sarah) fill in?
|
|
182
|
-
run_id=None,
|
|
183
|
-
file_ids=None,
|
|
184
|
-
metadata=None,
|
|
185
|
-
# file_ids=message.file_ids,
|
|
186
|
-
# metadata=message.metadata,
|
|
187
|
-
)
|
|
188
|
-
)
|
|
189
|
-
print("MESSAGES", openai_messages)
|
|
190
|
-
# TODO: cast back to message objects
|
|
191
|
-
return ListMessagesResponse(messages=openai_messages)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
@router.get("/{thread_id}/messages/{message_id}", tags=["messages"], response_model=OpenAIMessage)
|
|
195
|
-
def retrieve_message(
|
|
196
|
-
thread_id: str = Path(..., description="The unique identifier of the thread."),
|
|
197
|
-
message_id: str = Path(..., description="The unique identifier of the message."),
|
|
198
|
-
server: SyncServer = Depends(get_letta_server),
|
|
199
|
-
):
|
|
200
|
-
agent_id = thread_id
|
|
201
|
-
message = server.get_agent_message(agent_id=agent_id, message_id=message_id)
|
|
202
|
-
assert message is not None
|
|
203
|
-
return OpenAIMessage(
|
|
204
|
-
id=message_id,
|
|
205
|
-
created_at=int(message.created_at.timestamp()),
|
|
206
|
-
content=[Text(text=(message.text if message.text else ""))],
|
|
207
|
-
role=message.role,
|
|
208
|
-
thread_id=str(message.agent_id),
|
|
209
|
-
assistant_id=DEFAULT_PRESET, # TODO: update this
|
|
210
|
-
# TODO(sarah) fill in?
|
|
211
|
-
run_id=None,
|
|
212
|
-
file_ids=None,
|
|
213
|
-
metadata=None,
|
|
214
|
-
# file_ids=message.file_ids,
|
|
215
|
-
# metadata=message.metadata,
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
@router.get("/{thread_id}/messages/{message_id}/files/{file_id}", tags=["messages"], response_model=MessageFile)
|
|
220
|
-
def retrieve_message_file(
|
|
221
|
-
thread_id: str = Path(..., description="The unique identifier of the thread."),
|
|
222
|
-
message_id: str = Path(..., description="The unique identifier of the message."),
|
|
223
|
-
file_id: str = Path(..., description="The unique identifier of the file."),
|
|
224
|
-
):
|
|
225
|
-
# TODO: implement?
|
|
226
|
-
raise HTTPException(status_code=404, detail="Not yet implemented (coming soon)")
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
@router.post("/{thread_id}/messages/{message_id}", tags=["messages"], response_model=OpenAIMessage)
|
|
230
|
-
def modify_message(
|
|
231
|
-
thread_id: str = Path(..., description="The unique identifier of the thread."),
|
|
232
|
-
message_id: str = Path(..., description="The unique identifier of the message."),
|
|
233
|
-
request: ModifyMessageRequest = Body(...),
|
|
234
|
-
):
|
|
235
|
-
# TODO: add metada field to message so this can be modified
|
|
236
|
-
raise HTTPException(status_code=404, detail="Not yet implemented (coming soon)")
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
@router.post("/{thread_id}/runs", tags=["runs"], response_model=OpenAIRun)
|
|
240
|
-
def create_run(
|
|
241
|
-
thread_id: str = Path(..., description="The unique identifier of the thread."),
|
|
242
|
-
request: CreateRunRequest = Body(...),
|
|
243
|
-
server: SyncServer = Depends(get_letta_server),
|
|
244
|
-
):
|
|
245
|
-
|
|
246
|
-
# TODO: add request.instructions as a message?
|
|
247
|
-
agent_id = thread_id
|
|
248
|
-
# TODO: override preset of agent with request.assistant_id
|
|
249
|
-
agent = server.load_agent(agent_id=agent_id)
|
|
250
|
-
agent.inner_step(messages=[]) # already has messages added
|
|
251
|
-
run_id = str(uuid.uuid4())
|
|
252
|
-
create_time = int(get_utc_time().timestamp())
|
|
253
|
-
return OpenAIRun(
|
|
254
|
-
id=run_id,
|
|
255
|
-
created_at=create_time,
|
|
256
|
-
thread_id=str(agent_id),
|
|
257
|
-
assistant_id=DEFAULT_PRESET, # TODO: update this
|
|
258
|
-
status="completed", # TODO: eventaully allow offline execution
|
|
259
|
-
expires_at=create_time,
|
|
260
|
-
model=agent.agent_state.llm_config.model,
|
|
261
|
-
instructions=request.instructions,
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
@router.post("/runs", tags=["runs"], response_model=OpenAIRun)
|
|
266
|
-
def create_thread_and_run(
|
|
267
|
-
request: CreateThreadRunRequest = Body(...),
|
|
268
|
-
):
|
|
269
|
-
# TODO: add a bunch of messages and execute
|
|
270
|
-
raise HTTPException(status_code=404, detail="Not yet implemented (coming soon)")
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
@router.get("/{thread_id}/runs", tags=["runs"], response_model=List[OpenAIRun])
|
|
274
|
-
def list_runs(
|
|
275
|
-
thread_id: str = Path(..., description="The unique identifier of the thread."),
|
|
276
|
-
limit: int = Query(1000, description="How many runs to retrieve."),
|
|
277
|
-
order: str = Query("asc", description="Order of runs to retrieve (either 'asc' or 'desc')."),
|
|
278
|
-
after: str = Query(None, description="A cursor for use in pagination. `after` is an object ID that defines your place in the list."),
|
|
279
|
-
before: str = Query(None, description="A cursor for use in pagination. `after` is an object ID that defines your place in the list."),
|
|
280
|
-
):
|
|
281
|
-
# TODO: store run information in a DB so it can be returned here
|
|
282
|
-
raise HTTPException(status_code=404, detail="Not yet implemented (coming soon)")
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
@router.get("/{thread_id}/runs/{run_id}/steps", tags=["runs"], response_model=List[OpenAIRunStep])
|
|
286
|
-
def list_run_steps(
|
|
287
|
-
thread_id: str = Path(..., description="The unique identifier of the thread."),
|
|
288
|
-
run_id: str = Path(..., description="The unique identifier of the run."),
|
|
289
|
-
limit: int = Query(1000, description="How many run steps to retrieve."),
|
|
290
|
-
order: str = Query("asc", description="Order of run steps to retrieve (either 'asc' or 'desc')."),
|
|
291
|
-
after: str = Query(None, description="A cursor for use in pagination. `after` is an object ID that defines your place in the list."),
|
|
292
|
-
before: str = Query(None, description="A cursor for use in pagination. `after` is an object ID that defines your place in the list."),
|
|
293
|
-
):
|
|
294
|
-
# TODO: store run information in a DB so it can be returned here
|
|
295
|
-
raise HTTPException(status_code=404, detail="Not yet implemented (coming soon)")
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
@router.get("/{thread_id}/runs/{run_id}", tags=["runs"], response_model=OpenAIRun)
|
|
299
|
-
def retrieve_run(
|
|
300
|
-
thread_id: str = Path(..., description="The unique identifier of the thread."),
|
|
301
|
-
run_id: str = Path(..., description="The unique identifier of the run."),
|
|
302
|
-
):
|
|
303
|
-
raise HTTPException(status_code=404, detail="Not yet implemented (coming soon)")
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
@router.get("/{thread_id}/runs/{run_id}/steps/{step_id}", tags=["runs"], response_model=OpenAIRunStep)
|
|
307
|
-
def retrieve_run_step(
|
|
308
|
-
thread_id: str = Path(..., description="The unique identifier of the thread."),
|
|
309
|
-
run_id: str = Path(..., description="The unique identifier of the run."),
|
|
310
|
-
step_id: str = Path(..., description="The unique identifier of the run step."),
|
|
311
|
-
):
|
|
312
|
-
raise HTTPException(status_code=404, detail="Not yet implemented (coming soon)")
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
@router.post("/{thread_id}/runs/{run_id}", tags=["runs"], response_model=OpenAIRun)
|
|
316
|
-
def modify_run(
|
|
317
|
-
thread_id: str = Path(..., description="The unique identifier of the thread."),
|
|
318
|
-
run_id: str = Path(..., description="The unique identifier of the run."),
|
|
319
|
-
request: ModifyRunRequest = Body(...),
|
|
320
|
-
):
|
|
321
|
-
raise HTTPException(status_code=404, detail="Not yet implemented (coming soon)")
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
@router.post("/{thread_id}/runs/{run_id}/submit_tool_outputs", tags=["runs"], response_model=OpenAIRun)
|
|
325
|
-
def submit_tool_outputs_to_run(
|
|
326
|
-
thread_id: str = Path(..., description="The unique identifier of the thread."),
|
|
327
|
-
run_id: str = Path(..., description="The unique identifier of the run."),
|
|
328
|
-
request: SubmitToolOutputsToRunRequest = Body(...),
|
|
329
|
-
):
|
|
330
|
-
raise HTTPException(status_code=404, detail="Not yet implemented (coming soon)")
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
@router.post("/{thread_id}/runs/{run_id}/cancel", tags=["runs"], response_model=OpenAIRun)
|
|
334
|
-
def cancel_run(
|
|
335
|
-
thread_id: str = Path(..., description="The unique identifier of the thread."),
|
|
336
|
-
run_id: str = Path(..., description="The unique identifier of the run."),
|
|
337
|
-
):
|
|
338
|
-
raise HTTPException(status_code=404, detail="Not yet implemented (coming soon)")
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
from typing import List
|
|
2
|
-
|
|
3
|
-
from letta.orm.agents_tags import AgentsTags as AgentsTagsModel
|
|
4
|
-
from letta.orm.errors import NoResultFound
|
|
5
|
-
from letta.schemas.agents_tags import AgentsTags as PydanticAgentsTags
|
|
6
|
-
from letta.schemas.user import User as PydanticUser
|
|
7
|
-
from letta.utils import enforce_types
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class AgentsTagsManager:
|
|
11
|
-
"""Manager class to handle business logic related to Tags."""
|
|
12
|
-
|
|
13
|
-
def __init__(self):
|
|
14
|
-
from letta.server.server import db_context
|
|
15
|
-
|
|
16
|
-
self.session_maker = db_context
|
|
17
|
-
|
|
18
|
-
@enforce_types
|
|
19
|
-
def add_tag_to_agent(self, agent_id: str, tag: str, actor: PydanticUser) -> PydanticAgentsTags:
|
|
20
|
-
"""Add a tag to an agent."""
|
|
21
|
-
with self.session_maker() as session:
|
|
22
|
-
# Check if the tag already exists for this agent
|
|
23
|
-
try:
|
|
24
|
-
agents_tags_model = AgentsTagsModel.read(db_session=session, agent_id=agent_id, tag=tag, actor=actor)
|
|
25
|
-
return agents_tags_model.to_pydantic()
|
|
26
|
-
except NoResultFound:
|
|
27
|
-
agents_tags = PydanticAgentsTags(agent_id=agent_id, tag=tag).model_dump(exclude_none=True)
|
|
28
|
-
new_tag = AgentsTagsModel(**agents_tags, organization_id=actor.organization_id)
|
|
29
|
-
new_tag.create(session, actor=actor)
|
|
30
|
-
return new_tag.to_pydantic()
|
|
31
|
-
|
|
32
|
-
@enforce_types
|
|
33
|
-
def delete_all_tags_from_agent(self, agent_id: str, actor: PydanticUser):
|
|
34
|
-
"""Delete a tag from an agent. This is a permanent hard delete."""
|
|
35
|
-
tags = self.get_tags_for_agent(agent_id=agent_id, actor=actor)
|
|
36
|
-
for tag in tags:
|
|
37
|
-
self.delete_tag_from_agent(agent_id=agent_id, tag=tag, actor=actor)
|
|
38
|
-
|
|
39
|
-
@enforce_types
|
|
40
|
-
def delete_tag_from_agent(self, agent_id: str, tag: str, actor: PydanticUser):
|
|
41
|
-
"""Delete a tag from an agent."""
|
|
42
|
-
with self.session_maker() as session:
|
|
43
|
-
try:
|
|
44
|
-
# Retrieve and delete the tag association
|
|
45
|
-
tag_association = AgentsTagsModel.read(db_session=session, agent_id=agent_id, tag=tag, actor=actor)
|
|
46
|
-
tag_association.hard_delete(session, actor=actor)
|
|
47
|
-
except NoResultFound:
|
|
48
|
-
raise ValueError(f"Tag '{tag}' not found for agent '{agent_id}'.")
|
|
49
|
-
|
|
50
|
-
@enforce_types
|
|
51
|
-
def get_agents_by_tag(self, tag: str, actor: PydanticUser) -> List[str]:
|
|
52
|
-
"""Retrieve all agent IDs associated with a specific tag."""
|
|
53
|
-
with self.session_maker() as session:
|
|
54
|
-
# Query for all agents with the given tag
|
|
55
|
-
agents_with_tag = AgentsTagsModel.list(db_session=session, tag=tag, organization_id=actor.organization_id)
|
|
56
|
-
return [record.agent_id for record in agents_with_tag]
|
|
57
|
-
|
|
58
|
-
@enforce_types
|
|
59
|
-
def get_tags_for_agent(self, agent_id: str, actor: PydanticUser) -> List[str]:
|
|
60
|
-
"""Retrieve all tags associated with a specific agent."""
|
|
61
|
-
with self.session_maker() as session:
|
|
62
|
-
# Query for all tags associated with the given agent
|
|
63
|
-
tags_for_agent = AgentsTagsModel.list(db_session=session, agent_id=agent_id, organization_id=actor.organization_id)
|
|
64
|
-
return [record.tag for record in tags_for_agent]
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import warnings
|
|
2
|
-
from typing import List
|
|
3
|
-
|
|
4
|
-
from letta.orm.blocks_agents import BlocksAgents as BlocksAgentsModel
|
|
5
|
-
from letta.orm.errors import NoResultFound
|
|
6
|
-
from letta.schemas.blocks_agents import BlocksAgents as PydanticBlocksAgents
|
|
7
|
-
from letta.utils import enforce_types
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
# TODO: DELETE THIS ASAP
|
|
11
|
-
# TODO: So we have a patch where we manually specify CRUD operations
|
|
12
|
-
# TODO: This is because Agent is NOT migrated to the ORM yet
|
|
13
|
-
# TODO: Once we migrate Agent to the ORM, we should deprecate any agents relationship table managers
|
|
14
|
-
class BlocksAgentsManager:
|
|
15
|
-
"""Manager class to handle business logic related to Blocks and Agents."""
|
|
16
|
-
|
|
17
|
-
def __init__(self):
|
|
18
|
-
from letta.server.server import db_context
|
|
19
|
-
|
|
20
|
-
self.session_maker = db_context
|
|
21
|
-
|
|
22
|
-
@enforce_types
|
|
23
|
-
def add_block_to_agent(self, agent_id: str, block_id: str, block_label: str) -> PydanticBlocksAgents:
|
|
24
|
-
"""Add a block to an agent. If the label already exists on that agent, this will error."""
|
|
25
|
-
with self.session_maker() as session:
|
|
26
|
-
try:
|
|
27
|
-
# Check if the block-label combination already exists for this agent
|
|
28
|
-
blocks_agents_record = BlocksAgentsModel.read(db_session=session, agent_id=agent_id, block_label=block_label)
|
|
29
|
-
warnings.warn(f"Block label '{block_label}' already exists for agent '{agent_id}'.")
|
|
30
|
-
except NoResultFound:
|
|
31
|
-
blocks_agents_record = PydanticBlocksAgents(agent_id=agent_id, block_id=block_id, block_label=block_label)
|
|
32
|
-
blocks_agents_record = BlocksAgentsModel(**blocks_agents_record.model_dump(exclude_none=True))
|
|
33
|
-
blocks_agents_record.create(session)
|
|
34
|
-
|
|
35
|
-
return blocks_agents_record.to_pydantic()
|
|
36
|
-
|
|
37
|
-
@enforce_types
|
|
38
|
-
def remove_block_with_label_from_agent(self, agent_id: str, block_label: str) -> PydanticBlocksAgents:
|
|
39
|
-
"""Remove a block with a label from an agent."""
|
|
40
|
-
with self.session_maker() as session:
|
|
41
|
-
try:
|
|
42
|
-
# Find and delete the block-label association for the agent
|
|
43
|
-
blocks_agents_record = BlocksAgentsModel.read(db_session=session, agent_id=agent_id, block_label=block_label)
|
|
44
|
-
blocks_agents_record.hard_delete(session)
|
|
45
|
-
return blocks_agents_record.to_pydantic()
|
|
46
|
-
except NoResultFound:
|
|
47
|
-
raise ValueError(f"Block label '{block_label}' not found for agent '{agent_id}'.")
|
|
48
|
-
|
|
49
|
-
@enforce_types
|
|
50
|
-
def remove_block_with_id_from_agent(self, agent_id: str, block_id: str) -> PydanticBlocksAgents:
|
|
51
|
-
"""Remove a block with a label from an agent."""
|
|
52
|
-
with self.session_maker() as session:
|
|
53
|
-
try:
|
|
54
|
-
# Find and delete the block-label association for the agent
|
|
55
|
-
blocks_agents_record = BlocksAgentsModel.read(db_session=session, agent_id=agent_id, block_id=block_id)
|
|
56
|
-
blocks_agents_record.hard_delete(session)
|
|
57
|
-
return blocks_agents_record.to_pydantic()
|
|
58
|
-
except NoResultFound:
|
|
59
|
-
raise ValueError(f"Block id '{block_id}' not found for agent '{agent_id}'.")
|
|
60
|
-
|
|
61
|
-
@enforce_types
|
|
62
|
-
def update_block_id_for_agent(self, agent_id: str, block_label: str, new_block_id: str) -> PydanticBlocksAgents:
|
|
63
|
-
"""Update the block ID for a specific block label for an agent."""
|
|
64
|
-
with self.session_maker() as session:
|
|
65
|
-
try:
|
|
66
|
-
blocks_agents_record = BlocksAgentsModel.read(db_session=session, agent_id=agent_id, block_label=block_label)
|
|
67
|
-
blocks_agents_record.block_id = new_block_id
|
|
68
|
-
return blocks_agents_record.to_pydantic()
|
|
69
|
-
except NoResultFound:
|
|
70
|
-
raise ValueError(f"Block label '{block_label}' not found for agent '{agent_id}'.")
|
|
71
|
-
|
|
72
|
-
@enforce_types
|
|
73
|
-
def list_block_ids_for_agent(self, agent_id: str) -> List[str]:
|
|
74
|
-
"""List all block ids associated with a specific agent."""
|
|
75
|
-
with self.session_maker() as session:
|
|
76
|
-
blocks_agents_record = BlocksAgentsModel.list(db_session=session, agent_id=agent_id)
|
|
77
|
-
return [record.block_id for record in blocks_agents_record]
|
|
78
|
-
|
|
79
|
-
@enforce_types
|
|
80
|
-
def list_block_labels_for_agent(self, agent_id: str) -> List[str]:
|
|
81
|
-
"""List all block labels associated with a specific agent."""
|
|
82
|
-
with self.session_maker() as session:
|
|
83
|
-
blocks_agents_record = BlocksAgentsModel.list(db_session=session, agent_id=agent_id)
|
|
84
|
-
return [record.block_label for record in blocks_agents_record]
|
|
85
|
-
|
|
86
|
-
@enforce_types
|
|
87
|
-
def list_agent_ids_with_block(self, block_id: str) -> List[str]:
|
|
88
|
-
"""List all agents associated with a specific block."""
|
|
89
|
-
with self.session_maker() as session:
|
|
90
|
-
blocks_agents_record = BlocksAgentsModel.list(db_session=session, block_id=block_id)
|
|
91
|
-
return [record.agent_id for record in blocks_agents_record]
|
|
92
|
-
|
|
93
|
-
@enforce_types
|
|
94
|
-
def get_block_id_for_label(self, agent_id: str, block_label: str) -> str:
|
|
95
|
-
"""Get the block ID for a specific block label for an agent."""
|
|
96
|
-
with self.session_maker() as session:
|
|
97
|
-
try:
|
|
98
|
-
blocks_agents_record = BlocksAgentsModel.read(db_session=session, agent_id=agent_id, block_label=block_label)
|
|
99
|
-
return blocks_agents_record.block_id
|
|
100
|
-
except NoResultFound:
|
|
101
|
-
raise ValueError(f"Block label '{block_label}' not found for agent '{agent_id}'.")
|
|
102
|
-
|
|
103
|
-
@enforce_types
|
|
104
|
-
def remove_all_agent_blocks(self, agent_id: str):
|
|
105
|
-
for block_id in self.list_block_ids_for_agent(agent_id):
|
|
106
|
-
self.remove_block_with_id_from_agent(agent_id, block_id)
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import warnings
|
|
2
|
-
from typing import List, Optional
|
|
3
|
-
|
|
4
|
-
from sqlalchemy import select
|
|
5
|
-
from sqlalchemy.exc import IntegrityError
|
|
6
|
-
from sqlalchemy.orm import Session
|
|
7
|
-
|
|
8
|
-
from letta.orm.errors import NoResultFound
|
|
9
|
-
from letta.orm.organization import Organization
|
|
10
|
-
from letta.orm.tool import Tool
|
|
11
|
-
from letta.orm.tools_agents import ToolsAgents as ToolsAgentsModel
|
|
12
|
-
from letta.schemas.tools_agents import ToolsAgents as PydanticToolsAgents
|
|
13
|
-
|
|
14
|
-
class ToolsAgentsManager:
|
|
15
|
-
"""Manages the relationship between tools and agents."""
|
|
16
|
-
|
|
17
|
-
def __init__(self):
|
|
18
|
-
from letta.server.server import db_context
|
|
19
|
-
self.session_maker = db_context
|
|
20
|
-
|
|
21
|
-
def add_tool_to_agent(self, agent_id: str, tool_id: str, tool_name: str) -> PydanticToolsAgents:
|
|
22
|
-
"""Add a tool to an agent.
|
|
23
|
-
|
|
24
|
-
When a tool is added to an agent, it will be added to all agents in the same organization.
|
|
25
|
-
"""
|
|
26
|
-
with self.session_maker() as session:
|
|
27
|
-
try:
|
|
28
|
-
# Check if the tool-agent combination already exists for this agent
|
|
29
|
-
tools_agents_record = ToolsAgentsModel.read(db_session=session, agent_id=agent_id, tool_name=tool_name)
|
|
30
|
-
warnings.warn(f"Tool name '{tool_name}' already exists for agent '{agent_id}'.")
|
|
31
|
-
except NoResultFound:
|
|
32
|
-
tools_agents_record = PydanticToolsAgents(agent_id=agent_id, tool_id=tool_id, tool_name=tool_name)
|
|
33
|
-
tools_agents_record = ToolsAgentsModel(**tools_agents_record.model_dump(exclude_none=True))
|
|
34
|
-
tools_agents_record.create(session)
|
|
35
|
-
|
|
36
|
-
return tools_agents_record.to_pydantic()
|
|
37
|
-
|
|
38
|
-
def remove_tool_with_name_from_agent(self, agent_id: str, tool_name: str) -> None:
|
|
39
|
-
"""Remove a tool from an agent by its name.
|
|
40
|
-
|
|
41
|
-
When a tool is removed from an agent, it will be removed from all agents in the same organization.
|
|
42
|
-
"""
|
|
43
|
-
with self.session_maker() as session:
|
|
44
|
-
try:
|
|
45
|
-
# Find and delete the tool-agent association for the agent
|
|
46
|
-
tools_agents_record = ToolsAgentsModel.read(db_session=session, agent_id=agent_id, tool_name=tool_name)
|
|
47
|
-
tools_agents_record.hard_delete(session)
|
|
48
|
-
return tools_agents_record.to_pydantic()
|
|
49
|
-
except NoResultFound:
|
|
50
|
-
raise ValueError(f"Tool name '{tool_name}' not found for agent '{agent_id}'.")
|
|
51
|
-
|
|
52
|
-
def remove_tool_with_id_from_agent(self, agent_id: str, tool_id: str) -> PydanticToolsAgents:
|
|
53
|
-
"""Remove a tool with an ID from an agent."""
|
|
54
|
-
with self.session_maker() as session:
|
|
55
|
-
try:
|
|
56
|
-
tools_agents_record = ToolsAgentsModel.read(db_session=session, agent_id=agent_id, tool_id=tool_id)
|
|
57
|
-
tools_agents_record.hard_delete(session)
|
|
58
|
-
return tools_agents_record.to_pydantic()
|
|
59
|
-
except NoResultFound:
|
|
60
|
-
raise ValueError(f"Tool ID '{tool_id}' not found for agent '{agent_id}'.")
|
|
61
|
-
|
|
62
|
-
def list_tool_ids_for_agent(self, agent_id: str) -> List[str]:
|
|
63
|
-
"""List all tool IDs associated with a specific agent."""
|
|
64
|
-
with self.session_maker() as session:
|
|
65
|
-
tools_agents_record = ToolsAgentsModel.list(db_session=session, agent_id=agent_id)
|
|
66
|
-
return [record.tool_id for record in tools_agents_record]
|
|
67
|
-
|
|
68
|
-
def list_tool_names_for_agent(self, agent_id: str) -> List[str]:
|
|
69
|
-
"""List all tool names associated with a specific agent."""
|
|
70
|
-
with self.session_maker() as session:
|
|
71
|
-
tools_agents_record = ToolsAgentsModel.list(db_session=session, agent_id=agent_id)
|
|
72
|
-
return [record.tool_name for record in tools_agents_record]
|
|
73
|
-
|
|
74
|
-
def list_agent_ids_with_tool(self, tool_id: str) -> List[str]:
|
|
75
|
-
"""List all agents associated with a specific tool."""
|
|
76
|
-
with self.session_maker() as session:
|
|
77
|
-
tools_agents_record = ToolsAgentsModel.list(db_session=session, tool_id=tool_id)
|
|
78
|
-
return [record.agent_id for record in tools_agents_record]
|
|
79
|
-
|
|
80
|
-
def get_tool_id_for_name(self, agent_id: str, tool_name: str) -> str:
|
|
81
|
-
"""Get the tool ID for a specific tool name for an agent."""
|
|
82
|
-
with self.session_maker() as session:
|
|
83
|
-
try:
|
|
84
|
-
tools_agents_record = ToolsAgentsModel.read(db_session=session, agent_id=agent_id, tool_name=tool_name)
|
|
85
|
-
return tools_agents_record.tool_id
|
|
86
|
-
except NoResultFound:
|
|
87
|
-
raise ValueError(f"Tool name '{tool_name}' not found for agent '{agent_id}'.")
|
|
88
|
-
|
|
89
|
-
def remove_all_agent_tools(self, agent_id: str) -> None:
|
|
90
|
-
"""Remove all tools associated with an agent."""
|
|
91
|
-
with self.session_maker() as session:
|
|
92
|
-
tools_agents_records = ToolsAgentsModel.list(db_session=session, agent_id=agent_id)
|
|
93
|
-
for record in tools_agents_records:
|
|
94
|
-
record.hard_delete(session)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|