letta-nightly 0.6.4.dev20241213193437__py3-none-any.whl → 0.6.4.dev20241214104034__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 (62) hide show
  1. letta/__init__.py +1 -1
  2. letta/agent.py +54 -45
  3. letta/chat_only_agent.py +6 -8
  4. letta/cli/cli.py +2 -10
  5. letta/client/client.py +121 -138
  6. letta/config.py +0 -161
  7. letta/main.py +3 -8
  8. letta/memory.py +3 -14
  9. letta/o1_agent.py +1 -5
  10. letta/offline_memory_agent.py +2 -6
  11. letta/orm/__init__.py +2 -0
  12. letta/orm/agent.py +109 -0
  13. letta/orm/agents_tags.py +10 -18
  14. letta/orm/block.py +29 -4
  15. letta/orm/blocks_agents.py +5 -11
  16. letta/orm/custom_columns.py +152 -0
  17. letta/orm/message.py +3 -38
  18. letta/orm/organization.py +2 -7
  19. letta/orm/passage.py +10 -32
  20. letta/orm/source.py +5 -25
  21. letta/orm/sources_agents.py +13 -0
  22. letta/orm/sqlalchemy_base.py +54 -30
  23. letta/orm/tool.py +1 -19
  24. letta/orm/tools_agents.py +7 -24
  25. letta/orm/user.py +3 -4
  26. letta/schemas/agent.py +48 -65
  27. letta/schemas/memory.py +2 -1
  28. letta/schemas/sandbox_config.py +12 -1
  29. letta/server/rest_api/app.py +0 -5
  30. letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +1 -1
  31. letta/server/rest_api/routers/v1/agents.py +99 -78
  32. letta/server/rest_api/routers/v1/blocks.py +22 -25
  33. letta/server/rest_api/routers/v1/jobs.py +4 -4
  34. letta/server/rest_api/routers/v1/sandbox_configs.py +10 -10
  35. letta/server/rest_api/routers/v1/sources.py +12 -12
  36. letta/server/rest_api/routers/v1/tools.py +35 -15
  37. letta/server/rest_api/routers/v1/users.py +0 -46
  38. letta/server/server.py +172 -716
  39. letta/server/ws_api/server.py +0 -5
  40. letta/services/agent_manager.py +405 -0
  41. letta/services/block_manager.py +13 -21
  42. letta/services/helpers/agent_manager_helper.py +90 -0
  43. letta/services/organization_manager.py +0 -1
  44. letta/services/passage_manager.py +62 -62
  45. letta/services/sandbox_config_manager.py +3 -3
  46. letta/services/source_manager.py +22 -1
  47. letta/services/user_manager.py +11 -6
  48. letta/utils.py +2 -2
  49. {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/METADATA +1 -1
  50. {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/RECORD +53 -57
  51. letta/metadata.py +0 -407
  52. letta/schemas/agents_tags.py +0 -33
  53. letta/schemas/api_key.py +0 -21
  54. letta/schemas/blocks_agents.py +0 -32
  55. letta/schemas/tools_agents.py +0 -32
  56. letta/server/rest_api/routers/openai/assistants/threads.py +0 -338
  57. letta/services/agents_tags_manager.py +0 -64
  58. letta/services/blocks_agents_manager.py +0 -106
  59. letta/services/tools_agents_manager.py +0 -94
  60. {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/LICENSE +0 -0
  61. {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/WHEEL +0 -0
  62. {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/entry_points.txt +0 -0
@@ -19,11 +19,6 @@ class WebSocketServer:
19
19
  self.server = SyncServer(default_interface=self.interface)
20
20
 
21
21
  def shutdown_server(self):
22
- try:
23
- self.server.save_agents()
24
- print(f"Saved agents")
25
- except Exception as e:
26
- print(f"Saving agents failed with: {e}")
27
22
  try:
28
23
  self.interface.close()
29
24
  print(f"Closed the WS interface")
@@ -0,0 +1,405 @@
1
+ from typing import Dict, List, Optional
2
+
3
+ from letta.constants import BASE_MEMORY_TOOLS, BASE_TOOLS
4
+ from letta.orm import Agent as AgentModel
5
+ from letta.orm import Block as BlockModel
6
+ from letta.orm import Source as SourceModel
7
+ from letta.orm import Tool as ToolModel
8
+ from letta.orm.errors import NoResultFound
9
+ from letta.schemas.agent import AgentState as PydanticAgentState
10
+ from letta.schemas.agent import AgentType, CreateAgent, UpdateAgent
11
+ from letta.schemas.block import Block as PydanticBlock
12
+ from letta.schemas.embedding_config import EmbeddingConfig
13
+ from letta.schemas.llm_config import LLMConfig
14
+ from letta.schemas.source import Source as PydanticSource
15
+ from letta.schemas.tool_rule import ToolRule as PydanticToolRule
16
+ from letta.schemas.user import User as PydanticUser
17
+ from letta.services.block_manager import BlockManager
18
+ from letta.services.helpers.agent_manager_helper import (
19
+ _process_relationship,
20
+ _process_tags,
21
+ derive_system_message,
22
+ )
23
+ from letta.services.passage_manager import PassageManager
24
+ from letta.services.source_manager import SourceManager
25
+ from letta.services.tool_manager import ToolManager
26
+ from letta.utils import enforce_types
27
+
28
+
29
+ # Agent Manager Class
30
+ class AgentManager:
31
+ """Manager class to handle business logic related to Agents."""
32
+
33
+ def __init__(self):
34
+ from letta.server.server import db_context
35
+
36
+ self.session_maker = db_context
37
+ self.block_manager = BlockManager()
38
+ self.tool_manager = ToolManager()
39
+ self.source_manager = SourceManager()
40
+
41
+ # ======================================================================================================================
42
+ # Basic CRUD operations
43
+ # ======================================================================================================================
44
+ @enforce_types
45
+ def create_agent(
46
+ self,
47
+ agent_create: CreateAgent,
48
+ actor: PydanticUser,
49
+ ) -> PydanticAgentState:
50
+ system = derive_system_message(agent_type=agent_create.agent_type, system=agent_create.system)
51
+
52
+ # create blocks (note: cannot be linked into the agent_id is created)
53
+ block_ids = list(agent_create.block_ids or []) # Create a local copy to avoid modifying the original
54
+ for create_block in agent_create.memory_blocks:
55
+ block = self.block_manager.create_or_update_block(PydanticBlock(**create_block.model_dump()), actor=actor)
56
+ block_ids.append(block.id)
57
+
58
+ # TODO: Remove this block once we deprecate the legacy `tools` field
59
+ # create passed in `tools`
60
+ tool_names = []
61
+ if agent_create.include_base_tools:
62
+ tool_names.extend(BASE_TOOLS + BASE_MEMORY_TOOLS)
63
+ if agent_create.tools:
64
+ tool_names.extend(agent_create.tools)
65
+
66
+ tool_ids = agent_create.tool_ids or []
67
+ for tool_name in tool_names:
68
+ tool = self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=actor)
69
+ if tool:
70
+ tool_ids.append(tool.id)
71
+ # Remove duplicates
72
+ tool_ids = list(set(tool_ids))
73
+
74
+ return self._create_agent(
75
+ name=agent_create.name,
76
+ system=system,
77
+ agent_type=agent_create.agent_type,
78
+ llm_config=agent_create.llm_config,
79
+ embedding_config=agent_create.embedding_config,
80
+ block_ids=block_ids,
81
+ tool_ids=tool_ids,
82
+ source_ids=agent_create.source_ids or [],
83
+ tags=agent_create.tags or [],
84
+ description=agent_create.description,
85
+ metadata_=agent_create.metadata_,
86
+ tool_rules=agent_create.tool_rules,
87
+ actor=actor,
88
+ )
89
+
90
+ @enforce_types
91
+ def _create_agent(
92
+ self,
93
+ actor: PydanticUser,
94
+ name: str,
95
+ system: str,
96
+ agent_type: AgentType,
97
+ llm_config: LLMConfig,
98
+ embedding_config: EmbeddingConfig,
99
+ block_ids: List[str],
100
+ tool_ids: List[str],
101
+ source_ids: List[str],
102
+ tags: List[str],
103
+ description: Optional[str] = None,
104
+ metadata_: Optional[Dict] = None,
105
+ tool_rules: Optional[List[PydanticToolRule]] = None,
106
+ ) -> PydanticAgentState:
107
+ """Create a new agent."""
108
+ with self.session_maker() as session:
109
+ # Prepare the agent data
110
+ data = {
111
+ "name": name,
112
+ "system": system,
113
+ "agent_type": agent_type,
114
+ "llm_config": llm_config,
115
+ "embedding_config": embedding_config,
116
+ "organization_id": actor.organization_id,
117
+ "description": description,
118
+ "metadata_": metadata_,
119
+ "tool_rules": tool_rules,
120
+ }
121
+
122
+ # Create the new agent using SqlalchemyBase.create
123
+ new_agent = AgentModel(**data)
124
+ _process_relationship(session, new_agent, "tools", ToolModel, tool_ids, replace=True)
125
+ _process_relationship(session, new_agent, "sources", SourceModel, source_ids, replace=True)
126
+ _process_relationship(session, new_agent, "core_memory", BlockModel, block_ids, replace=True)
127
+ _process_tags(new_agent, tags, replace=True)
128
+ new_agent.create(session, actor=actor)
129
+
130
+ # Convert to PydanticAgentState and return
131
+ return new_agent.to_pydantic()
132
+
133
+ @enforce_types
134
+ def update_agent(self, agent_id: str, agent_update: UpdateAgent, actor: PydanticUser) -> PydanticAgentState:
135
+ """
136
+ Update an existing agent.
137
+
138
+ Args:
139
+ agent_id: The ID of the agent to update.
140
+ agent_update: UpdateAgent object containing the updated fields.
141
+ actor: User performing the action.
142
+
143
+ Returns:
144
+ PydanticAgentState: The updated agent as a Pydantic model.
145
+ """
146
+ with self.session_maker() as session:
147
+ # Retrieve the existing agent
148
+ agent = AgentModel.read(db_session=session, identifier=agent_id, actor=actor)
149
+
150
+ # Update scalar fields directly
151
+ scalar_fields = {"name", "system", "llm_config", "embedding_config", "message_ids", "tool_rules", "description", "metadata_"}
152
+ for field in scalar_fields:
153
+ value = getattr(agent_update, field, None)
154
+ if value is not None:
155
+ setattr(agent, field, value)
156
+
157
+ # Update relationships using _process_relationship and _process_tags
158
+ if agent_update.tool_ids is not None:
159
+ _process_relationship(session, agent, "tools", ToolModel, agent_update.tool_ids, replace=True)
160
+ if agent_update.source_ids is not None:
161
+ _process_relationship(session, agent, "sources", SourceModel, agent_update.source_ids, replace=True)
162
+ if agent_update.block_ids is not None:
163
+ _process_relationship(session, agent, "core_memory", BlockModel, agent_update.block_ids, replace=True)
164
+ if agent_update.tags is not None:
165
+ _process_tags(agent, agent_update.tags, replace=True)
166
+
167
+ # Commit and refresh the agent
168
+ agent.update(session, actor=actor)
169
+
170
+ # Convert to PydanticAgentState and return
171
+ return agent.to_pydantic()
172
+
173
+ @enforce_types
174
+ def list_agents(
175
+ self,
176
+ actor: PydanticUser,
177
+ tags: Optional[List[str]] = None,
178
+ match_all_tags: bool = False,
179
+ cursor: Optional[str] = None,
180
+ limit: Optional[int] = 50,
181
+ **kwargs,
182
+ ) -> List[PydanticAgentState]:
183
+ """
184
+ List agents that have the specified tags.
185
+ """
186
+ with self.session_maker() as session:
187
+ agents = AgentModel.list(
188
+ db_session=session,
189
+ tags=tags,
190
+ match_all_tags=match_all_tags,
191
+ cursor=cursor,
192
+ limit=limit,
193
+ organization_id=actor.organization_id if actor else None,
194
+ **kwargs,
195
+ )
196
+
197
+ return [agent.to_pydantic() for agent in agents]
198
+
199
+ @enforce_types
200
+ def get_agent_by_id(self, agent_id: str, actor: PydanticUser) -> PydanticAgentState:
201
+ """Fetch an agent by its ID."""
202
+ with self.session_maker() as session:
203
+ agent = AgentModel.read(db_session=session, identifier=agent_id, actor=actor)
204
+ return agent.to_pydantic()
205
+
206
+ @enforce_types
207
+ def get_agent_by_name(self, agent_name: str, actor: PydanticUser) -> PydanticAgentState:
208
+ """Fetch an agent by its ID."""
209
+ with self.session_maker() as session:
210
+ agent = AgentModel.read(db_session=session, name=agent_name, actor=actor)
211
+ return agent.to_pydantic()
212
+
213
+ @enforce_types
214
+ def delete_agent(self, agent_id: str, actor: PydanticUser) -> PydanticAgentState:
215
+ """
216
+ Deletes an agent and its associated relationships.
217
+ Ensures proper permission checks and cascades where applicable.
218
+
219
+ Args:
220
+ agent_id: ID of the agent to be deleted.
221
+ actor: User performing the action.
222
+
223
+ Returns:
224
+ PydanticAgentState: The deleted agent state
225
+ """
226
+ with self.session_maker() as session:
227
+ # Retrieve the agent
228
+ agent = AgentModel.read(db_session=session, identifier=agent_id, actor=actor)
229
+
230
+ # TODO: @mindy delete this piece when we have a proper passages/sources implementation
231
+ # TODO: This is done very hacky on purpose
232
+ # TODO: 1000 limit is also wack
233
+ passage_manager = PassageManager()
234
+ passage_manager.delete_passages(actor=actor, agent_id=agent_id, limit=1000)
235
+
236
+ agent_state = agent.to_pydantic()
237
+ agent.hard_delete(session)
238
+ return agent_state
239
+
240
+ # ======================================================================================================================
241
+ # Source Management
242
+ # ======================================================================================================================
243
+ @enforce_types
244
+ def attach_source(self, agent_id: str, source_id: str, actor: PydanticUser) -> None:
245
+ """
246
+ Attaches a source to an agent.
247
+
248
+ Args:
249
+ agent_id: ID of the agent to attach the source to
250
+ source_id: ID of the source to attach
251
+ actor: User performing the action
252
+
253
+ Raises:
254
+ ValueError: If either agent or source doesn't exist
255
+ IntegrityError: If the source is already attached to the agent
256
+ """
257
+ with self.session_maker() as session:
258
+ # Verify both agent and source exist and user has permission to access them
259
+ agent = AgentModel.read(db_session=session, identifier=agent_id, actor=actor)
260
+
261
+ # The _process_relationship helper already handles duplicate checking via unique constraint
262
+ _process_relationship(
263
+ session=session,
264
+ agent=agent,
265
+ relationship_name="sources",
266
+ model_class=SourceModel,
267
+ item_ids=[source_id],
268
+ allow_partial=False,
269
+ replace=False, # Extend existing sources rather than replace
270
+ )
271
+
272
+ # Commit the changes
273
+ agent.update(session, actor=actor)
274
+
275
+ @enforce_types
276
+ def list_attached_sources(self, agent_id: str, actor: PydanticUser) -> List[PydanticSource]:
277
+ """
278
+ Lists all sources attached to an agent.
279
+
280
+ Args:
281
+ agent_id: ID of the agent to list sources for
282
+ actor: User performing the action
283
+
284
+ Returns:
285
+ List[str]: List of source IDs attached to the agent
286
+ """
287
+ with self.session_maker() as session:
288
+ # Verify agent exists and user has permission to access it
289
+ agent = AgentModel.read(db_session=session, identifier=agent_id, actor=actor)
290
+
291
+ # Use the lazy-loaded relationship to get sources
292
+ return [source.to_pydantic() for source in agent.sources]
293
+
294
+ @enforce_types
295
+ def detach_source(self, agent_id: str, source_id: str, actor: PydanticUser) -> None:
296
+ """
297
+ Detaches a source from an agent.
298
+
299
+ Args:
300
+ agent_id: ID of the agent to detach the source from
301
+ source_id: ID of the source to detach
302
+ actor: User performing the action
303
+ """
304
+ with self.session_maker() as session:
305
+ # Verify agent exists and user has permission to access it
306
+ agent = AgentModel.read(db_session=session, identifier=agent_id, actor=actor)
307
+
308
+ # Remove the source from the relationship
309
+ agent.sources = [s for s in agent.sources if s.id != source_id]
310
+
311
+ # Commit the changes
312
+ agent.update(session, actor=actor)
313
+
314
+ # ======================================================================================================================
315
+ # Block management
316
+ # ======================================================================================================================
317
+ @enforce_types
318
+ def get_block_with_label(
319
+ self,
320
+ agent_id: str,
321
+ block_label: str,
322
+ actor: PydanticUser,
323
+ ) -> PydanticBlock:
324
+ """Gets a block attached to an agent by its label."""
325
+ with self.session_maker() as session:
326
+ agent = AgentModel.read(db_session=session, identifier=agent_id, actor=actor)
327
+ for block in agent.core_memory:
328
+ if block.label == block_label:
329
+ return block.to_pydantic()
330
+ raise NoResultFound(f"No block with label '{block_label}' found for agent '{agent_id}'")
331
+
332
+ @enforce_types
333
+ def update_block_with_label(
334
+ self,
335
+ agent_id: str,
336
+ block_label: str,
337
+ new_block_id: str,
338
+ actor: PydanticUser,
339
+ ) -> PydanticAgentState:
340
+ """Updates which block is assigned to a specific label for an agent."""
341
+ with self.session_maker() as session:
342
+ agent = AgentModel.read(db_session=session, identifier=agent_id, actor=actor)
343
+ new_block = BlockModel.read(db_session=session, identifier=new_block_id, actor=actor)
344
+
345
+ if new_block.label != block_label:
346
+ raise ValueError(f"New block label '{new_block.label}' doesn't match required label '{block_label}'")
347
+
348
+ # Remove old block with this label if it exists
349
+ agent.core_memory = [b for b in agent.core_memory if b.label != block_label]
350
+
351
+ # Add new block
352
+ agent.core_memory.append(new_block)
353
+ agent.update(session, actor=actor)
354
+ return agent.to_pydantic()
355
+
356
+ @enforce_types
357
+ def attach_block(self, agent_id: str, block_id: str, actor: PydanticUser) -> PydanticAgentState:
358
+ """Attaches a block to an agent."""
359
+ with self.session_maker() as session:
360
+ agent = AgentModel.read(db_session=session, identifier=agent_id, actor=actor)
361
+ block = BlockModel.read(db_session=session, identifier=block_id, actor=actor)
362
+
363
+ agent.core_memory.append(block)
364
+ agent.update(session, actor=actor)
365
+ return agent.to_pydantic()
366
+
367
+ @enforce_types
368
+ def detach_block(
369
+ self,
370
+ agent_id: str,
371
+ block_id: str,
372
+ actor: PydanticUser,
373
+ ) -> PydanticAgentState:
374
+ """Detaches a block from an agent."""
375
+ with self.session_maker() as session:
376
+ agent = AgentModel.read(db_session=session, identifier=agent_id, actor=actor)
377
+ original_length = len(agent.core_memory)
378
+
379
+ agent.core_memory = [b for b in agent.core_memory if b.id != block_id]
380
+
381
+ if len(agent.core_memory) == original_length:
382
+ raise NoResultFound(f"No block with id '{block_id}' found for agent '{agent_id}' with actor id: '{actor.id}'")
383
+
384
+ agent.update(session, actor=actor)
385
+ return agent.to_pydantic()
386
+
387
+ @enforce_types
388
+ def detach_block_with_label(
389
+ self,
390
+ agent_id: str,
391
+ block_label: str,
392
+ actor: PydanticUser,
393
+ ) -> PydanticAgentState:
394
+ """Detaches a block with the specified label from an agent."""
395
+ with self.session_maker() as session:
396
+ agent = AgentModel.read(db_session=session, identifier=agent_id, actor=actor)
397
+ original_length = len(agent.core_memory)
398
+
399
+ agent.core_memory = [b for b in agent.core_memory if b.label != block_label]
400
+
401
+ if len(agent.core_memory) == original_length:
402
+ raise NoResultFound(f"No block with label '{block_label}' found for agent '{agent_id}' with actor id: '{actor.id}'")
403
+
404
+ agent.update(session, actor=actor)
405
+ return agent.to_pydantic()
@@ -7,7 +7,6 @@ from letta.schemas.block import Block
7
7
  from letta.schemas.block import Block as PydanticBlock
8
8
  from letta.schemas.block import BlockUpdate, Human, Persona
9
9
  from letta.schemas.user import User as PydanticUser
10
- from letta.services.blocks_agents_manager import BlocksAgentsManager
11
10
  from letta.utils import enforce_types, list_human_files, list_persona_files
12
11
 
13
12
 
@@ -37,33 +36,17 @@ class BlockManager:
37
36
  @enforce_types
38
37
  def update_block(self, block_id: str, block_update: BlockUpdate, actor: PydanticUser) -> PydanticBlock:
39
38
  """Update a block by its ID with the given BlockUpdate object."""
40
- # TODO: REMOVE THIS ONCE AGENT IS ON ORM -> Update blocks_agents
41
- blocks_agents_manager = BlocksAgentsManager()
42
- agent_ids = []
43
- if block_update.label:
44
- agent_ids = blocks_agents_manager.list_agent_ids_with_block(block_id=block_id)
45
- for agent_id in agent_ids:
46
- blocks_agents_manager.remove_block_with_id_from_agent(agent_id=agent_id, block_id=block_id)
39
+ # Safety check for block
47
40
 
48
41
  with self.session_maker() as session:
49
- # Update block
50
42
  block = BlockModel.read(db_session=session, identifier=block_id, actor=actor)
51
43
  update_data = block_update.model_dump(exclude_unset=True, exclude_none=True)
44
+
52
45
  for key, value in update_data.items():
53
46
  setattr(block, key, value)
54
- try:
55
- block.to_pydantic()
56
- except Exception as e:
57
- # invalid pydantic model
58
- raise ValueError(f"Failed to create pydantic model: {e}")
59
- block.update(db_session=session, actor=actor)
60
-
61
- # TODO: REMOVE THIS ONCE AGENT IS ON ORM -> Update blocks_agents
62
- if block_update.label:
63
- for agent_id in agent_ids:
64
- blocks_agents_manager.add_block_to_agent(agent_id=agent_id, block_id=block_id, block_label=block_update.label)
65
47
 
66
- return block.to_pydantic()
48
+ block.update(db_session=session, actor=actor)
49
+ return block.to_pydantic()
67
50
 
68
51
  @enforce_types
69
52
  def delete_block(self, block_id: str, actor: PydanticUser) -> PydanticBlock:
@@ -111,6 +94,15 @@ class BlockManager:
111
94
  except NoResultFound:
112
95
  return None
113
96
 
97
+ @enforce_types
98
+ def get_all_blocks_by_ids(self, block_ids: List[str], actor: Optional[PydanticUser] = None) -> List[PydanticBlock]:
99
+ # TODO: We can do this much more efficiently by listing, instead of executing individual queries per block_id
100
+ blocks = []
101
+ for block_id in block_ids:
102
+ block = self.get_block_by_id(block_id, actor=actor)
103
+ blocks.append(block)
104
+ return blocks
105
+
114
106
  @enforce_types
115
107
  def add_default_blocks(self, actor: PydanticUser):
116
108
  for persona_file in list_persona_files():
@@ -0,0 +1,90 @@
1
+ from typing import List, Optional
2
+
3
+ from letta.orm.agent import Agent as AgentModel
4
+ from letta.orm.agents_tags import AgentsTags
5
+ from letta.orm.errors import NoResultFound
6
+ from letta.prompts import gpt_system
7
+ from letta.schemas.agent import AgentType
8
+
9
+
10
+ # Static methods
11
+ def _process_relationship(
12
+ session, agent: AgentModel, relationship_name: str, model_class, item_ids: List[str], allow_partial=False, replace=True
13
+ ):
14
+ """
15
+ Generalized function to handle relationships like tools, sources, and blocks using item IDs.
16
+
17
+ Args:
18
+ session: The database session.
19
+ agent: The AgentModel instance.
20
+ relationship_name: The name of the relationship attribute (e.g., 'tools', 'sources').
21
+ model_class: The ORM class corresponding to the related items.
22
+ item_ids: List of IDs to set or update.
23
+ allow_partial: If True, allows missing items without raising errors.
24
+ replace: If True, replaces the entire relationship; otherwise, extends it.
25
+
26
+ Raises:
27
+ ValueError: If `allow_partial` is False and some IDs are missing.
28
+ """
29
+ current_relationship = getattr(agent, relationship_name, [])
30
+ if not item_ids:
31
+ if replace:
32
+ setattr(agent, relationship_name, [])
33
+ return
34
+
35
+ # Retrieve models for the provided IDs
36
+ found_items = session.query(model_class).filter(model_class.id.in_(item_ids)).all()
37
+
38
+ # Validate all items are found if allow_partial is False
39
+ if not allow_partial and len(found_items) != len(item_ids):
40
+ missing = set(item_ids) - {item.id for item in found_items}
41
+ raise NoResultFound(f"Items not found in {relationship_name}: {missing}")
42
+
43
+ if replace:
44
+ # Replace the relationship
45
+ setattr(agent, relationship_name, found_items)
46
+ else:
47
+ # Extend the relationship (only add new items)
48
+ current_ids = {item.id for item in current_relationship}
49
+ new_items = [item for item in found_items if item.id not in current_ids]
50
+ current_relationship.extend(new_items)
51
+
52
+
53
+ def _process_tags(agent: AgentModel, tags: List[str], replace=True):
54
+ """
55
+ Handles tags for an agent.
56
+
57
+ Args:
58
+ agent: The AgentModel instance.
59
+ tags: List of tags to set or update.
60
+ replace: If True, replaces all tags; otherwise, extends them.
61
+ """
62
+ if not tags:
63
+ if replace:
64
+ agent.tags = []
65
+ return
66
+
67
+ # Ensure tags are unique and prepare for replacement/extension
68
+ new_tags = {AgentsTags(agent_id=agent.id, tag=tag) for tag in set(tags)}
69
+ if replace:
70
+ agent.tags = list(new_tags)
71
+ else:
72
+ existing_tags = {t.tag for t in agent.tags}
73
+ agent.tags.extend([tag for tag in new_tags if tag.tag not in existing_tags])
74
+
75
+
76
+ def derive_system_message(agent_type: AgentType, system: Optional[str] = None):
77
+ if system is None:
78
+ # TODO: don't hardcode
79
+ if agent_type == AgentType.memgpt_agent:
80
+ system = gpt_system.get_system_text("memgpt_chat")
81
+ elif agent_type == AgentType.o1_agent:
82
+ system = gpt_system.get_system_text("memgpt_modified_o1")
83
+ elif agent_type == AgentType.offline_memory_agent:
84
+ system = gpt_system.get_system_text("memgpt_offline_memory")
85
+ elif agent_type == AgentType.chat_only_agent:
86
+ system = gpt_system.get_system_text("memgpt_convo_only")
87
+ else:
88
+ raise ValueError(f"Invalid agent type: {agent_type}")
89
+
90
+ return system
@@ -13,7 +13,6 @@ class OrganizationManager:
13
13
  DEFAULT_ORG_NAME = "default_org"
14
14
 
15
15
  def __init__(self):
16
- # This is probably horrible but we reuse this technique from metadata.py
17
16
  # TODO: Please refactor this out
18
17
  # I am currently working on a ORM refactor and would like to make a more minimal set of changes
19
18
  # - Matt