letta-nightly 0.6.2.dev20241210104242__py3-none-any.whl → 0.6.2.dev20241211031658__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 (42) hide show
  1. letta/agent.py +32 -43
  2. letta/agent_store/db.py +12 -54
  3. letta/agent_store/storage.py +10 -9
  4. letta/cli/cli.py +1 -0
  5. letta/client/client.py +3 -2
  6. letta/config.py +2 -2
  7. letta/data_sources/connectors.py +4 -3
  8. letta/embeddings.py +29 -9
  9. letta/functions/function_sets/base.py +36 -11
  10. letta/metadata.py +13 -2
  11. letta/o1_agent.py +2 -3
  12. letta/offline_memory_agent.py +2 -1
  13. letta/orm/__init__.py +1 -0
  14. letta/orm/file.py +1 -0
  15. letta/orm/mixins.py +12 -2
  16. letta/orm/organization.py +3 -0
  17. letta/orm/passage.py +72 -0
  18. letta/orm/sqlalchemy_base.py +36 -7
  19. letta/orm/sqlite_functions.py +140 -0
  20. letta/orm/user.py +1 -1
  21. letta/schemas/agent.py +4 -3
  22. letta/schemas/letta_message.py +5 -1
  23. letta/schemas/letta_request.py +3 -3
  24. letta/schemas/passage.py +6 -4
  25. letta/schemas/sandbox_config.py +1 -0
  26. letta/schemas/tool_rule.py +0 -3
  27. letta/server/rest_api/app.py +34 -12
  28. letta/server/rest_api/routers/v1/agents.py +19 -6
  29. letta/server/server.py +118 -44
  30. letta/server/static_files/assets/{index-4848e3d7.js → index-048c9598.js} +1 -1
  31. letta/server/static_files/assets/{index-43ab4d62.css → index-0e31b727.css} +1 -1
  32. letta/server/static_files/index.html +2 -2
  33. letta/services/passage_manager.py +225 -0
  34. letta/services/source_manager.py +2 -1
  35. letta/services/tool_execution_sandbox.py +18 -6
  36. letta/settings.py +2 -0
  37. {letta_nightly-0.6.2.dev20241210104242.dist-info → letta_nightly-0.6.2.dev20241211031658.dist-info}/METADATA +10 -15
  38. {letta_nightly-0.6.2.dev20241210104242.dist-info → letta_nightly-0.6.2.dev20241211031658.dist-info}/RECORD +41 -39
  39. letta/agent_store/chroma.py +0 -297
  40. {letta_nightly-0.6.2.dev20241210104242.dist-info → letta_nightly-0.6.2.dev20241211031658.dist-info}/LICENSE +0 -0
  41. {letta_nightly-0.6.2.dev20241210104242.dist-info → letta_nightly-0.6.2.dev20241211031658.dist-info}/WHEEL +0 -0
  42. {letta_nightly-0.6.2.dev20241210104242.dist-info → letta_nightly-0.6.2.dev20241211031658.dist-info}/entry_points.txt +0 -0
letta/server/server.py CHANGED
@@ -16,7 +16,6 @@ import letta.constants as constants
16
16
  import letta.server.utils as server_utils
17
17
  import letta.system as system
18
18
  from letta.agent import Agent, save_agent
19
- from letta.agent_store.db import attach_base
20
19
  from letta.agent_store.storage import StorageConnector, TableType
21
20
  from letta.chat_only_agent import ChatOnlyAgent
22
21
  from letta.credentials import LettaCredentials
@@ -70,17 +69,18 @@ from letta.schemas.memory import (
70
69
  )
71
70
  from letta.schemas.message import Message, MessageCreate, MessageRole, MessageUpdate
72
71
  from letta.schemas.organization import Organization
73
- from letta.schemas.passage import Passage
72
+ from letta.schemas.passage import Passage as PydanticPassage
74
73
  from letta.schemas.source import Source
75
74
  from letta.schemas.tool import Tool, ToolCreate
76
75
  from letta.schemas.usage import LettaUsageStatistics
77
- from letta.schemas.user import User
76
+ from letta.schemas.user import User as PydanticUser
78
77
  from letta.services.agents_tags_manager import AgentsTagsManager
79
78
  from letta.services.block_manager import BlockManager
