letta-nightly 0.1.7.dev20240924104148__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of letta-nightly might be problematic. Click here for more details.

Files changed (189) hide show
  1. letta/__init__.py +24 -0
  2. letta/__main__.py +3 -0
  3. letta/agent.py +1427 -0
  4. letta/agent_store/chroma.py +295 -0
  5. letta/agent_store/db.py +546 -0
  6. letta/agent_store/lancedb.py +177 -0
  7. letta/agent_store/milvus.py +198 -0
  8. letta/agent_store/qdrant.py +201 -0
  9. letta/agent_store/storage.py +188 -0
  10. letta/benchmark/benchmark.py +96 -0
  11. letta/benchmark/constants.py +14 -0
  12. letta/cli/cli.py +689 -0
  13. letta/cli/cli_config.py +1282 -0
  14. letta/cli/cli_load.py +166 -0
  15. letta/client/__init__.py +0 -0
  16. letta/client/admin.py +171 -0
  17. letta/client/client.py +2360 -0
  18. letta/client/streaming.py +90 -0
  19. letta/client/utils.py +61 -0
  20. letta/config.py +484 -0
  21. letta/configs/anthropic.json +13 -0
  22. letta/configs/letta_hosted.json +11 -0
  23. letta/configs/openai.json +12 -0
  24. letta/constants.py +134 -0
  25. letta/credentials.py +140 -0
  26. letta/data_sources/connectors.py +247 -0
  27. letta/embeddings.py +218 -0
  28. letta/errors.py +26 -0
  29. letta/functions/__init__.py +0 -0
  30. letta/functions/function_sets/base.py +174 -0
  31. letta/functions/function_sets/extras.py +132 -0
  32. letta/functions/functions.py +105 -0
  33. letta/functions/schema_generator.py +205 -0
  34. letta/humans/__init__.py +0 -0
  35. letta/humans/examples/basic.txt +1 -0
  36. letta/humans/examples/cs_phd.txt +9 -0
  37. letta/interface.py +314 -0
  38. letta/llm_api/__init__.py +0 -0
  39. letta/llm_api/anthropic.py +383 -0
  40. letta/llm_api/azure_openai.py +155 -0
  41. letta/llm_api/cohere.py +396 -0
  42. letta/llm_api/google_ai.py +468 -0
  43. letta/llm_api/llm_api_tools.py +485 -0
  44. letta/llm_api/openai.py +470 -0
  45. letta/local_llm/README.md +3 -0
  46. letta/local_llm/__init__.py +0 -0
  47. letta/local_llm/chat_completion_proxy.py +279 -0
  48. letta/local_llm/constants.py +31 -0
  49. letta/local_llm/function_parser.py +68 -0
  50. letta/local_llm/grammars/__init__.py +0 -0
  51. letta/local_llm/grammars/gbnf_grammar_generator.py +1324 -0
  52. letta/local_llm/grammars/json.gbnf +26 -0
  53. letta/local_llm/grammars/json_func_calls_with_inner_thoughts.gbnf +32 -0
  54. letta/local_llm/groq/api.py +97 -0
  55. letta/local_llm/json_parser.py +202 -0
  56. letta/local_llm/koboldcpp/api.py +62 -0
  57. letta/local_llm/koboldcpp/settings.py +23 -0
  58. letta/local_llm/llamacpp/api.py +58 -0
  59. letta/local_llm/llamacpp/settings.py +22 -0
  60. letta/local_llm/llm_chat_completion_wrappers/__init__.py +0 -0
  61. letta/local_llm/llm_chat_completion_wrappers/airoboros.py +452 -0
  62. letta/local_llm/llm_chat_completion_wrappers/chatml.py +470 -0
  63. letta/local_llm/llm_chat_completion_wrappers/configurable_wrapper.py +387 -0
  64. letta/local_llm/llm_chat_completion_wrappers/dolphin.py +246 -0
  65. letta/local_llm/llm_chat_completion_wrappers/llama3.py +345 -0
  66. letta/local_llm/llm_chat_completion_wrappers/simple_summary_wrapper.py +156 -0
  67. letta/local_llm/llm_chat_completion_wrappers/wrapper_base.py +11 -0
  68. letta/local_llm/llm_chat_completion_wrappers/zephyr.py +345 -0
  69. letta/local_llm/lmstudio/api.py +100 -0
  70. letta/local_llm/lmstudio/settings.py +29 -0
  71. letta/local_llm/ollama/api.py +88 -0
  72. letta/local_llm/ollama/settings.py +32 -0
  73. letta/local_llm/settings/__init__.py +0 -0
  74. letta/local_llm/settings/deterministic_mirostat.py +45 -0
  75. letta/local_llm/settings/settings.py +72 -0
  76. letta/local_llm/settings/simple.py +28 -0
  77. letta/local_llm/utils.py +265 -0
  78. letta/local_llm/vllm/api.py +63 -0
  79. letta/local_llm/webui/api.py +60 -0
  80. letta/local_llm/webui/legacy_api.py +58 -0
  81. letta/local_llm/webui/legacy_settings.py +23 -0
  82. letta/local_llm/webui/settings.py +24 -0
  83. letta/log.py +76 -0
  84. letta/main.py +437 -0
  85. letta/memory.py +440 -0
  86. letta/metadata.py +884 -0
  87. letta/openai_backcompat/__init__.py +0 -0
  88. letta/openai_backcompat/openai_object.py +437 -0
  89. letta/persistence_manager.py +148 -0
  90. letta/personas/__init__.py +0 -0
  91. letta/personas/examples/anna_pa.txt +13 -0
  92. letta/personas/examples/google_search_persona.txt +15 -0
  93. letta/personas/examples/memgpt_doc.txt +6 -0
  94. letta/personas/examples/memgpt_starter.txt +4 -0
  95. letta/personas/examples/sam.txt +14 -0
  96. letta/personas/examples/sam_pov.txt +14 -0
  97. letta/personas/examples/sam_simple_pov_gpt35.txt +13 -0
  98. letta/personas/examples/sqldb/test.db +0 -0
  99. letta/prompts/__init__.py +0 -0
  100. letta/prompts/gpt_summarize.py +14 -0
  101. letta/prompts/gpt_system.py +26 -0
  102. letta/prompts/system/memgpt_base.txt +49 -0
  103. letta/prompts/system/memgpt_chat.txt +58 -0
  104. letta/prompts/system/memgpt_chat_compressed.txt +13 -0
  105. letta/prompts/system/memgpt_chat_fstring.txt +51 -0
  106. letta/prompts/system/memgpt_doc.txt +50 -0
  107. letta/prompts/system/memgpt_gpt35_extralong.txt +53 -0
  108. letta/prompts/system/memgpt_intuitive_knowledge.txt +31 -0
  109. letta/prompts/system/memgpt_modified_chat.txt +23 -0
  110. letta/pytest.ini +0 -0
  111. letta/schemas/agent.py +117 -0
  112. letta/schemas/api_key.py +21 -0
  113. letta/schemas/block.py +135 -0
  114. letta/schemas/document.py +21 -0
  115. letta/schemas/embedding_config.py +54 -0
  116. letta/schemas/enums.py +35 -0
  117. letta/schemas/job.py +38 -0
  118. letta/schemas/letta_base.py +80 -0
  119. letta/schemas/letta_message.py +175 -0
  120. letta/schemas/letta_request.py +23 -0
  121. letta/schemas/letta_response.py +28 -0
  122. letta/schemas/llm_config.py +54 -0
  123. letta/schemas/memory.py +224 -0
  124. letta/schemas/message.py +727 -0
  125. letta/schemas/openai/chat_completion_request.py +123 -0
  126. letta/schemas/openai/chat_completion_response.py +136 -0
  127. letta/schemas/openai/chat_completions.py +123 -0
  128. letta/schemas/openai/embedding_response.py +11 -0
  129. letta/schemas/openai/openai.py +157 -0
  130. letta/schemas/organization.py +20 -0
  131. letta/schemas/passage.py +80 -0
  132. letta/schemas/source.py +62 -0
  133. letta/schemas/tool.py +143 -0
  134. letta/schemas/usage.py +18 -0
  135. letta/schemas/user.py +33 -0
  136. letta/server/__init__.py +0 -0
  137. letta/server/constants.py +6 -0
  138. letta/server/rest_api/__init__.py +0 -0
  139. letta/server/rest_api/admin/__init__.py +0 -0
  140. letta/server/rest_api/admin/agents.py +21 -0
  141. letta/server/rest_api/admin/tools.py +83 -0
  142. letta/server/rest_api/admin/users.py +98 -0
  143. letta/server/rest_api/app.py +193 -0
  144. letta/server/rest_api/auth/__init__.py +0 -0
  145. letta/server/rest_api/auth/index.py +43 -0
  146. letta/server/rest_api/auth_token.py +22 -0
  147. letta/server/rest_api/interface.py +726 -0
  148. letta/server/rest_api/routers/__init__.py +0 -0
  149. letta/server/rest_api/routers/openai/__init__.py +0 -0
  150. letta/server/rest_api/routers/openai/assistants/__init__.py +0 -0
  151. letta/server/rest_api/routers/openai/assistants/assistants.py +115 -0
  152. letta/server/rest_api/routers/openai/assistants/schemas.py +121 -0
  153. letta/server/rest_api/routers/openai/assistants/threads.py +336 -0
  154. letta/server/rest_api/routers/openai/chat_completions/__init__.py +0 -0
  155. letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +131 -0
  156. letta/server/rest_api/routers/v1/__init__.py +15 -0
  157. letta/server/rest_api/routers/v1/agents.py +543 -0
  158. letta/server/rest_api/routers/v1/blocks.py +73 -0
  159. letta/server/rest_api/routers/v1/jobs.py +46 -0
  160. letta/server/rest_api/routers/v1/llms.py +28 -0
  161. letta/server/rest_api/routers/v1/organizations.py +61 -0
  162. letta/server/rest_api/routers/v1/sources.py +199 -0
  163. letta/server/rest_api/routers/v1/tools.py +103 -0
  164. letta/server/rest_api/routers/v1/users.py +109 -0
  165. letta/server/rest_api/static_files.py +74 -0
  166. letta/server/rest_api/utils.py +69 -0
  167. letta/server/server.py +1995 -0
  168. letta/server/startup.sh +8 -0
  169. letta/server/static_files/assets/index-0cbf7ad5.js +274 -0
  170. letta/server/static_files/assets/index-156816da.css +1 -0
  171. letta/server/static_files/assets/index-486e3228.js +274 -0
  172. letta/server/static_files/favicon.ico +0 -0
  173. letta/server/static_files/index.html +39 -0
  174. letta/server/static_files/memgpt_logo_transparent.png +0 -0
  175. letta/server/utils.py +46 -0
  176. letta/server/ws_api/__init__.py +0 -0
  177. letta/server/ws_api/example_client.py +104 -0
  178. letta/server/ws_api/interface.py +108 -0
  179. letta/server/ws_api/protocol.py +100 -0
  180. letta/server/ws_api/server.py +145 -0
  181. letta/settings.py +165 -0
  182. letta/streaming_interface.py +396 -0
  183. letta/system.py +207 -0
  184. letta/utils.py +1065 -0
  185. letta_nightly-0.1.7.dev20240924104148.dist-info/LICENSE +190 -0
  186. letta_nightly-0.1.7.dev20240924104148.dist-info/METADATA +98 -0
  187. letta_nightly-0.1.7.dev20240924104148.dist-info/RECORD +189 -0
  188. letta_nightly-0.1.7.dev20240924104148.dist-info/WHEEL +4 -0
  189. letta_nightly-0.1.7.dev20240924104148.dist-info/entry_points.txt +3 -0
