From 8f13c078ef8d3159b73349d83a338daf3cb15639 Mon Sep 17 00:00:00 2001 From: cthomas Date: Thu, 12 Jun 2025 16:54:29 -0700 Subject: [PATCH] feat: add stop reason object (#2783) --- letta/__init__.py | 1 + letta/schemas/letta_stop_reason.py | 44 ++++++++++++++++++++++++++++++ letta/server/rest_api/app.py | 2 ++ 3 files changed, 47 insertions(+) create mode 100644 letta/schemas/letta_stop_reason.py diff --git a/letta/__init__.py b/letta/__init__.py index 1d620eb7..6cc0e81b 100644 --- a/letta/__init__.py +++ b/letta/__init__.py @@ -17,6 +17,7 @@ from letta.schemas.enums import JobStatus from letta.schemas.file import FileMetadata from letta.schemas.job import Job from letta.schemas.letta_message import LettaMessage +from letta.schemas.letta_stop_reason import LettaStopReason from letta.schemas.llm_config import LLMConfig from letta.schemas.memory import ArchivalMemorySummary, BasicBlockMemory, ChatMemory, Memory, RecallMemorySummary from letta.schemas.message import Message diff --git a/letta/schemas/letta_stop_reason.py b/letta/schemas/letta_stop_reason.py new file mode 100644 index 00000000..e5c65a3b --- /dev/null +++ b/letta/schemas/letta_stop_reason.py @@ -0,0 +1,44 @@ +from enum import Enum +from typing import Literal + +from pydantic import BaseModel, Field + + +class StopReasonType(str, Enum): + end_turn = "end_turn" + error = "error" + invalid_tool_call = "invalid_tool_call" + max_steps = "max_steps" + no_tool_call = "no_tool_call" + + +class LettaStopReason(BaseModel): + """ + The stop reason from letta used during streaming response. + """ + + message_type: Literal["stop_reason"] = "stop_reason" + stop_reason: StopReasonType = Field(..., description="The type of the message.") + + +def create_letta_stop_reason_schema(): + return { + "properties": { + "message_type": { + "type": "string", + "const": "stop_reason", + "title": "Message Type", + "description": "The type of the message.", + "default": "stop_reason", + }, + "stop_reason": { + "type": "string", + "enum": list(StopReasonType.__members__.keys()), + "title": "Stop Reason", + }, + }, + "type": "object", + "required": ["stop_reason"], + "title": "LettaStopReason", + "description": "Letta provided stop reason for why agent loop ended.", + } diff --git a/letta/server/rest_api/app.py b/letta/server/rest_api/app.py index 7c249087..5d166c67 100644 --- a/letta/server/rest_api/app.py +++ b/letta/server/rest_api/app.py @@ -24,6 +24,7 @@ from letta.schemas.letta_message_content import ( create_letta_message_content_union_schema, create_letta_user_message_content_union_schema, ) +from letta.schemas.letta_stop_reason import create_letta_stop_reason_schema from letta.server.constants import REST_DEFAULT_PORT # NOTE(charles): these are extra routes that are not part of v1 but we still need to mount to pass tests @@ -68,6 +69,7 @@ def generate_openapi_schema(app: FastAPI): letta_docs["components"]["schemas"]["LettaMessageContentUnion"] = create_letta_message_content_union_schema() letta_docs["components"]["schemas"]["LettaAssistantMessageContentUnion"] = create_letta_assistant_message_content_union_schema() letta_docs["components"]["schemas"]["LettaUserMessageContentUnion"] = create_letta_user_message_content_union_schema() + letta_docs["components"]["schemas"]["LettaStopReason"] = create_letta_stop_reason_schema() # Update the app's schema with our modified version app.openapi_schema = letta_docs