feat: REST API PUT /prompt_versions/{id}/tags/{tag_name} — upsert tag#12428
feat: REST API PUT /prompt_versions/{id}/tags/{tag_name} — upsert tag#12428mikeldking wants to merge 2 commits intomainfrom
Conversation
Implements the REST endpoint to create or move a named prompt version tag. If the tag already exists on another version of the same prompt, it is moved to the specified version (upsert semantics using ON CONFLICT DO UPDATE on the unique (name, prompt_id) constraint). Closes #12424 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
|
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
The filter_by queries were passing plain strings where the column type expects Identifier, causing SQLAlchemy bind-param assertion failures.
anticorrelator
left a comment
There was a problem hiding this comment.
Looks good — clean implementation that follows existing patterns well. One thing to address:
Upsert silently wipes existing tag description when no body is sent
When request_body is None or {}, description resolves to None. Since insert_on_conflict with DO_UPDATE updates all non-PK columns (via _clean(stmt.excluded.items())), a caller who only wants to move a tag to a different version loses its description as a side effect.
This affects both the no-body and empty-body cases. A few options:
- Only include
descriptionin the conflict update set when it's explicitly provided in the body - Use
COALESCE(excluded.description, prompt_version_tag.description)to preserve existing values - Document the behavior explicitly if it's intentional
Also worth adding test coverage for: (a) upsert that updates description, (b) move that verifies description is preserved/cleared as intended.
Summary
Implements
PUT /prompt_versions/{prompt_version_id}/tags/{tag_name}— the upsert endpoint for prompt version tags, as specified in #12424.ON CONFLICT DO UPDATEon the unique(name, prompt_id)constraint)204 No Contenton successMEMBER+write access (viaDepends(is_not_locked)){"description": "..."}— all fields optional, body may be omitted entirelyWhat changed
src/phoenix/server/api/routers/v1/prompts.py: AddedUpsertPromptVersionTagRequestBodymodel andPUT /prompt_versions/{prompt_version_id}/tags/{tag_name}endpointschemas/openapi.json: Added new path andUpsertPromptVersionTagRequestBodyschema componenttests/unit/server/api/routers/v1/test_prompts.py: Six new unit tests covering create, move, no-body, invalid version ID, version not found, and invalid tag name casestests/integration/_helpers.py: Registered the new PUT endpoint in_VIEWER_BLOCKED_WRITE_OPERATIONSto ensure viewer-role access control coverageHow to verify
Checklist
createPromptVersionTagPOST endpoint)except ValidationErrorused (consistent with other Identifier validation in file)Depends(is_not_locked)applied🤖 Generated with Claude Code