letta-nightly 0.10.0.dev20250805104522__py3-none-any.whl → 0.11.0.dev20250807000848__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.
Files changed (66) hide show
  1. letta/__init__.py +1 -4
  2. letta/agent.py +1 -2
  3. letta/agents/base_agent.py +4 -7
  4. letta/agents/letta_agent.py +59 -51
  5. letta/agents/letta_agent_batch.py +1 -2
  6. letta/agents/voice_agent.py +1 -2
  7. letta/agents/voice_sleeptime_agent.py +1 -3
  8. letta/constants.py +4 -1
  9. letta/embeddings.py +1 -1
  10. letta/functions/function_sets/base.py +0 -1
  11. letta/functions/mcp_client/types.py +4 -0
  12. letta/groups/supervisor_multi_agent.py +1 -1
  13. letta/interfaces/anthropic_streaming_interface.py +16 -24
  14. letta/interfaces/openai_streaming_interface.py +16 -28
  15. letta/llm_api/llm_api_tools.py +3 -3
  16. letta/local_llm/vllm/api.py +3 -0
  17. letta/orm/__init__.py +3 -1
  18. letta/orm/agent.py +8 -0
  19. letta/orm/archive.py +86 -0
  20. letta/orm/archives_agents.py +27 -0
  21. letta/orm/job.py +5 -1
  22. letta/orm/mixins.py +8 -0
  23. letta/orm/organization.py +7 -8
  24. letta/orm/passage.py +12 -10
  25. letta/orm/sqlite_functions.py +2 -2
  26. letta/orm/tool.py +5 -4
  27. letta/schemas/agent.py +4 -2
  28. letta/schemas/agent_file.py +18 -1
  29. letta/schemas/archive.py +44 -0
  30. letta/schemas/embedding_config.py +2 -16
  31. letta/schemas/enums.py +2 -1
  32. letta/schemas/group.py +28 -3
  33. letta/schemas/job.py +4 -0
  34. letta/schemas/llm_config.py +29 -14
  35. letta/schemas/memory.py +9 -3
  36. letta/schemas/npm_requirement.py +12 -0
  37. letta/schemas/passage.py +3 -3
  38. letta/schemas/providers/letta.py +1 -1
  39. letta/schemas/providers/vllm.py +4 -4
  40. letta/schemas/sandbox_config.py +3 -1
  41. letta/schemas/tool.py +10 -38
  42. letta/schemas/tool_rule.py +2 -2
  43. letta/server/db.py +8 -2
  44. letta/server/rest_api/routers/v1/agents.py +9 -8
  45. letta/server/server.py +6 -40
  46. letta/server/startup.sh +3 -0
  47. letta/services/agent_manager.py +92 -31
  48. letta/services/agent_serialization_manager.py +62 -3
  49. letta/services/archive_manager.py +269 -0
  50. letta/services/helpers/agent_manager_helper.py +111 -37
  51. letta/services/job_manager.py +24 -0
  52. letta/services/passage_manager.py +98 -54
  53. letta/services/tool_executor/core_tool_executor.py +0 -1
  54. letta/services/tool_executor/sandbox_tool_executor.py +2 -2
  55. letta/services/tool_executor/tool_execution_manager.py +1 -1
  56. letta/services/tool_manager.py +70 -26
  57. letta/services/tool_sandbox/base.py +2 -2
  58. letta/services/tool_sandbox/local_sandbox.py +5 -1
  59. letta/templates/template_helper.py +8 -0
  60. {letta_nightly-0.10.0.dev20250805104522.dist-info → letta_nightly-0.11.0.dev20250807000848.dist-info}/METADATA +5 -6
  61. {letta_nightly-0.10.0.dev20250805104522.dist-info → letta_nightly-0.11.0.dev20250807000848.dist-info}/RECORD +64 -61
  62. letta/client/client.py +0 -2207
  63. letta/orm/enums.py +0 -21
  64. {letta_nightly-0.10.0.dev20250805104522.dist-info → letta_nightly-0.11.0.dev20250807000848.dist-info}/LICENSE +0 -0
  65. {letta_nightly-0.10.0.dev20250805104522.dist-info → letta_nightly-0.11.0.dev20250807000848.dist-info}/WHEEL +0 -0
  66. {letta_nightly-0.10.0.dev20250805104522.dist-info → letta_nightly-0.11.0.dev20250807000848.dist-info}/entry_points.txt +0 -0
