letta-nightly 0.5.1.dev20241031104107__py3-none-any.whl → 0.5.1.dev20241102104033__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.

letta/client/client.py CHANGED
@@ -225,6 +225,9 @@ class AbstractClient(object):
225
225
  def get_tool_id(self, name: str) -> Optional[str]:
226
226
  raise NotImplementedError
227
227
 
228
+ def add_base_tools(self) -> List[Tool]:
229
+ raise NotImplementedError
230
+
228
231
  def load_data(self, connector: DataConnector, source_name: str):
229
232
  raise NotImplementedError
230
233
 
@@ -791,7 +794,7 @@ class RESTClient(AbstractClient):
791
794
  name: Optional[str] = None,
792
795
  stream_steps: bool = False,
793
796
  stream_tokens: bool = False,
794
- include_full_message: Optional[bool] = False,
797
+ include_full_message: bool = False,
795
798
  ) -> Union[LettaResponse, Generator[LettaStreamingResponse, None, None]]:
796
799
  """
797
800
  Send a message to an agent
@@ -812,7 +815,12 @@ class RESTClient(AbstractClient):
812
815
  # TODO: figure out how to handle stream_steps and stream_tokens
813
816
 
814
817
  # When streaming steps is True, stream_tokens must be False
815
- request = LettaRequest(messages=messages, stream_steps=stream_steps, stream_tokens=stream_tokens, return_message_object=True)
818
+ request = LettaRequest(
819
+ messages=messages,
820
+ stream_steps=stream_steps,
821
+ stream_tokens=stream_tokens,
822
+ return_message_object=include_full_message,
823
+ )
816
824
  if stream_tokens or stream_steps:
817
825
  from letta.client.streaming import _sse_post
818
826
 
@@ -827,12 +835,12 @@ class RESTClient(AbstractClient):
827
835
  response = LettaResponse(**response.json())
828
836
 
829
837
  # simplify messages
830
- if not include_full_message:
831
- messages = []
832
- for m in response.messages:
833
- assert isinstance(m, Message)
834
- messages += m.to_letta_message()
835
- response.messages = messages
838
+ # if not include_full_message:
839
+ # messages = []
840
+ # for m in response.messages:
841
+ # assert isinstance(m, Message)
842
+ # messages += m.to_letta_message()
843
+ # response.messages = messages
836
844
 
837
845
  return response
838
846
 
@@ -1266,6 +1274,13 @@ class RESTClient(AbstractClient):
1266
1274
  raise ValueError(f"Failed to get tool: {response.text}")
1267
1275
  return response.json()
1268
1276
 
1277
+ def add_base_tools(self) -> List[Tool]:
1278
+ response = requests.post(f"{self.base_url}/{self.api_prefix}/tools/add-base-tools/", headers=self.headers)
1279
+ if response.status_code != 200:
1280
+ raise ValueError(f"Failed to add base tools: {response.text}")
1281
+
1282
+ return [Tool(**tool) for tool in response.json()]
1283
+
1269
1284
  def create_tool(
1270
1285
  self,
1271
1286
  func: Callable,
letta/constants.py CHANGED
@@ -36,7 +36,6 @@ DEFAULT_PRESET = "memgpt_chat"
36
36
  # Tools
37
37
  BASE_TOOLS = [
38
38
  "send_message",
39
- # "pause_heartbeats",
40
39
  "conversation_search",
41
40
  "conversation_search_date",
42
41
  "archival_memory_insert",
letta/orm/__init__.py CHANGED
@@ -0,0 +1,4 @@
1
+ from letta.orm.base import Base
2
+ from letta.orm.organization import Organization
3
+ from letta.orm.tool import Tool
4
+ from letta.orm.user import User
@@ -107,23 +107,32 @@ class SqlalchemyBase(CommonSqlalchemyMetaMixins, Base):
107
107
  """
108
108
  # Start the query
109
109
  query = select(cls)
110
+ # Collect query conditions for better error reporting
111
+ query_conditions = []
110
112
 
111
113
  # If an identifier is provided, add it to the query conditions
112
114
  if identifier is not None:
113
115
  identifier = cls.get_uid_from_identifier(identifier)
114
116
  query = query.where(cls._id == identifier)
117
+ query_conditions.append(f"id='{identifier}'")
115
118
 
116
119
  if kwargs:
117
120
  query = query.filter_by(**kwargs)
121
+ query_conditions.append(", ".join(f"{key}='{value}'" for key, value in kwargs.items()))
118
122
 
119
123
  if actor:
120
124
  query = cls.apply_access_predicate(query, actor, access)
125
+ query_conditions.append(f"access level in {access} for actor='{actor}'")
121
126
 
