letta-nightly 0.6.27.dev20250219104103__py3-none-any.whl → 0.6.28.dev20250220163833__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 (61) hide show
  1. letta/__init__.py +1 -1
  2. letta/agent.py +13 -1
  3. letta/client/client.py +2 -0
  4. letta/constants.py +2 -0
  5. letta/functions/schema_generator.py +6 -6
  6. letta/helpers/converters.py +153 -0
  7. letta/helpers/tool_rule_solver.py +11 -1
  8. letta/llm_api/anthropic.py +10 -5
  9. letta/llm_api/aws_bedrock.py +1 -1
  10. letta/llm_api/azure_openai_constants.py +1 -0
  11. letta/llm_api/deepseek.py +303 -0
  12. letta/llm_api/llm_api_tools.py +81 -1
  13. letta/llm_api/openai.py +13 -0
  14. letta/local_llm/chat_completion_proxy.py +15 -2
  15. letta/local_llm/lmstudio/api.py +75 -1
  16. letta/orm/__init__.py +1 -0
  17. letta/orm/agent.py +14 -5
  18. letta/orm/custom_columns.py +31 -110
  19. letta/orm/identity.py +39 -0
  20. letta/orm/organization.py +2 -0
  21. letta/schemas/agent.py +13 -1
  22. letta/schemas/identity.py +44 -0
  23. letta/schemas/llm_config.py +2 -0
  24. letta/schemas/message.py +1 -1
  25. letta/schemas/openai/chat_completion_response.py +2 -0
  26. letta/schemas/providers.py +72 -1
  27. letta/schemas/tool_rule.py +9 -1
  28. letta/serialize_schemas/__init__.py +1 -0
  29. letta/serialize_schemas/agent.py +36 -0
  30. letta/serialize_schemas/base.py +12 -0
  31. letta/serialize_schemas/custom_fields.py +69 -0
  32. letta/serialize_schemas/message.py +15 -0
  33. letta/server/db.py +111 -0
  34. letta/server/rest_api/app.py +8 -0
  35. letta/server/rest_api/interface.py +114 -9
  36. letta/server/rest_api/routers/v1/__init__.py +2 -0
  37. letta/server/rest_api/routers/v1/agents.py +7 -1
  38. letta/server/rest_api/routers/v1/identities.py +111 -0
  39. letta/server/server.py +13 -116
  40. letta/services/agent_manager.py +54 -6
  41. letta/services/block_manager.py +1 -1
  42. letta/services/helpers/agent_manager_helper.py +15 -0
  43. letta/services/identity_manager.py +140 -0
  44. letta/services/job_manager.py +1 -1
  45. letta/services/message_manager.py +1 -1
  46. letta/services/organization_manager.py +1 -1
  47. letta/services/passage_manager.py +1 -1
  48. letta/services/provider_manager.py +1 -1
  49. letta/services/sandbox_config_manager.py +1 -1
  50. letta/services/source_manager.py +1 -1
  51. letta/services/step_manager.py +1 -1
  52. letta/services/tool_manager.py +1 -1
  53. letta/services/user_manager.py +1 -1
  54. letta/settings.py +3 -0
  55. letta/tracing.py +205 -0
  56. letta/utils.py +4 -0
  57. {letta_nightly-0.6.27.dev20250219104103.dist-info → letta_nightly-0.6.28.dev20250220163833.dist-info}/METADATA +9 -2
  58. {letta_nightly-0.6.27.dev20250219104103.dist-info → letta_nightly-0.6.28.dev20250220163833.dist-info}/RECORD +61 -48
  59. {letta_nightly-0.6.27.dev20250219104103.dist-info → letta_nightly-0.6.28.dev20250220163833.dist-info}/LICENSE +0 -0
  60. {letta_nightly-0.6.27.dev20250219104103.dist-info → letta_nightly-0.6.28.dev20250220163833.dist-info}/WHEEL +0 -0
  61. {letta_nightly-0.6.27.dev20250219104103.dist-info → letta_nightly-0.6.28.dev20250220163833.dist-info}/entry_points.txt +0 -0
@@ -317,6 +317,9 @@ class StreamingServerInterface(AgentChunkStreamingInterface):
317
317
  self.debug = False
318
318
  self.timeout = 10 * 60 # 10 minute timeout
319
319
 