letta/client/client.py DELETED
@@ -1,2207 +0,0 @@
1
- import time
2
- from typing import Callable, Dict, List, Optional, Union
3
-
4
- import requests
5
-
6
- from letta.constants import ADMIN_PREFIX, BASE_MEMORY_TOOLS, BASE_TOOLS, DEFAULT_HUMAN, DEFAULT_PERSONA, FUNCTION_RETURN_CHAR_LIMIT
7
- from letta.data_sources.connectors import DataConnector
8
- from letta.functions.functions import parse_source_code
9
- from letta.schemas.agent import AgentState, AgentType, CreateAgent, UpdateAgent
10
- from letta.schemas.block import Block, BlockUpdate, CreateBlock, Human, Persona
11
- from letta.schemas.embedding_config import EmbeddingConfig
12
-
13
- # new schemas
14
- from letta.schemas.enums import JobStatus, MessageRole
15
- from letta.schemas.environment_variables import SandboxEnvironmentVariable
16
- from letta.schemas.file import FileMetadata
17
- from letta.schemas.job import Job
18
- from letta.schemas.letta_message import LettaMessage, LettaMessageUnion
19
- from letta.schemas.letta_request import LettaRequest, LettaStreamingRequest
20
- from letta.schemas.letta_response import LettaResponse
21
- from letta.schemas.llm_config import LLMConfig
22
- from letta.schemas.memory import ArchivalMemorySummary, ChatMemory, CreateArchivalMemory, Memory, RecallMemorySummary
23
- from letta.schemas.message import Message, MessageCreate
24
- from letta.schemas.openai.chat_completion_response import UsageStatistics
25
- from letta.schemas.organization import Organization
26
- from letta.schemas.passage import Passage
27
- from letta.schemas.response_format import ResponseFormatUnion
28
- from letta.schemas.run import Run
29
- from letta.schemas.sandbox_config import E2BSandboxConfig, LocalSandboxConfig, SandboxConfig
30
- from letta.schemas.source import Source, SourceCreate, SourceUpdate
31
- from letta.schemas.tool import Tool, ToolCreate, ToolUpdate
32
- from letta.schemas.tool_rule import BaseToolRule
33
- from letta.utils import get_human_text, get_persona_text
34
-
35
-
36
- class AbstractClient(object):
37
- def __init__(
38
- self,
39
- debug: bool = False,
40
- ):
41
- self.debug = debug
42
-
43
- def agent_exists(self, agent_id: Optional[str] = None, agent_name: Optional[str] = None) -> bool:
44
- raise NotImplementedError
45
-
46
- def create_agent(
47
- self,
48
- name: Optional[str] = None,
49
- agent_type: Optional[AgentType] = AgentType.memgpt_agent,
50
- embedding_config: Optional[EmbeddingConfig] = None,
51
- llm_config: Optional[LLMConfig] = None,
52
- memory=None,
53
- block_ids: Optional[List[str]] = None,
54
- system: Optional[str] = None,
55
- tool_ids: Optional[List[str]] = None,
56
- tool_rules: Optional[List[BaseToolRule]] = None,
57
- include_base_tools: Optional[bool] = True,
58
- metadata: Optional[Dict] = {"human:": DEFAULT_HUMAN, "persona": DEFAULT_PERSONA},
59
- description: Optional[str] = None,
60
- tags: Optional[List[str]] = None,
61
- message_buffer_autoclear: bool = False,
62
- response_format: Optional[ResponseFormatUnion] = None,
63
- ) -> AgentState:
64
- raise NotImplementedError
65
-
66
- def update_agent(
67
- self,
68
- agent_id: str,
69
- name: Optional[str] = None,
70
- description: Optional[str] = None,
71
- system: Optional[str] = None,
72
- tool_ids: Optional[List[str]] = None,
73
- metadata: Optional[Dict] = None,
74
- llm_config: Optional[LLMConfig] = None,
75
- embedding_config: Optional[EmbeddingConfig] = None,
76
- message_ids: Optional[List[str]] = None,
77
- memory: Optional[Memory] = None,
78
- tags: Optional[List[str]] = None,
79
- response_format: Optional[ResponseFormatUnion] = None,
80
- ):
81
- raise NotImplementedError
82
-
83
- def get_tools_from_agent(self, agent_id: str) -> List[Tool]:
84
- raise NotImplementedError
85
-
86
- def attach_tool(self, agent_id: str, tool_id: str) -> AgentState:
87
- raise NotImplementedError
88
-
89
- def detach_tool(self, agent_id: str, tool_id: str) -> AgentState:
90
- raise NotImplementedError
91
-
92
- def rename_agent(self, agent_id: str, new_name: str) -> AgentState:
93
- raise NotImplementedError
94
-
95
- def delete_agent(self, agent_id: str) -> None:
96
- raise NotImplementedError
97
-
98
- def get_agent(self, agent_id: str) -> AgentState:
99
- raise NotImplementedError
100
-
101
- def get_agent_id(self, agent_name: str) -> AgentState:
102
- raise NotImplementedError
103
-
104
- def get_in_context_memory(self, agent_id: str) -> Memory:
105
- raise NotImplementedError
106
-
107
- def update_in_context_memory(self, agent_id: str, section: str, value: Union[List[str], str]) -> Memory:
108
- raise NotImplementedError
109
-
110
- def get_archival_memory_summary(self, agent_id: str) -> ArchivalMemorySummary:
111
- raise NotImplementedError
112
-
113
- def get_recall_memory_summary(self, agent_id: str) -> RecallMemorySummary:
114
- raise NotImplementedError
115
-
116
- def get_in_context_messages(self, agent_id: str) -> List[Message]:
117
- raise NotImplementedError
118
-
119
- def send_message(
120
- self,
121
- message: str,
122
- role: str,
123
- agent_id: Optional[str] = None,
124
- name: Optional[str] = None,
125
- stream: Optional[bool] = False,
126
- stream_steps: bool = False,
127
- stream_tokens: bool = False,
128
- max_steps: Optional[int] = None,
129
- ) -> LettaResponse:
130
- raise NotImplementedError
131
-
132
- def user_message(self, agent_id: str, message: str) -> LettaResponse:
133
- raise NotImplementedError
134
-
135
- def create_human(self, name: str, text: str) -> Human:
136
- raise NotImplementedError
137
-
138
- def create_persona(self, name: str, text: str) -> Persona:
139
- raise NotImplementedError
140
-
141
- def list_humans(self) -> List[Human]:
142
- raise NotImplementedError
143
-
144
- def list_personas(self) -> List[Persona]:
145
- raise NotImplementedError
146
-
147
- def update_human(self, human_id: str, text: str) -> Human:
148
- raise NotImplementedError
149
-
150
- def update_persona(self, persona_id: str, text: str) -> Persona:
151
- raise NotImplementedError
152
-
153
- def get_persona(self, id: str) -> Persona:
154
- raise NotImplementedError
155
-
156
- def get_human(self, id: str) -> Human:
157
- raise NotImplementedError
158
-
159
- def get_persona_id(self, name: str) -> str:
160
- raise NotImplementedError
161
-
162
- def get_human_id(self, name: str) -> str:
163
- raise NotImplementedError
164
-
165
- def delete_persona(self, id: str):
166
- raise NotImplementedError
167
-
168
- def delete_human(self, id: str):
169
- raise NotImplementedError
170
-
171
- def load_langchain_tool(self, langchain_tool: "LangChainBaseTool", additional_imports_module_attr_map: dict[str, str] = None) -> Tool:
172
- raise NotImplementedError
173
-
174
- def load_composio_tool(self, action: "ActionType") -> Tool:
175
- raise NotImplementedError
176
-
177
- def create_tool(self, func, tags: Optional[List[str]] = None, return_char_limit: int = FUNCTION_RETURN_CHAR_LIMIT) -> Tool:
178
- raise NotImplementedError
179
-
180
- def create_or_update_tool(self, func, tags: Optional[List[str]] = None, return_char_limit: int = FUNCTION_RETURN_CHAR_LIMIT) -> Tool:
181
- raise NotImplementedError
182
-
183
- def update_tool(
184
- self,
185
- id: str,
186
- description: Optional[str] = None,
187
- func: Optional[Callable] = None,
188
- tags: Optional[List[str]] = None,
189
- return_char_limit: int = FUNCTION_RETURN_CHAR_LIMIT,
190
- ) -> Tool:
191
- raise NotImplementedError
192
-
193
- def list_tools(self, after: Optional[str] = None, limit: Optional[int] = 50) -> List[Tool]:
194
- raise NotImplementedError
195
-
196
- def get_tool(self, id: str) -> Tool:
197
- raise NotImplementedError
198
-
199
- def delete_tool(self, id: str):
200
- raise NotImplementedError
201
-
202
- def get_tool_id(self, name: str) -> Optional[str]:
203
- raise NotImplementedError
204
-
205
- def list_attached_tools(self, agent_id: str) -> List[Tool]:
206
- """
207
- List all tools attached to an agent.
208
-
209
- Args:
210
- agent_id (str): ID of the agent
211
-
212
- Returns:
213
- List[Tool]: A list of attached tools
214
- """
215
- raise NotImplementedError
216
-
217
- def upsert_base_tools(self) -> List[Tool]:
218
- raise NotImplementedError
219
-
220
- def load_data(self, connector: DataConnector, source_name: str):
221
- raise NotImplementedError
222
-
223
- def load_file_to_source(self, filename: str, source_id: str, blocking=True) -> Job:
224
- raise NotImplementedError
225
-
226
- def delete_file_from_source(self, source_id: str, file_id: str) -> None:
227
- raise NotImplementedError
228
-
229
- def create_source(self, name: str, embedding_config: Optional[EmbeddingConfig] = None) -> Source:
230
- raise NotImplementedError
231
-
232
- def delete_source(self, source_id: str):
233
- raise NotImplementedError
234
-
235
- def get_source(self, source_id: str) -> Source:
236
- raise NotImplementedError
237
-
238
- def get_source_id(self, source_name: str) -> str:
239
- raise NotImplementedError
240
-
241
- def attach_source(self, agent_id: str, source_id: Optional[str] = None, source_name: Optional[str] = None) -> AgentState:
242
- raise NotImplementedError
243
-
244
- def detach_source(self, agent_id: str, source_id: Optional[str] = None, source_name: Optional[str] = None) -> AgentState:
245
- raise NotImplementedError
246
-
247
- def list_sources(self) -> List[Source]:
248
- raise NotImplementedError
249
-
250
- def list_attached_sources(self, agent_id: str) -> List[Source]:
251
- raise NotImplementedError
252
-
253
- def list_files_from_source(self, source_id: str, limit: int = 1000, after: Optional[str] = None) -> List[FileMetadata]:
254
- raise NotImplementedError
255
-
256
- def update_source(self, source_id: str, name: Optional[str] = None) -> Source:
257
- raise NotImplementedError
258
-
259
- def insert_archival_memory(self, agent_id: str, memory: str) -> List[Passage]:
260
- raise NotImplementedError
261
-
262
- def delete_archival_memory(self, agent_id: str, memory_id: str):
263
- raise NotImplementedError
264
-
265
- def get_archival_memory(
266
- self, agent_id: str, after: Optional[str] = None, before: Optional[str] = None, limit: Optional[int] = 1000
267
- ) -> List[Passage]:
268
- raise NotImplementedError
269
-
270
- def get_messages(
271
- self, agent_id: str, after: Optional[str] = None, before: Optional[str] = None, limit: Optional[int] = 1000
272
- ) -> List[LettaMessage]:
273
- raise NotImplementedError
274
-
275
- def list_model_configs(self) -> List[LLMConfig]:
276
- raise NotImplementedError
277
-
278
- def list_embedding_configs(self) -> List[EmbeddingConfig]:
279
- raise NotImplementedError
280
-
281
- def create_org(self, name: Optional[str] = None) -> Organization:
282
- raise NotImplementedError
283
-
284
- def list_orgs(self, after: Optional[str] = None, limit: Optional[int] = 50) -> List[Organization]:
285
- raise NotImplementedError
286
-
287
- def delete_org(self, org_id: str) -> Organization:
288
- raise NotImplementedError
289
-
290
- def create_sandbox_config(self, config: Union[LocalSandboxConfig, E2BSandboxConfig]) -> SandboxConfig:
291
- """
292
- Create a new sandbox configuration.
293
-
294
- Args:
295
- config (Union[LocalSandboxConfig, E2BSandboxConfig]): The sandbox settings.
296
-
297
- Returns:
298
- SandboxConfig: The created sandbox configuration.
299
- """
300
- raise NotImplementedError
301
-
302
- def update_sandbox_config(self, sandbox_config_id: str, config: Union[LocalSandboxConfig, E2BSandboxConfig]) -> SandboxConfig:
303
- """
304
- Update an existing sandbox configuration.
305
-
306
- Args:
307
- sandbox_config_id (str): The ID of the sandbox configuration to update.
308
- config (Union[LocalSandboxConfig, E2BSandboxConfig]): The updated sandbox settings.
309
-
310
- Returns:
311
- SandboxConfig: The updated sandbox configuration.
312
- """
313
- raise NotImplementedError
314
-
315
- def delete_sandbox_config(self, sandbox_config_id: str) -> None:
316
- """
317
- Delete a sandbox configuration.
318
-
319
- Args:
320
- sandbox_config_id (str): The ID of the sandbox configuration to delete.
321
- """
322
- raise NotImplementedError
323
-
324
- def list_sandbox_configs(self, limit: int = 50, after: Optional[str] = None) -> List[SandboxConfig]:
325
- """
326
- List all sandbox configurations.
327
-
328
- Args:
329
- limit (int, optional): The maximum number of sandbox configurations to return. Defaults to 50.
330
- after (Optional[str], optional): The pagination cursor for retrieving the next set of results.
331
-
332
- Returns:
333
- List[SandboxConfig]: A list of sandbox configurations.
334
- """
335
- raise NotImplementedError
336
-
337
- def create_sandbox_env_var(
338
- self, sandbox_config_id: str, key: str, value: str, description: Optional[str] = None
339
- ) -> SandboxEnvironmentVariable:
340
- """
341
- Create a new environment variable for a sandbox configuration.
342
-
343
- Args:
344
- sandbox_config_id (str): The ID of the sandbox configuration to associate the environment variable with.
345
- key (str): The name of the environment variable.
346
- value (str): The value of the environment variable.
347
- description (Optional[str], optional): A description of the environment variable. Defaults to None.
348
-
349
- Returns:
350
- SandboxEnvironmentVariable: The created environment variable.
351
- """
352
- raise NotImplementedError
353
-
354
- def update_sandbox_env_var(
355
- self, env_var_id: str, key: Optional[str] = None, value: Optional[str] = None, description: Optional[str] = None
356
- ) -> SandboxEnvironmentVariable:
357
- """
358
- Update an existing environment variable.
359
-
360
- Args:
361
- env_var_id (str): The ID of the environment variable to update.
362
- key (Optional[str], optional): The updated name of the environment variable. Defaults to None.
363
- value (Optional[str], optional): The updated value of the environment variable. Defaults to None.
364
- description (Optional[str], optional): The updated description of the environment variable. Defaults to None.
365
-
366
- Returns:
367
- SandboxEnvironmentVariable: The updated environment variable.
368
- """
369
- raise NotImplementedError
370
-
371
- def delete_sandbox_env_var(self, env_var_id: str) -> None:
372
- """
373
- Delete an environment variable by its ID.
374
-
375
- Args:
376
- env_var_id (str): The ID of the environment variable to delete.
377
- """
378
- raise NotImplementedError
379
-
380
- def list_sandbox_env_vars(
381
- self, sandbox_config_id: str, limit: int = 50, after: Optional[str] = None
382
- ) -> List[SandboxEnvironmentVariable]:
383
- """
384
- List all environment variables associated with a sandbox configuration.
385
-
386
- Args:
387
- sandbox_config_id (str): The ID of the sandbox configuration to retrieve environment variables for.
388
- limit (int, optional): The maximum number of environment variables to return. Defaults to 50.
389
- after (Optional[str], optional): The pagination cursor for retrieving the next set of results.
390
-
391
- Returns:
392
- List[SandboxEnvironmentVariable]: A list of environment variables.
393
- """
394
- raise NotImplementedError
395
-
396
- def attach_block(self, agent_id: str, block_id: str) -> AgentState:
397
- """
398
- Attach a block to an agent.
399
-
400
- Args:
401
- agent_id (str): ID of the agent
402
- block_id (str): ID of the block to attach
403
- """
404
- raise NotImplementedError
405
-
406
- def detach_block(self, agent_id: str, block_id: str) -> AgentState:
407
- """
408
- Detach a block from an agent.
409
-
410
- Args:
411
- agent_id (str): ID of the agent
412
- block_id (str): ID of the block to detach
413
- """
414
- raise NotImplementedError
415
-
416
-
417
- class RESTClient(AbstractClient):
418
- """
419
- REST client for Letta
420
-
421
- Attributes:
422
- base_url (str): Base URL of the REST API
423
- headers (Dict): Headers for the REST API (includes token)
424
- """
425
-
426
- def __init__(
427
- self,
428
- base_url: str,
429
- token: Optional[str] = None,
430
- password: Optional[str] = None,
431
- api_prefix: str = "v1",
432
- debug: bool = False,
433
- default_llm_config: Optional[LLMConfig] = None,
434
- default_embedding_config: Optional[EmbeddingConfig] = None,
435
- headers: Optional[Dict] = None,
436
- ):
437
- """
438
- Initializes a new instance of Client class.
439
-
440
- Args:
441
- user_id (str): The user ID.
442
- debug (bool): Whether to print debug information.
443
- default_llm_config (Optional[LLMConfig]): The default LLM configuration.
444
- default_embedding_config (Optional[EmbeddingConfig]): The default embedding configuration.
445
- headers (Optional[Dict]): The additional headers for the REST API.
446
- token (Optional[str]): The token for the REST API when using managed letta service.
447
- password (Optional[str]): The password for the REST API when using self hosted letta service.
448
- """
449
- super().__init__(debug=debug)
450
- self.base_url = base_url
451
- self.api_prefix = api_prefix
452
- if token:
453
- self.headers = {"accept": "application/json", "Authorization": f"Bearer {token}"}
454
- elif password:
455
- self.headers = {"accept": "application/json", "Authorization": f"Bearer {password}"}
456
- else:
457
- self.headers = {"accept": "application/json"}
458
- if headers:
459
- self.headers.update(headers)
460
- self._default_llm_config = default_llm_config
461
- self._default_embedding_config = default_embedding_config
462
-
463
- def list_agents(
464
- self,
465
- tags: Optional[List[str]] = None,
466
- query_text: Optional[str] = None,
467
- limit: int = 50,
468
- before: Optional[str] = None,
469
- after: Optional[str] = None,
470
- ) -> List[AgentState]:
471
- params = {"limit": limit}
472
- if tags:
473
- params["tags"] = tags
474
- params["match_all_tags"] = False
475
-
476
- if query_text:
477
- params["query_text"] = query_text
478
-
479
- if before:
480
- params["before"] = before
481
-
482
- if after:
483
- params["after"] = after
484
-
485
- response = requests.get(f"{self.base_url}/{self.api_prefix}/agents", headers=self.headers, params=params)
486
- return [AgentState(**agent) for agent in response.json()]
487
-
488
- def agent_exists(self, agent_id: str) -> bool:
489
- """
490
- Check if an agent exists
491
-
492
- Args:
493
- agent_id (str): ID of the agent
494
- agent_name (str): Name of the agent
495
-
496
- Returns:
497
- exists (bool): `True` if the agent exists, `False` otherwise
498
- """
499
-
500
- response = requests.get(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}", headers=self.headers)
501
- if response.status_code == 404:
502
- # not found error
503
- return False
504
- elif response.status_code == 200:
505
- return True
506
- else:
507
- raise ValueError(f"Failed to check if agent exists: {response.text}")
508
-
509
- def create_agent(
510
- self,
511
- name: Optional[str] = None,
512
- # agent config
513
- agent_type: Optional[AgentType] = AgentType.memgpt_agent,
514
- # model configs
515
- embedding_config: EmbeddingConfig = None,
516
- llm_config: LLMConfig = None,
517
- # memory
518
- memory: Memory = ChatMemory(human=get_human_text(DEFAULT_HUMAN), persona=get_persona_text(DEFAULT_PERSONA)),
519
- # Existing blocks
520
- block_ids: Optional[List[str]] = None,
521
- # system
522
- system: Optional[str] = None,
523
- # tools
524
- tool_ids: Optional[List[str]] = None,
525
- tool_rules: Optional[List[BaseToolRule]] = None,
526
- include_base_tools: Optional[bool] = True,
527
- include_multi_agent_tools: Optional[bool] = False,
528
- # metadata
529
- metadata: Optional[Dict] = {"human:": DEFAULT_HUMAN, "persona": DEFAULT_PERSONA},
530
- description: Optional[str] = None,
531
- initial_message_sequence: Optional[List[Message]] = None,
532
- tags: Optional[List[str]] = None,
533
- message_buffer_autoclear: bool = False,
534
- response_format: Optional[ResponseFormatUnion] = None,
535
- ) -> AgentState:
536
- """Create an agent
537
-
538
- Args:
539
- name (str): Name of the agent
540
- embedding_config (EmbeddingConfig): Embedding configuration
541
- llm_config (LLMConfig): LLM configuration
542
- memory (Memory): Memory configuration
543
- system (str): System configuration
544
- tool_ids (List[str]): List of tool ids
545
- include_base_tools (bool): Include base tools
546
- metadata (Dict): Metadata
547
- description (str): Description
548
- tags (List[str]): Tags for filtering agents
549
-
550
- Returns:
551
- agent_state (AgentState): State of the created agent
552
- """
553
- tool_ids = tool_ids or []
554
- tool_names = []
555
- if include_base_tools:
556
- tool_names += BASE_TOOLS
557
- tool_names += BASE_MEMORY_TOOLS
558
- tool_ids += [self.get_tool_id(tool_name=name) for name in tool_names]
559
-
560
- assert embedding_config or self._default_embedding_config, "Embedding config must be provided"
561
- assert llm_config or self._default_llm_config, "LLM config must be provided"
562
-
563
- # TODO: This should not happen here, we need to have clear separation between create/add blocks
564
- # TODO: This is insanely hacky and a result of allowing free-floating blocks
565
- # TODO: When we create the block, it gets it's own block ID
566
- blocks = []
567
- for block in memory.get_blocks():
568
- blocks.append(
569
- self.create_block(
570
- label=block.label,
571
- value=block.value,
572
- limit=block.limit,
573
- template_name=block.template_name,
574
- is_template=block.is_template,
575
- )
576
- )
577
- memory.blocks = blocks
578
- block_ids = block_ids or []
579
-
580
- # create agent
581
- create_params = {
582
- "description": description,
583
- "metadata": metadata,
584
- "memory_blocks": [],
585
- "block_ids": [b.id for b in memory.get_blocks()] + block_ids,
586
- "tool_ids": tool_ids,
587
- "tool_rules": tool_rules,
588
- "system": system,
589
- "agent_type": agent_type,
590
- "llm_config": llm_config if llm_config else self._default_llm_config,
591
- "embedding_config": embedding_config if embedding_config else self._default_embedding_config,
592
- "initial_message_sequence": initial_message_sequence,
593
- "tags": tags,
594
- "include_base_tools": include_base_tools,
595
- "message_buffer_autoclear": message_buffer_autoclear,
596
- "include_multi_agent_tools": include_multi_agent_tools,
597
- "response_format": response_format,
598
- }
599
-
600
- # Only add name if it's not None
601
- if name is not None:
602
- create_params["name"] = name
603
-
604
- request = CreateAgent(**create_params)
605
-
606
- # Use model_dump_json() instead of model_dump()
607
- # If we use model_dump(), the datetime objects will not be serialized correctly
608
- # response = requests.post(f"{self.base_url}/{self.api_prefix}/agents", json=request.model_dump(), headers=self.headers)
609
- response = requests.post(
610
- f"{self.base_url}/{self.api_prefix}/agents",
611
- data=request.model_dump_json(), # Use model_dump_json() instead of json=model_dump()
612
- headers={"Content-Type": "application/json", **self.headers},
613
- )
614
-
615
- if response.status_code != 200:
616
- raise ValueError(f"Status {response.status_code} - Failed to create agent: {response.text}")
617
-
618
- # gather agent state
619
- agent_state = AgentState(**response.json())
620
-
621
- # refresh and return agent
622
- return self.get_agent(agent_state.id)
623
-
624
- def update_agent(
625
- self,
626
- agent_id: str,
627
- name: Optional[str] = None,
628
- description: Optional[str] = None,
629
- system: Optional[str] = None,
630
- tool_ids: Optional[List[str]] = None,
631
- metadata: Optional[Dict] = None,
632
- llm_config: Optional[LLMConfig] = None,
633
- embedding_config: Optional[EmbeddingConfig] = None,
634
- message_ids: Optional[List[str]] = None,
635
- tags: Optional[List[str]] = None,
636
- response_format: Optional[ResponseFormatUnion] = None,
637
- ) -> AgentState:
638
- """
639
- Update an existing agent
640
-
641
- Args:
642
- agent_id (str): ID of the agent
643
- name (str): Name of the agent
644
- description (str): Description of the agent
645
- system (str): System configuration
646
- tool_ids (List[str]): List of tools
647
- metadata (Dict): Metadata
648
- llm_config (LLMConfig): LLM configuration
649
- embedding_config (EmbeddingConfig): Embedding configuration
650
- message_ids (List[str]): List of message IDs
651
- tags (List[str]): Tags for filtering agents
652
-
653
- Returns:
654
- agent_state (AgentState): State of the updated agent
655
- """
656
- request = UpdateAgent(
657
- name=name,
658
- system=system,
659
- tool_ids=tool_ids,
660
- tags=tags,
661
- description=description,
662
- metadata=metadata,
663
- llm_config=llm_config,
664
- embedding_config=embedding_config,
665
- message_ids=message_ids,
666
- response_format=response_format,
667
- )
668
- response = requests.patch(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}", json=request.model_dump(), headers=self.headers)
669
- if response.status_code != 200:
670
- raise ValueError(f"Failed to update agent: {response.text}")
671
- return AgentState(**response.json())
672
-
673
- def get_tools_from_agent(self, agent_id: str) -> List[Tool]:
674
- """
675
- Get tools to an existing agent
676
-
677
- Args:
678
- agent_id (str): ID of the agent
679
-
680
- Returns:
681
- List[Tool]: A List of Tool objs
682
- """
683
- response = requests.get(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/tools", headers=self.headers)
684
- if response.status_code != 200:
685
- raise ValueError(f"Failed to get tools from agents: {response.text}")
686
- return [Tool(**tool) for tool in response.json()]
687
-
688
- def attach_tool(self, agent_id: str, tool_id: str) -> AgentState:
689
- """
690
- Add tool to an existing agent
691
-
692
- Args:
693
- agent_id (str): ID of the agent
694
- tool_id (str): A tool id
695
-
696
- Returns:
697
- agent_state (AgentState): State of the updated agent
698
- """
699
- response = requests.patch(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/tools/attach/{tool_id}", headers=self.headers)
700
- if response.status_code != 200:
701
- raise ValueError(f"Failed to update agent: {response.text}")
702
- return AgentState(**response.json())
703
-
704
- def detach_tool(self, agent_id: str, tool_id: str) -> AgentState:
705
- """
706
- Removes tools from an existing agent
707
-
708
- Args:
709
- agent_id (str): ID of the agent
710
- tool_id (str): The tool id
711
-
712
- Returns:
713
- agent_state (AgentState): State of the updated agent
714
- """
715
-
716
- response = requests.patch(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/tools/detach/{tool_id}", headers=self.headers)
717
- if response.status_code != 200:
718
- raise ValueError(f"Failed to update agent: {response.text}")
719
- return AgentState(**response.json())
720
-
721
- def rename_agent(self, agent_id: str, new_name: str) -> AgentState:
722
- """
723
- Rename an agent
724
-
725
- Args:
726
- agent_id (str): ID of the agent
727
- new_name (str): New name for the agent
728
-
729
- Returns:
730
- agent_state (AgentState): State of the updated agent
731
- """
732
- return self.update_agent(agent_id, name=new_name)
733
-
734
- def delete_agent(self, agent_id: str) -> None:
735
- """
736
- Delete an agent
737
-
738
- Args:
739
- agent_id (str): ID of the agent to delete
740
- """
741
- response = requests.delete(f"{self.base_url}/{self.api_prefix}/agents/{str(agent_id)}", headers=self.headers)
742
- assert response.status_code == 200, f"Failed to delete agent: {response.text}"
743
-
744
- def get_agent(self, agent_id: Optional[str] = None, agent_name: Optional[str] = None) -> AgentState:
745
- """
746
- Get an agent's state by it's ID.
747
-
748
- Args:
749
- agent_id (str): ID of the agent
750
-
751
- Returns:
752
- agent_state (AgentState): State representation of the agent
753
- """
754
- response = requests.get(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}", headers=self.headers)
755
- assert response.status_code == 200, f"Failed to get agent: {response.text}"
756
- return AgentState(**response.json())
757
-
758
- def get_agent_id(self, agent_name: str) -> AgentState:
759
- """
760
- Get the ID of an agent by name (names are unique per user)
761
-
762
- Args:
763
- agent_name (str): Name of the agent
764
-
765
- Returns:
766
- agent_id (str): ID of the agent
767
- """
768
- # TODO: implement this
769
- response = requests.get(f"{self.base_url}/{self.api_prefix}/agents", headers=self.headers, params={"name": agent_name})
770
- agents = [AgentState(**agent) for agent in response.json()]
771
- if len(agents) == 0:
772
- return None
773
- agents = [agents[0]] # TODO: @matt monkeypatched
774
- assert len(agents) == 1, f"Multiple agents with the same name: {[(agents.name, agents.id) for agents in agents]}"
775
- return agents[0].id
776
-
777
- # memory
778
- def get_in_context_memory(self, agent_id: str) -> Memory:
779
- """
780
- Get the in-contxt (i.e. core) memory of an agent
781
-
782
- Args:
783
- agent_id (str): ID of the agent
784
-
785
- Returns:
786
- memory (Memory): In-context memory of the agent
787
- """
788
- response = requests.get(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/core-memory", headers=self.headers)
789
- if response.status_code != 200:
790
- raise ValueError(f"Failed to get in-context memory: {response.text}")
791
- return Memory(**response.json())
792
-
793
- def get_core_memory(self, agent_id: str) -> Memory:
794
- return self.get_in_context_memory(agent_id)
795
-
796
- def update_in_context_memory(self, agent_id: str, section: str, value: Union[List[str], str]) -> Memory:
797
- """
798
- Update the in-context memory of an agent
799
-
800
- Args:
801
- agent_id (str): ID of the agent
802
-
803
- Returns:
804
- memory (Memory): The updated in-context memory of the agent
805
-
806
- """
807
- memory_update_dict = {section: value}
808
- response = requests.patch(
809
- f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/core-memory", json=memory_update_dict, headers=self.headers
810
- )
811
- if response.status_code != 200:
812
- raise ValueError(f"Failed to update in-context memory: {response.text}")
813
- return Memory(**response.json())
814
-
815
- def get_archival_memory_summary(self, agent_id: str) -> ArchivalMemorySummary:
816
- """
817
- Get a summary of the archival memory of an agent
818
-
819
- Args:
820
- agent_id (str): ID of the agent
821
-
822
- Returns:
823
- summary (ArchivalMemorySummary): Summary of the archival memory
824
-
825
- """
826
- response = requests.get(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/context", headers=self.headers)
827
- if response.status_code != 200:
828
- raise ValueError(f"Failed to get archival memory summary: {response.text}")
829
- return ArchivalMemorySummary(size=response.json().get("num_archival_memory", 0))
830
-
831
- def get_recall_memory_summary(self, agent_id: str) -> RecallMemorySummary:
832
- """
833
- Get a summary of the recall memory of an agent
834
-
835
- Args:
836
- agent_id (str): ID of the agent
837
-
838
- Returns:
839
- summary (RecallMemorySummary): Summary of the recall memory
840
- """
841
- response = requests.get(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/context", headers=self.headers)
842
- if response.status_code != 200:
843
- raise ValueError(f"Failed to get recall memory summary: {response.text}")
844
- return RecallMemorySummary(size=response.json().get("num_recall_memory", 0))
845
-
846
- def get_in_context_messages(self, agent_id: str) -> List[Message]:
847
- """
848
- Get in-context messages of an agent
849
-
850
- Args:
851
- agent_id (str): ID of the agent
852
-
853
- Returns:
854
- messages (List[Message]): List of in-context messages
855
- """
856
- response = requests.get(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/context", headers=self.headers)
857
- if response.status_code != 200:
858
- raise ValueError(f"Failed to get recall memory summary: {response.text}")
859
- return [Message(**message) for message in response.json().get("messages", "")]
860
-
861
- # agent interactions
862
-
863
- def user_message(self, agent_id: str, message: str) -> LettaResponse:
864
- """
865
- Send a message to an agent as a user
866
-
867
- Args:
868
- agent_id (str): ID of the agent
869
- message (str): Message to send
870
-
871
- Returns:
872
- response (LettaResponse): Response from the agent
873
- """
874
- return self.send_message(agent_id=agent_id, message=message, role="user")
875
-
876
- def save(self):
877
- raise NotImplementedError
878
-
879
- # archival memory
880
-
881
- def get_archival_memory(
882
- self, agent_id: str, before: Optional[str] = None, after: Optional[str] = None, limit: Optional[int] = 1000
883
- ) -> List[Passage]:
884
- """
885
- Get archival memory from an agent with pagination.
886
-
887
- Args:
888
- agent_id (str): ID of the agent
889
- before (str): Get memories before a certain time
890
- after (str): Get memories after a certain time
891
- limit (int): Limit number of memories
892
-
893
- Returns:
894
- passages (List[Passage]): List of passages
895
- """
896
- params = {"limit": limit}
897
- if before:
898
- params["before"] = str(before)
899
- if after:
900
- params["after"] = str(after)
901
- response = requests.get(
902
- f"{self.base_url}/{self.api_prefix}/agents/{str(agent_id)}/archival-memory", params=params, headers=self.headers
903
- )
904
- assert response.status_code == 200, f"Failed to get archival memory: {response.text}"
905
- return [Passage(**passage) for passage in response.json()]
906
-
907
- def insert_archival_memory(self, agent_id: str, memory: str) -> List[Passage]:
908
- """
909
- Insert archival memory into an agent
910
-
911
- Args:
912
- agent_id (str): ID of the agent
913
- memory (str): Memory string to insert
914
-
915
- Returns:
916
- passages (List[Passage]): List of inserted passages
917
- """
918
- request = CreateArchivalMemory(text=memory)
919
- response = requests.post(
920
- f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/archival-memory", headers=self.headers, json=request.model_dump()
921
- )
922
- if response.status_code != 200:
923
- raise ValueError(f"Failed to insert archival memory: {response.text}")
924
- return [Passage(**passage) for passage in response.json()]
925
-
926
- def delete_archival_memory(self, agent_id: str, memory_id: str):
927
- """
928
- Delete archival memory from an agent
929
-
930
- Args:
931
- agent_id (str): ID of the agent
932
- memory_id (str): ID of the memory
933
- """
934
- response = requests.delete(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/archival-memory/{memory_id}", headers=self.headers)
935
- assert response.status_code == 200, f"Failed to delete archival memory: {response.text}"
936
-
937
- # messages (recall memory)
938
-
939
- def get_messages(
940
- self, agent_id: str, before: Optional[str] = None, after: Optional[str] = None, limit: Optional[int] = 1000
941
- ) -> List[LettaMessage]:
942
- """
943
- Get messages from an agent with pagination.
944
-
945
- Args:
946
- agent_id (str): ID of the agent
947
- before (str): Get messages before a certain time
948
- after (str): Get messages after a certain time
949
- limit (int): Limit number of messages
950
-
951
- Returns:
952
- messages (List[Message]): List of messages
953
- """
954
-
955
- params = {"before": before, "after": after, "limit": limit, "msg_object": True}
956
- response = requests.get(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/messages", params=params, headers=self.headers)
957
- if response.status_code != 200:
958
- raise ValueError(f"Failed to get messages: {response.text}")
959
- return [LettaMessage(**message) for message in response.json()]
960
-
961
- def send_message(
962
- self,
963
- message: str,
964
- role: str,
965
- agent_id: Optional[str] = None,
966
- name: Optional[str] = None,
967
- stream: Optional[bool] = False,
968
- stream_steps: bool = False,
969
- stream_tokens: bool = False,
970
- max_steps: Optional[int] = 10,
971
- ) -> LettaResponse:
972
- """
973
- Send a message to an agent
974
-
975
- Args:
976
- message (str): Message to send
977
- role (str): Role of the message
978
- agent_id (str): ID of the agent
979
- name(str): Name of the sender
980
- stream (bool): Stream the response (default: `False`)
981
- stream_tokens (bool): Stream tokens (default: `False`)
982
- max_steps (int): Maximum number of steps the agent should take (default: 10)
983
-
984
- Returns:
985
- response (LettaResponse): Response from the agent
986
- """
987
- # TODO: implement include_full_message
988
- messages = [MessageCreate(role=MessageRole(role), content=message, name=name)]
989
- # TODO: figure out how to handle stream_steps and stream_tokens
990
-
991
- # When streaming steps is True, stream_tokens must be False
992
- if stream_tokens or stream_steps:
993
- from letta.client.streaming import _sse_post
994
-
995
- request = LettaStreamingRequest(messages=messages, stream_tokens=stream_tokens)
996
- return _sse_post(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/messages/stream", request.model_dump(), self.headers)
997
- else:
998
- request = LettaRequest(messages=messages)
999
- response = requests.post(
1000
- f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/messages", json=request.model_dump(), headers=self.headers
1001
- )
1002
- if response.status_code != 200:
1003
- raise ValueError(f"Failed to send message: {response.text}")
1004
- response = LettaResponse(**response.json())
1005
-
1006
- # simplify messages
1007
- # if not include_full_message:
1008
- # messages = []
1009
- # for m in response.messages:
1010
- # assert isinstance(m, Message)
1011
- # messages += m.to_letta_messages()
1012
- # response.messages = messages
1013
-
1014
- return response
1015
-
1016
- def send_message_async(
1017
- self,
1018
- message: str,
1019
- role: str,
1020
- agent_id: Optional[str] = None,
1021
- name: Optional[str] = None,
1022
- ) -> Run:
1023
- """
1024
- Send a message to an agent (async, returns a job)
1025
-
1026
- Args:
1027
- message (str): Message to send
1028
- role (str): Role of the message
1029
- agent_id (str): ID of the agent
1030
- name(str): Name of the sender
1031
-
1032
- Returns:
1033
- job (Job): Information about the async job
1034
- """
1035
- messages = [MessageCreate(role=MessageRole(role), content=message, name=name)]
1036
-
1037
- request = LettaRequest(messages=messages)
1038
- response = requests.post(
1039
- f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/messages/async",
1040
- json=request.model_dump(),
1041
- headers=self.headers,
1042
- )
1043
- if response.status_code != 200:
1044
- raise ValueError(f"Failed to send message: {response.text}")
1045
- response = Run(**response.json())
1046
-
1047
- return response
1048
-
1049
- # humans / personas
1050
-
1051
- def list_blocks(self, label: Optional[str] = None, templates_only: Optional[bool] = True) -> List[Block]:
1052
- params = {"label": label, "templates_only": templates_only}
1053
- response = requests.get(f"{self.base_url}/{self.api_prefix}/blocks", params=params, headers=self.headers)
1054
- if response.status_code != 200:
1055
- raise ValueError(f"Failed to list blocks: {response.text}")
1056
-
1057
- if label == "human":
1058
- return [Human(**human) for human in response.json()]
1059
- elif label == "persona":
1060
- return [Persona(**persona) for persona in response.json()]
1061
- else:
1062
- return [Block(**block) for block in response.json()]
1063
-
1064
- def create_block(
1065
- self, label: str, value: str, limit: Optional[int] = None, template_name: Optional[str] = None, is_template: bool = False
1066
- ) -> Block: #
1067
- request_kwargs = dict(label=label, value=value, template=is_template, template_name=template_name)
1068
- if limit:
1069
- request_kwargs["limit"] = limit
1070
- request = CreateBlock(**request_kwargs)
1071
- response = requests.post(f"{self.base_url}/{self.api_prefix}/blocks", json=request.model_dump(), headers=self.headers)
1072
- if response.status_code != 200:
1073
- raise ValueError(f"Failed to create block: {response.text}")
1074
- if request.label == "human":
1075
- return Human(**response.json())
1076
- elif request.label == "persona":
1077
- return Persona(**response.json())
1078
- else:
1079
- return Block(**response.json())
1080
-
1081
- def update_block(self, block_id: str, name: Optional[str] = None, text: Optional[str] = None, limit: Optional[int] = None) -> Block:
1082
- request = BlockUpdate(id=block_id, template_name=name, value=text, limit=limit if limit else self.get_block(block_id).limit)
1083
- response = requests.post(f"{self.base_url}/{self.api_prefix}/blocks/{block_id}", json=request.model_dump(), headers=self.headers)
1084
- if response.status_code != 200:
1085
- raise ValueError(f"Failed to update block: {response.text}")
1086
- return Block(**response.json())
1087
-
1088
- def get_block(self, block_id: str) -> Optional[Block]:
1089
- response = requests.get(f"{self.base_url}/{self.api_prefix}/blocks/{block_id}", headers=self.headers)
1090
- if response.status_code == 404:
1091
- return None
1092
- elif response.status_code != 200:
1093
- raise ValueError(f"Failed to get block: {response.text}")
1094
- return Block(**response.json())
1095
-
1096
- def get_block_id(self, name: str, label: str) -> str:
1097
- params = {"name": name, "label": label}
1098
- response = requests.get(f"{self.base_url}/{self.api_prefix}/blocks", params=params, headers=self.headers)
1099
- if response.status_code != 200:
1100
- raise ValueError(f"Failed to get block ID: {response.text}")
1101
- blocks = [Block(**block) for block in response.json()]
1102
- if len(blocks) == 0:
1103
- return None
1104
- elif len(blocks) > 1:
1105
- raise ValueError(f"Multiple blocks found with name {name}")
1106
- return blocks[0].id
1107
-
1108
- def delete_block(self, id: str) -> Block:
1109
- response = requests.delete(f"{self.base_url}/{self.api_prefix}/blocks/{id}", headers=self.headers)
1110
- assert response.status_code == 200, f"Failed to delete block: {response.text}"
1111
- if response.status_code != 200:
1112
- raise ValueError(f"Failed to delete block: {response.text}")
1113
- return Block(**response.json())
1114
-
1115
- def list_humans(self):
1116
- """
1117
- List available human block templates
1118
-
1119
- Returns:
1120
- humans (List[Human]): List of human blocks
1121
- """
1122
- blocks = self.list_blocks(label="human")
1123
- return [Human(**block.model_dump()) for block in blocks]
1124
-
1125
- def create_human(self, name: str, text: str) -> Human:
1126
- """
1127
- Create a human block template (saved human string to pre-fill `ChatMemory`)
1128
-
1129
- Args:
1130
- name (str): Name of the human block template
1131
- text (str): Text of the human block template
1132
-
1133
- Returns:
1134
- human (Human): Human block
1135
- """
1136
- return self.create_block(label="human", template_name=name, value=text, is_template=True)
1137
-
1138
- def update_human(self, human_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Human:
1139
- """
1140
- Update a human block template
1141
-
1142
- Args:
1143
- human_id (str): ID of the human block
1144
- text (str): Text of the human block
1145
-
1146
- Returns:
1147
- human (Human): Updated human block
1148
- """
1149
- request = UpdateHuman(id=human_id, template_name=name, value=text)
1150
- response = requests.post(f"{self.base_url}/{self.api_prefix}/blocks/{human_id}", json=request.model_dump(), headers=self.headers)
1151
- if response.status_code != 200:
1152
- raise ValueError(f"Failed to update human: {response.text}")
1153
- return Human(**response.json())
1154
-
1155
- def list_personas(self):
1156
- """
1157
- List available persona block templates
1158
-
1159
- Returns:
1160
- personas (List[Persona]): List of persona blocks
1161
- """
1162
- blocks = self.list_blocks(label="persona")
1163
- return [Persona(**block.model_dump()) for block in blocks]
1164
-
1165
- def create_persona(self, name: str, text: str) -> Persona:
1166
- """
1167
- Create a persona block template (saved persona string to pre-fill `ChatMemory`)
1168
-
1169
- Args:
1170
- name (str): Name of the persona block
1171
- text (str): Text of the persona block
1172
-
1173
- Returns:
1174
- persona (Persona): Persona block
1175
- """
1176
- return self.create_block(label="persona", template_name=name, value=text, is_template=True)
1177
-
1178
- def update_persona(self, persona_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Persona:
1179
- """
1180
- Update a persona block template
1181
-
1182
- Args:
1183
- persona_id (str): ID of the persona block
1184
- text (str): Text of the persona block
1185
-
1186
- Returns:
1187
- persona (Persona): Updated persona block
1188
- """
1189
- request = UpdatePersona(id=persona_id, template_name=name, value=text)
1190
- response = requests.post(f"{self.base_url}/{self.api_prefix}/blocks/{persona_id}", json=request.model_dump(), headers=self.headers)
1191
- if response.status_code != 200:
1192
- raise ValueError(f"Failed to update persona: {response.text}")
1193
- return Persona(**response.json())
1194
-
1195
- def get_persona(self, persona_id: str) -> Persona:
1196
- """
1197
- Get a persona block template
1198
-
1199
- Args:
1200
- id (str): ID of the persona block
1201
-
1202
- Returns:
1203
- persona (Persona): Persona block
1204
- """
1205
- return self.get_block(persona_id)
1206
-
1207
- def get_persona_id(self, name: str) -> str:
1208
- """
1209
- Get the ID of a persona block template
1210
-
1211
- Args:
1212
- name (str): Name of the persona block
1213
-
1214
- Returns:
1215
- id (str): ID of the persona block
1216
- """
1217
- return self.get_block_id(name, "persona")
1218
-
1219
- def delete_persona(self, persona_id: str) -> Persona:
1220
- """
1221
- Delete a persona block template
1222
-
1223
- Args:
1224
- id (str): ID of the persona block
1225
- """
1226
- return self.delete_block(persona_id)
1227
-
1228
- def get_human(self, human_id: str) -> Human:
1229
- """
1230
- Get a human block template
1231
-
1232
- Args:
1233
- id (str): ID of the human block
1234
-
1235
- Returns:
1236
- human (Human): Human block
1237
- """
1238
- return self.get_block(human_id)
1239
-
1240
- def get_human_id(self, name: str) -> str:
1241
- """
1242
- Get the ID of a human block template
1243
-
1244
- Args:
1245
- name (str): Name of the human block
1246
-
1247
- Returns:
1248
- id (str): ID of the human block
1249
- """
1250
- return self.get_block_id(name, "human")
1251
-
1252
- def delete_human(self, human_id: str) -> Human:
1253
- """
1254
- Delete a human block template
1255
-
1256
- Args:
1257
- id (str): ID of the human block
1258
- """
1259
- return self.delete_block(human_id)
1260
-
1261
- # sources
1262
-
1263
- def get_source(self, source_id: str) -> Source:
1264
- """
1265
- Get a source given the ID.
1266
-
1267
- Args:
1268
- source_id (str): ID of the source
1269
-
1270
- Returns:
1271
- source (Source): Source
1272
- """
1273
- response = requests.get(f"{self.base_url}/{self.api_prefix}/sources/{source_id}", headers=self.headers)
1274
- if response.status_code != 200:
1275
- raise ValueError(f"Failed to get source: {response.text}")
1276
- return Source(**response.json())
1277
-
1278
- def get_source_id(self, source_name: str) -> str:
1279
- """
1280
- Get the ID of a source
1281
-
1282
- Args:
1283
- source_name (str): Name of the source
1284
-
1285
- Returns:
1286
- source_id (str): ID of the source
1287
- """
1288
- response = requests.get(f"{self.base_url}/{self.api_prefix}/sources/name/{source_name}", headers=self.headers)
1289
- if response.status_code != 200:
1290
- raise ValueError(f"Failed to get source ID: {response.text}")
1291
- return response.json()
1292
-
1293
- def list_sources(self) -> List[Source]:
1294
- """
1295
- List available sources
1296
-
1297
- Returns:
1298
- sources (List[Source]): List of sources
1299
- """
1300
- response = requests.get(f"{self.base_url}/{self.api_prefix}/sources", headers=self.headers)
1301
- if response.status_code != 200:
1302
- raise ValueError(f"Failed to list sources: {response.text}")
1303
- return [Source(**source) for source in response.json()]
1304
-
1305
- def delete_source(self, source_id: str):
1306
- """
1307
- Delete a source
1308
-
1309
- Args:
1310
- source_id (str): ID of the source
1311
- """
1312
- response = requests.delete(f"{self.base_url}/{self.api_prefix}/sources/{str(source_id)}", headers=self.headers)
1313
- assert response.status_code == 200, f"Failed to delete source: {response.text}"
1314
-
1315
- def get_job(self, job_id: str) -> Job:
1316
- response = requests.get(f"{self.base_url}/{self.api_prefix}/jobs/{job_id}", headers=self.headers)
1317
- if response.status_code != 200:
1318
- raise ValueError(f"Failed to get job: {response.text}")
1319
- return Job(**response.json())
1320
-
1321
- def delete_job(self, job_id: str) -> Job:
1322
- response = requests.delete(f"{self.base_url}/{self.api_prefix}/jobs/{job_id}", headers=self.headers)
1323
- if response.status_code != 200:
1324
- raise ValueError(f"Failed to delete job: {response.text}")
1325
- return Job(**response.json())
1326
-
1327
- def list_jobs(self):
1328
- response = requests.get(f"{self.base_url}/{self.api_prefix}/jobs", headers=self.headers)
1329
- return [Job(**job) for job in response.json()]
1330
-
1331
- def list_active_jobs(self):
1332
- response = requests.get(f"{self.base_url}/{self.api_prefix}/jobs/active", headers=self.headers)
1333
- return [Job(**job) for job in response.json()]
1334
-
1335
- def load_data(self, connector: DataConnector, source_name: str):
1336
- raise NotImplementedError
1337
-
1338
- def load_file_to_source(self, filename: str, source_id: str, blocking=True) -> Job:
1339
- """
1340
- Load a file into a source
1341
-
1342
- Args:
1343
- filename (str): Name of the file
1344
- source_id (str): ID of the source
1345
- blocking (bool): Block until the job is complete
1346
-
1347
- Returns:
1348
- job (Job): Data loading job including job status and metadata
1349
- """
1350
- files = {"file": open(filename, "rb")}
1351
-
1352
- # create job
1353
- response = requests.post(f"{self.base_url}/{self.api_prefix}/sources/{source_id}/upload", files=files, headers=self.headers)
1354
- if response.status_code != 200:
1355
- raise ValueError(f"Failed to upload file to source: {response.text}")
1356
-
1357
- job = Job(**response.json())
1358
- if blocking:
1359
- # wait until job is completed
1360
- while True:
1361
- job = self.get_job(job.id)
1362
- if job.status == JobStatus.completed:
1363
- break
1364
- elif job.status == JobStatus.failed:
1365
- raise ValueError(f"Job failed: {job.metadata}")
1366
- time.sleep(1)
1367
- return job
1368
-
1369
- def delete_file_from_source(self, source_id: str, file_id: str) -> None:
1370
- response = requests.delete(f"{self.base_url}/{self.api_prefix}/sources/{source_id}/{file_id}", headers=self.headers)
1371
- if response.status_code not in [200, 204]:
1372
- raise ValueError(f"Failed to delete tool: {response.text}")
1373
-
1374
- def create_source(self, name: str, embedding_config: Optional[EmbeddingConfig] = None) -> Source:
1375
- """
1376
- Create a source
1377
-
1378
- Args:
1379
- name (str): Name of the source
1380
-
1381
- Returns:
1382
- source (Source): Created source
1383
- """
1384
- assert embedding_config or self._default_embedding_config, "Must specify embedding_config for source"
1385
- source_create = SourceCreate(name=name, embedding_config=embedding_config or self._default_embedding_config)
1386
- payload = source_create.model_dump()
1387
- response = requests.post(f"{self.base_url}/{self.api_prefix}/sources", json=payload, headers=self.headers)
1388
- response_json = response.json()
1389
- return Source(**response_json)
1390
-
1391
- def list_attached_sources(self, agent_id: str) -> List[Source]:
1392
- """
1393
- List sources attached to an agent
1394
-
1395
- Args:
1396
- agent_id (str): ID of the agent
1397
-
1398
- Returns:
1399
- sources (List[Source]): List of sources
1400
- """
1401
- response = requests.get(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/sources", headers=self.headers)
1402
- if response.status_code != 200:
1403
- raise ValueError(f"Failed to list attached sources: {response.text}")
1404
- return [Source(**source) for source in response.json()]
1405
-
1406
- def list_files_from_source(self, source_id: str, limit: int = 1000, after: Optional[str] = None) -> List[FileMetadata]:
1407
- """
1408
- List files from source with pagination support.
1409
-
1410
- Args:
1411
- source_id (str): ID of the source
1412
- limit (int): Number of files to return
1413
- after (str): Get files after a certain time
1414
-
1415
- Returns:
1416
- List[FileMetadata]: List of files
1417
- """
1418
- # Prepare query parameters for pagination
1419
- params = {"limit": limit, "after": after}
1420
-
1421
- # Make the request to the FastAPI endpoint
1422
- response = requests.get(f"{self.base_url}/{self.api_prefix}/sources/{source_id}/files", headers=self.headers, params=params)
1423
-
1424
- if response.status_code != 200:
1425
- raise ValueError(f"Failed to list files with source id {source_id}: [{response.status_code}] {response.text}")
1426
-
1427
- # Parse the JSON response
1428
- return [FileMetadata(**metadata) for metadata in response.json()]
1429
-
1430
- def update_source(self, source_id: str, name: Optional[str] = None) -> Source:
1431
- """
1432
- Update a source
1433
-
1434
- Args:
1435
- source_id (str): ID of the source
1436
- name (str): Name of the source
1437
-
1438
- Returns:
1439
- source (Source): Updated source
1440
- """
1441
- request = SourceUpdate(name=name)
1442
- response = requests.patch(f"{self.base_url}/{self.api_prefix}/sources/{source_id}", json=request.model_dump(), headers=self.headers)
1443
- if response.status_code != 200:
1444
- raise ValueError(f"Failed to update source: {response.text}")
1445
- return Source(**response.json())
1446
-
1447
- def attach_source(self, source_id: str, agent_id: str) -> AgentState:
1448
- """
1449
- Attach a source to an agent
1450
-
1451
- Args:
1452
- agent_id (str): ID of the agent
1453
- source_id (str): ID of the source
1454
- source_name (str): Name of the source
1455
- """
1456
- params = {"agent_id": agent_id}
1457
- response = requests.patch(
1458
- f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/sources/attach/{source_id}", params=params, headers=self.headers
1459
- )
1460
- assert response.status_code == 200, f"Failed to attach source to agent: {response.text}"
1461
- return AgentState(**response.json())
1462
-
1463
- def detach_source(self, source_id: str, agent_id: str) -> AgentState:
1464
- """Detach a source from an agent"""
1465
- params = {"agent_id": str(agent_id)}
1466
- response = requests.patch(
1467
- f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/sources/detach/{source_id}", params=params, headers=self.headers
1468
- )
1469
- assert response.status_code == 200, f"Failed to detach source from agent: {response.text}"
1470
- return AgentState(**response.json())
1471
-
1472
- # tools
1473
-
1474
- def get_tool_id(self, tool_name: str):
1475
- """
1476
- Get the ID of a tool
1477
-
1478
- Args:
1479
- name (str): Name of the tool
1480
-
1481
- Returns:
1482
- id (str): ID of the tool (`None` if not found)
1483
- """
1484
- response = requests.get(f"{self.base_url}/{self.api_prefix}/tools", headers=self.headers)
1485
- if response.status_code != 200:
1486
- raise ValueError(f"Failed to get tool: {response.text}")
1487
-
1488
- tools = [tool for tool in [Tool(**tool) for tool in response.json()] if tool.name == tool_name]
1489
- if not tools:
1490
- return None
1491
- return tools[0].id
1492
-
1493
- def list_attached_tools(self, agent_id: str) -> List[Tool]:
1494
- """
1495
- List all tools attached to an agent.
1496
-
1497
- Args:
1498
- agent_id (str): ID of the agent
1499
-
1500
- Returns:
1501
- List[Tool]: A list of attached tools
1502
- """
1503
- response = requests.get(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/tools", headers=self.headers)
1504
- if response.status_code != 200:
1505
- raise ValueError(f"Failed to list attached tools: {response.text}")
1506
- return [Tool(**tool) for tool in response.json()]
1507
-
1508
- def upsert_base_tools(self) -> List[Tool]:
1509
- response = requests.post(f"{self.base_url}/{self.api_prefix}/tools/add-base-tools/", headers=self.headers)
1510
- if response.status_code != 200:
1511
- raise ValueError(f"Failed to add base tools: {response.text}")
1512
-
1513
- return [Tool(**tool) for tool in response.json()]
1514
-
1515
- def create_tool(
1516
- self,
1517
- func: Callable,
1518
- tags: Optional[List[str]] = None,
1519
- return_char_limit: int = FUNCTION_RETURN_CHAR_LIMIT,
1520
- ) -> Tool:
1521
- """
1522
- Create a tool. This stores the source code of function on the server, so that the server can execute the function and generate an OpenAI JSON schemas for it when using with an agent.
1523
-
1524
- Args:
1525
- func (callable): The function to create a tool for.
1526
- name: (str): Name of the tool (must be unique per-user.)
1527
- tags (Optional[List[str]], optional): Tags for the tool. Defaults to None.
1528
- return_char_limit (int): The character limit for the tool's return value. Defaults to FUNCTION_RETURN_CHAR_LIMIT.
1529
-
1530
- Returns:
1531
- tool (Tool): The created tool.
1532
- """
1533
- source_code = parse_source_code(func)
1534
- source_type = "python"
1535
-
1536
- # call server function
1537
- request = ToolCreate(source_type=source_type, source_code=source_code, return_char_limit=return_char_limit)
1538
- if tags:
1539
- request.tags = tags
1540
- response = requests.post(f"{self.base_url}/{self.api_prefix}/tools", json=request.model_dump(), headers=self.headers)
1541
- if response.status_code != 200:
1542
- raise ValueError(f"Failed to create tool: {response.text}")
1543
- return Tool(**response.json())
1544
-
1545
- def create_or_update_tool(
1546
- self,
1547
- func: Callable,
1548
- tags: Optional[List[str]] = None,
1549
- return_char_limit: int = FUNCTION_RETURN_CHAR_LIMIT,
1550
- ) -> Tool:
1551
- """
1552
- Creates or updates a tool. This stores the source code of function on the server, so that the server can execute the function and generate an OpenAI JSON schemas for it when using with an agent.
1553
-
1554
- Args:
1555
- func (callable): The function to create a tool for.
1556
- name: (str): Name of the tool (must be unique per-user.)
1557
- tags (Optional[List[str]], optional): Tags for the tool. Defaults to None.
1558
- return_char_limit (int): The character limit for the tool's return value. Defaults to FUNCTION_RETURN_CHAR_LIMIT.
1559
-
1560
- Returns:
1561
- tool (Tool): The created tool.
1562
- """
1563
- source_code = parse_source_code(func)
1564
- source_type = "python"
1565
-
1566
- # call server function
1567
- request = ToolCreate(source_type=source_type, source_code=source_code, return_char_limit=return_char_limit)
1568
- if tags:
1569
- request.tags = tags
1570
- response = requests.put(f"{self.base_url}/{self.api_prefix}/tools", json=request.model_dump(), headers=self.headers)
1571
- if response.status_code != 200:
1572
- raise ValueError(f"Failed to create tool: {response.text}")
1573
- return Tool(**response.json())
1574
-
1575
- def update_tool(
1576
- self,
1577
- id: str,
1578
- description: Optional[str] = None,
1579
- func: Optional[Callable] = None,
1580
- tags: Optional[List[str]] = None,
1581
- return_char_limit: int = FUNCTION_RETURN_CHAR_LIMIT,
1582
- ) -> Tool:
1583
- """
1584
- Update a tool with provided parameters (name, func, tags)
1585
-
1586
- Args:
1587
- id (str): ID of the tool
1588
- name (str): Name of the tool
1589
- func (callable): Function to wrap in a tool
1590
- tags (List[str]): Tags for the tool
1591
- return_char_limit (int): The character limit for the tool's return value. Defaults to FUNCTION_RETURN_CHAR_LIMIT.
1592
-
1593
- Returns:
1594
- tool (Tool): Updated tool
1595
- """
1596
- if func:
1597
- source_code = parse_source_code(func)
1598
- else:
1599
- source_code = None
1600
-
1601
- source_type = "python"
1602
-
1603
- request = ToolUpdate(
1604
- description=description,
1605
- source_type=source_type,
1606
- source_code=source_code,
1607
- tags=tags,
1608
- return_char_limit=return_char_limit,
1609
- )
1610
- response = requests.patch(f"{self.base_url}/{self.api_prefix}/tools/{id}", json=request.model_dump(), headers=self.headers)
1611
- if response.status_code != 200:
1612
- raise ValueError(f"Failed to update tool: {response.text}")
1613
- return Tool(**response.json())
1614
-
1615
- def list_tools(self, after: Optional[str] = None, limit: Optional[int] = 50) -> List[Tool]:
1616
- """
1617
- List available tools for the user.
1618
-
1619
- Returns:
1620
- tools (List[Tool]): List of tools
1621
- """
1622
- params = {}
1623
- if after:
1624
- params["after"] = after
1625
- if limit:
1626
- params["limit"] = limit
1627
-
1628
- response = requests.get(f"{self.base_url}/{self.api_prefix}/tools", params=params, headers=self.headers)
1629
- if response.status_code != 200:
1630
- raise ValueError(f"Failed to list tools: {response.text}")
1631
- return [Tool(**tool) for tool in response.json()]
1632
-
1633
- def delete_tool(self, name: str):
1634
- """
1635
- Delete a tool given the ID.
1636
-
1637
- Args:
1638
- id (str): ID of the tool
1639
- """
1640
- response = requests.delete(f"{self.base_url}/{self.api_prefix}/tools/{name}", headers=self.headers)
1641
- if response.status_code != 200:
1642
- raise ValueError(f"Failed to delete tool: {response.text}")
1643
-
1644
- def get_tool(self, id: str) -> Optional[Tool]:
1645
- """
1646
- Get a tool give its ID.
1647
-
1648
- Args:
1649
- id (str): ID of the tool
1650
-
1651
- Returns:
1652
- tool (Tool): Tool
1653
- """
1654
- response = requests.get(f"{self.base_url}/{self.api_prefix}/tools/{id}", headers=self.headers)
1655
- if response.status_code == 404:
1656
- return None
1657
- elif response.status_code != 200:
1658
- raise ValueError(f"Failed to get tool: {response.text}")
1659
- return Tool(**response.json())
1660
-
1661
- def set_default_llm_config(self, llm_config: LLMConfig):
1662
- """
1663
- Set the default LLM configuration
1664
-
1665
- Args:
1666
- llm_config (LLMConfig): LLM configuration
1667
- """
1668
- self._default_llm_config = llm_config
1669
-
1670
- def set_default_embedding_config(self, embedding_config: EmbeddingConfig):
1671
- """
1672
- Set the default embedding configuration
1673
-
1674
- Args:
1675
- embedding_config (EmbeddingConfig): Embedding configuration
1676
- """
1677
- self._default_embedding_config = embedding_config
1678
-
1679
- def list_llm_configs(self) -> List[LLMConfig]:
1680
- """
1681
- List available LLM configurations
1682
-
1683
- Returns:
1684
- configs (List[LLMConfig]): List of LLM configurations
1685
- """
1686
- response = requests.get(f"{self.base_url}/{self.api_prefix}/models", headers=self.headers)
1687
- if response.status_code != 200:
1688
- raise ValueError(f"Failed to list LLM configs: {response.text}")
1689
- return [LLMConfig(**config) for config in response.json()]
1690
-
1691
- def list_embedding_configs(self) -> List[EmbeddingConfig]:
1692
- """
1693
- List available embedding configurations
1694
-
1695
- Returns:
1696
- configs (List[EmbeddingConfig]): List of embedding configurations
1697
- """
1698
- response = requests.get(f"{self.base_url}/{self.api_prefix}/models/embedding", headers=self.headers)
1699
- if response.status_code != 200:
1700
- raise ValueError(f"Failed to list embedding configs: {response.text}")
1701
- return [EmbeddingConfig(**config) for config in response.json()]
1702
-
1703
- def list_orgs(self, after: Optional[str] = None, limit: Optional[int] = 50) -> List[Organization]:
1704
- """
1705
- Retrieves a list of all organizations in the database, with optional pagination.
1706
-
1707
- @param after: the pagination cursor, if any
1708
- @param limit: the maximum number of organizations to retrieve
1709
- @return: a list of Organization objects
1710
- """
1711
- params = {"after": after, "limit": limit}
1712
- response = requests.get(f"{self.base_url}/{ADMIN_PREFIX}/orgs", headers=self.headers, params=params)
1713
- if response.status_code != 200:
1714
- raise ValueError(f"Failed to retrieve organizations: {response.text}")
1715
- return [Organization(**org_data) for org_data in response.json()]
1716
-
1717
- def create_org(self, name: Optional[str] = None) -> Organization:
1718
- """
1719
- Creates an organization with the given name. If not provided, we generate a random one.
1720
-
1721
- @param name: the name of the organization
1722
- @return: the created Organization
1723
- """
1724
- payload = {"name": name}
1725
- response = requests.post(f"{self.base_url}/{ADMIN_PREFIX}/orgs", headers=self.headers, json=payload)
1726
- if response.status_code != 200:
1727
- raise ValueError(f"Failed to create org: {response.text}")
1728
- return Organization(**response.json())
1729
-
1730
- def delete_org(self, org_id: str) -> Organization:
1731
- """
1732
- Deletes an organization by its ID.
1733
-
1734
- @param org_id: the ID of the organization to delete
1735
- @return: the deleted Organization object
1736
- """
1737
- # Define query parameters with org_id
1738
- params = {"org_id": org_id}
1739
-
1740
- # Make the DELETE request with query parameters
1741
- response = requests.delete(f"{self.base_url}/{ADMIN_PREFIX}/orgs", headers=self.headers, params=params)
1742
-
1743
- if response.status_code == 404:
1744
- raise ValueError(f"Organization with ID '{org_id}' does not exist")
1745
- elif response.status_code != 200:
1746
- raise ValueError(f"Failed to delete organization: {response.text}")
1747
-
1748
- # Parse and return the deleted organization
1749
- return Organization(**response.json())
1750
-
1751
- def create_sandbox_config(self, config: Union[LocalSandboxConfig, E2BSandboxConfig]) -> SandboxConfig:
1752
- """
1753
- Create a new sandbox configuration.
1754
-
1755
- Args:
1756
- config (Union[LocalSandboxConfig, E2BSandboxConfig]): The sandbox settings.
1757
-
1758
- Returns:
1759
- SandboxConfig: The created sandbox configuration.
1760
- """
1761
- payload = {
1762
- "config": config.model_dump(),
1763
- }
1764
- response = requests.post(f"{self.base_url}/{self.api_prefix}/sandbox-config", headers=self.headers, json=payload)
1765
- if response.status_code != 200:
1766
- raise ValueError(f"Failed to create sandbox config: {response.text}")
1767
- return SandboxConfig(**response.json())
1768
-
1769
- def update_sandbox_config(self, sandbox_config_id: str, config: Union[LocalSandboxConfig, E2BSandboxConfig]) -> SandboxConfig:
1770
- """
1771
- Update an existing sandbox configuration.
1772
-
1773
- Args:
1774
- sandbox_config_id (str): The ID of the sandbox configuration to update.
1775
- config (Union[LocalSandboxConfig, E2BSandboxConfig]): The updated sandbox settings.
1776
-
1777
- Returns:
1778
- SandboxConfig: The updated sandbox configuration.
1779
- """
1780
- payload = {
1781
- "config": config.model_dump(),
1782
- }
1783
- response = requests.patch(
1784
- f"{self.base_url}/{self.api_prefix}/sandbox-config/{sandbox_config_id}",
1785
- headers=self.headers,
1786
- json=payload,
1787
- )
1788
- if response.status_code != 200:
1789
- raise ValueError(f"Failed to update sandbox config with ID '{sandbox_config_id}': {response.text}")
1790
- return SandboxConfig(**response.json())
1791
-
1792
- def delete_sandbox_config(self, sandbox_config_id: str) -> None:
1793
- """
1794
- Delete a sandbox configuration.
1795
-
1796
- Args:
1797
- sandbox_config_id (str): The ID of the sandbox configuration to delete.
1798
- """
1799
- response = requests.delete(f"{self.base_url}/{self.api_prefix}/sandbox-config/{sandbox_config_id}", headers=self.headers)
1800
- if response.status_code == 404:
1801
- raise ValueError(f"Sandbox config with ID '{sandbox_config_id}' does not exist")
1802
- elif response.status_code != 204:
1803
- raise ValueError(f"Failed to delete sandbox config with ID '{sandbox_config_id}': {response.text}")
1804
-
1805
- def list_sandbox_configs(self, limit: int = 50, after: Optional[str] = None) -> List[SandboxConfig]:
1806
- """
1807
- List all sandbox configurations.
1808
-
1809
- Args:
1810
- limit (int, optional): The maximum number of sandbox configurations to return. Defaults to 50.
1811
- after (Optional[str], optional): The pagination cursor for retrieving the next set of results.
1812
-
1813
- Returns:
1814
- List[SandboxConfig]: A list of sandbox configurations.
1815
- """
1816
- params = {"limit": limit, "after": after}
1817
- response = requests.get(f"{self.base_url}/{self.api_prefix}/sandbox-config", headers=self.headers, params=params)
1818
- if response.status_code != 200:
1819
- raise ValueError(f"Failed to list sandbox configs: {response.text}")
1820
- return [SandboxConfig(**config_data) for config_data in response.json()]
1821
-
1822
- def create_sandbox_env_var(
1823
- self, sandbox_config_id: str, key: str, value: str, description: Optional[str] = None
1824
- ) -> SandboxEnvironmentVariable:
1825
- """
1826
- Create a new environment variable for a sandbox configuration.
1827
-
1828
- Args:
1829
- sandbox_config_id (str): The ID of the sandbox configuration to associate the environment variable with.
1830
- key (str): The name of the environment variable.
1831
- value (str): The value of the environment variable.
1832
- description (Optional[str], optional): A description of the environment variable. Defaults to None.
1833
-
1834
- Returns:
1835
- SandboxEnvironmentVariable: The created environment variable.
1836
- """
1837
- payload = {"key": key, "value": value, "description": description}
1838
- response = requests.post(
1839
- f"{self.base_url}/{self.api_prefix}/sandbox-config/{sandbox_config_id}/environment-variable",
1840
- headers=self.headers,
1841
- json=payload,
1842
- )
1843
- if response.status_code != 200:
1844
- raise ValueError(f"Failed to create environment variable for sandbox config ID '{sandbox_config_id}': {response.text}")
1845
- return SandboxEnvironmentVariable(**response.json())
1846
-
1847
- def update_sandbox_env_var(
1848
- self, env_var_id: str, key: Optional[str] = None, value: Optional[str] = None, description: Optional[str] = None
1849
- ) -> SandboxEnvironmentVariable:
1850
- """
1851
- Update an existing environment variable.
1852
-
1853
- Args:
1854
- env_var_id (str): The ID of the environment variable to update.
1855
- key (Optional[str], optional): The updated name of the environment variable. Defaults to None.
1856
- value (Optional[str], optional): The updated value of the environment variable. Defaults to None.
1857
- description (Optional[str], optional): The updated description of the environment variable. Defaults to None.
1858
-
1859
- Returns:
1860
- SandboxEnvironmentVariable: The updated environment variable.
1861
- """
1862
- payload = {k: v for k, v in {"key": key, "value": value, "description": description}.items() if v is not None}
1863
- response = requests.patch(
1864
- f"{self.base_url}/{self.api_prefix}/sandbox-config/environment-variable/{env_var_id}",
1865
- headers=self.headers,
1866
- json=payload,
1867
- )
1868
- if response.status_code != 200:
1869
- raise ValueError(f"Failed to update environment variable with ID '{env_var_id}': {response.text}")
1870
- return SandboxEnvironmentVariable(**response.json())
1871
-
1872
- def delete_sandbox_env_var(self, env_var_id: str) -> None:
1873
- """
1874
- Delete an environment variable by its ID.
1875
-
1876
- Args:
1877
- env_var_id (str): The ID of the environment variable to delete.
1878
- """
1879
- response = requests.delete(
1880
- f"{self.base_url}/{self.api_prefix}/sandbox-config/environment-variable/{env_var_id}", headers=self.headers
1881
- )
1882
- if response.status_code == 404:
1883
- raise ValueError(f"Environment variable with ID '{env_var_id}' does not exist")
1884
- elif response.status_code != 204:
1885
- raise ValueError(f"Failed to delete environment variable with ID '{env_var_id}': {response.text}")
1886
-
1887
- def list_sandbox_env_vars(
1888
- self, sandbox_config_id: str, limit: int = 50, after: Optional[str] = None
1889
- ) -> List[SandboxEnvironmentVariable]:
1890
- """
1891
- List all environment variables associated with a sandbox configuration.
1892
-
1893
- Args:
1894
- sandbox_config_id (str): The ID of the sandbox configuration to retrieve environment variables for.
1895
- limit (int, optional): The maximum number of environment variables to return. Defaults to 50.
1896
- after (Optional[str], optional): The pagination cursor for retrieving the next set of results.
1897
-
1898
- Returns:
1899
- List[SandboxEnvironmentVariable]: A list of environment variables.
1900
- """
1901
- params = {"limit": limit, "after": after}
1902
- response = requests.get(
1903
- f"{self.base_url}/{self.api_prefix}/sandbox-config/{sandbox_config_id}/environment-variable",
1904
- headers=self.headers,
1905
- params=params,
1906
- )
1907
- if response.status_code != 200:
1908
- raise ValueError(f"Failed to list environment variables for sandbox config ID '{sandbox_config_id}': {response.text}")
1909
- return [SandboxEnvironmentVariable(**var_data) for var_data in response.json()]
1910
-
1911
- def update_agent_memory_block_label(self, agent_id: str, current_label: str, new_label: str) -> Memory:
1912
- """Rename a block in the agent's core memory
1913
-
1914
- Args:
1915
- agent_id (str): The agent ID
1916
- current_label (str): The current label of the block
1917
- new_label (str): The new label of the block
1918
-
1919
- Returns:
1920
- memory (Memory): The updated memory
1921
- """
1922
- block = self.get_agent_memory_block(agent_id, current_label)
1923
- return self.update_block(block.id, label=new_label)
1924
-
1925
- def attach_block(self, agent_id: str, block_id: str) -> AgentState:
1926
- """
1927
- Attach a block to an agent.
1928
-
1929
- Args:
1930
- agent_id (str): ID of the agent
1931
- block_id (str): ID of the block to attach
1932
- """
1933
- response = requests.patch(
1934
- f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/core-memory/blocks/attach/{block_id}",
1935
- headers=self.headers,
1936
- )
1937
- if response.status_code != 200:
1938
- raise ValueError(f"Failed to attach block to agent: {response.text}")
1939
- return AgentState(**response.json())
1940
-
1941
- def detach_block(self, agent_id: str, block_id: str) -> AgentState:
1942
- """
1943
- Detach a block from an agent.
1944
-
1945
- Args:
1946
- agent_id (str): ID of the agent
1947
- block_id (str): ID of the block to detach
1948
- """
1949
- response = requests.patch(
1950
- f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/core-memory/blocks/detach/{block_id}", headers=self.headers
1951
- )
1952
- if response.status_code != 200:
1953
- raise ValueError(f"Failed to detach block from agent: {response.text}")
1954
- return AgentState(**response.json())
1955
-
1956
- def list_agent_memory_blocks(self, agent_id: str) -> List[Block]:
1957
- """
1958
- Get all the blocks in the agent's core memory
1959
-
1960
- Args:
1961
- agent_id (str): The agent ID
1962
-
1963
- Returns:
1964
- blocks (List[Block]): The blocks in the agent's core memory
1965
- """
1966
- response = requests.get(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/core-memory/blocks", headers=self.headers)
1967
- if response.status_code != 200:
1968
- raise ValueError(f"Failed to get agent memory blocks: {response.text}")
1969
- return [Block(**block) for block in response.json()]
1970
-
1971
- def get_agent_memory_block(self, agent_id: str, label: str) -> Block:
1972
- """
1973
- Get a block in the agent's core memory by its label
1974
-
1975
- Args:
1976
- agent_id (str): The agent ID
1977
- label (str): The label in the agent's core memory
1978
-
1979
- Returns:
1980
- block (Block): The block corresponding to the label
1981
- """
1982
- response = requests.get(
1983
- f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/core-memory/blocks/{label}",
1984
- headers=self.headers,
1985
- )
1986
- if response.status_code != 200:
1987
- raise ValueError(f"Failed to get agent memory block: {response.text}")
1988
- return Block(**response.json())
1989
-
1990
- def update_agent_memory_block(
1991
- self,
1992
- agent_id: str,
1993
- label: str,
1994
- value: Optional[str] = None,
1995
- limit: Optional[int] = None,
1996
- ):
1997
- """
1998
- Update a block in the agent's core memory by specifying its label
1999
-
2000
- Args:
2001
- agent_id (str): The agent ID
2002
- label (str): The label of the block
2003
- value (str): The new value of the block
2004
- limit (int): The new limit of the block
2005
-
2006
- Returns:
2007
- block (Block): The updated block
2008
- """
2009
- # setup data
2010
- data = {}
2011
- if value:
2012
- data["value"] = value
2013
- if limit:
2014
- data["limit"] = limit
2015
- response = requests.patch(
2016
- f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/core-memory/blocks/{label}",
2017
- headers=self.headers,
2018
- json=data,
2019
- )
2020
- if response.status_code != 200:
2021
- raise ValueError(f"Failed to update agent memory block: {response.text}")
2022
- return Block(**response.json())
2023
-
2024
- def update_block(
2025
- self,
2026
- block_id: str,
2027
- label: Optional[str] = None,
2028
- value: Optional[str] = None,
2029
- limit: Optional[int] = None,
2030
- ):
2031
- """
2032
- Update a block given the ID with the provided fields
2033
-
2034
- Args:
2035
- block_id (str): ID of the block
2036
- label (str): Label to assign to the block
2037
- value (str): Value to assign to the block
2038
- limit (int): Token limit to assign to the block
2039
-
2040
- Returns:
2041
- block (Block): Updated block
2042
- """
2043
- data = {}
2044
- if value:
2045
- data["value"] = value
2046
- if limit:
2047
- data["limit"] = limit
2048
- if label:
2049
- data["label"] = label
2050
- response = requests.patch(
2051
- f"{self.base_url}/{self.api_prefix}/blocks/{block_id}",
2052
- headers=self.headers,
2053
- json=data,
2054
- )
2055
- if response.status_code != 200:
2056
- raise ValueError(f"Failed to update block: {response.text}")
2057
- return Block(**response.json())
2058
-
2059
- def get_run_messages(
2060
- self,
2061
- run_id: str,
2062
- before: Optional[str] = None,
2063
- after: Optional[str] = None,
2064
- limit: Optional[int] = 100,
2065
- ascending: bool = True,
2066
- role: Optional[MessageRole] = None,
2067
- ) -> List[LettaMessageUnion]:
2068
- """
2069
- Get messages associated with a job with filtering options.
2070
-
2071
- Args:
2072
- job_id: ID of the job
2073
- before: Cursor for pagination
2074
- after: Cursor for pagination
2075
- limit: Maximum number of messages to return
2076
- ascending: Sort order by creation time
2077
- role: Filter by message role (user/assistant/system/tool)
2078
- Returns:
2079
- List of messages matching the filter criteria
2080
- """
2081
- params = {
2082
- "before": before,
2083
- "after": after,
2084
- "limit": limit,
2085
- "ascending": ascending,
2086
- "role": role,
2087
- }
2088
- # Remove None values
2089
- params = {k: v for k, v in params.items() if v is not None}
2090
-
2091
- response = requests.get(f"{self.base_url}/{self.api_prefix}/runs/{run_id}/messages", params=params)
2092
- if response.status_code != 200:
2093
- raise ValueError(f"Failed to get run messages: {response.text}")
2094
- return [LettaMessage(**message) for message in response.json()]
2095
-
2096
- def get_run_usage(
2097
- self,
2098
- run_id: str,
2099
- ) -> List[UsageStatistics]:
2100
- """
2101
- Get usage statistics associated with a job.
2102
-
2103
- Args:
2104
- job_id (str): ID of the job
2105
-
2106
- Returns:
2107
- List[UsageStatistics]: List of usage statistics associated with the job
2108
- """
2109
- response = requests.get(
2110
- f"{self.base_url}/{self.api_prefix}/runs/{run_id}/usage",
2111
- headers=self.headers,
2112
- )
2113
- if response.status_code != 200:
2114
- raise ValueError(f"Failed to get run usage statistics: {response.text}")
2115
- return [UsageStatistics(**stat) for stat in [response.json()]]
2116
-
2117
- def get_run(self, run_id: str) -> Run:
2118
- """
2119
- Get a run by ID.
2120
-
2121
- Args:
2122
- run_id (str): ID of the run
2123
-
2124
- Returns:
2125
- run (Run): Run
2126
- """
2127
- response = requests.get(
2128
- f"{self.base_url}/{self.api_prefix}/runs/{run_id}",
2129
- headers=self.headers,
2130
- )
2131
- if response.status_code != 200:
2132
- raise ValueError(f"Failed to get run: {response.text}")
2133
- return Run(**response.json())
2134
-
2135
- def delete_run(self, run_id: str) -> None:
2136
- """
2137
- Delete a run by ID.
2138
-
2139
- Args:
2140
- run_id (str): ID of the run
2141
- """
2142
- response = requests.delete(
2143
- f"{self.base_url}/{self.api_prefix}/runs/{run_id}",
2144
- headers=self.headers,
2145
- )
2146
- if response.status_code != 200:
2147
- raise ValueError(f"Failed to delete run: {response.text}")
2148
-
2149
- def list_runs(self) -> List[Run]:
2150
- """
2151
- List all runs.
2152
-
2153
- Returns:
2154
- runs (List[Run]): List of runs
2155
- """
2156
- response = requests.get(
2157
- f"{self.base_url}/{self.api_prefix}/runs",
2158
- headers=self.headers,
2159
- )
2160
- if response.status_code != 200:
2161
- raise ValueError(f"Failed to list runs: {response.text}")
2162
- return [Run(**run) for run in response.json()]
2163
-
2164
- def list_active_runs(self) -> List[Run]:
2165
- """
2166
- List all active runs.
2167
-
2168
- Returns:
2169
- runs (List[Run]): List of active runs
2170
- """
2171
- response = requests.get(
2172
- f"{self.base_url}/{self.api_prefix}/runs/active",
2173
- headers=self.headers,
2174
- )
2175
- if response.status_code != 200:
2176
- raise ValueError(f"Failed to list active runs: {response.text}")
2177
- return [Run(**run) for run in response.json()]
2178
-
2179
- def get_tags(
2180
- self,
2181
- after: Optional[str] = None,
2182
- limit: int = 100,
2183
- query_text: Optional[str] = None,
2184
- ) -> List[str]:
2185
- """
2186
- Get a list of all unique tags.
2187
-
2188
- Args:
2189
- after: Optional cursor for pagination (first tag seen)
2190
- limit: Optional maximum number of tags to return
2191
- query_text: Optional text to filter tags
2192
-
2193
- Returns:
2194
- List[str]: List of unique tags
2195
- """
2196
- params = {}
2197
- if after:
2198
- params["after"] = after
2199
- if limit:
2200
- params["limit"] = limit
2201
- if query_text:
2202
- params["query_text"] = query_text
2203
-
2204
- response = requests.get(f"{self.base_url}/{self.api_prefix}/tags", headers=self.headers, params=params)
2205
- if response.status_code != 200:
2206
- raise ValueError(f"Failed to get tags: {response.text}")
2207
- return response.json()