80
79
  from letta.services.blocks_agents_manager import BlocksAgentsManager
81
80
  from letta.services.job_manager import JobManager
82
81
  from letta.services.message_manager import MessageManager
83
82
  from letta.services.organization_manager import OrganizationManager
83
+ from letta.services.passage_manager import PassageManager
84
84
  from letta.services.per_agent_lock_manager import PerAgentLockManager
85
85
  from letta.services.sandbox_config_manager import SandboxConfigManager
86
86
  from letta.services.source_manager import SourceManager
@@ -125,7 +125,7 @@ class Server(object):
125
125
  def create_agent(
126
126
  self,
127
127
  request: CreateAgent,
128
- actor: User,
128
+ actor: PydanticUser,
129
129
  # interface
130
130
  interface: Union[AgentInterface, None] = None,
131
131
  ) -> AgentState:
@@ -166,8 +166,6 @@ from letta.settings import model_settings, settings, tool_settings
166
166
 
167
167
  config = LettaConfig.load()
168
168
 
169
- attach_base()
170
-
171
169
  if settings.letta_pg_uri_no_default:
172
170
  config.recall_storage_type = "postgres"
173
171
  config.recall_storage_uri = settings.letta_pg_uri_no_default
@@ -245,6 +243,7 @@ class SyncServer(Server):
245
243
 
246
244
  # Managers that interface with data models
247
245
  self.organization_manager = OrganizationManager()
246
+ self.passage_manager = PassageManager()
248
247
  self.user_manager = UserManager()
249
248
  self.tool_manager = ToolManager()
250
249
  self.block_manager = BlockManager()
@@ -379,6 +378,8 @@ class SyncServer(Server):
379
378
  interface = interface or self.default_interface_factory()
380
379
  if agent_state.agent_type == AgentType.memgpt_agent:
381
380
  agent = Agent(agent_state=agent_state, interface=interface, user=actor, initial_message_sequence=initial_message_sequence)
381
+ elif agent_state.agent_type == AgentType.offline_memory_agent:
382
+ agent = OfflineMemoryAgent(agent_state=agent_state, interface=interface, user=actor, initial_message_sequence=initial_message_sequence)
382
383
  else:
383
384
  assert initial_message_sequence is None, f"Initial message sequence is not supported for O1Agents"
384
385
  agent = O1Agent(agent_state=agent_state, interface=interface, user=actor)
@@ -496,7 +497,12 @@ class SyncServer(Server):
496
497
 
497
498
  # attach data to agent from source
498
499
  source_connector = StorageConnector.get_storage_connector(TableType.PASSAGES, self.config, user_id=user_id)
499
- letta_agent.attach_source(data_source, source_connector, self.ms)
500
+ letta_agent.attach_source(
501
+ user=self.user_manager.get_user_by_id(user_id=user_id),
502
+ source_id=data_source,
503
+ source_manager=letta_agent.source_manager,
504
+ ms=self.ms
505
+ )
500
506
 
501
507
  elif command.lower() == "dump" or command.lower().startswith("dump "):
502
508
  # Check if there's an additional argument that's an integer
@@ -511,7 +517,7 @@ class SyncServer(Server):
511
517
  letta_agent.interface.print_messages_raw(letta_agent.messages)
512
518
 
513
519
  elif command.lower() == "memory":
514
- ret_str = f"\nDumping memory contents:\n" + f"\n{str(letta_agent.agent_state.memory)}" + f"\n{str(letta_agent.archival_memory)}"
520
+ ret_str = f"\nDumping memory contents:\n" + f"\n{str(letta_agent.agent_state.memory)}" + f"\n{str(letta_agent.passage_manager)}"
515
521
  return ret_str
516
522
 
517
523
  elif command.lower() == "pop" or command.lower().startswith("pop "):
@@ -767,7 +773,7 @@ class SyncServer(Server):
767
773
  def create_agent(
768
774
  self,
769
775
  request: CreateAgent,
770
- actor: User,
776
+ actor: PydanticUser,
771
777
  # interface
772
778
  interface: Union[AgentInterface, None] = None,
773
779
  ) -> AgentState:
@@ -824,6 +830,12 @@ class SyncServer(Server):
824
830
  if not user:
825
831
  raise ValueError(f"cannot find user with associated client id: {user_id}")