320
+ # for expect_reasoning_content, we should accumulate `content`
321
+ self.expect_reasoning_content_buffer = None
322
+
320
323
  def _reset_inner_thoughts_json_reader(self):
321
324
  # A buffer for accumulating function arguments (we want to buffer keys and run checks on each one)
322
325
  self.function_args_reader = JSONInnerThoughtsExtractor(inner_thoughts_key=self.inner_thoughts_kwarg, wait_for_first_key=True)
@@ -387,6 +390,39 @@ class StreamingServerInterface(AgentChunkStreamingInterface):
387
390
  # Wipe the inner thoughts buffers
388
391
  self._reset_inner_thoughts_json_reader()
389
392
 
393
+ # If we were in reasoning mode and accumulated a json block, attempt to release it as chunks
394
+ # if self.expect_reasoning_content_buffer is not None:
395
+ # try:
396
+ # # NOTE: this is hardcoded for our DeepSeek API integration
397
+ # json_reasoning_content = json.loads(self.expect_reasoning_content_buffer)
398
+
399
+ # if "name" in json_reasoning_content:
400
+ # self._push_to_buffer(
401
+ # ToolCallMessage(
402
+ # id=message_id,
403
+ # date=message_date,
404
+ # tool_call=ToolCallDelta(
405
+ # name=json_reasoning_content["name"],
406
+ # arguments=None,
407
+ # tool_call_id=None,
408
+ # ),
409
+ # )
410
+ # )
411
+ # if "arguments" in json_reasoning_content:
412
+ # self._push_to_buffer(
413
+ # ToolCallMessage(
414
+ # id=message_id,
415
+ # date=message_date,
416
+ # tool_call=ToolCallDelta(
417
+ # name=None,
418
+ # arguments=json_reasoning_content["arguments"],
419
+ # tool_call_id=None,
420
+ # ),
421
+ # )
422
+ # )
423
+ # except Exception as e:
424
+ # print(f"Failed to interpret reasoning content ({self.expect_reasoning_content_buffer}) as JSON: {e}")
425
+
390
426
  def step_complete(self):
391
427
  """Signal from the agent that one 'step' finished (step = LLM response + tool execution)"""
392
428
  if not self.multi_step:
@@ -410,7 +446,13 @@ class StreamingServerInterface(AgentChunkStreamingInterface):
410
446
  return
411
447
 
412
448
  def _process_chunk_to_letta_style(
413
- self, chunk: ChatCompletionChunkResponse, message_id: str, message_date: datetime
449
+ self,
450
+ chunk: ChatCompletionChunkResponse,
451
+ message_id: str,
452
+ message_date: datetime,
453
+ # if we expect `reasoning_content``, then that's what gets mapped to ReasoningMessage
454
+ # and `content` needs to be handled outside the interface
455
+ expect_reasoning_content: bool = False,
414
456
  ) -> Optional[Union[ReasoningMessage, ToolCallMessage, AssistantMessage]]:
415
457
  """
416
458
  Example data from non-streaming response looks like:
@@ -426,6 +468,7 @@ class StreamingServerInterface(AgentChunkStreamingInterface):
426
468
 
427
469
  if (
428
470
  message_delta.content is None
471
+ and (expect_reasoning_content and message_delta.reasoning_content is None)
429
472
  and message_delta.tool_calls is None
430
473
  and message_delta.function_call is None
431
474
  and choice.finish_reason is None
@@ -435,17 +478,68 @@ class StreamingServerInterface(AgentChunkStreamingInterface):
435
478
  return None
436
479
 
437
480
  # inner thoughts
438
- if message_delta.content is not None:
439
- if message_delta.content == "":
440
- print("skipping empty content")
441
- processed_chunk = None
481
+ if expect_reasoning_content and message_delta.reasoning_content is not None:
482
+ processed_chunk = ReasoningMessage(
483
+ id=message_id,
484
+ date=message_date,
485
+ reasoning=message_delta.reasoning_content,
486
+ )
487
+ elif expect_reasoning_content and message_delta.content is not None:
488
+ # "ignore" content if we expect reasoning content
489
+ if self.expect_reasoning_content_buffer is None:
490
+ self.expect_reasoning_content_buffer = message_delta.content
442
491
  else:
443
- processed_chunk = ReasoningMessage(
492
+ self.expect_reasoning_content_buffer += message_delta.content
493
+
494
+ # we expect this to be pure JSON
495
+ # OptimisticJSONParser
496
+
497
+ # If we can pull a name out, pull it
498
+
499
+ try:
500
+ # NOTE: this is hardcoded for our DeepSeek API integration
501
+ json_reasoning_content = json.loads(self.expect_reasoning_content_buffer)
502
+ print(f"json_reasoning_content: {json_reasoning_content}")
503
+
504
+ processed_chunk = ToolCallMessage(
444
505
  id=message_id,
445
506
  date=message_date,
446
- reasoning=message_delta.content,
507
+ tool_call=ToolCallDelta(
508
+ name=json_reasoning_content.get("name"),
509
+ arguments=json.dumps(json_reasoning_content.get("arguments")),
510
+ tool_call_id=None,
511
+ ),
447
512
  )
448
513
 
514
+ except json.JSONDecodeError as e:
515
+ print(f"Failed to interpret reasoning content ({self.expect_reasoning_content_buffer}) as JSON: {e}")
516
+
517
+ return None
518
+ # Else,
519
+ # return None
520
+ # processed_chunk = ToolCallMessage(
521
+ # id=message_id,
522
+ # date=message_date,
523
+ # tool_call=ToolCallDelta(
524
+ # # name=tool_call_delta.get("name"),
525
+ # name=None,
526
+ # arguments=message_delta.content,
527
+ # # tool_call_id=tool_call_delta.get("id"),
528
+ # tool_call_id=None,
529
+ # ),
530
+ # )
531
+ # return processed_chunk
532
+
533
+ # TODO eventually output as tool call outputs?
534
+ # print(f"Hiding content delta stream: '{message_delta.content}'")
535
+ # return None
536
+ elif message_delta.content is not None:
537
+ processed_chunk = ReasoningMessage(
538
+ id=message_id,
539
+ date=message_date,
540
+ reasoning=message_delta.content,
541
+ )
542
+
449
543
  # tool calls
450
544
  elif message_delta.tool_calls is not None and len(message_delta.tool_calls) > 0:
451
545
  tool_call = message_delta.tool_calls[0]
@@ -890,7 +984,13 @@ class StreamingServerInterface(AgentChunkStreamingInterface):
890
984
 
891
985
  return processed_chunk
892
986
 
893
- def process_chunk(self, chunk: ChatCompletionChunkResponse, message_id: str, message_date: datetime):
987
+ def process_chunk(
988
+ self,
989
+ chunk: ChatCompletionChunkResponse,
990
+ message_id: str,
991
+ message_date: datetime,
992
+ expect_reasoning_content: bool = False,
993
+ ):
894
994
  """Process a streaming chunk from an OpenAI-compatible server.
895
995
 
896
996
  Example data from non-streaming response looks like:
@@ -910,7 +1010,12 @@ class StreamingServerInterface(AgentChunkStreamingInterface):
910
1010
  # processed_chunk = self._process_chunk_to_openai_style(chunk)
911
1011
  raise NotImplementedError("OpenAI proxy streaming temporarily disabled")
912
1012
  else:
913
- processed_chunk = self._process_chunk_to_letta_style(chunk=chunk, message_id=message_id, message_date=message_date)
1013
+ processed_chunk = self._process_chunk_to_letta_style(
1014
+ chunk=chunk,
1015
+ message_id=message_id,
1016
+ message_date=message_date,
1017
+ expect_reasoning_content=expect_reasoning_content,
1018
+ )
914
1019
  if processed_chunk is None:
915
1020
  return
916
1021
 
@@ -1,6 +1,7 @@
1
1
  from letta.server.rest_api.routers.v1.agents import router as agents_router
2
2
  from letta.server.rest_api.routers.v1.blocks import router as blocks_router
3
3
  from letta.server.rest_api.routers.v1.health import router as health_router
4
+ from letta.server.rest_api.routers.v1.identities import router as identities_router
4
5
  from letta.server.rest_api.routers.v1.jobs import router as jobs_router
5
6
  from letta.server.rest_api.routers.v1.llms import router as llm_router
6
7
  from letta.server.rest_api.routers.v1.providers import router as providers_router