letta/client/client.py ADDED
@@ -0,0 +1,2360 @@
1
+ import logging
2
+ import time
3
+ from typing import Callable, Dict, Generator, List, Optional, Union
4
+
5
+ import requests
6
+
7
+ import letta.utils
8
+ from letta.config import LettaConfig
9
+ from letta.constants import BASE_TOOLS, DEFAULT_HUMAN, DEFAULT_PERSONA
10
+ from letta.data_sources.connectors import DataConnector
11
+ from letta.functions.functions import parse_source_code
12
+ from letta.memory import get_memory_functions
13
+ from letta.schemas.agent import AgentState, CreateAgent, UpdateAgentState
14
+ from letta.schemas.block import (
15
+ Block,
16
+ CreateBlock,
17
+ CreateHuman,
18
+ CreatePersona,
19
+ Human,
20
+ Persona,
21
+ UpdateBlock,
22
+ UpdateHuman,
23
+ UpdatePersona,
24
+ )
25
+ from letta.schemas.embedding_config import EmbeddingConfig
26
+
27
+ # new schemas
28
+ from letta.schemas.enums import JobStatus, MessageRole
29
+ from letta.schemas.job import Job
30
+ from letta.schemas.letta_request import LettaRequest
31
+ from letta.schemas.letta_response import LettaResponse, LettaStreamingResponse
32
+ from letta.schemas.llm_config import LLMConfig
33
+ from letta.schemas.memory import (
34
+ ArchivalMemorySummary,
35
+ ChatMemory,
36
+ CreateArchivalMemory,
37
+ Memory,
38
+ RecallMemorySummary,
39
+ )
40
+ from letta.schemas.message import Message, MessageCreate, UpdateMessage
41
+ from letta.schemas.openai.chat_completions import ToolCall
42
+ from letta.schemas.passage import Passage
43
+ from letta.schemas.source import Source, SourceCreate, SourceUpdate
44
+ from letta.schemas.tool import Tool, ToolCreate, ToolUpdate
45
+ from letta.schemas.user import UserCreate
46
+ from letta.server.rest_api.interface import QueuingInterface
47
+ from letta.server.server import SyncServer
48
+ from letta.utils import get_human_text, get_persona_text
49
+
50
+
51
+ def create_client(base_url: Optional[str] = None, token: Optional[str] = None):
52
+ if base_url is None:
53
+ return LocalClient()
54
+ else:
55
+ return RESTClient(base_url, token)
56
+
57
+
58
+ class AbstractClient(object):
59
+ def __init__(
60
+ self,
61
+ auto_save: bool = False,
62
+ debug: bool = False,
63
+ ):
64
+ self.auto_save = auto_save
65
+ self.debug = debug
66
+
67
+ def agent_exists(self, agent_id: Optional[str] = None, agent_name: Optional[str] = None) -> bool:
68
+ raise NotImplementedError
69
+
70
+ def create_agent(
71
+ self,
72
+ name: Optional[str] = None,
73
+ embedding_config: Optional[EmbeddingConfig] = None,
74
+ llm_config: Optional[LLMConfig] = None,
75
+ memory: Memory = ChatMemory(human=get_human_text(DEFAULT_HUMAN), persona=get_persona_text(DEFAULT_PERSONA)),
76
+ system: Optional[str] = None,
77
+ tools: Optional[List[str]] = None,
78
+ include_base_tools: Optional[bool] = True,
79
+ metadata: Optional[Dict] = {"human:": DEFAULT_HUMAN, "persona": DEFAULT_PERSONA},
80
+ description: Optional[str] = None,
81
+ ) -> AgentState:
82
+ raise NotImplementedError
83
+
84
+ def update_agent(
85
+ self,
86
+ agent_id: str,
87
+ name: Optional[str] = None,
88
+ description: Optional[str] = None,
89
+ system: Optional[str] = None,
90
+ tools: Optional[List[str]] = None,
91
+ metadata: Optional[Dict] = None,
92
+ llm_config: Optional[LLMConfig] = None,
93
+ embedding_config: Optional[EmbeddingConfig] = None,
94
+ message_ids: Optional[List[str]] = None,
95
+ memory: Optional[Memory] = None,
96
+ ):
97
+ raise NotImplementedError
98
+
99
+ def rename_agent(self, agent_id: str, new_name: str):
100
+ raise NotImplementedError
101
+
102
+ def delete_agent(self, agent_id: str):
103
+ raise NotImplementedError
104
+
105
+ def get_agent(self, agent_id: str) -> AgentState:
106
+ raise NotImplementedError
107
+
108
+ def get_agent_id(self, agent_name: str) -> AgentState:
109
+ raise NotImplementedError
110
+
111
+ def get_in_context_memory(self, agent_id: str) -> Memory:
112
+ raise NotImplementedError
113
+
114
+ def update_in_context_memory(self, agent_id: str, section: str, value: Union[List[str], str]) -> Memory:
115
+ raise NotImplementedError
116
+
117
+ def get_archival_memory_summary(self, agent_id: str) -> ArchivalMemorySummary:
118
+ raise NotImplementedError
119
+
120
+ def get_recall_memory_summary(self, agent_id: str) -> RecallMemorySummary:
121
+ raise NotImplementedError
122
+
123
+ def get_in_context_messages(self, agent_id: str) -> List[Message]:
124
+ raise NotImplementedError
125
+
126
+ def send_message(
127
+ self,
128
+ message: str,
129
+ role: str,
130
+ agent_id: Optional[str] = None,
131
+ name: Optional[str] = None,
132
+ stream: Optional[bool] = False,
133
+ include_full_message: Optional[bool] = False,
134
+ ) -> LettaResponse:
135
+ raise NotImplementedError
136
+
137
+ def user_message(self, agent_id: str, message: str, include_full_message: Optional[bool] = False) -> LettaResponse:
138
+ raise NotImplementedError
139
+
140
+ def create_human(self, name: str, text: str) -> Human:
141
+ raise NotImplementedError
142
+
143
+ def create_persona(self, name: str, text: str) -> Persona:
144
+ raise NotImplementedError
145
+
146
+ def list_humans(self) -> List[Human]:
147
+ raise NotImplementedError
148
+
149
+ def list_personas(self) -> List[Persona]:
150
+ raise NotImplementedError
151
+
152
+ def update_human(self, human_id: str, text: str) -> Human:
153
+ raise NotImplementedError
154
+
155
+ def update_persona(self, persona_id: str, text: str) -> Persona:
156
+ raise NotImplementedError
157
+
158
+ def get_persona(self, id: str) -> Persona:
159
+ raise NotImplementedError
160
+
161
+ def get_human(self, id: str) -> Human:
162
+ raise NotImplementedError
163
+
164
+ def get_persona_id(self, name: str) -> str:
165
+ raise NotImplementedError
166
+
167
+ def get_human_id(self, name: str) -> str:
168
+ raise NotImplementedError
169
+
170
+ def delete_persona(self, id: str):
171
+ raise NotImplementedError
172
+
173
+ def delete_human(self, id: str):
174
+ raise NotImplementedError
175
+
176
+ def create_tool(
177
+ self,
178
+ func,
179
+ name: Optional[str] = None,
180
+ update: Optional[bool] = True,
181
+ tags: Optional[List[str]] = None,
182
+ ) -> Tool:
183
+ raise NotImplementedError
184
+
185
+ def update_tool(
186
+ self,
187
+ id: str,
188
+ name: Optional[str] = None,
189
+ func: Optional[Callable] = None,
190
+ tags: Optional[List[str]] = None,
191
+ ) -> Tool:
192
+ raise NotImplementedError
193
+
194
+ def list_tools(self) -> List[Tool]:
195
+ raise NotImplementedError
196
+
197
+ def get_tool(self, id: str) -> Tool:
198
+ raise NotImplementedError
199
+
200
+ def delete_tool(self, id: str):
201
+ raise NotImplementedError
202
+
203
+ def get_tool_id(self, name: str) -> Optional[str]:
204
+ raise NotImplementedError
205
+
206
+ def load_data(self, connector: DataConnector, source_name: str):
207
+ raise NotImplementedError
208
+
209
+ def load_file_into_source(self, filename: str, source_id: str, blocking=True) -> Job:
210
+ raise NotImplementedError
211
+
212
+ def create_source(self, name: str) -> Source:
213
+ raise NotImplementedError
214
+
215
+ def delete_source(self, source_id: str):
216
+ raise NotImplementedError
217
+
218
+ def get_source(self, source_id: str) -> Source:
219
+ raise NotImplementedError
220
+
221
+ def get_source_id(self, source_name: str) -> str:
222
+ raise NotImplementedError
223
+
224
+ def attach_source_to_agent(self, agent_id: str, source_id: Optional[str] = None, source_name: Optional[str] = None):
225
+ raise NotImplementedError
226
+
227
+ def detach_source_from_agent(self, agent_id: str, source_id: Optional[str] = None, source_name: Optional[str] = None):
228
+ raise NotImplementedError
229
+
230
+ def list_sources(self) -> List[Source]:
231
+ raise NotImplementedError
232
+
233
+ def list_attached_sources(self, agent_id: str) -> List[Source]:
234
+ raise NotImplementedError
235
+
236
+ def update_source(self, source_id: str, name: Optional[str] = None) -> Source:
237
+ raise NotImplementedError
238
+
239
+ def insert_archival_memory(self, agent_id: str, memory: str) -> List[Passage]:
240
+ raise NotImplementedError
241
+
242
+ def delete_archival_memory(self, agent_id: str, memory_id: str):
243
+ raise NotImplementedError
244
+
245
+ def get_archival_memory(
246
+ self, agent_id: str, before: Optional[str] = None, after: Optional[str] = None, limit: Optional[int] = 1000
247
+ ) -> List[Passage]:
248
+ raise NotImplementedError
249
+
250
+ def get_messages(
251
+ self, agent_id: str, before: Optional[str] = None, after: Optional[str] = None, limit: Optional[int] = 1000
252
+ ) -> List[Message]:
253
+ raise NotImplementedError
254
+
255
+ def list_models(self) -> List[LLMConfig]:
256
+ raise NotImplementedError
257
+
258
+ def list_embedding_models(self) -> List[EmbeddingConfig]:
259
+ raise NotImplementedError
260
+
261
+
262
+ class RESTClient(AbstractClient):
263
+ """
264
+ REST client for Letta
265
+
266
+ Attributes:
267
+ base_url (str): Base URL of the REST API
268
+ headers (Dict): Headers for the REST API (includes token)
269
+ """
270
+
271
+ def __init__(
272
+ self,
273
+ base_url: str,
274
+ token: str,
275
+ api_prefix: str = "v1",
276
+ debug: bool = False,
277
+ ):
278
+ """
279
+ Initializes a new instance of Client class.
280
+
281
+ Args:
282
+ auto_save (bool): Whether to automatically save changes.
283
+ user_id (str): The user ID.
284
+ debug (bool): Whether to print debug information.
285
+ """
286
+ super().__init__(debug=debug)
287
+ self.base_url = base_url
288
+ self.api_prefix = api_prefix
289
+ self.headers = {"accept": "application/json", "authorization": f"Bearer {token}"}
290
+
291
+ def list_agents(self) -> List[AgentState]:
292
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/agents", headers=self.headers)
293
+ return [AgentState(**agent) for agent in response.json()]
294
+
295
+ def agent_exists(self, agent_id: str) -> bool:
296
+ """
297
+ Check if an agent exists
298
+
299
+ Args:
300
+ agent_id (str): ID of the agent
301
+ agent_name (str): Name of the agent
302
+
303
+ Returns:
304
+ exists (bool): `True` if the agent exists, `False` otherwise
305
+ """
306
+
307
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}", headers=self.headers)
308
+ if response.status_code == 404:
309
+ # not found error
310
+ return False
311
+ elif response.status_code == 200:
312
+ return True
313
+ else:
314
+ raise ValueError(f"Failed to check if agent exists: {response.text}")
315
+
316
+ def create_agent(
317
+ self,
318
+ name: Optional[str] = None,
319
+ # model configs
320
+ embedding_config: Optional[EmbeddingConfig] = None,
321
+ llm_config: Optional[LLMConfig] = None,
322
+ # memory
323
+ memory: Memory = ChatMemory(human=get_human_text(DEFAULT_HUMAN), persona=get_persona_text(DEFAULT_PERSONA)),
324
+ # system
325
+ system: Optional[str] = None,
326
+ # tools
327
+ tools: Optional[List[str]] = None,
328
+ include_base_tools: Optional[bool] = True,
329
+ # metadata
330
+ metadata: Optional[Dict] = {"human:": DEFAULT_HUMAN, "persona": DEFAULT_PERSONA},
331
+ description: Optional[str] = None,
332
+ ) -> AgentState:
333
+ """Create an agent
334
+
335
+ Args:
336
+ name (str): Name of the agent
337
+ embedding_config (EmbeddingConfig): Embedding configuration
338
+ llm_config (LLMConfig): LLM configuration
339
+ memory (Memory): Memory configuration
340
+ system (str): System configuration
341
+ tools (List[str]): List of tools
342
+ include_base_tools (bool): Include base tools
343
+ metadata (Dict): Metadata
344
+ description (str): Description
345
+
346
+ Returns:
347
+ agent_state (AgentState): State of the created agent
348
+ """
349
+
350
+ # TODO: implement this check once name lookup works
351
+ # if name:
352
+ # exist_agent_id = self.get_agent_id(agent_name=name)
353
+
354
+ # raise ValueError(f"Agent with name {name} already exists")
355
+
356
+ # construct list of tools
357
+ tool_names = []
358
+ if tools:
359
+ tool_names += tools
360
+ if include_base_tools:
361
+ tool_names += BASE_TOOLS
362
+
363
+ # add memory tools
364
+ memory_functions = get_memory_functions(memory)
365
+ for func_name, func in memory_functions.items():
366
+ tool = self.create_tool(func, name=func_name, tags=["memory", "letta-base"], update=True)
367
+ tool_names.append(tool.name)
368
+
369
+ # create agent
370
+ request = CreateAgent(
371
+ name=name,
372
+ description=description,
373
+ metadata_=metadata,
374
+ memory=memory,
375
+ tools=tool_names,
376
+ system=system,
377
+ llm_config=llm_config,
378
+ embedding_config=embedding_config,
379
+ )
380
+
381
+ response = requests.post(f"{self.base_url}/{self.api_prefix}/agents", json=request.model_dump(), headers=self.headers)
382
+ if response.status_code != 200:
383
+ raise ValueError(f"Status {response.status_code} - Failed to create agent: {response.text}")
384
+ return AgentState(**response.json())
385
+
386
+ def update_message(
387
+ self,
388
+ agent_id: str,
389
+ message_id: str,
390
+ role: Optional[MessageRole] = None,
391
+ text: Optional[str] = None,
392
+ name: Optional[str] = None,
393
+ tool_calls: Optional[List[ToolCall]] = None,
394
+ tool_call_id: Optional[str] = None,
395
+ ) -> Message:
396
+ request = UpdateMessage(
397
+ id=message_id,
398
+ role=role,
399
+ text=text,
400
+ name=name,
401
+ tool_calls=tool_calls,
402
+ tool_call_id=tool_call_id,
403
+ )
404
+ response = requests.patch(
405
+ f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/messages/{message_id}", json=request.model_dump(), headers=self.headers
406
+ )
407
+ if response.status_code != 200:
408
+ raise ValueError(f"Failed to update message: {response.text}")
409
+ return Message(**response.json())
410
+
411
+ def update_agent(
412
+ self,
413
+ agent_id: str,
414
+ name: Optional[str] = None,
415
+ description: Optional[str] = None,
416
+ system: Optional[str] = None,
417
+ tools: Optional[List[str]] = None,
418
+ metadata: Optional[Dict] = None,
419
+ llm_config: Optional[LLMConfig] = None,
420
+ embedding_config: Optional[EmbeddingConfig] = None,
421
+ message_ids: Optional[List[str]] = None,
422
+ memory: Optional[Memory] = None,
423
+ ):
424
+ """
425
+ Update an existing agent
426
+
427
+ Args:
428
+ agent_id (str): ID of the agent
429
+ name (str): Name of the agent
430
+ description (str): Description of the agent
431
+ system (str): System configuration
432
+ tools (List[str]): List of tools
433
+ metadata (Dict): Metadata
434
+ llm_config (LLMConfig): LLM configuration
435
+ embedding_config (EmbeddingConfig): Embedding configuration
436
+ message_ids (List[str]): List of message IDs
437
+ memory (Memory): Memory configuration
438
+
439
+ Returns:
440
+ agent_state (AgentState): State of the updated agent
441
+ """
442
+ request = UpdateAgentState(
443
+ id=agent_id,
444
+ name=name,
445
+ system=system,
446
+ tools=tools,
447
+ description=description,
448
+ metadata_=metadata,
449
+ llm_config=llm_config,
450
+ embedding_config=embedding_config,
451
+ message_ids=message_ids,
452
+ memory=memory,
453
+ )
454
+ response = requests.patch(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}", json=request.model_dump(), headers=self.headers)
455
+ if response.status_code != 200:
456
+ raise ValueError(f"Failed to update agent: {response.text}")
457
+ return AgentState(**response.json())
458
+
459
+ def rename_agent(self, agent_id: str, new_name: str):
460
+ """
461
+ Rename an agent
462
+
463
+ Args:
464
+ agent_id (str): ID of the agent
465
+ new_name (str): New name for the agent
466
+
467
+ """
468
+ return self.update_agent(agent_id, name=new_name)
469
+
470
+ def delete_agent(self, agent_id: str):
471
+ """
472
+ Delete an agent
473
+
474
+ Args:
475
+ agent_id (str): ID of the agent to delete
476
+ """
477
+ response = requests.delete(f"{self.base_url}/{self.api_prefix}/agents/{str(agent_id)}", headers=self.headers)
478
+ assert response.status_code == 200, f"Failed to delete agent: {response.text}"
479
+
480
+ def get_agent(self, agent_id: Optional[str] = None, agent_name: Optional[str] = None) -> AgentState:
481
+ """
482
+ Get an agent's state by it's ID.
483
+
484
+ Args:
485
+ agent_id (str): ID of the agent
486
+
487
+ Returns:
488
+ agent_state (AgentState): State representation of the agent
489
+ """
490
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}", headers=self.headers)
491
+ assert response.status_code == 200, f"Failed to get agent: {response.text}"
492
+ return AgentState(**response.json())
493
+
494
+ def get_agent_id(self, agent_name: str) -> AgentState:
495
+ """
496
+ Get the ID of an agent by name (names are unique per user)
497
+
498
+ Args:
499
+ agent_name (str): Name of the agent
500
+
501
+ Returns:
502
+ agent_id (str): ID of the agent
503
+ """
504
+ # TODO: implement this
505
+ raise NotImplementedError
506
+
507
+ # memory
508
+ def get_in_context_memory(self, agent_id: str) -> Memory:
509
+ """
510
+ Get the in-contxt (i.e. core) memory of an agent
511
+
512
+ Args:
513
+ agent_id (str): ID of the agent
514
+
515
+ Returns:
516
+ memory (Memory): In-context memory of the agent
517
+ """
518
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/memory", headers=self.headers)
519
+ if response.status_code != 200:
520
+ raise ValueError(f"Failed to get in-context memory: {response.text}")
521
+ return Memory(**response.json())
522
+
523
+ def get_core_memory(self, agent_id: str) -> Memory:
524
+ return self.get_in_context_memory(agent_id)
525
+
526
+ def update_in_context_memory(self, agent_id: str, section: str, value: Union[List[str], str]) -> Memory:
527
+ """
528
+ Update the in-context memory of an agent
529
+
530
+ Args:
531
+ agent_id (str): ID of the agent
532
+
533
+ Returns:
534
+ memory (Memory): The updated in-context memory of the agent
535
+
536
+ """
537
+ memory_update_dict = {section: value}
538
+ response = requests.patch(
539
+ f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/memory", json=memory_update_dict, headers=self.headers
540
+ )
541
+ if response.status_code != 200:
542
+ raise ValueError(f"Failed to update in-context memory: {response.text}")
543
+ return Memory(**response.json())
544
+
545
+ def get_archival_memory_summary(self, agent_id: str) -> ArchivalMemorySummary:
546
+ """
547
+ Get a summary of the archival memory of an agent
548
+
549
+ Args:
550
+ agent_id (str): ID of the agent
551
+
552
+ Returns:
553
+ summary (ArchivalMemorySummary): Summary of the archival memory
554
+
555
+ """
556
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/memory/archival", headers=self.headers)
557
+ if response.status_code != 200:
558
+ raise ValueError(f"Failed to get archival memory summary: {response.text}")
559
+ return ArchivalMemorySummary(**response.json())
560
+
561
+ def get_recall_memory_summary(self, agent_id: str) -> RecallMemorySummary:
562
+ """
563
+ Get a summary of the recall memory of an agent
564
+
565
+ Args:
566
+ agent_id (str): ID of the agent
567
+
568
+ Returns:
569
+ summary (RecallMemorySummary): Summary of the recall memory
570
+ """
571
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/memory/recall", headers=self.headers)
572
+ if response.status_code != 200:
573
+ raise ValueError(f"Failed to get recall memory summary: {response.text}")
574
+ return RecallMemorySummary(**response.json())
575
+
576
+ def get_in_context_messages(self, agent_id: str) -> List[Message]:
577
+ """
578
+ Get in-context messages of an agent
579
+
580
+ Args:
581
+ agent_id (str): ID of the agent
582
+
583
+ Returns:
584
+ messages (List[Message]): List of in-context messages
585
+ """
586
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/memory/messages", headers=self.headers)
587
+ if response.status_code != 200:
588
+ raise ValueError(f"Failed to get in-context messages: {response.text}")
589
+ return [Message(**message) for message in response.json()]
590
+
591
+ # agent interactions
592
+
593
+ def user_message(self, agent_id: str, message: str, include_full_message: Optional[bool] = False) -> LettaResponse:
594
+ """
595
+ Send a message to an agent as a user
596
+
597
+ Args:
598
+ agent_id (str): ID of the agent
599
+ message (str): Message to send
600
+
601
+ Returns:
602
+ response (LettaResponse): Response from the agent
603
+ """
604
+ return self.send_message(agent_id, message, role="user", include_full_message=include_full_message)
605
+
606
+ def save(self):
607
+ raise NotImplementedError
608
+
609
+ # archival memory
610
+
611
+ def get_archival_memory(
612
+ self, agent_id: str, before: Optional[str] = None, after: Optional[str] = None, limit: Optional[int] = 1000
613
+ ) -> List[Passage]:
614
+ """
615
+ Get archival memory from an agent with pagination.
616
+
617
+ Args:
618
+ agent_id (str): ID of the agent
619
+ before (str): Get memories before a certain time
620
+ after (str): Get memories after a certain time
621
+ limit (int): Limit number of memories
622
+
623
+ Returns:
624
+ passages (List[Passage]): List of passages
625
+ """
626
+ params = {"limit": limit}
627
+ if before:
628
+ params["before"] = str(before)
629
+ if after:
630
+ params["after"] = str(after)
631
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/agents/{str(agent_id)}/archival", params=params, headers=self.headers)
632
+ assert response.status_code == 200, f"Failed to get archival memory: {response.text}"
633
+ return [Passage(**passage) for passage in response.json()]
634
+
635
+ def insert_archival_memory(self, agent_id: str, memory: str) -> List[Passage]:
636
+ """
637
+ Insert archival memory into an agent
638
+
639
+ Args:
640
+ agent_id (str): ID of the agent
641
+ memory (str): Memory string to insert
642
+
643
+ Returns:
644
+ passages (List[Passage]): List of inserted passages
645
+ """
646
+ request = CreateArchivalMemory(text=memory)
647
+ response = requests.post(
648
+ f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/archival", headers=self.headers, json=request.model_dump()
649
+ )
650
+ if response.status_code != 200:
651
+ raise ValueError(f"Failed to insert archival memory: {response.text}")
652
+ return [Passage(**passage) for passage in response.json()]
653
+
654
+ def delete_archival_memory(self, agent_id: str, memory_id: str):
655
+ """
656
+ Delete archival memory from an agent
657
+
658
+ Args:
659
+ agent_id (str): ID of the agent
660
+ memory_id (str): ID of the memory
661
+ """
662
+ response = requests.delete(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/archival/{memory_id}", headers=self.headers)
663
+ assert response.status_code == 200, f"Failed to delete archival memory: {response.text}"
664
+
665
+ # messages (recall memory)
666
+
667
+ def get_messages(
668
+ self, agent_id: str, before: Optional[str] = None, after: Optional[str] = None, limit: Optional[int] = 1000
669
+ ) -> List[Message]:
670
+ """
671
+ Get messages from an agent with pagination.
672
+
673
+ Args:
674
+ agent_id (str): ID of the agent
675
+ before (str): Get messages before a certain time
676
+ after (str): Get messages after a certain time
677
+ limit (int): Limit number of messages
678
+
679
+ Returns:
680
+ messages (List[Message]): List of messages
681
+ """
682
+
683
+ params = {"before": before, "after": after, "limit": limit, "msg_object": True}
684
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/messages", params=params, headers=self.headers)
685
+ if response.status_code != 200:
686
+ raise ValueError(f"Failed to get messages: {response.text}")
687
+ return [Message(**message) for message in response.json()]
688
+
689
+ def send_message(
690
+ self,
691
+ agent_id: str,
692
+ message: str,
693
+ role: str,
694
+ name: Optional[str] = None,
695
+ stream_steps: bool = False,
696
+ stream_tokens: bool = False,
697
+ include_full_message: Optional[bool] = False,
698
+ ) -> Union[LettaResponse, Generator[LettaStreamingResponse, None, None]]:
699
+ """
700
+ Send a message to an agent
701
+
702
+ Args:
703
+ message (str): Message to send
704
+ role (str): Role of the message
705
+ agent_id (str): ID of the agent
706
+ name(str): Name of the sender
707
+ stream (bool): Stream the response (default: `False`)
708
+ stream_tokens (bool): Stream tokens (default: `False`)
709
+
710
+ Returns:
711
+ response (LettaResponse): Response from the agent
712
+ """
713
+ # TODO: implement include_full_message
714
+ messages = [MessageCreate(role=MessageRole(role), text=message, name=name)]
715
+ # TODO: figure out how to handle stream_steps and stream_tokens
716
+
717
+ # When streaming steps is True, stream_tokens must be False
718
+ request = LettaRequest(messages=messages, stream_steps=stream_steps, stream_tokens=stream_tokens, return_message_object=True)
719
+ if stream_tokens or stream_steps:
720
+ from letta.client.streaming import _sse_post
721
+
722
+ request.return_message_object = False
723
+ return _sse_post(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/messages", request.model_dump(), self.headers)
724
+ else:
725
+ response = requests.post(
726
+ f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/messages", json=request.model_dump(), headers=self.headers
727
+ )
728
+ if response.status_code != 200:
729
+ raise ValueError(f"Failed to send message: {response.text}")
730
+ response = LettaResponse(**response.json())
731
+
732
+ # simplify messages
733
+ if not include_full_message:
734
+ messages = []
735
+ for message in response.messages:
736
+ messages += message.to_letta_message()
737
+ response.messages = messages
738
+
739
+ return response
740
+
741
+ # humans / personas
742
+
743
+ def list_blocks(self, label: Optional[str] = None, templates_only: Optional[bool] = True) -> List[Block]:
744
+ params = {"label": label, "templates_only": templates_only}
745
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/blocks", params=params, headers=self.headers)
746
+ if response.status_code != 200:
747
+ raise ValueError(f"Failed to list blocks: {response.text}")
748
+
749
+ if label == "human":
750
+ return [Human(**human) for human in response.json()]
751
+ elif label == "persona":
752
+ return [Persona(**persona) for persona in response.json()]
753
+ else:
754
+ return [Block(**block) for block in response.json()]
755
+
756
+ def create_block(self, label: str, name: str, text: str) -> Block: #
757
+ request = CreateBlock(label=label, name=name, value=text)
758
+ response = requests.post(f"{self.base_url}/{self.api_prefix}/blocks", json=request.model_dump(), headers=self.headers)
759
+ if response.status_code != 200:
760
+ raise ValueError(f"Failed to create block: {response.text}")
761
+ if request.label == "human":
762
+ return Human(**response.json())
763
+ elif request.label == "persona":
764
+ return Persona(**response.json())
765
+ else:
766
+ return Block(**response.json())
767
+
768
+ def update_block(self, block_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Block:
769
+ request = UpdateBlock(id=block_id, name=name, value=text)
770
+ response = requests.post(f"{self.base_url}/{self.api_prefix}/blocks/{block_id}", json=request.model_dump(), headers=self.headers)
771
+ if response.status_code != 200:
772
+ raise ValueError(f"Failed to update block: {response.text}")
773
+ return Block(**response.json())
774
+
775
+ def get_block(self, block_id: str) -> Block:
776
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/blocks/{block_id}", headers=self.headers)
777
+ if response.status_code == 404:
778
+ return None
779
+ elif response.status_code != 200:
780
+ raise ValueError(f"Failed to get block: {response.text}")
781
+ return Block(**response.json())
782
+
783
+ def get_block_id(self, name: str, label: str) -> str:
784
+ params = {"name": name, "label": label}
785
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/blocks", params=params, headers=self.headers)
786
+ if response.status_code != 200:
787
+ raise ValueError(f"Failed to get block ID: {response.text}")
788
+ blocks = [Block(**block) for block in response.json()]
789
+ if len(blocks) == 0:
790
+ return None
791
+ elif len(blocks) > 1:
792
+ raise ValueError(f"Multiple blocks found with name {name}")
793
+ return blocks[0].id
794
+
795
+ def delete_block(self, id: str) -> Block:
796
+ response = requests.delete(f"{self.base_url}/{self.api_prefix}/blocks/{id}", headers=self.headers)
797
+ assert response.status_code == 200, f"Failed to delete block: {response.text}"
798
+ if response.status_code != 200:
799
+ raise ValueError(f"Failed to delete block: {response.text}")
800
+ return Block(**response.json())
801
+
802
+ def list_humans(self):
803
+ """
804
+ List available human block templates
805
+
806
+ Returns:
807
+ humans (List[Human]): List of human blocks
808
+ """
809
+ blocks = self.list_blocks(label="human")
810
+ return [Human(**block.model_dump()) for block in blocks]
811
+
812
+ def create_human(self, name: str, text: str) -> Human:
813
+ """
814
+ Create a human block template (saved human string to pre-fill `ChatMemory`)
815
+
816
+ Args:
817
+ name (str): Name of the human block
818
+ text (str): Text of the human block
819
+
820
+ Returns:
821
+ human (Human): Human block
822
+ """
823
+ return self.create_block(label="human", name=name, text=text)
824
+
825
+ def update_human(self, human_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Human:
826
+ """
827
+ Update a human block template
828
+
829
+ Args:
830
+ human_id (str): ID of the human block
831
+ text (str): Text of the human block
832
+
833
+ Returns:
834
+ human (Human): Updated human block
835
+ """
836
+ request = UpdateHuman(id=human_id, name=name, value=text)
837
+ response = requests.post(f"{self.base_url}/{self.api_prefix}/blocks/{human_id}", json=request.model_dump(), headers=self.headers)
838
+ if response.status_code != 200:
839
+ raise ValueError(f"Failed to update human: {response.text}")
840
+ return Human(**response.json())
841
+
842
+ def list_personas(self):
843
+ """
844
+ List available persona block templates
845
+
846
+ Returns:
847
+ personas (List[Persona]): List of persona blocks
848
+ """
849
+ blocks = self.list_blocks(label="persona")
850
+ return [Persona(**block.model_dump()) for block in blocks]
851
+
852
+ def create_persona(self, name: str, text: str) -> Persona:
853
+ """
854
+ Create a persona block template (saved persona string to pre-fill `ChatMemory`)
855
+
856
+ Args:
857
+ name (str): Name of the persona block
858
+ text (str): Text of the persona block
859
+
860
+ Returns:
861
+ persona (Persona): Persona block
862
+ """
863
+ return self.create_block(label="persona", name=name, text=text)
864
+
865
+ def update_persona(self, persona_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Persona:
866
+ """
867
+ Update a persona block template
868
+
869
+ Args:
870
+ persona_id (str): ID of the persona block
871
+ text (str): Text of the persona block
872
+
873
+ Returns:
874
+ persona (Persona): Updated persona block
875
+ """
876
+ request = UpdatePersona(id=persona_id, name=name, value=text)
877
+ response = requests.post(f"{self.base_url}/{self.api_prefix}/blocks/{persona_id}", json=request.model_dump(), headers=self.headers)
878
+ if response.status_code != 200:
879
+ raise ValueError(f"Failed to update persona: {response.text}")
880
+ return Persona(**response.json())
881
+
882
+ def get_persona(self, persona_id: str) -> Persona:
883
+ """
884
+ Get a persona block template
885
+
886
+ Args:
887
+ id (str): ID of the persona block
888
+
889
+ Returns:
890
+ persona (Persona): Persona block
891
+ """
892
+ return self.get_block(persona_id)
893
+
894
+ def get_persona_id(self, name: str) -> str:
895
+ """
896
+ Get the ID of a persona block template
897
+
898
+ Args:
899
+ name (str): Name of the persona block
900
+
901
+ Returns:
902
+ id (str): ID of the persona block
903
+ """
904
+ return self.get_block_id(name, "persona")
905
+
906
+ def delete_persona(self, persona_id: str) -> Persona:
907
+ """
908
+ Delete a persona block template
909
+
910
+ Args:
911
+ id (str): ID of the persona block
912
+ """
913
+ return self.delete_block(persona_id)
914
+
915
+ def get_human(self, human_id: str) -> Human:
916
+ """
917
+ Get a human block template
918
+
919
+ Args:
920
+ id (str): ID of the human block
921
+
922
+ Returns:
923
+ human (Human): Human block
924
+ """
925
+ return self.get_block(human_id)
926
+
927
+ def get_human_id(self, name: str) -> str:
928
+ """
929
+ Get the ID of a human block template
930
+
931
+ Args:
932
+ name (str): Name of the human block
933
+
934
+ Returns:
935
+ id (str): ID of the human block
936
+ """
937
+ return self.get_block_id(name, "human")
938
+
939
+ def delete_human(self, human_id: str) -> Human:
940
+ """
941
+ Delete a human block template
942
+
943
+ Args:
944
+ id (str): ID of the human block
945
+ """
946
+ return self.delete_block(human_id)
947
+
948
+ # sources
949
+
950
+ def get_source(self, source_id: str) -> Source:
951
+ """
952
+ Get a source given the ID.
953
+
954
+ Args:
955
+ source_id (str): ID of the source
956
+
957
+ Returns:
958
+ source (Source): Source
959
+ """
960
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/sources/{source_id}", headers=self.headers)
961
+ if response.status_code != 200:
962
+ raise ValueError(f"Failed to get source: {response.text}")
963
+ return Source(**response.json())
964
+
965
+ def get_source_id(self, source_name: str) -> str:
966
+ """
967
+ Get the ID of a source
968
+
969
+ Args:
970
+ source_name (str): Name of the source
971
+
972
+ Returns:
973
+ source_id (str): ID of the source
974
+ """
975
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/sources/name/{source_name}", headers=self.headers)
976
+ if response.status_code != 200:
977
+ raise ValueError(f"Failed to get source ID: {response.text}")
978
+ return response.json()
979
+
980
+ def list_sources(self) -> List[Source]:
981
+ """
982
+ List available sources
983
+
984
+ Returns:
985
+ sources (List[Source]): List of sources
986
+ """
987
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/sources", headers=self.headers)
988
+ if response.status_code != 200:
989
+ raise ValueError(f"Failed to list sources: {response.text}")
990
+ return [Source(**source) for source in response.json()]
991
+
992
+ def delete_source(self, source_id: str):
993
+ """
994
+ Delete a source
995
+
996
+ Args:
997
+ source_id (str): ID of the source
998
+ """
999
+ response = requests.delete(f"{self.base_url}/{self.api_prefix}/sources/{str(source_id)}", headers=self.headers)
1000
+ assert response.status_code == 200, f"Failed to delete source: {response.text}"
1001
+
1002
+ def get_job(self, job_id: str) -> Job:
1003
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/jobs/{job_id}", headers=self.headers)
1004
+ if response.status_code != 200:
1005
+ raise ValueError(f"Failed to get job: {response.text}")
1006
+ return Job(**response.json())
1007
+
1008
+ def list_jobs(self):
1009
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/jobs", headers=self.headers)
1010
+ return [Job(**job) for job in response.json()]
1011
+
1012
+ def list_active_jobs(self):
1013
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/jobs/active", headers=self.headers)
1014
+ return [Job(**job) for job in response.json()]
1015
+
1016
+ def load_data(self, connector: DataConnector, source_name: str):
1017
+ raise NotImplementedError
1018
+
1019
+ def load_file_into_source(self, filename: str, source_id: str, blocking=True):
1020
+ """
1021
+ Load a file into a source
1022
+
1023
+ Args:
1024
+ filename (str): Name of the file
1025
+ source_id (str): ID of the source
1026
+ blocking (bool): Block until the job is complete
1027
+
1028
+ Returns:
1029
+ job (Job): Data loading job including job status and metadata
1030
+ """
1031
+ files = {"file": open(filename, "rb")}
1032
+
1033
+ # create job
1034
+ response = requests.post(f"{self.base_url}/{self.api_prefix}/sources/{source_id}/upload", files=files, headers=self.headers)
1035
+ if response.status_code != 200:
1036
+ raise ValueError(f"Failed to upload file to source: {response.text}")
1037
+
1038
+ job = Job(**response.json())
1039
+ if blocking:
1040
+ # wait until job is completed
1041
+ while True:
1042
+ job = self.get_job(job.id)
1043
+ if job.status == JobStatus.completed:
1044
+ break
1045
+ elif job.status == JobStatus.failed:
1046
+ raise ValueError(f"Job failed: {job.metadata}")
1047
+ time.sleep(1)
1048
+ return job
1049
+
1050
+ def create_source(self, name: str) -> Source:
1051
+ """
1052
+ Create a source
1053
+
1054
+ Args:
1055
+ name (str): Name of the source
1056
+
1057
+ Returns:
1058
+ source (Source): Created source
1059
+ """
1060
+ payload = {"name": name}
1061
+ response = requests.post(f"{self.base_url}/{self.api_prefix}/sources", json=payload, headers=self.headers)
1062
+ response_json = response.json()
1063
+ return Source(**response_json)
1064
+
1065
+ def list_attached_sources(self, agent_id: str) -> List[Source]:
1066
+ """
1067
+ List sources attached to an agent
1068
+
1069
+ Args:
1070
+ agent_id (str): ID of the agent
1071
+
1072
+ Returns:
1073
+ sources (List[Source]): List of sources
1074
+ """
1075
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/sources", headers=self.headers)
1076
+ if response.status_code != 200:
1077
+ raise ValueError(f"Failed to list attached sources: {response.text}")
1078
+ return [Source(**source) for source in response.json()]
1079
+
1080
+ def update_source(self, source_id: str, name: Optional[str] = None) -> Source:
1081
+ """
1082
+ Update a source
1083
+
1084
+ Args:
1085
+ source_id (str): ID of the source
1086
+ name (str): Name of the source
1087
+
1088
+ Returns:
1089
+ source (Source): Updated source
1090
+ """
1091
+ request = SourceUpdate(id=source_id, name=name)
1092
+ response = requests.patch(f"{self.base_url}/{self.api_prefix}/sources/{source_id}", json=request.model_dump(), headers=self.headers)
1093
+ if response.status_code != 200:
1094
+ raise ValueError(f"Failed to update source: {response.text}")
1095
+ return Source(**response.json())
1096
+
1097
+ def attach_source_to_agent(self, source_id: str, agent_id: str):
1098
+ """
1099
+ Attach a source to an agent
1100
+
1101
+ Args:
1102
+ agent_id (str): ID of the agent
1103
+ source_id (str): ID of the source
1104
+ source_name (str): Name of the source
1105
+ """
1106
+ params = {"agent_id": agent_id}
1107
+ response = requests.post(f"{self.base_url}/{self.api_prefix}/sources/{source_id}/attach", params=params, headers=self.headers)
1108
+ assert response.status_code == 200, f"Failed to attach source to agent: {response.text}"
1109
+
1110
+ def detach_source(self, source_id: str, agent_id: str):
1111
+ """Detach a source from an agent"""
1112
+ params = {"agent_id": str(agent_id)}
1113
+ response = requests.post(f"{self.base_url}/{self.api_prefix}/sources/{source_id}/detach", params=params, headers=self.headers)
1114
+ assert response.status_code == 200, f"Failed to detach source from agent: {response.text}"
1115
+
1116
+ # server configuration commands
1117
+
1118
+ def list_models(self):
1119
+ """
1120
+ List available LLM models
1121
+
1122
+ Returns:
1123
+ models (List[LLMConfig]): List of LLM models
1124
+ """
1125
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/models", headers=self.headers)
1126
+ if response.status_code != 200:
1127
+ raise ValueError(f"Failed to list models: {response.text}")
1128
+ return [LLMConfig(**model) for model in response.json()]
1129
+
1130
+ def list_embedding_models(self):
1131
+ """
1132
+ List available embedding models
1133
+
1134
+ Returns:
1135
+ models (List[EmbeddingConfig]): List of embedding models
1136
+ """
1137
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/models/embedding", headers=self.headers)
1138
+ if response.status_code != 200:
1139
+ raise ValueError(f"Failed to list embedding models: {response.text}")
1140
+ return [EmbeddingConfig(**model) for model in response.json()]
1141
+
1142
+ # tools
1143
+
1144
+ def get_tool_id(self, tool_name: str):
1145
+ """
1146
+ Get the ID of a tool
1147
+
1148
+ Args:
1149
+ name (str): Name of the tool
1150
+
1151
+ Returns:
1152
+ id (str): ID of the tool (`None` if not found)
1153
+ """
1154
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/tools/name/{tool_name}", headers=self.headers)
1155
+ if response.status_code == 404:
1156
+ return None
1157
+ elif response.status_code != 200:
1158
+ raise ValueError(f"Failed to get tool: {response.text}")
1159
+ return response.json()
1160
+
1161
+ def create_tool(
1162
+ self,
1163
+ func: Callable,
1164
+ name: Optional[str] = None,
1165
+ update: Optional[bool] = True, # TODO: actually use this
1166
+ tags: Optional[List[str]] = None,
1167
+ ) -> Tool:
1168
+ """
1169
+ 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.
1170
+
1171
+ Args:
1172
+ func (callable): The function to create a tool for.
1173
+ name: (str): Name of the tool (must be unique per-user.)
1174
+ tags (Optional[List[str]], optional): Tags for the tool. Defaults to None.
1175
+ update (bool, optional): Update the tool if it already exists. Defaults to True.
1176
+
1177
+ Returns:
1178
+ tool (Tool): The created tool.
1179
+ """
1180
+
1181
+ # TODO: check tool update code
1182
+ # TODO: check if tool already exists
1183
+
1184
+ # TODO: how to load modules?
1185
+ # parse source code/schema
1186
+ source_code = parse_source_code(func)
1187
+ source_type = "python"
1188
+
1189
+ # TODO: Check if tool already exists
1190
+ # if name:
1191
+ # tool_id = self.get_tool_id(tool_name=name)
1192
+ # if tool_id:
1193
+ # raise ValueError(f"Tool with name {name} (id={tool_id}) already exists")
1194
+
1195
+ # call server function
1196
+ request = ToolCreate(source_type=source_type, source_code=source_code, name=name, tags=tags)
1197
+ response = requests.post(f"{self.base_url}/{self.api_prefix}/tools", json=request.model_dump(), headers=self.headers)
1198
+ if response.status_code != 200:
1199
+ raise ValueError(f"Failed to create tool: {response.text}")
1200
+ return Tool(**response.json())
1201
+
1202
+ def update_tool(
1203
+ self,
1204
+ id: str,
1205
+ name: Optional[str] = None,
1206
+ func: Optional[Callable] = None,
1207
+ tags: Optional[List[str]] = None,
1208
+ ) -> Tool:
1209
+ """
1210
+ Update a tool with provided parameters (name, func, tags)
1211
+
1212
+ Args:
1213
+ id (str): ID of the tool
1214
+ name (str): Name of the tool
1215
+ func (callable): Function to wrap in a tool
1216
+ tags (List[str]): Tags for the tool
1217
+
1218
+ Returns:
1219
+ tool (Tool): Updated tool
1220
+ """
1221
+ if func:
1222
+ source_code = parse_source_code(func)
1223
+ else:
1224
+ source_code = None
1225
+
1226
+ source_type = "python"
1227
+
1228
+ request = ToolUpdate(id=id, source_type=source_type, source_code=source_code, tags=tags, name=name)
1229
+ response = requests.patch(f"{self.base_url}/{self.api_prefix}/tools/{id}", json=request.model_dump(), headers=self.headers)
1230
+ if response.status_code != 200:
1231
+ raise ValueError(f"Failed to update tool: {response.text}")
1232
+ return Tool(**response.json())
1233
+
1234
+ # def create_tool(
1235
+ # self,
1236
+ # func,
1237
+ # name: Optional[str] = None,
1238
+ # update: Optional[bool] = True, # TODO: actually use this
1239
+ # tags: Optional[List[str]] = None,
1240
+ # ):
1241
+ # """Create a tool
1242
+
1243
+ # Args:
1244
+ # func (callable): The function to create a tool for.
1245
+ # tags (Optional[List[str]], optional): Tags for the tool. Defaults to None.
1246
+ # update (bool, optional): Update the tool if it already exists. Defaults to True.
1247
+
1248
+ # Returns:
1249
+ # Tool object
1250
+ # """
1251
+
1252
+ # # TODO: check if tool already exists
1253
+ # # TODO: how to load modules?
1254
+ # # parse source code/schema
1255
+ # source_code = parse_source_code(func)
1256
+ # json_schema = generate_schema(func, name)
1257
+ # source_type = "python"
1258
+ # json_schema["name"]
1259
+
1260
+ # # create data
1261
+ # data = {"source_code": source_code, "source_type": source_type, "tags": tags, "json_schema": json_schema, "update": update}
1262
+ # try:
1263
+ # CreateToolRequest(**data) # validate data
1264
+ # except Exception as e:
1265
+ # raise ValueError(f"Failed to create tool: {e}, invalid input {data}")
1266
+
1267
+ # # make REST request
1268
+ # response = requests.post(f"{self.base_url}/{self.api_prefix}/tools", json=data, headers=self.headers)
1269
+ # if response.status_code != 200:
1270
+ # raise ValueError(f"Failed to create tool: {response.text}")
1271
+ # return ToolModel(**response.json())
1272
+
1273
+ def list_tools(self) -> List[Tool]:
1274
+ """
1275
+ List available tools for the user.
1276
+
1277
+ Returns:
1278
+ tools (List[Tool]): List of tools
1279
+ """
1280
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/tools", headers=self.headers)
1281
+ if response.status_code != 200:
1282
+ raise ValueError(f"Failed to list tools: {response.text}")
1283
+ return [Tool(**tool) for tool in response.json()]
1284
+
1285
+ def delete_tool(self, name: str):
1286
+ """
1287
+ Delete a tool given the ID.
1288
+
1289
+ Args:
1290
+ id (str): ID of the tool
1291
+ """
1292
+ response = requests.delete(f"{self.base_url}/{self.api_prefix}/tools/{name}", headers=self.headers)
1293
+ if response.status_code != 200:
1294
+ raise ValueError(f"Failed to delete tool: {response.text}")
1295
+
1296
+ def get_tool(self, id: str) -> Optional[Tool]:
1297
+ """
1298
+ Get a tool give its ID.
1299
+
1300
+ Args:
1301
+ id (str): ID of the tool
1302
+
1303
+ Returns:
1304
+ tool (Tool): Tool
1305
+ """
1306
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/tools/{id}", headers=self.headers)
1307
+ if response.status_code == 404:
1308
+ return None
1309
+ elif response.status_code != 200:
1310
+ raise ValueError(f"Failed to get tool: {response.text}")
1311
+ return Tool(**response.json())
1312
+
1313
+ def get_tool_id(self, name: str) -> Optional[str]:
1314
+ """
1315
+ Get a tool ID by its name.
1316
+
1317
+ Args:
1318
+ id (str): ID of the tool
1319
+
1320
+ Returns:
1321
+ tool (Tool): Tool
1322
+ """
1323
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/tools/name/{name}", headers=self.headers)
1324
+ if response.status_code == 404:
1325
+ return None
1326
+ elif response.status_code != 200:
1327
+ raise ValueError(f"Failed to get tool: {response.text}")
1328
+ return response.json()
1329
+
1330
+
1331
+ class LocalClient(AbstractClient):
1332
+ """
1333
+ A local client for Letta, which corresponds to a single user.
1334
+
1335
+ Attributes:
1336
+ auto_save (bool): Whether to automatically save changes.
1337
+ user_id (str): The user ID.
1338
+ debug (bool): Whether to print debug information.
1339
+ interface (QueuingInterface): The interface for the client.
1340
+ server (SyncServer): The server for the client.
1341
+ """
1342
+
1343
+ def __init__(
1344
+ self,
1345
+ auto_save: bool = False,
1346
+ user_id: Optional[str] = None,
1347
+ debug: bool = False,
1348
+ ):
1349
+ """
1350
+ Initializes a new instance of Client class.
1351
+
1352
+ Args:
1353
+ auto_save (bool): Whether to automatically save changes.
1354
+ user_id (str): The user ID.
1355
+ debug (bool): Whether to print debug information.
1356
+ """
1357
+ self.auto_save = auto_save
1358
+
1359
+ # set logging levels
1360
+ letta.utils.DEBUG = debug
1361
+ logging.getLogger().setLevel(logging.CRITICAL)
1362
+
1363
+ self.interface = QueuingInterface(debug=debug)
1364
+ self.server = SyncServer(default_interface_factory=lambda: self.interface)
1365
+
1366
+ # save user_id that `LocalClient` is associated with
1367
+ if user_id:
1368
+ self.user_id = user_id
1369
+ else:
1370
+ # get default user
1371
+ self.user_id = self.server.get_default_user().id
1372
+
1373
+ # agents
1374
+
1375
+ def list_agents(self) -> List[AgentState]:
1376
+ self.interface.clear()
1377
+
1378
+ # TODO: fix the server function
1379
+ # return self.server.list_agents(user_id=self.user_id)
1380
+
1381
+ return self.server.ms.list_agents(user_id=self.user_id)
1382
+
1383
+ def agent_exists(self, agent_id: Optional[str] = None, agent_name: Optional[str] = None) -> bool:
1384
+ """
1385
+ Check if an agent exists
1386
+
1387
+ Args:
1388
+ agent_id (str): ID of the agent
1389
+ agent_name (str): Name of the agent
1390
+
1391
+ Returns:
1392
+ exists (bool): `True` if the agent exists, `False` otherwise
1393
+ """
1394
+
1395
+ if not (agent_id or agent_name):
1396
+ raise ValueError(f"Either agent_id or agent_name must be provided")
1397
+ if agent_id and agent_name:
1398
+ raise ValueError(f"Only one of agent_id or agent_name can be provided")
1399
+ existing = self.list_agents()
1400
+ if agent_id:
1401
+ return str(agent_id) in [str(agent.id) for agent in existing]
1402
+ else:
1403
+ return agent_name in [str(agent.name) for agent in existing]
1404
+
1405
+ def create_agent(
1406
+ self,
1407
+ name: Optional[str] = None,
1408
+ # model configs
1409
+ embedding_config: Optional[EmbeddingConfig] = None,
1410
+ llm_config: Optional[LLMConfig] = None,
1411
+ # memory
1412
+ memory: Memory = ChatMemory(human=get_human_text(DEFAULT_HUMAN), persona=get_persona_text(DEFAULT_PERSONA)),
1413
+ # system
1414
+ system: Optional[str] = None,
1415
+ # tools
1416
+ tools: Optional[List[str]] = None,
1417
+ include_base_tools: Optional[bool] = True,
1418
+ # metadata
1419
+ metadata: Optional[Dict] = {"human:": DEFAULT_HUMAN, "persona": DEFAULT_PERSONA},
1420
+ description: Optional[str] = None,
1421
+ ) -> AgentState:
1422
+ """Create an agent
1423
+
1424
+ Args:
1425
+ name (str): Name of the agent
1426
+ embedding_config (EmbeddingConfig): Embedding configuration
1427
+ llm_config (LLMConfig): LLM configuration
1428
+ memory (Memory): Memory configuration
1429
+ system (str): System configuration
1430
+ tools (List[str]): List of tools
1431
+ include_base_tools (bool): Include base tools
1432
+ metadata (Dict): Metadata
1433
+ description (str): Description
1434
+
1435
+ Returns:
1436
+ agent_state (AgentState): State of the created agent
1437
+ """
1438
+
1439
+ if name and self.agent_exists(agent_name=name):
1440
+ raise ValueError(f"Agent with name {name} already exists (user_id={self.user_id})")
1441
+
1442
+ # construct list of tools
1443
+ tool_names = []
1444
+ if tools:
1445
+ tool_names += tools
1446
+ if include_base_tools:
1447
+ tool_names += BASE_TOOLS
1448
+
1449
+ # add memory tools
1450
+ memory_functions = get_memory_functions(memory)
1451
+ for func_name, func in memory_functions.items():
1452
+ tool = self.create_tool(func, name=func_name, tags=["memory", "letta-base"], update=True)
1453
+ tool_names.append(tool.name)
1454
+
1455
+ self.interface.clear()
1456
+
1457
+ # create agent
1458
+ agent_state = self.server.create_agent(
1459
+ CreateAgent(
1460
+ name=name,
1461
+ description=description,
1462
+ metadata_=metadata,
1463
+ memory=memory,
1464
+ tools=tool_names,
1465
+ system=system,
1466
+ llm_config=llm_config,
1467
+ embedding_config=embedding_config,
1468
+ ),
1469
+ user_id=self.user_id,
1470
+ )
1471
+ return agent_state
1472
+
1473
+ def update_message(
1474
+ self,
1475
+ agent_id: str,
1476
+ message_id: str,
1477
+ role: Optional[MessageRole] = None,
1478
+ text: Optional[str] = None,
1479
+ name: Optional[str] = None,
1480
+ tool_calls: Optional[List[ToolCall]] = None,
1481
+ tool_call_id: Optional[str] = None,
1482
+ ) -> Message:
1483
+ message = self.server.update_agent_message(
1484
+ agent_id=agent_id,
1485
+ request=UpdateMessage(
1486
+ id=message_id,
1487
+ role=role,
1488
+ text=text,
1489
+ name=name,
1490
+ tool_calls=tool_calls,
1491
+ tool_call_id=tool_call_id,
1492
+ ),
1493
+ )
1494
+ return message
1495
+
1496
+ def update_agent(
1497
+ self,
1498
+ agent_id: str,
1499
+ name: Optional[str] = None,
1500
+ description: Optional[str] = None,
1501
+ system: Optional[str] = None,
1502
+ tools: Optional[List[str]] = None,
1503
+ metadata: Optional[Dict] = None,
1504
+ llm_config: Optional[LLMConfig] = None,
1505
+ embedding_config: Optional[EmbeddingConfig] = None,
1506
+ message_ids: Optional[List[str]] = None,
1507
+ memory: Optional[Memory] = None,
1508
+ ):
1509
+ """
1510
+ Update an existing agent
1511
+
1512
+ Args:
1513
+ agent_id (str): ID of the agent
1514
+ name (str): Name of the agent
1515
+ description (str): Description of the agent
1516
+ system (str): System configuration
1517
+ tools (List[str]): List of tools
1518
+ metadata (Dict): Metadata
1519
+ llm_config (LLMConfig): LLM configuration
1520
+ embedding_config (EmbeddingConfig): Embedding configuration
1521
+ message_ids (List[str]): List of message IDs
1522
+ memory (Memory): Memory configuration
1523
+
1524
+ Returns:
1525
+ agent_state (AgentState): State of the updated agent
1526
+ """
1527
+ self.interface.clear()
1528
+ agent_state = self.server.update_agent(
1529
+ UpdateAgentState(
1530
+ id=agent_id,
1531
+ name=name,
1532
+ system=system,
1533
+ tools=tools,
1534
+ description=description,
1535
+ metadata_=metadata,
1536
+ llm_config=llm_config,
1537
+ embedding_config=embedding_config,
1538
+ message_ids=message_ids,
1539
+ memory=memory,
1540
+ ),
1541
+ user_id=self.user_id,
1542
+ )
1543
+ return agent_state
1544
+
1545
+ def rename_agent(self, agent_id: str, new_name: str):
1546
+ """
1547
+ Rename an agent
1548
+
1549
+ Args:
1550
+ agent_id (str): ID of the agent
1551
+ new_name (str): New name for the agent
1552
+ """
1553
+ self.update_agent(agent_id, name=new_name)
1554
+
1555
+ def delete_agent(self, agent_id: str):
1556
+ """
1557
+ Delete an agent
1558
+
1559
+ Args:
1560
+ agent_id (str): ID of the agent to delete
1561
+ """
1562
+ self.server.delete_agent(user_id=self.user_id, agent_id=agent_id)
1563
+
1564
+ def get_agent(self, agent_id: str) -> AgentState:
1565
+ """
1566
+ Get an agent's state by it's ID.
1567
+
1568
+ Args:
1569
+ agent_id (str): ID of the agent
1570
+
1571
+ Returns:
1572
+ agent_state (AgentState): State representation of the agent
1573
+ """
1574
+ # TODO: include agent_name
1575
+ self.interface.clear()
1576
+ return self.server.get_agent_state(user_id=self.user_id, agent_id=agent_id)
1577
+
1578
+ def get_agent_id(self, agent_name: str) -> AgentState:
1579
+ """
1580
+ Get the ID of an agent by name (names are unique per user)
1581
+
1582
+ Args:
1583
+ agent_name (str): Name of the agent
1584
+
1585
+ Returns:
1586
+ agent_id (str): ID of the agent
1587
+ """
1588
+
1589
+ self.interface.clear()
1590
+ assert agent_name, f"Agent name must be provided"
1591
+ return self.server.get_agent_id(name=agent_name, user_id=self.user_id)
1592
+
1593
+ # memory
1594
+ def get_in_context_memory(self, agent_id: str) -> Memory:
1595
+ """
1596
+ Get the in-contxt (i.e. core) memory of an agent
1597
+
1598
+ Args:
1599
+ agent_id (str): ID of the agent
1600
+
1601
+ Returns:
1602
+ memory (Memory): In-context memory of the agent
1603
+ """
1604
+ memory = self.server.get_agent_memory(agent_id=agent_id)
1605
+ return memory
1606
+
1607
+ def get_core_memory(self, agent_id: str) -> Memory:
1608
+ return self.get_in_context_memory(agent_id)
1609
+
1610
+ def update_in_context_memory(self, agent_id: str, section: str, value: Union[List[str], str]) -> Memory:
1611
+ """
1612
+ Update the in-context memory of an agent
1613
+
1614
+ Args:
1615
+ agent_id (str): ID of the agent
1616
+
1617
+ Returns:
1618
+ memory (Memory): The updated in-context memory of the agent
1619
+
1620
+ """
1621
+ # TODO: implement this (not sure what it should look like)
1622
+ memory = self.server.update_agent_core_memory(user_id=self.user_id, agent_id=agent_id, new_memory_contents={section: value})
1623
+ return memory
1624
+
1625
+ def get_archival_memory_summary(self, agent_id: str) -> ArchivalMemorySummary:
1626
+ """
1627
+ Get a summary of the archival memory of an agent
1628
+
1629
+ Args:
1630
+ agent_id (str): ID of the agent
1631
+
1632
+ Returns:
1633
+ summary (ArchivalMemorySummary): Summary of the archival memory
1634
+
1635
+ """
1636
+ return self.server.get_archival_memory_summary(agent_id=agent_id)
1637
+
1638
+ def get_recall_memory_summary(self, agent_id: str) -> RecallMemorySummary:
1639
+ """
1640
+ Get a summary of the recall memory of an agent
1641
+
1642
+ Args:
1643
+ agent_id (str): ID of the agent
1644
+
1645
+ Returns:
1646
+ summary (RecallMemorySummary): Summary of the recall memory
1647
+ """
1648
+ return self.server.get_recall_memory_summary(agent_id=agent_id)
1649
+
1650
+ def get_in_context_messages(self, agent_id: str) -> List[Message]:
1651
+ """
1652
+ Get in-context messages of an agent
1653
+
1654
+ Args:
1655
+ agent_id (str): ID of the agent
1656
+
1657
+ Returns:
1658
+ messages (List[Message]): List of in-context messages
1659
+ """
1660
+ return self.server.get_in_context_messages(agent_id=agent_id)
1661
+
1662
+ # agent interactions
1663
+
1664
+ def send_message(
1665
+ self,
1666
+ message: str,
1667
+ role: str,
1668
+ agent_id: Optional[str] = None,
1669
+ agent_name: Optional[str] = None,
1670
+ stream_steps: bool = False,
1671
+ stream_tokens: bool = False,
1672
+ include_full_message: Optional[bool] = False,
1673
+ ) -> LettaResponse:
1674
+ """
1675
+ Send a message to an agent
1676
+
1677
+ Args:
1678
+ message (str): Message to send
1679
+ role (str): Role of the message
1680
+ agent_id (str): ID of the agent
1681
+ name(str): Name of the sender
1682
+ stream (bool): Stream the response (default: `False`)
1683
+
1684
+ Returns:
1685
+ response (LettaResponse): Response from the agent
1686
+ """
1687
+ if not agent_id:
1688
+ # lookup agent by name
1689
+ assert agent_name, f"Either agent_id or agent_name must be provided"
1690
+ agent_id = self.get_agent_id(agent_name=agent_name)
1691
+
1692
+ agent_state = self.get_agent(agent_id=agent_id)
1693
+
1694
+ if stream_steps or stream_tokens:
1695
+ # TODO: implement streaming with stream=True/False
1696
+ raise NotImplementedError
1697
+ self.interface.clear()
1698
+ if role == "system":
1699
+ usage = self.server.system_message(user_id=self.user_id, agent_id=agent_id, message=message)
1700
+ elif role == "user":
1701
+ usage = self.server.user_message(user_id=self.user_id, agent_id=agent_id, message=message)
1702
+ else:
1703
+ raise ValueError(f"Role {role} not supported")
1704
+
1705
+ # auto-save
1706
+ if self.auto_save:
1707
+ self.save()
1708
+
1709
+ # TODO: need to make sure date/timestamp is propely passed
1710
+ # TODO: update self.interface.to_list() to return actual Message objects
1711
+ # here, the message objects will have faulty created_by timestamps
1712
+ messages = self.interface.to_list()
1713
+ for m in messages:
1714
+ assert isinstance(m, Message), f"Expected Message object, got {type(m)}"
1715
+ letta_messages = []
1716
+ for m in messages:
1717
+ letta_messages += m.to_letta_message()
1718
+ return LettaResponse(messages=letta_messages, usage=usage)
1719
+
1720
+ # format messages
1721
+ if include_full_message:
1722
+ letta_messages = messages
1723
+ else:
1724
+ letta_messages = []
1725
+ for m in messages:
1726
+ letta_messages += m.to_letta_message()
1727
+
1728
+ return LettaResponse(messages=letta_messages, usage=usage)
1729
+
1730
+ def user_message(self, agent_id: str, message: str, include_full_message: Optional[bool] = False) -> LettaResponse:
1731
+ """
1732
+ Send a message to an agent as a user
1733
+
1734
+ Args:
1735
+ agent_id (str): ID of the agent
1736
+ message (str): Message to send
1737
+
1738
+ Returns:
1739
+ response (LettaResponse): Response from the agent
1740
+ """
1741
+ self.interface.clear()
1742
+ return self.send_message(role="user", agent_id=agent_id, message=message, include_full_message=include_full_message)
1743
+
1744
+ def run_command(self, agent_id: str, command: str) -> LettaResponse:
1745
+ """
1746
+ Run a command on the agent
1747
+
1748
+ Args:
1749
+ agent_id (str): The agent ID
1750
+ command (str): The command to run
1751
+
1752
+ Returns:
1753
+ LettaResponse: The response from the agent
1754
+
1755
+ """
1756
+ self.interface.clear()
1757
+ usage = self.server.run_command(user_id=self.user_id, agent_id=agent_id, command=command)
1758
+
1759
+ # auto-save
1760
+ if self.auto_save:
1761
+ self.save()
1762
+
1763
+ # NOTE: messages/usage may be empty, depending on the command
1764
+ return LettaResponse(messages=self.interface.to_list(), usage=usage)
1765
+
1766
+ def save(self):
1767
+ self.server.save_agents()
1768
+
1769
+ # archival memory
1770
+
1771
+ # humans / personas
1772
+
1773
+ def create_human(self, name: str, text: str):
1774
+ """
1775
+ Create a human block template (saved human string to pre-fill `ChatMemory`)
1776
+
1777
+ Args:
1778
+ name (str): Name of the human block
1779
+ text (str): Text of the human block
1780
+
1781
+ Returns:
1782
+ human (Human): Human block
1783
+ """
1784
+ return self.server.create_block(CreateHuman(name=name, value=text, user_id=self.user_id), user_id=self.user_id)
1785
+
1786
+ def create_persona(self, name: str, text: str):
1787
+ """
1788
+ Create a persona block template (saved persona string to pre-fill `ChatMemory`)
1789
+
1790
+ Args:
1791
+ name (str): Name of the persona block
1792
+ text (str): Text of the persona block
1793
+
1794
+ Returns:
1795
+ persona (Persona): Persona block
1796
+ """
1797
+ return self.server.create_block(CreatePersona(name=name, value=text, user_id=self.user_id), user_id=self.user_id)
1798
+
1799
+ def list_humans(self):
1800
+ """
1801
+ List available human block templates
1802
+
1803
+ Returns:
1804
+ humans (List[Human]): List of human blocks
1805
+ """
1806
+ return self.server.get_blocks(label="human", user_id=self.user_id, template=True)
1807
+
1808
+ def list_personas(self) -> List[Persona]:
1809
+ """
1810
+ List available persona block templates
1811
+
1812
+ Returns:
1813
+ personas (List[Persona]): List of persona blocks
1814
+ """
1815
+ return self.server.get_blocks(label="persona", user_id=self.user_id, template=True)
1816
+
1817
+ def update_human(self, human_id: str, text: str):
1818
+ """
1819
+ Update a human block template
1820
+
1821
+ Args:
1822
+ human_id (str): ID of the human block
1823
+ text (str): Text of the human block
1824
+
1825
+ Returns:
1826
+ human (Human): Updated human block
1827
+ """
1828
+ return self.server.update_block(UpdateHuman(id=human_id, value=text, user_id=self.user_id, template=True))
1829
+
1830
+ def update_persona(self, persona_id: str, text: str):
1831
+ """
1832
+ Update a persona block template
1833
+
1834
+ Args:
1835
+ persona_id (str): ID of the persona block
1836
+ text (str): Text of the persona block
1837
+
1838
+ Returns:
1839
+ persona (Persona): Updated persona block
1840
+ """
1841
+ return self.server.update_block(UpdatePersona(id=persona_id, value=text, user_id=self.user_id, template=True))
1842
+
1843
+ def get_persona(self, id: str) -> Persona:
1844
+ """
1845
+ Get a persona block template
1846
+
1847
+ Args:
1848
+ id (str): ID of the persona block
1849
+
1850
+ Returns:
1851
+ persona (Persona): Persona block
1852
+ """
1853
+ assert id, f"Persona ID must be provided"
1854
+ return Persona(**self.server.get_block(id).model_dump())
1855
+
1856
+ def get_human(self, id: str) -> Human:
1857
+ """
1858
+ Get a human block template
1859
+
1860
+ Args:
1861
+ id (str): ID of the human block
1862
+
1863
+ Returns:
1864
+ human (Human): Human block
1865
+ """
1866
+ assert id, f"Human ID must be provided"
1867
+ return Human(**self.server.get_block(id).model_dump())
1868
+
1869
+ def get_persona_id(self, name: str) -> str:
1870
+ """
1871
+ Get the ID of a persona block template
1872
+
1873
+ Args:
1874
+ name (str): Name of the persona block
1875
+
1876
+ Returns:
1877
+ id (str): ID of the persona block
1878
+ """
1879
+ persona = self.server.get_blocks(name=name, label="persona", user_id=self.user_id, template=True)
1880
+ if not persona:
1881
+ return None
1882
+ return persona[0].id
1883
+
1884
+ def get_human_id(self, name: str) -> str:
1885
+ """
1886
+ Get the ID of a human block template
1887
+
1888
+ Args:
1889
+ name (str): Name of the human block
1890
+
1891
+ Returns:
1892
+ id (str): ID of the human block
1893
+ """
1894
+ human = self.server.get_blocks(name=name, label="human", user_id=self.user_id, template=True)
1895
+ if not human:
1896
+ return None
1897
+ return human[0].id
1898
+
1899
+ def delete_persona(self, id: str):
1900
+ """
1901
+ Delete a persona block template
1902
+
1903
+ Args:
1904
+ id (str): ID of the persona block
1905
+ """
1906
+ self.server.delete_block(id)
1907
+
1908
+ def delete_human(self, id: str):
1909
+ """
1910
+ Delete a human block template
1911
+
1912
+ Args:
1913
+ id (str): ID of the human block
1914
+ """
1915
+ self.server.delete_block(id)
1916
+
1917
+ # tools
1918
+
1919
+ # TODO: merge this into create_tool
1920
+ def add_tool(self, tool: Tool, update: Optional[bool] = True) -> Tool:
1921
+ """
1922
+ Adds a tool directly.
1923
+
1924
+ Args:
1925
+ tool (Tool): The tool to add.
1926
+ update (bool, optional): Update the tool if it already exists. Defaults to True.
1927
+
1928
+ Returns:
1929
+ None
1930
+ """
1931
+ existing_tool_id = self.get_tool_id(tool.name)
1932
+ if existing_tool_id:
1933
+ if update:
1934
+ self.server.update_tool(
1935
+ ToolUpdate(
1936
+ id=existing_tool_id,
1937
+ source_type=tool.source_type,
1938
+ source_code=tool.source_code,
1939
+ tags=tool.tags,
1940
+ json_schema=tool.json_schema,
1941
+ name=tool.name,
1942
+ )
1943
+ )
1944
+ else:
1945
+ raise ValueError(f"Tool with name {tool.name} already exists")
1946
+
1947
+ # call server function
1948
+ return self.server.create_tool(
1949
+ ToolCreate(
1950
+ source_type=tool.source_type, source_code=tool.source_code, name=tool.name, json_schema=tool.json_schema, tags=tool.tags
1951
+ ),
1952
+ user_id=self.user_id,
1953
+ update=update,
1954
+ )
1955
+
1956
+ # TODO: Use the above function `add_tool` here as there is duplicate logic
1957
+ def create_tool(
1958
+ self,
1959
+ func,
1960
+ name: Optional[str] = None,
1961
+ update: Optional[bool] = True, # TODO: actually use this
1962
+ tags: Optional[List[str]] = None,
1963
+ ) -> Tool:
1964
+ """
1965
+ 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.
1966
+
1967
+ Args:
1968
+ func (callable): The function to create a tool for.
1969
+ name: (str): Name of the tool (must be unique per-user.)
1970
+ tags (Optional[List[str]], optional): Tags for the tool. Defaults to None.
1971
+ update (bool, optional): Update the tool if it already exists. Defaults to True.
1972
+
1973
+ Returns:
1974
+ tool (Tool): The created tool.
1975
+ """
1976
+ # TODO: check if tool already exists
1977
+ # TODO: how to load modules?
1978
+ # parse source code/schema
1979
+ source_code = parse_source_code(func)
1980
+ source_type = "python"
1981
+ if not tags:
1982
+ tags = []
1983
+
1984
+ # call server function
1985
+ return self.server.create_tool(
1986
+ # ToolCreate(source_type=source_type, source_code=source_code, name=tool_name, json_schema=json_schema, tags=tags),
1987
+ ToolCreate(source_type=source_type, source_code=source_code, name=name, tags=tags),
1988
+ user_id=self.user_id,
1989
+ update=update,
1990
+ )
1991
+
1992
+ def update_tool(
1993
+ self,
1994
+ id: str,
1995
+ name: Optional[str] = None,
1996
+ func: Optional[callable] = None,
1997
+ tags: Optional[List[str]] = None,
1998
+ ) -> Tool:
1999
+ """
2000
+ Update a tool with provided parameters (name, func, tags)
2001
+
2002
+ Args:
2003
+ id (str): ID of the tool
2004
+ name (str): Name of the tool
2005
+ func (callable): Function to wrap in a tool
2006
+ tags (List[str]): Tags for the tool
2007
+
2008
+ Returns:
2009
+ tool (Tool): Updated tool
2010
+ """
2011
+ if func:
2012
+ source_code = parse_source_code(func)
2013
+ else:
2014
+ source_code = None
2015
+
2016
+ source_type = "python"
2017
+
2018
+ return self.server.update_tool(ToolUpdate(id=id, source_type=source_type, source_code=source_code, tags=tags, name=name))
2019
+
2020
+ def list_tools(self):
2021
+ """
2022
+ List available tools for the user.
2023
+
2024
+ Returns:
2025
+ tools (List[Tool]): List of tools
2026
+ """
2027
+ tools = self.server.list_tools(user_id=self.user_id)
2028
+ return tools
2029
+
2030
+ def get_tool(self, id: str) -> Optional[Tool]:
2031
+ """
2032
+ Get a tool give its ID.
2033
+
2034
+ Args:
2035
+ id (str): ID of the tool
2036
+
2037
+ Returns:
2038
+ tool (Tool): Tool
2039
+ """
2040
+ return self.server.get_tool(id)
2041
+
2042
+ def delete_tool(self, id: str):
2043
+ """
2044
+ Delete a tool given the ID.
2045
+
2046
+ Args:
2047
+ id (str): ID of the tool
2048
+ """
2049
+ return self.server.delete_tool(id)
2050
+
2051
+ def get_tool_id(self, name: str) -> Optional[str]:
2052
+ """
2053
+ Get the ID of a tool
2054
+
2055
+ Args:
2056
+ name (str): Name of the tool
2057
+
2058
+ Returns:
2059
+ id (str): ID of the tool (`None` if not found)
2060
+ """
2061
+ return self.server.get_tool_id(name, self.user_id)
2062
+
2063
+ # data sources
2064
+
2065
+ def load_data(self, connector: DataConnector, source_name: str):
2066
+ """
2067
+ Load data into a source
2068
+
2069
+ Args:
2070
+ connector (DataConnector): Data connector
2071
+ source_name (str): Name of the source
2072
+ """
2073
+ self.server.load_data(user_id=self.user_id, connector=connector, source_name=source_name)
2074
+
2075
+ def load_file_into_source(self, filename: str, source_id: str, blocking=True):
2076
+ """
2077
+ Load a file into a source
2078
+
2079
+ Args:
2080
+ filename (str): Name of the file
2081
+ source_id (str): ID of the source
2082
+ blocking (bool): Block until the job is complete
2083
+
2084
+ Returns:
2085
+ job (Job): Data loading job including job status and metadata
2086
+ """
2087
+ job = self.server.create_job(user_id=self.user_id)
2088
+
2089
+ # TODO: implement blocking vs. non-blocking
2090
+ self.server.load_file_to_source(source_id=source_id, file_path=filename, job_id=job.id)
2091
+ return job
2092
+
2093
+ def get_job(self, job_id: str):
2094
+ return self.server.get_job(job_id=job_id)
2095
+
2096
+ def list_jobs(self):
2097
+ return self.server.list_jobs(user_id=self.user_id)
2098
+
2099
+ def list_active_jobs(self):
2100
+ return self.server.list_active_jobs(user_id=self.user_id)
2101
+
2102
+ def create_source(self, name: str) -> Source:
2103
+ """
2104
+ Create a source
2105
+
2106
+ Args:
2107
+ name (str): Name of the source
2108
+
2109
+ Returns:
2110
+ source (Source): Created source
2111
+ """
2112
+ request = SourceCreate(name=name)
2113
+ return self.server.create_source(request=request, user_id=self.user_id)
2114
+
2115
+ def delete_source(self, source_id: str):
2116
+ """
2117
+ Delete a source
2118
+
2119
+ Args:
2120
+ source_id (str): ID of the source
2121
+ """
2122
+
2123
+ # TODO: delete source data
2124
+ self.server.delete_source(source_id=source_id, user_id=self.user_id)
2125
+
2126
+ def get_source(self, source_id: str) -> Source:
2127
+ """
2128
+ Get a source given the ID.
2129
+
2130
+ Args:
2131
+ source_id (str): ID of the source
2132
+
2133
+ Returns:
2134
+ source (Source): Source
2135
+ """
2136
+ return self.server.get_source(source_id=source_id, user_id=self.user_id)
2137
+
2138
+ def get_source_id(self, source_name: str) -> str:
2139
+ """
2140
+ Get the ID of a source
2141
+
2142
+ Args:
2143
+ source_name (str): Name of the source
2144
+
2145
+ Returns:
2146
+ source_id (str): ID of the source
2147
+ """
2148
+ return self.server.get_source_id(source_name=source_name, user_id=self.user_id)
2149
+
2150
+ def attach_source_to_agent(self, agent_id: str, source_id: Optional[str] = None, source_name: Optional[str] = None):
2151
+ """
2152
+ Attach a source to an agent
2153
+
2154
+ Args:
2155
+ agent_id (str): ID of the agent
2156
+ source_id (str): ID of the source
2157
+ source_name (str): Name of the source
2158
+ """
2159
+ self.server.attach_source_to_agent(source_id=source_id, source_name=source_name, agent_id=agent_id, user_id=self.user_id)
2160
+
2161
+ def detach_source_from_agent(self, agent_id: str, source_id: Optional[str] = None, source_name: Optional[str] = None):
2162
+ self.server.detach_source_from_agent(source_id=source_id, source_name=source_name, agent_id=agent_id, user_id=self.user_id)
2163
+
2164
+ def list_sources(self) -> List[Source]:
2165
+ """
2166
+ List available sources
2167
+
2168
+ Returns:
2169
+ sources (List[Source]): List of sources
2170
+ """
2171
+
2172
+ return self.server.list_all_sources(user_id=self.user_id)
2173
+
2174
+ def list_attached_sources(self, agent_id: str) -> List[Source]:
2175
+ """
2176
+ List sources attached to an agent
2177
+
2178
+ Args:
2179
+ agent_id (str): ID of the agent
2180
+
2181
+ Returns:
2182
+ sources (List[Source]): List of sources
2183
+ """
2184
+ return self.server.list_attached_sources(agent_id=agent_id)
2185
+
2186
+ def update_source(self, source_id: str, name: Optional[str] = None) -> Source:
2187
+ """
2188
+ Update a source
2189
+
2190
+ Args:
2191
+ source_id (str): ID of the source
2192
+ name (str): Name of the source
2193
+
2194
+ Returns:
2195
+ source (Source): Updated source
2196
+ """
2197
+ # TODO should the arg here just be "source_update: Source"?
2198
+ request = SourceUpdate(id=source_id, name=name)
2199
+ return self.server.update_source(request=request, user_id=self.user_id)
2200
+
2201
+ # archival memory
2202
+
2203
+ def insert_archival_memory(self, agent_id: str, memory: str) -> List[Passage]:
2204
+ """
2205
+ Insert archival memory into an agent
2206
+
2207
+ Args:
2208
+ agent_id (str): ID of the agent
2209
+ memory (str): Memory string to insert
2210
+
2211
+ Returns:
2212
+ passages (List[Passage]): List of inserted passages
2213
+ """
2214
+ return self.server.insert_archival_memory(user_id=self.user_id, agent_id=agent_id, memory_contents=memory)
2215
+
2216
+ def delete_archival_memory(self, agent_id: str, memory_id: str):
2217
+ """
2218
+ Delete archival memory from an agent
2219
+
2220
+ Args:
2221
+ agent_id (str): ID of the agent
2222
+ memory_id (str): ID of the memory
2223
+ """
2224
+ self.server.delete_archival_memory(user_id=self.user_id, agent_id=agent_id, memory_id=memory_id)
2225
+
2226
+ def get_archival_memory(
2227
+ self, agent_id: str, before: Optional[str] = None, after: Optional[str] = None, limit: Optional[int] = 1000
2228
+ ) -> List[Passage]:
2229
+ """
2230
+ Get archival memory from an agent with pagination.
2231
+
2232
+ Args:
2233
+ agent_id (str): ID of the agent
2234
+ before (str): Get memories before a certain time
2235
+ after (str): Get memories after a certain time
2236
+ limit (int): Limit number of memories
2237
+
2238
+ Returns:
2239
+ passages (List[Passage]): List of passages
2240
+ """
2241
+
2242
+ return self.server.get_agent_archival_cursor(user_id=self.user_id, agent_id=agent_id, before=before, after=after, limit=limit)
2243
+
2244
+ # recall memory
2245
+
2246
+ def get_messages(
2247
+ self, agent_id: str, before: Optional[str] = None, after: Optional[str] = None, limit: Optional[int] = 1000
2248
+ ) -> List[Message]:
2249
+ """
2250
+ Get messages from an agent with pagination.
2251
+
2252
+ Args:
2253
+ agent_id (str): ID of the agent
2254
+ before (str): Get messages before a certain time
2255
+ after (str): Get messages after a certain time
2256
+ limit (int): Limit number of messages
2257
+
2258
+ Returns:
2259
+ messages (List[Message]): List of messages
2260
+ """
2261
+
2262
+ self.interface.clear()
2263
+ return self.server.get_agent_recall_cursor(
2264
+ user_id=self.user_id,
2265
+ agent_id=agent_id,
2266
+ before=before,
2267
+ after=after,
2268
+ limit=limit,
2269
+ reverse=True,
2270
+ return_message_object=True,
2271
+ )
2272
+
2273
+ def list_models(self) -> List[LLMConfig]:
2274
+ """
2275
+ List available LLM models
2276
+
2277
+ Returns:
2278
+ models (List[LLMConfig]): List of LLM models
2279
+ """
2280
+ return self.server.list_models()
2281
+
2282
+ def list_embedding_models(self) -> List[EmbeddingConfig]:
2283
+ """
2284
+ List available embedding models
2285
+
2286
+ Returns:
2287
+ models (List[EmbeddingConfig]): List of embedding models
2288
+ """
2289
+ return [self.server.server_embedding_config]
2290
+
2291
+ def list_blocks(self, label: Optional[str] = None, templates_only: Optional[bool] = True) -> List[Block]:
2292
+ """
2293
+ List available blocks
2294
+
2295
+ Args:
2296
+ label (str): Label of the block
2297
+ templates_only (bool): List only templates
2298
+
2299
+ Returns:
2300
+ blocks (List[Block]): List of blocks
2301
+ """
2302
+ return self.server.get_blocks(label=label, template=templates_only)
2303
+
2304
+ def create_block(self, name: str, text: str, label: Optional[str] = None) -> Block: #
2305
+ """
2306
+ Create a block
2307
+
2308
+ Args:
2309
+ label (str): Label of the block
2310
+ name (str): Name of the block
2311
+ text (str): Text of the block
2312
+
2313
+ Returns:
2314
+ block (Block): Created block
2315
+ """
2316
+ return self.server.create_block(CreateBlock(label=label, name=name, value=text, user_id=self.user_id), user_id=self.user_id)
2317
+
2318
+ def update_block(self, block_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Block:
2319
+ """
2320
+ Update a block
2321
+
2322
+ Args:
2323
+ block_id (str): ID of the block
2324
+ name (str): Name of the block
2325
+ text (str): Text of the block
2326
+
2327
+ Returns:
2328
+ block (Block): Updated block
2329
+ """
2330
+ return self.server.update_block(UpdateBlock(id=block_id, name=name, value=text))
2331
+
2332
+ def get_block(self, block_id: str) -> Block:
2333
+ """
2334
+ Get a block
2335
+
2336
+ Args:
2337
+ block_id (str): ID of the block
2338
+
2339
+ Returns:
2340
+ block (Block): Block
2341
+ """
2342
+ return self.server.get_block(block_id)
2343
+
2344
+ def delete_block(self, id: str) -> Block:
2345
+ """
2346
+ Delete a block
2347
+
2348
+ Args:
2349
+ id (str): ID of the block
2350
+
2351
+ Returns:
2352
+ block (Block): Deleted block
2353
+ """
2354
+ return self.server.delete_block(id)
2355
+
2356
+ def set_default_llm_config(self, llm_config: LLMConfig):
2357
+ self.server.server_llm_config = llm_config
2358
+
2359
+ def set_default_embedding_config(self, embedding_config: EmbeddingConfig):
2360
+ self.server.server_embedding_config = embedding_config