826
832
 
833
+ if request.llm_config is None:
834
+ raise ValueError("llm_config is required")
835
+
836
+ if request.embedding_config is None:
837
+ raise ValueError("embedding_config is required")
838
+
827
839
  # created and persist the agent state in the DB
828
840
  agent_state = PersistedAgentState(
829
841
  name=request.name,
@@ -843,7 +855,7 @@ class SyncServer(Server):
843
855
  self.ms.create_agent(agent_state)
844
856
 
845
857
  # create the agent object
846
- if request.initial_message_sequence:
858
+ if request.initial_message_sequence is not None:
847
859
  # init_messages = [Message(user_id=user_id, agent_id=agent_state.id, role=message.role, text=message.text) for message in request.initial_message_sequence]
848
860
  init_messages = []
849
861
  for message in request.initial_message_sequence:
@@ -913,6 +925,7 @@ class SyncServer(Server):
913
925
 
914
926
  # get `Tool` objects
915
927
  tools = [self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=user) for tool_name in agent_state.tool_names]
928
+ tools = [tool for tool in tools if tool is not None]
916
929
 
917
930
  # get `Source` objects
918
931
  sources = self.list_attached_sources(agent_id=agent_id)
@@ -926,7 +939,7 @@ class SyncServer(Server):
926
939
  def update_agent(
927
940
  self,
928
941
  request: UpdateAgentState,
929
- actor: User,
942
+ actor: PydanticUser,
930
943
  ) -> AgentState:
931
944
  """Update the agents core memory block, return the new state"""
932
945
  try:
@@ -1143,7 +1156,7 @@ class SyncServer(Server):
1143
1156
 
1144
1157
  def get_archival_memory_summary(self, agent_id: str) -> ArchivalMemorySummary:
1145
1158
  agent = self.load_agent(agent_id=agent_id)
1146
- return ArchivalMemorySummary(size=len(agent.archival_memory))
1159
+ return ArchivalMemorySummary(size=agent.passage_manager.size(actor=self.default_user))
1147
1160
 
1148
1161
  def get_recall_memory_summary(self, agent_id: str) -> RecallMemorySummary:
1149
1162
  agent = self.load_agent(agent_id=agent_id)
@@ -1168,7 +1181,56 @@ class SyncServer(Server):
1168
1181
  message = agent.message_manager.get_message_by_id(id=message_id, actor=self.default_user)
1169
1182
  return message
1170
1183
 
1171
- def get_agent_archival(self, user_id: str, agent_id: str, start: int, count: int) -> List[Passage]:
1184
+ def get_agent_messages(
1185
+ self,
1186
+ agent_id: str,
1187
+ start: int,
1188
+ count: int,
1189
+ ) -> Union[List[Message], List[LettaMessage]]:
1190
+ """Paginated query of all messages in agent message queue"""
1191
+ # Get the agent object (loaded in memory)
1192
+ letta_agent = self.load_agent(agent_id=agent_id)
1193
+
1194
+ if start < 0 or count < 0:
1195
+ raise ValueError("Start and count values should be non-negative")
1196
+
1197
+ if start + count < len(letta_agent._messages): # messages can be returned from whats in memory
1198
+ # Reverse the list to make it in reverse chronological order
1199
+ reversed_messages = letta_agent._messages[::-1]
1200
+ # Check if start is within the range of the list
1201
+ if start >= len(reversed_messages):
1202
+ raise IndexError("Start index is out of range")
1203
+
1204
+ # Calculate the end index, ensuring it does not exceed the list length
1205
+ end_index = min(start + count, len(reversed_messages))
1206
+
1207
+ # Slice the list for pagination
1208
+ messages = reversed_messages[start:end_index]
1209
+
1210
+ else:
1211
+ # need to access persistence manager for additional messages
1212
+
1213
+ # get messages using message manager
1214
+ page = letta_agent.message_manager.list_user_messages_for_agent(
1215
+ agent_id=agent_id,
1216
+ actor=self.default_user,
1217
+ cursor=start,
1218
+ limit=count,
1219
+ )
1220
+
1221
+ messages = page
1222
+ assert all(isinstance(m, Message) for m in messages)
1223
+
1224
+ ## Convert to json
1225
+ ## Add a tag indicating in-context or not
1226
+ # json_messages = [record.to_json() for record in messages]
1227
+ # in_context_message_ids = [str(m.id) for m in letta_agent._messages]
1228
+ # for d in json_messages:
1229
+ # d["in_context"] = True if str(d["id"]) in in_context_message_ids else False
1230
+
1231
+ return messages
1232
+
1233
+ def get_agent_archival(self, user_id: str, agent_id: str, cursor: Optional[str] = None, limit: int = 50) -> List[PydanticPassage]:
1172
1234
  """Paginated query of all messages in agent archival memory"""