@@ -15,6 +16,7 @@ ROUTERS = [
15
16
  tools_router,
16
17
  sources_router,
17
18
  agents_router,
19
+ identities_router,
18
20
  llm_router,
19
21
  blocks_router,
20
22
  jobs_router,
@@ -23,6 +23,7 @@ from letta.schemas.tool import Tool
23
23
  from letta.schemas.user import User
24
24
  from letta.server.rest_api.utils import get_letta_server
25
25
  from letta.server.server import SyncServer
26
+ from letta.tracing import trace_method
26
27
 
27
28
  # These can be forward refs, but because Fastapi needs them at runtime the must be imported normally
28
29
 
@@ -50,6 +51,7 @@ def list_agents(
50
51
  project_id: Optional[str] = Query(None, description="Search agents by project id"),
51
52
  template_id: Optional[str] = Query(None, description="Search agents by template id"),
52
53
  base_template_id: Optional[str] = Query(None, description="Search agents by base template id"),
54
+ identifier_key: Optional[str] = Query(None, description="Search agents by identifier key"),
53
55
  ):
54
56
  """
55
57
  List all agents associated with a given user.
@@ -65,6 +67,7 @@ def list_agents(
65
67
  "project_id": project_id,
66
68
  "template_id": template_id,
67
69
  "base_template_id": base_template_id,
70
+ "identifier_key": identifier_key,
68
71
  }.items()
69
72
  if value is not None
70
73
  }
@@ -111,6 +114,7 @@ def create_agent(
111
114
  agent: CreateAgentRequest = Body(...),
112
115
  server: "SyncServer" = Depends(get_letta_server),
113
116
  user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
117
+ x_project: Optional[str] = Header(None, alias="X-Project"), # Only handled by next js middleware
114
118
  ):
115
119
  """
116
120
  Create a new agent with the specified configuration.
@@ -460,6 +464,7 @@ def modify_message(
460
464
  response_model=LettaResponse,
461
465
  operation_id="send_message",
462
466
  )
467
+ @trace_method("POST /v1/agents/{agent_id}/messages")
463
468
  async def send_message(
464
469
  agent_id: str,
465
470
  server: SyncServer = Depends(get_letta_server),
@@ -498,6 +503,7 @@ async def send_message(
498
503
  }
499
504
  },
500
505
  )
