Skip to content

Commit 2d00ac4

Browse files
carenthomaskianjones9sarahwoodersmattzh72cliandy
authored
chore: bump v0.8.17 (#2725)
Co-authored-by: Kian Jones <[email protected]> Co-authored-by: Sarah Wooders <[email protected]> Co-authored-by: Matthew Zhou <[email protected]> Co-authored-by: Andy Li <[email protected]> Co-authored-by: jnjpng <[email protected]> Co-authored-by: Jin Peng <[email protected]> Co-authored-by: cpacker <[email protected]> Co-authored-by: Shubham Naik <[email protected]> Co-authored-by: Shubham Naik <[email protected]> Co-authored-by: Kevin Lin <[email protected]> Co-authored-by: Eric Ly <[email protected]> Co-authored-by: Eric Ly <[email protected]>
1 parent 8bce540 commit 2d00ac4

File tree

14 files changed

+772
-164
lines changed

14 files changed

+772
-164
lines changed

letta/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
__version__ = version("letta")
66
except PackageNotFoundError:
77
# Fallback for development installations
8-
__version__ = "0.8.16"
8+
__version__ = "0.8.17"
99

1010
if os.environ.get("LETTA_VERSION"):
1111
__version__ = os.environ["LETTA_VERSION"]

letta/schemas/letta_message.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,13 @@ def serialize_datetime(self, dt: datetime, _info):
6363
dt = dt.replace(tzinfo=timezone.utc)
6464
return dt.isoformat(timespec="seconds")
6565

66-
@field_serializer("is_err", when_used="unless-none")
67-
def serialize_is_err(self, value: bool | None, _info):
66+
@field_serializer("is_err", mode="wrap")
67+
def serialize_is_err(self, value: bool | None, handler, _info):
6868
"""
6969
Only serialize is_err field when it's True (for debugging purposes).
7070
When is_err is None or False, this field will be excluded from the JSON output.
7171
"""
72-
return value if value is True else None
72+
return handler(value) if value is True else None
7373

7474

7575
class SystemMessage(LettaMessage):

letta/schemas/message.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ def to_letta_messages(
269269
otid=otid,
270270
sender_id=self.sender_id,
271271
step_id=self.step_id,
272+
is_err=self.is_err,
272273
)
273274
)
274275
# Otherwise, we may have a list of multiple types
@@ -287,6 +288,7 @@ def to_letta_messages(
287288
otid=otid,
288289
sender_id=self.sender_id,
289290
step_id=self.step_id,
291+
is_err=self.is_err,
290292
)
291293
)
292294
elif isinstance(content_part, ReasoningContent):
@@ -301,6 +303,7 @@ def to_letta_messages(
301303
name=self.name,
302304
otid=otid,
303305
step_id=self.step_id,
306+
is_err=self.is_err,
304307
)
305308
)
306309
elif isinstance(content_part, RedactedReasoningContent):
@@ -315,6 +318,7 @@ def to_letta_messages(
315318
otid=otid,
316319
sender_id=self.sender_id,
317320
step_id=self.step_id,
321+
is_err=self.is_err,
318322
)
319323
)
320324
elif isinstance(content_part, OmittedReasoningContent):
@@ -328,6 +332,7 @@ def to_letta_messages(
328332
name=self.name,
329333
otid=otid,
330334
step_id=self.step_id,
335+
is_err=self.is_err,
331336
)
332337
)
333338
else:
@@ -355,6 +360,7 @@ def to_letta_messages(
355360
otid=otid,
356361
sender_id=self.sender_id,
357362
step_id=self.step_id,
363+
is_err=self.is_err,
358364
)
359365
)
360366
else:
@@ -371,6 +377,7 @@ def to_letta_messages(
371377
otid=otid,
372378
sender_id=self.sender_id,
373379
step_id=self.step_id,
380+
is_err=self.is_err,
374381
)
375382
)
376383
elif self.role == MessageRole.tool:
@@ -416,6 +423,7 @@ def to_letta_messages(
416423
otid=Message.generate_otid_from_id(self.id, len(messages)),
417424
sender_id=self.sender_id,
418425
step_id=self.step_id,
426+
is_err=self.is_err,
419427
)
420428
)
421429
elif self.role == MessageRole.user:
@@ -437,6 +445,7 @@ def to_letta_messages(
437445
otid=self.otid,
438446
sender_id=self.sender_id,
439447
step_id=self.step_id,
448+
is_err=self.is_err,
440449
)
441450
)
442451
elif self.role == MessageRole.system:

letta/server/db.py

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import asyncio
12
import os
23
import threading
34
import time
@@ -116,6 +117,13 @@ def __init__(self):
116117
self.config = LettaConfig.load()
117118
self.logger = get_logger(__name__)
118119

120+
if settings.db_max_concurrent_sessions:
121+
self._db_semaphore = asyncio.Semaphore(settings.db_max_concurrent_sessions)
122+
self.logger.info(f"Initialized database throttling with max {settings.db_max_concurrent_sessions} concurrent sessions")
123+
else:
124+
self.logger.info("Database throttling is disabled")
125+
self._db_semaphore = None
126+
119127
def initialize_sync(self, force: bool = False) -> None:
120128
"""Initialize the synchronous database engine if not already initialized."""
121129
with self._lock:
@@ -364,16 +372,33 @@ def session(self, name: str = "default") -> Generator[Any, None, None]:
364372
@trace_method
365373
@asynccontextmanager
366374
async def async_session(self, name: str = "default") -> AsyncGenerator[AsyncSession, None]:
367-
"""Async context manager for database sessions."""
368-
session_factory = self.get_async_session_factory(name)
369-
if not session_factory:
370-
raise ValueError(f"No async session factory found for '{name}' or async database is not configured")
375+
"""Async context manager for database sessions with throttling."""
376+
if self._db_semaphore:
377+
async with self._db_semaphore:
378+
session_factory = self.get_async_session_factory(name)
379+
if not session_factory:
380+
raise ValueError(f"No async session factory found for '{name}' or async database is not configured")
381+
382+
session = session_factory()
383+
try:
384+
yield session
385+
finally:
386+
await session.close()
387+
else:
388+
session_factory = self.get_async_session_factory(name)
389+
if not session_factory:
390+
raise ValueError(f"No async session factory found for '{name}' or async database is not configured")
371391

372-
session = session_factory()
373-
try:
374-
yield session
375-
finally:
376-
await session.close()
392+
session = session_factory()
393+
try:
394+
yield session
395+
finally:
396+
await session.close()
397+
398+
@trace_method
399+
def session_caller_trace(self, caller_info: str):
400+
"""Trace sync db caller information for debugging purposes."""
401+
pass # wrapper used for otel tracing only
377402

378403
@trace_method
379404
def session_caller_trace(self, caller_info: str):

letta/server/rest_api/streaming_response.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -125,14 +125,7 @@ async def stream_response(self, send: Send) -> None:
125125
if not isinstance(content, bytes):
126126
content = content.encode(self.charset)
127127
more_body = False
128-
await send(
129-
{
130-
"type": "http.response.body",
131-
"body": content,
132-
"more_body": more_body,
133-
}
134-
)
135-
raise Exception(f"An exception occurred mid-stream with status code {status_code}", detail={"content": content})
128+
raise Exception(f"An exception occurred mid-stream with status code {status_code} with content {content}")
136129
else:
137130
content = chunk
138131

letta/server/rest_api/utils.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,22 +120,22 @@ async def sse_async_generator(
120120
yield sse_formatter(usage.model_dump(exclude={"steps_messages"}))
121121

122122
except ContextWindowExceededError as e:
123-
log_error_to_sentry(e)
123+
capture_sentry_exception(e)
124124
logger.error(f"ContextWindowExceededError error: {e}")
125125
yield sse_formatter({"error": f"Stream failed: {e}", "code": str(e.code.value) if e.code else None})
126126

127127
except RateLimitExceededError as e:
128-
log_error_to_sentry(e)
128+
capture_sentry_exception(e)
129129
logger.error(f"RateLimitExceededError error: {e}")
130130
yield sse_formatter({"error": f"Stream failed: {e}", "code": str(e.code.value) if e.code else None})
131131

132132
except Exception as e:
133-
log_error_to_sentry(e)
133+
capture_sentry_exception(e)
134134
logger.error(f"Caught unexpected Exception: {e}")
135135
yield sse_formatter({"error": "Stream failed (internal error occurred)"})
136136

137137
except Exception as e:
138-
log_error_to_sentry(e)
138+
capture_sentry_exception(e)
139139
logger.error(f"Caught unexpected Exception: {e}")
140140
yield sse_formatter({"error": "Stream failed (decoder encountered an error)"})
141141

0 commit comments

Comments
 (0)