Expose model switching (switch_profile) in agent-server REST API#2795
Expose model switching (switch_profile) in agent-server REST API#2795VascoSch92 wants to merge 2 commits intomainfrom
Conversation
Python API breakage checks — ✅ PASSEDResult: ✅ PASSED |
REST API breakage checks (OpenAPI) — ✅ PASSEDResult: ✅ PASSED |
all-hands-bot
left a comment
There was a problem hiding this comment.
Taste Rating: 🟢 Good taste
What's Right:
- Simple, direct solution: profile_name → switch_profile() with clean error mapping
- No unnecessary complexity (2 levels max, single responsibility)
- Appropriate error handling: FileNotFoundError→404, ValueError→400
- Tests cover all code paths (router-layer mocking is correct here)
- Solves real user problems (#2794, #416)
VERDICT: ✅ Approve
KEY INSIGHT: Straightforward REST wrapper exposing existing functionality - does exactly what it needs to do, nothing more.
all-hands-bot
left a comment
There was a problem hiding this comment.
QA Report: switch_profile Endpoint
Verdict: ✅ PASS
The switch_profile endpoint is working correctly. All tests pass, the implementation is solid, and the API behaves exactly as specified in all tested scenarios.
Environment Setup
✅ Success - Environment configured without issues
$ make build
# Dependencies installed successfully
# Pre-commit hooks configuredCI & Test Status
✅ All Checks Passed
Relevant CI checks:
- ✅
agent-server-tests- PASSED - ✅
pre-commit- PASSED - ✅
Python API breakage checks- PASSED - ✅
REST API breakage checks- PASSED - ✅
Check OpenAPI Schema- PASSED
Unit tests (local):
$ uv run pytest tests/agent_server/test_conversation_router.py -k "switch_conversation_profile" -v
tests/agent_server/test_conversation_router.py::test_switch_conversation_profile_success PASSED [25%]
tests/agent_server/test_conversation_router.py::test_switch_conversation_profile_not_found PASSED [50%]
tests/agent_server/test_conversation_router.py::test_switch_conversation_profile_nonexistent_profile PASSED [75%]
tests/agent_server/test_conversation_router.py::test_switch_conversation_profile_corrupted_profile PASSED [100%]
4 passed in 0.36sFunctional Verification
✅ All Scenarios Verified
I created and ran a comprehensive integration test that exercises the endpoint exactly as a real HTTP client would. All 5 scenarios work correctly:
Test 1: Successful Profile Switch ✅
POST /api/conversations/{id}/switch_profile
Payload: {"profile_name": "gpt-4o"}
Status: 200
Response: {"success": true}
✓ Endpoint accepts JSON with profile_name field
✓ Returns 200 on success
✓ Calls conversation.switch_profile() with the correct profile name
Test 2: Non-existent Conversation ✅
POST /api/conversations/{fake-id}/switch_profile
Payload: {"profile_name": "gpt"}
Status: 404
Response: {"detail": "Not Found"}
✓ Returns 404 when conversation doesn't exist
Test 3: Non-existent Profile ✅
POST /api/conversations/{id}/switch_profile
Payload: {"profile_name": "nonexistent"}
Status: 404
Response: {"detail": "Profile 'nonexistent' not found"}
✓ Returns 404 when profile doesn't exist (FileNotFoundError)
✓ Error message includes the profile name for clarity
Test 4: Corrupted Profile ✅
POST /api/conversations/{id}/switch_profile
Payload: {"profile_name": "corrupted"}
Status: 400
Response: {"detail": "Invalid profile format"}
✓ Returns 400 when profile is invalid (ValueError)
✓ Error message describes the issue
Test 5: Missing Required Parameter ✅
POST /api/conversations/{id}/switch_profile
Payload: {}
Status: 422
✓ Returns 422 when profile_name is missing
Code Quality Observations
✅ Implementation is clean and follows established patterns:
-
Error Handling - Properly maps Python exceptions to HTTP status codes:
FileNotFoundError→ 404 (profile not found)ValueError→ 400 (corrupted profile)- Missing conversation → 404
-
Consistency - Follows the same pattern as other conversation endpoints
-
Testing - Comprehensive test coverage with all 4 scenarios
-
API Design - Uses
Body(..., embed=True)to require JSON body with named field, preventing ambiguity
Issues Found
None - No issues found during QA.
Verification Evidence
Full integration test output:
======================================================================
Testing switch_profile Endpoint - Integration Test
======================================================================
[Test 1] Switching to 'gpt-4o' profile...
POST /api/conversations/{id}/switch_profile
Payload: {'profile_name': 'gpt-4o'}
Status: 200
Response: {'success': True}
✓ Successfully switched profile
[Test 2] Trying non-existent conversation...
Status: 404
✓ Correctly returned 404 for non-existent conversation
[Test 3] Trying non-existent profile...
Status: 404
Response: {'detail': "Profile 'nonexistent' not found"}
✓ Correctly returned 404 with descriptive error
[Test 4] Trying corrupted profile...
Status: 400
Response: {'detail': 'Invalid profile format'}
✓ Correctly returned 400 with descriptive error
[Test 5] Missing profile_name parameter...
Status: 422
✓ Correctly returned 422 for missing required parameter
======================================================================
All Integration Tests Passed!
======================================================================
Summary
✅ PASS - The switch_profile endpoint is production-ready:
- ✅ Endpoint implementation is correct
- ✅ Error handling is comprehensive
- ✅ All unit tests pass
- ✅ Integration testing confirms real-world behavior
- ✅ CI checks all passing
- ✅ No regressions detected
- ✅ API design follows established patterns
Recommendation: This PR is ready to merge.
Summary
Issue Number
Closes #2794
Closes #416
How to Test
Type
Notes
Agent Server images for this PR
• GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server
Variants & Base Images
eclipse-temurin:17-jdknikolaik/python-nodejs:python3.13-nodejs22-slimgolang:1.21-bookwormPull (multi-arch manifest)
# Each variant is a multi-arch manifest supporting both amd64 and arm64 docker pull ghcr.io/openhands/agent-server:91ce99a-pythonRun
All tags pushed for this build
About Multi-Architecture Support
91ce99a-python) is a multi-arch manifest supporting both amd64 and arm6491ce99a-python-amd64) are also available if needed