506
+ @trace_method("POST /v1/agents/{agent_id}/messages/stream")
501
507
  async def send_message_streaming(
502
508
  agent_id: str,
503
509
  server: SyncServer = Depends(get_letta_server),
@@ -509,7 +515,6 @@ async def send_message_streaming(
509
515
  This endpoint accepts a message from a user and processes it through the agent.
510
516
  It will stream the steps of the response always, and stream the tokens if 'stream_tokens' is set to True.
511
517
  """
512
-
513
518
  actor = server.user_manager.get_user_or_default(user_id=user_id)
514
519
  result = await server.send_message_to_agent(
515
520
  agent_id=agent_id,
@@ -574,6 +579,7 @@ async def process_message_background(
574
579
  response_model=Run,
575
580
  operation_id="create_agent_message_async",
576
581
  )
582
+ @trace_method("POST /v1/agents/{agent_id}/messages/async")
577
583
  async def send_message_async(
578
584
  agent_id: str,
579
585
  background_tasks: BackgroundTasks,
@@ -0,0 +1,111 @@
1
+ from typing import TYPE_CHECKING, List, Optional
2
+
3
+ from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query
4
+
5
+ from letta.orm.errors import NoResultFound
6
+ from letta.schemas.identity import Identity, IdentityCreate, IdentityType, IdentityUpdate
7
+ from letta.server.rest_api.utils import get_letta_server
8
+
9
+ if TYPE_CHECKING:
10
+ from letta.server.server import SyncServer
11
+
12
+ router = APIRouter(prefix="/identities", tags=["identities"])
13
+
14
+
15
+ @router.get("/", tags=["identities"], response_model=List[Identity], operation_id="list_identities")
16
+ def list_identities(
17
+ name: Optional[str] = Query(None),
18
+ project_id: Optional[str] = Query(None),
19
+ identity_type: Optional[IdentityType] = Query(None),
20
+ before: Optional[str] = Query(None),
21
+ after: Optional[str] = Query(None),
22
+ limit: Optional[int] = Query(50),
23
+ server: "SyncServer" = Depends(get_letta_server),
24
+ user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
25
+ ):
26
+ """
27
+ Get a list of all identities in the database
28
+ """
29
+ try:
30
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
31
+
32
+ identities = server.identity_manager.list_identities(
33
+ name=name, project_id=project_id, identity_type=identity_type, before=before, after=after, limit=limit, actor=actor
34
+ )
35
+ except HTTPException:
36
+ raise
37
+ except Exception as e:
38
+ raise HTTPException(status_code=500, detail=f"{e}")
39
+ return identities
40
+
41
+
42
+ @router.get("/{identifier_key}", tags=["identities"], response_model=Identity, operation_id="get_identity_from_identifier_key")
43
+ def retrieve_identity(
44
+ identifier_key: str,
45
+ server: "SyncServer" = Depends(get_letta_server),
46
+ ):
47
+ try:
48
+ return server.identity_manager.get_identity_from_identifier_key(identifier_key=identifier_key)
49
+ except NoResultFound as e:
50
+ raise HTTPException(status_code=404, detail=str(e))
51
+
52
+
53
+ @router.post("/", tags=["identities"], response_model=Identity, operation_id="create_identity")
54
+ def create_identity(
55
+ identity: IdentityCreate = Body(...),
56
+ server: "SyncServer" = Depends(get_letta_server),
57
+ user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
58
+ x_project: Optional[str] = Header(None, alias="X-Project"), # Only handled by next js middleware
59
+ ):
60
+ try:
61
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
62
+ return server.identity_manager.create_identity(identity=identity, actor=actor)
63
+ except HTTPException:
64
+ raise
65
+ except Exception as e:
66
+ raise HTTPException(status_code=500, detail=f"{e}")
67
+
68
+
69
+ @router.put("/", tags=["identities"], response_model=Identity, operation_id="upsert_identity")
70
+ def upsert_identity(
71
+ identity: IdentityCreate = Body(...),
72
+ server: "SyncServer" = Depends(get_letta_server),
73
+ user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
74
+ x_project: Optional[str] = Header(None, alias="X-Project"), # Only handled by next js middleware
75
+ ):
76
+ try:
77
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
78
+ return server.identity_manager.upsert_identity(identity=identity, actor=actor)
79
+ except HTTPException:
80
+ raise
81
+ except Exception as e:
82
+ raise HTTPException(status_code=500, detail=f"{e}")
83
+
84
+
85
+ @router.patch("/{identifier_key}", tags=["identities"], response_model=Identity, operation_id="update_identity")
86
+ def modify_identity(
87
+ identifier_key: str,
88
+ identity: IdentityUpdate = Body(...),
89
+ server: "SyncServer" = Depends(get_letta_server),
90
+ user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
91
+ ):
92
+ try:
93
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
94
+ return server.identity_manager.update_identity_by_key(identifier_key=identifier_key, identity=identity, actor=actor)
95
+ except HTTPException:
96
+ raise
97
+ except Exception as e:
98
+ raise HTTPException(status_code=500, detail=f"{e}")
99
+
100
+
101
+ @router.delete("/{identifier_key}", tags=["identities"], operation_id="delete_identity")
102
+ def delete_identity(
103
+ identifier_key: str,
104
+ server: "SyncServer" = Depends(get_letta_server),
105
+ user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
106
+ ):
107
+ """
108
+ Delete an identity by its identifier key
109
+ """
110
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
111
+ server.identity_manager.delete_identity_by_key(identifier_key=identifier_key, actor=actor)
letta/server/server.py CHANGED
@@ -18,6 +18,7 @@ import letta.server.utils as server_utils
18
18
  import letta.system as system
19
19
  from letta.agent import Agent, save_agent
20
20
  from letta.chat_only_agent import ChatOnlyAgent
21
+ from letta.config import LettaConfig
21
22
  from letta.data_sources.connectors import DataConnector, load_data
22
23
  from letta.helpers.datetime_helpers import get_utc_time
23
24
  from letta.helpers.json_helpers import json_dumps, json_loads
@@ -27,7 +28,6 @@ from letta.interface import AgentInterface # abstract
27
28
  from letta.interface import CLIInterface # for printing to terminal
28
29
  from letta.log import get_logger
29
30
  from letta.offline_memory_agent import OfflineMemoryAgent
30
- from letta.orm import Base
31
31
  from letta.orm.errors import NoResultFound
32
32
  from letta.schemas.agent import AgentState, AgentType, CreateAgent
33
33
  from letta.schemas.block import BlockUpdate
@@ -48,6 +48,7 @@ from letta.schemas.providers import (
48
48
  AnthropicBedrockProvider,
49
49
  AnthropicProvider,
50
50
  AzureProvider,
51
+ DeepSeekProvider,
51
52
  GoogleAIProvider,
52
53
  GoogleVertexProvider,
53
54
  GroqProvider,
@@ -70,6 +71,7 @@ from letta.server.rest_api.interface import StreamingServerInterface
70
71
  from letta.server.rest_api.utils import sse_async_generator
71
72
  from letta.services.agent_manager import AgentManager
72
73
  from letta.services.block_manager import BlockManager
74
+ from letta.services.identity_manager import IdentityManager
73
75
  from letta.services.job_manager import JobManager
74
76
  from letta.services.message_manager import MessageManager
75
77
  from letta.services.organization_manager import OrganizationManager
@@ -82,8 +84,11 @@ from letta.services.step_manager import StepManager
82
84
  from letta.services.tool_execution_sandbox import ToolExecutionSandbox
83
85
  from letta.services.tool_manager import ToolManager
84
86
  from letta.services.user_manager import UserManager
87
+ from letta.settings import model_settings, settings, tool_settings
88
+ from letta.tracing import trace_method
85
89
  from letta.utils import get_friendly_error_msg
86
90
 
91
+ config = LettaConfig.load()
87
92
  logger = get_logger(__name__)
88
93
 
89
94
 
@@ -145,118 +150,6 @@ class Server(object):
145
150
  raise NotImplementedError
146
151
 
147
152
 
148
- from contextlib import contextmanager
149
-
150
- from rich.console import Console
151
- from rich.panel import Panel
152
- from rich.text import Text
153
- from sqlalchemy import create_engine
154
- from sqlalchemy.orm import sessionmaker
155
-
156
- from letta.config import LettaConfig
157
-
158
- # NOTE: hack to see if single session management works
159
- from letta.settings import model_settings, settings, tool_settings
160
-
161
- config = LettaConfig.load()
162
-
163
-
164
- def print_sqlite_schema_error():
165
- """Print a formatted error message for SQLite schema issues"""
166
- console = Console()
167
- error_text = Text()
168
- error_text.append("Existing SQLite DB schema is invalid, and schema migrations are not supported for SQLite. ", style="bold red")
169
- error_text.append("To have migrations supported between Letta versions, please run Letta with Docker (", style="white")
170
- error_text.append("https://docs.letta.com/server/docker", style="blue underline")
171
- error_text.append(") or use Postgres by setting ", style="white")
172
- error_text.append("LETTA_PG_URI", style="yellow")
173
- error_text.append(".\n\n", style="white")
174
- error_text.append("If you wish to keep using SQLite, you can reset your database by removing the DB file with ", style="white")
175
- error_text.append("rm ~/.letta/sqlite.db", style="yellow")
176
- error_text.append(" or downgrade to your previous version of Letta.", style="white")
177
-
178
- console.print(Panel(error_text, border_style="red"))
179
-
180
-
181
- @contextmanager
182
- def db_error_handler():
183
- """Context manager for handling database errors"""
184
- try:
185
- yield
186
- except Exception as e:
187
- # Handle other SQLAlchemy errors
188
- print(e)
189
- print_sqlite_schema_error()
190
- # raise ValueError(f"SQLite DB error: {str(e)}")
191
- exit(1)
192
-
193
-
194
- if settings.letta_pg_uri_no_default:
195
- print("Creating postgres engine")
196
- config.recall_storage_type = "postgres"
197
- config.recall_storage_uri = settings.letta_pg_uri_no_default
198
- config.archival_storage_type = "postgres"
199
- config.archival_storage_uri = settings.letta_pg_uri_no_default
200
-
201
- # create engine
202
- engine = create_engine(
203
- settings.letta_pg_uri,
204
- pool_size=settings.pg_pool_size,
205
- max_overflow=settings.pg_max_overflow,
206
- pool_timeout=settings.pg_pool_timeout,
207
- pool_recycle=settings.pg_pool_recycle,
208
- echo=settings.pg_echo,
209
- )
210
- else:
211
- # TODO: don't rely on config storage
212
- engine_path = "sqlite:///" + os.path.join(config.recall_storage_path, "sqlite.db")
213
- logger.info("Creating sqlite engine " + engine_path)
214
-
215
- engine = create_engine(engine_path)
216
-
217
- # Store the original connect method
218
- original_connect = engine.connect
219
-
220
- def wrapped_connect(*args, **kwargs):
221
- with db_error_handler():
222
- # Get the connection
223
- connection = original_connect(*args, **kwargs)
224
-
225
- # Store the original execution method
226
- original_execute = connection.execute
227
-
228
- # Wrap the execute method of the connection
229
- def wrapped_execute(*args, **kwargs):
230
- with db_error_handler():
231
- return original_execute(*args, **kwargs)
232
-
233
- # Replace the connection's execute method
234
- connection.execute = wrapped_execute
235
-
236
- return connection
237
-
238
- # Replace the engine's connect method
239
- engine.connect = wrapped_connect
240
-
241
- Base.metadata.create_all(bind=engine)
242
-
243
- SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
244
-
245
-
246
- # Dependency
247
- def get_db():
248
- db = SessionLocal()
249
- try:
250
- yield db
251
- finally:
252
- db.close()
253
-
254
-
255
- from contextlib import contextmanager
256
-
257
- db_context = contextmanager(get_db)
258
-
259
-
260
153
  class SyncServer(Server):
261
154
  """Simple single-threaded / blocking server process"""
262
155
 
@@ -304,6 +197,7 @@ class SyncServer(Server):
304
197
  self.agent_manager = AgentManager()
305
198
  self.provider_manager = ProviderManager()
306
199
  self.step_manager = StepManager()
200
+ self.identity_manager = IdentityManager()
307
201
 
308
202
  # Managers that interface with parallelism
309
203
  self.per_agent_lock_manager = PerAgentLockManager()
@@ -415,6 +309,8 @@ class SyncServer(Server):
415
309
  else model_settings.lmstudio_base_url + "/v1"
416
310
  )
417
311
  self._enabled_providers.append(LMStudioOpenAIProvider(base_url=lmstudio_url))
312
+ if model_settings.deepseek_api_key:
313
+ self._enabled_providers.append(DeepSeekProvider(api_key=model_settings.deepseek_api_key))
418
314
 
419
315
  def load_agent(self, agent_id: str, actor: User, interface: Union[AgentInterface, None] = None) -> Agent:
420
316
  """Updated method to load agents from persisted storage"""
@@ -1256,6 +1152,7 @@ class SyncServer(Server):
1256
1152
  actions = self.get_composio_client(api_key=api_key).actions.get(apps=[composio_app_name])
1257
1153
  return actions
1258
1154
 
1155
+ @trace_method("Send Message")
1259
1156
  async def send_message_to_agent(
1260
1157
  self,
1261
1158
  agent_id: str,
@@ -1273,7 +1170,6 @@ class SyncServer(Server):
1273
1170
  metadata: Optional[dict] = None,
1274
1171
  ) -> Union[StreamingResponse, LettaResponse]:
1275
1172
  """Split off into a separate function so that it can be imported in the /chat/completion proxy."""
1276
-
1277
1173
  # TODO: @charles is this the correct way to handle?
1278
1174
  include_final_message = True
1279
1175
 
@@ -1292,11 +1188,12 @@ class SyncServer(Server):
1292
1188
  # Disable token streaming if not OpenAI or Anthropic
1293
1189
  # TODO: cleanup this logic
1294
1190
  llm_config = letta_agent.agent_state.llm_config
1191
+ supports_token_streaming = ["openai", "anthropic", "deepseek"]
1295
1192
  if stream_tokens and (
1296
- llm_config.model_endpoint_type not in ["openai", "anthropic"] or "inference.memgpt.ai" in llm_config.model_endpoint
1193
+ llm_config.model_endpoint_type not in supports_token_streaming or "inference.memgpt.ai" in llm_config.model_endpoint
1297
1194
  ):
1298
1195
  warnings.warn(
1299
- f"Token streaming is only supported for models with type 'openai' or 'anthropic' in the model_endpoint: agent has endpoint type {llm_config.model_endpoint_type} and {llm_config.model_endpoint}. Setting stream_tokens to False."
1196
+ f"Token streaming is only supported for models with type {' or '.join(supports_token_streaming)} in the model_endpoint: agent has endpoint type {llm_config.model_endpoint_type} and {llm_config.model_endpoint}. Setting stream_tokens to False."
1300
1197
  )
1301
1198
  stream_tokens = False
1302
1199