1173
1235
  if self.user_manager.get_user_by_id(user_id=user_id) is None:
1174
1236
  raise ValueError(f"User user_id={user_id} does not exist")
@@ -1179,22 +1241,22 @@ class SyncServer(Server):
1179
1241
  letta_agent = self.load_agent(agent_id=agent_id)
1180
1242
 
1181
1243
  # iterate over records
1182
- db_iterator = letta_agent.archival_memory.storage.get_all_paginated(page_size=count, offset=start)
1244
+ records = letta_agent.passage_manager.list_passages(
1245
+ actor=self.default_user,
1246
+ agent_id=agent_id,
1247
+ cursor=cursor,
1248
+ limit=limit,
1249
+ )
1183
1250
 
1184
- # get a single page of messages
1185
- page = next(db_iterator, [])
1186
- return page
1251
+ return records
1187
1252
 
1188
1253
  def get_agent_archival_cursor(
1189
1254
  self,
1190
1255
  user_id: str,
1191
1256
  agent_id: str,
1192
- after: Optional[str] = None,
1193
- before: Optional[str] = None,
1257
+ cursor: Optional[str] = None,
1194
1258
  limit: Optional[int] = 100,
1195
- order_by: Optional[str] = "created_at",
1196
- reverse: Optional[bool] = False,
1197
- ) -> List[Passage]:
1259
+ ) -> List[PydanticPassage]:
1198
1260
  if self.user_manager.get_user_by_id(user_id=user_id) is None:
1199
1261
  raise LettaUserNotFoundError(f"User user_id={user_id} does not exist")
1200
1262
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
@@ -1203,14 +1265,15 @@ class SyncServer(Server):
1203
1265
  # Get the agent object (loaded in memory)
1204
1266
  letta_agent = self.load_agent(agent_id=agent_id)
1205
1267
 