122
127
  if hasattr(cls, "is_deleted"):
123
128
  query = query.where(cls.is_deleted == False)
129
+ query_conditions.append("is_deleted=False")
124
130
  if found := db_session.execute(query).scalar():
125
131
  return found
126
- raise NoResultFound(f"{cls.__name__} with id {identifier} not found")
132
+
133
+ # Construct a detailed error message based on query conditions
134
+ conditions_str = ", ".join(query_conditions) if query_conditions else "no specific conditions"
135
+ raise NoResultFound(f"{cls.__name__} not found with {conditions_str}")
127
136
 
128
137
  def create(self, db_session: "Session", actor: Optional["User"] = None) -> Type["SqlalchemyBase"]:
129
138
  if actor:
letta/schemas/agent.py CHANGED
@@ -63,7 +63,7 @@ class AgentState(BaseAgent, validate_assignment=True):
63
63
  tools: List[str] = Field(..., description="The tools used by the agent.")
64
64
 
65
65
  # tool rules
66
- tool_rules: List[BaseToolRule] = Field(..., description="The list of tool rules.")
66
+ tool_rules: Optional[List[BaseToolRule]] = Field(default=None, description="The list of tool rules.")
67
67
 
68
68
  # system prompt
69
69
  system: str = Field(..., description="The system prompt used by the agent.")
@@ -3,7 +3,7 @@ from typing import List, Union
3
3
  from pydantic import BaseModel, Field
4
4
 
5
5
  from letta.schemas.enums import MessageStreamStatus
6
- from letta.schemas.letta_message import LettaMessage
6
+ from letta.schemas.letta_message import LettaMessage, LettaMessageUnion
7
7
  from letta.schemas.message import Message
8
8
  from letta.schemas.usage import LettaUsageStatistics
9
9
  from letta.utils import json_dumps
@@ -21,7 +21,7 @@ class LettaResponse(BaseModel):
21
21
  usage (LettaUsageStatistics): The usage statistics
22
22
  """
23
23
 
24
- messages: Union[List[Message], List[LettaMessage]] = Field(..., description="The messages returned by the agent.")
24
+ messages: Union[List[Message], List[LettaMessageUnion]] = Field(..., description="The messages returned by the agent.")
25
25
  usage: LettaUsageStatistics = Field(..., description="The usage statistics of the agent.")
26
26
 
27
27
  def __str__(self):
@@ -9,6 +9,7 @@ from fastapi import FastAPI
9
9
  from starlette.middleware.cors import CORSMiddleware
10
10
 
11
11
  from letta.constants import ADMIN_PREFIX, API_PREFIX, OPENAI_API_PREFIX
12
+ from letta.schemas.letta_response import LettaResponse
12
13
  from letta.server.constants import REST_DEFAULT_PORT
13
14
 
14
15
  # NOTE(charles): these are extra routes that are not part of v1 but we still need to mount to pass tests
@@ -54,6 +55,12 @@ password = None
54
55
  # password = secrets.token_urlsafe(16)
55
56
  # #typer.secho(f"Generated admin server password for this session: {password}", fg=typer.colors.GREEN)
56
57
 
58
+ import logging
59
+
60
+ from fastapi import FastAPI
61
+
62
+ log = logging.getLogger("uvicorn")
63
+
57
64
 
58
65
  def create_application() -> "FastAPI":
59
66
  """the application start routine"""
@@ -122,6 +129,9 @@ def create_application() -> "FastAPI":
122
129
  openai_docs["info"]["title"] = "OpenAI Assistants API"
123
130
  letta_docs["paths"] = {k: v for k, v in letta_docs["paths"].items() if not k.startswith("/openai")}
124
131
  letta_docs["info"]["title"] = "Letta API"
132
+ letta_docs["components"]["schemas"]["LettaResponse"] = {
133
+ "properties": LettaResponse.model_json_schema(ref_template="#/components/schemas/LettaResponse/properties/{model}")["$defs"]
134
+ }
125
135
 
126
136
  # Split the API docs into Letta API, and OpenAI Assistants compatible API
127
137
  for name, docs in [
@@ -4,7 +4,6 @@ from typing import Dict, List, Optional, Union
4
4
 
5
5
  from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query, status
6
6
  from fastapi.responses import JSONResponse, StreamingResponse
7
- from starlette.responses import StreamingResponse
8
7
 
9
8
  from letta.constants import DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG
10
9
  from letta.schemas.agent import AgentState, CreateAgent, UpdateAgentState
@@ -359,7 +358,20 @@ def update_message(
359
358
  return server.update_agent_message(agent_id=agent_id, request=request)
360
359
 
361
360
 
362
- @router.post("/{agent_id}/messages", response_model=None, operation_id="create_agent_message")
361
+ @router.post(
362
+ "/{agent_id}/messages",
363
+ response_model=None,
364
+ operation_id="create_agent_message",
365
+ responses={
366
+ 200: {
367
+ "description": "Successful response",
368
+ "content": {
369
+ "application/json": {"$ref": "#/components/schemas/LettaResponse"}, # Use model_json_schema() instead of model directly
370
+ "text/event-stream": {"description": "Server-Sent Events stream"},
371
+ },
372
+ }
373
+ },
374
+ )
363
375
  async def send_message(
364
376
  agent_id: str,
365
377
  server: SyncServer = Depends(get_letta_server),
@@ -373,7 +385,7 @@ async def send_message(
373
385
  """
