From 05c2b4ffd3ec34ec16f9d522ec0cf43564bb9d7c Mon Sep 17 00:00:00 2001 From: cthomas Date: Sun, 14 Sep 2025 17:23:50 -0700 Subject: [PATCH] feat: add tags to feedback endpoint (#2881) --- letta/server/rest_api/routers/v1/steps.py | 5 +++-- letta/services/step_manager.py | 6 +++++- letta/utils.py | 2 ++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/letta/server/rest_api/routers/v1/steps.py b/letta/server/rest_api/routers/v1/steps.py index 98560fff..891ec55b 100644 --- a/letta/server/rest_api/routers/v1/steps.py +++ b/letta/server/rest_api/routers/v1/steps.py @@ -116,12 +116,13 @@ async def retrieve_step_trace( class AddFeedbackRequest(BaseModel): feedback: FeedbackType | None = Field(None, description="Whether this feedback is positive or negative") + tags: list[str] | None = Field(None, description="Feedback tags to add to the step") @router.patch("/{step_id}/feedback", response_model=Step, operation_id="add_feedback") async def add_feedback( step_id: str, - feedback: AddFeedbackRequest = Body(...), + request: AddFeedbackRequest = Body(...), actor_id: Optional[str] = Header(None, alias="user_id"), server: SyncServer = Depends(get_letta_server), ): @@ -130,7 +131,7 @@ async def add_feedback( """ try: actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id) - return await server.step_manager.add_feedback_async(step_id=step_id, feedback=feedback, actor=actor) + return await server.step_manager.add_feedback_async(step_id=step_id, feedback=request.feedback, tags=request.tags, actor=actor) except NoResultFound: raise HTTPException(status_code=404, detail="Step not found") diff --git a/letta/services/step_manager.py b/letta/services/step_manager.py index 4007bf78..9dd107ce 100644 --- a/letta/services/step_manager.py +++ b/letta/services/step_manager.py @@ -197,12 +197,16 @@ class StepManager: @enforce_types @trace_method - async def add_feedback_async(self, step_id: str, feedback: Optional[FeedbackType], actor: PydanticUser) -> PydanticStep: + async def add_feedback_async( + self, step_id: str, feedback: FeedbackType | None, actor: PydanticUser, tags: list[str] | None = None + ) -> PydanticStep: async with db_registry.async_session() as session: step = await StepModel.read_async(db_session=session, identifier=step_id, actor=actor) if not step: raise NoResultFound(f"Step with id {step_id} does not exist") step.feedback = feedback + if tags: + step.tags = tags step = await step.update_async(session) return step.to_pydantic() diff --git a/letta/utils.py b/letta/utils.py index 793dfe99..3723c33c 100644 --- a/letta/utils.py +++ b/letta/utils.py @@ -536,6 +536,8 @@ def enforce_types(func): if origin is Union: # Handle Union types (including Optional) return any(matches_type(value, arg) for arg in args) + elif hasattr(hint, "__class__") and hint.__class__.__name__ == "UnionType": # Handle Python 3.10+ X | Y syntax + return any(matches_type(value, arg) for arg in args) elif origin is list and isinstance(value, list): # Handle List[T] element_type = args[0] if args else None return all(isinstance(v, element_type) for v in value) if element_type else True