fix: structured outputs for send_message, LettaMessage

This commit is contained in:
Andy Li
2025-07-24 14:50:52 -07:00
committed by GitHub
parent 357e30fc55
commit 94d10589c8
6 changed files with 238 additions and 130 deletions

View File

@@ -27,7 +27,6 @@ from sqlalchemy import text
import letta
from letta.constants import (
CLI_WARNING_PREFIX,
CORE_MEMORY_HUMAN_CHAR_LIMIT,
CORE_MEMORY_PERSONA_CHAR_LIMIT,
DEFAULT_CORE_MEMORY_SOURCE_CHAR_LIMIT,
@@ -851,47 +850,32 @@ def parse_json(string) -> dict:
raise e
def validate_function_response(function_response_string: any, return_char_limit: int, strict: bool = False, truncate: bool = True) -> str:
def validate_function_response(function_response: Any, return_char_limit: int, strict: bool = False, truncate: bool = True) -> str:
"""Check to make sure that a function used by Letta returned a valid response. Truncates to return_char_limit if necessary.
Responses need to be strings (or None) that fall under a certain text count limit.
This makes sure that we can coerce the function_response into a string that meets our criteria. We handle some soft coercion.
If strict is True, we raise a ValueError if function_response is not a string or None.
"""
if not isinstance(function_response_string, str):
# Soft correction for a few basic types
if isinstance(function_response, str):
function_response_string = function_response
if function_response_string is None:
# function_response_string = "Empty (no function output)"
function_response_string = "None" # backcompat
elif function_response is None:
function_response_string = "None"
elif isinstance(function_response_string, dict):
if strict:
# TODO add better error message
raise ValueError(function_response_string)
elif strict:
raise ValueError(f"Strict mode violation. Function returned type: {type(function_response).__name__}")
# Allow dict through since it will be cast to json.dumps()
try:
# TODO find a better way to do this that won't result in double escapes
function_response_string = json_dumps(function_response_string)
except:
raise ValueError(function_response_string)
elif isinstance(function_response, dict):
# As functions can return arbitrary data, if there's already nesting somewhere in the response, it's difficult
# for us to not result in double escapes.
function_response_string = json_dumps(function_response)
else:
logger.debug(f"Function returned type {type(function_response).__name__}. Coercing to string.")
function_response_string = str(function_response)
else:
if strict:
# TODO add better error message
raise ValueError(function_response_string)
# Try to convert to a string, but throw a warning to alert the user
try:
function_response_string = str(function_response_string)
except:
raise ValueError(function_response_string)
# Now check the length and make sure it doesn't go over the limit
# TODO we should change this to a max token limit that's variable based on tokens remaining (or context-window)
if truncate and len(function_response_string) > return_char_limit:
print(
f"{CLI_WARNING_PREFIX}function return was over limit ({len(function_response_string)} > {return_char_limit}) and was truncated"
)
logger.warning(f"function return was over limit ({len(function_response_string)} > {return_char_limit}) and was truncated")
function_response_string = f"{function_response_string[:return_char_limit]}... [NOTE: function output was truncated since it exceeded the character limit ({len(function_response_string)} > {return_char_limit})]"
return function_response_string