374
386
  actor = server.get_user_or_default(user_id=user_id)
375
387
 
376
- return await send_message_to_agent(
388
+ result = await send_message_to_agent(
377
389
  server=server,
378
390
  agent_id=agent_id,
379
391
  user_id=actor.id,
@@ -386,6 +398,7 @@ async def send_message(
386
398
  assistant_message_function_name=request.assistant_message_function_name,
387
399
  assistant_message_function_kwarg=request.assistant_message_function_kwarg,
388
400
  )
401
+ return result
389
402
 
390
403
 
391
404
  # TODO: move this into server.py?
@@ -104,3 +104,15 @@ def update_tool(
104
104
  """
105
105
  actor = server.get_user_or_default(user_id=user_id)
106
106
  return server.tool_manager.update_tool_by_id(tool_id, actor.id, request)
107
+
108
+
109
+ @router.post("/add-base-tools", response_model=List[Tool], operation_id="add_base_tools")
110
+ def add_base_tools(
111
+ server: SyncServer = Depends(get_letta_server),
112
+ user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
113
+ ):
114
+ """
115
+ Add base tools
116
+ """
117
+ actor = server.get_user_or_default(user_id=user_id)
118
+ return server.tool_manager.add_base_tools(actor=actor)
letta/server/server.py CHANGED
@@ -35,8 +35,9 @@ from letta.interface import AgentInterface # abstract
35
35
  from letta.interface import CLIInterface # for printing to terminal
36
36
  from letta.log import get_logger
37
37
  from letta.memory import get_memory_functions
38
- from letta.metadata import Base, MetadataStore
38
+ from letta.metadata import MetadataStore
39
39
  from letta.o1_agent import O1Agent
40
+ from letta.orm import Base
40
41
  from letta.orm.errors import NoResultFound
41
42
  from letta.prompts import gpt_system
42
43
  from letta.providers import (
@@ -169,6 +170,8 @@ from letta.settings import model_settings, settings, tool_settings
169
170
 
170
171
  config = LettaConfig.load()
171
172
 
173
+ attach_base()
174
+
172
175
  if settings.letta_pg_uri_no_default:
173
176
  config.recall_storage_type = "postgres"
174
177
  config.recall_storage_uri = settings.letta_pg_uri_no_default
@@ -181,13 +184,10 @@ else:
181
184
  # TODO: don't rely on config storage
182
185
  engine = create_engine("sqlite:///" + os.path.join(config.recall_storage_path, "sqlite.db"))
183
186
 
187
+ Base.metadata.create_all(bind=engine)
184
188
 
185
189
  SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
186
190
 
187
- attach_base()
188
-
189
- Base.metadata.create_all(bind=engine)
190
-
191
191
 
192
192
  # Dependency
193
193
  def get_db():
@@ -254,7 +254,7 @@ class SyncServer(Server):
254
254
  self.default_org = self.organization_manager.create_default_organization()
255
255
  self.default_user = self.user_manager.create_default_user()
256
256
  self.add_default_blocks(self.default_user.id)
257
- self.tool_manager.add_default_tools(module_name="base", actor=self.default_user)
257
+ self.tool_manager.add_base_tools(actor=self.default_user)
258
258
 
259
259
  # If there is a default org/user
260
260
  # This logic may have to change in the future
letta/server/startup.sh CHANGED
@@ -1,5 +1,8 @@
1
1
  #!/bin/sh
2
2
  echo "Starting MEMGPT server..."
3
+
4
+ alembic upgrade head
5
+
3
6
  if [ "$MEMGPT_ENVIRONMENT" = "DEVELOPMENT" ] ; then
4
7
  echo "Starting in development mode!"
5
8
  uvicorn letta.server.rest_api.app:app --reload --reload-dir /letta --host 0.0.0.0 --port 8283