1206
- # iterate over recorde
1207
- cursor, records = letta_agent.archival_memory.storage.get_all_cursor(
1208
- after=after, before=before, limit=limit, order_by=order_by, reverse=reverse
1268
+ # iterate over records
1269
+ records = letta_agent.passage_manager.list_passages(
1270
+ actor=self.default_user, agent_id=agent_id, cursor=cursor, limit=limit,
1209
1271
  )
1210
1272
  return records
1211
1273
 
1212
- def insert_archival_memory(self, user_id: str, agent_id: str, memory_contents: str) -> List[Passage]:
1213
- if self.user_manager.get_user_by_id(user_id=user_id) is None:
1274
+ def insert_archival_memory(self, user_id: str, agent_id: str, memory_contents: str) -> List[PydanticPassage]:
1275
+ actor = self.user_manager.get_user_by_id(user_id=user_id)
1276
+ if actor is None:
1214
1277
  raise ValueError(f"User user_id={user_id} does not exist")
1215
1278
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1216
1279
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -1219,17 +1282,20 @@ class SyncServer(Server):
1219
1282
  letta_agent = self.load_agent(agent_id=agent_id)
1220
1283
 
1221
1284
  # Insert into archival memory
1222
- passage_ids = letta_agent.archival_memory.insert(memory_string=memory_contents, return_ids=True)
1285
+ passage_ids = self.passage_manager.insert_passage(
1286
+ agent_state=letta_agent.agent_state, agent_id=agent_id, text=memory_contents, actor=actor, return_ids=True
1287
+ )
1223
1288
 
1224
1289
  # Update the agent
1225
1290
  # TODO: should this update the system prompt?
1226
1291
  save_agent(letta_agent, self.ms)
1227
1292
 
1228
1293
  # TODO: this is gross, fix
1229
- return [letta_agent.archival_memory.storage.get(id=passage_id) for passage_id in passage_ids]
1294
+ return [self.passage_manager.get_passage_by_id(passage_id=passage_id, actor=actor) for passage_id in passage_ids]
1230
1295
 
1231
1296
  def delete_archival_memory(self, user_id: str, agent_id: str, memory_id: str):
1232
- if self.user_manager.get_user_by_id(user_id=user_id) is None:
1297
+ actor = self.user_manager.get_user_by_id(user_id=user_id)
1298
+ if actor is None:
1233
1299
  raise ValueError(f"User user_id={user_id} does not exist")
1234
1300
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1235
1301
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -1241,7 +1307,7 @@ class SyncServer(Server):
1241
1307
 
1242
1308
  # Delete by ID
1243
1309
  # TODO check if it exists first, and throw error if not
1244
- letta_agent.archival_memory.storage.delete({"id": memory_id})
1310
+ letta_agent.passage_manager.delete_passage_by_id(passage_id=memory_id, actor=actor)
1245
1311
 
1246
1312
  # TODO: return archival memory
1247
1313
 
@@ -1387,6 +1453,12 @@ class SyncServer(Server):
1387
1453
  except NoResultFound:
1388
1454
  logger.error(f"Agent with id {agent_state.id} has nonexistent user {agent_state.user_id}")
1389
1455
 
1456
+ # delete all passages associated with this agent
1457
+ # TODO: REMOVE THIS ONCE WE MIGRATE AGENTMODEL TO ORM
1458
+ passages = self.passage_manager.list_passages(actor=actor, agent_id=agent_state.id)
1459
+ for passage in passages:
1460
+ self.passage_manager.delete_passage_by_id(passage.id, actor=actor)
1461
+
1390
1462
  # First, if the agent is in the in-memory cache we should remove it
1391
1463
  # List of {'user_id': user_id, 'agent_id': agent_id, 'agent': agent_obj} dicts
1392
1464
  try:
@@ -1429,7 +1501,7 @@ class SyncServer(Server):
1429
1501
  self.ms.delete_api_key(api_key=api_key)
1430
1502
  return api_key_obj
1431
1503
 
1432
- def delete_source(self, source_id: str, actor: User):
1504
+ def delete_source(self, source_id: str, actor: PydanticUser):
1433
1505
  """Delete a data source"""
1434
1506
  self.source_manager.delete_source(source_id=source_id, actor=actor)
1435
1507
 
@@ -1439,7 +1511,7 @@ class SyncServer(Server):
1439
1511
 
1440
1512
  # TODO: delete data from agent passage stores (?)
1441
1513
 
1442
- def load_file_to_source(self, source_id: str, file_path: str, job_id: str, actor: User) -> Job:
1514
+ def load_file_to_source(self, source_id: str, file_path: str, job_id: str, actor: PydanticUser) -> Job:
1443
1515
 
1444
1516
  # update job
1445
1517
  job = self.job_manager.get_job_by_id(job_id, actor=actor)
@@ -1466,6 +1538,7 @@ class SyncServer(Server):
1466
1538
  user_id: str,
1467
1539
  connector: DataConnector,
1468
1540
  source_name: str,
1541
+ agent_id: Optional[str] = None,
1469
1542
  ) -> Tuple[int, int]:
1470
1543
  """Load data from a DataConnector into a source for a specified user_id"""
1471
1544
  # TODO: this should be implemented as a batch job or at least async, since it may take a long time
@@ -1480,14 +1553,13 @@ class SyncServer(Server):
1480
1553
  passage_store = StorageConnector.get_storage_connector(TableType.PASSAGES, self.config, user_id=user_id)
1481
1554
 
1482
1555
  # load data into the document store
1483
- passage_count, document_count = load_data(connector, source, passage_store, self.source_manager, actor=user)
1556
+ passage_count, document_count = load_data(connector, source, passage_store, self.source_manager, actor=user, agent_id=agent_id)
1484
1557
  return passage_count, document_count
1485
1558
 
1486
1559
  def attach_source_to_agent(
1487
1560
  self,
1488
1561
  user_id: str,
1489
1562
  agent_id: str,
1490
- # source_id: str,
1491
1563
  source_id: Optional[str] = None,
1492
1564
  source_name: Optional[str] = None,
1493
1565
  ) -> Source:
@@ -1499,15 +1571,14 @@ class SyncServer(Server):
1499
1571
  data_source = self.source_manager.get_source_by_name(source_name=source_name, actor=user)
1500
1572
  else:
1501
1573
  raise ValueError(f"Need to provide at least source_id or source_name to find the source.")
1502
- # get connection to data source storage
1503
- source_connector = StorageConnector.get_storage_connector(TableType.PASSAGES, self.config, user_id=user_id)
1574
+
1504
1575
  assert data_source, f"Data source with id={source_id} or name={source_name} does not exist"
1505
1576
 
1506
1577
  # load agent
1507
1578
  agent = self.load_agent(agent_id=agent_id)
1508
1579
 
1509
1580
  # attach source to agent
1510
- agent.attach_source(data_source.id, source_connector, self.ms)
1581
+ agent.attach_source(user=user, source_id=data_source.id, source_manager=self.source_manager, ms=self.ms)
1511
1582
 
1512
1583
  return data_source
1513
1584
 
@@ -1530,8 +1601,7 @@ class SyncServer(Server):
1530
1601
 
1531
1602
  # delete all Passage objects with source_id==source_id from agent's archival memory
1532
1603
  agent = self.load_agent(agent_id=agent_id)
1533
- archival_memory = agent.archival_memory
1534
- archival_memory.storage.delete({"source_id": source_id})
1604
+ agent.passage_manager.delete_passages(actor=user, limit=100, source_id=source_id)
1535
1605
 
1536
1606
  # delete agent-source mapping
1537
1607
  self.ms.detach_source(agent_id=agent_id, source_id=source_id)
@@ -1545,11 +1615,11 @@ class SyncServer(Server):
1545
1615
 
1546
1616
  return [self.source_manager.get_source_by_id(source_id=id) for id in source_ids]
1547
1617
 
1548
- def list_data_source_passages(self, user_id: str, source_id: str) -> List[Passage]:
1618
+ def list_data_source_passages(self, user_id: str, source_id: str) -> List[PydanticPassage]:
1549
1619
  warnings.warn("list_data_source_passages is not yet implemented, returning empty list.", category=UserWarning)
1550
1620
  return []
1551
1621
 
1552
- def list_all_sources(self, actor: User) -> List[Source]:
1622
+ def list_all_sources(self, actor: PydanticUser) -> List[Source]:
1553
1623
  """List all sources (w/ extra metadata) belonging to a user"""
1554
1624
 
1555
1625
  sources = self.source_manager.list_sources(actor=actor)
@@ -1589,7 +1659,7 @@ class SyncServer(Server):
1589
1659
 
1590
1660
  return sources_with_metadata
1591
1661
 
1592
- def add_default_external_tools(self, actor: User) -> bool:
1662
+ def add_default_external_tools(self, actor: PydanticUser) -> bool:
1593
1663
  """Add default langchain tools. Return true if successful, false otherwise."""
1594
1664
  success = True
1595
1665
  tool_creates = ToolCreate.load_default_langchain_tools()
@@ -1646,7 +1716,7 @@ class SyncServer(Server):
1646
1716
  save_agent(letta_agent, self.ms)
1647
1717
  return response
1648
1718
 
1649
- def get_user_or_default(self, user_id: Optional[str]) -> User:
1719
+ def get_user_or_default(self, user_id: Optional[str]) -> PydanticUser:
1650
1720
  """Get the user object for user_id if it exists, otherwise return the default user object"""
1651
1721
  if user_id is None:
1652
1722
  user_id = self.user_manager.DEFAULT_USER_ID
@@ -1827,6 +1897,8 @@ class SyncServer(Server):
1827
1897
  date=get_utc_time(),
1828
1898
  status="success",
1829
1899
  function_return=function_response,
1900
+ stdout=sandbox_run_result.stdout,
1901
+ stderr=sandbox_run_result.stderr,
1830
1902
  )
1831
1903
  except Exception as e:
1832
1904
  # same as agent.py
@@ -1842,6 +1914,8 @@ class SyncServer(Server):
1842
1914
  date=get_utc_time(),
1843
1915
  status="error",
1844
1916
  function_return=error_msg,
1917
+ stdout=[''],
1918
+ stderr=[traceback.format_exc()],
1845
1919
  )
1846
1920
 
1847
1921
  # Composio wrappers