Skip to content

Expose model switching (switch_profile) in agent-server REST API#2795

Open
VascoSch92 wants to merge 2 commits intomainfrom
2794-expose-model-switching-switch_profile-in-agent-server-rest-api
Open

Expose model switching (switch_profile) in agent-server REST API#2795
VascoSch92 wants to merge 2 commits intomainfrom
2794-expose-model-switching-switch_profile-in-agent-server-rest-api

Conversation

@VascoSch92
Copy link
Copy Markdown
Contributor

@VascoSch92 VascoSch92 commented Apr 10, 2026

  • A human has tested these changes.

Summary

  • Add POST /{conversation_id}/switch_profile endpoint to conversation_router.py, allowing API clients to switch a conversation's LLM mid-session using named profiles from LLMProfileStore
  • Handle FileNotFoundError (unknown profile → 404) and ValueError (corrupted profile → 400) at the endpoint boundary
  • Add 3 tests: success path, conversation not found, and nonexistent profile

Issue Number

Closes #2794
Closes #416

How to Test

  • test_switch_conversation_profile_success — valid profile switches correctly, returns 200
  • test_switch_conversation_profile_not_found — missing conversation returns 404
  • test_switch_conversation_profile_nonexistent_profile — missing profile returns 404 with detail message

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes


Agent Server images for this PR

GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server

Variants & Base Images

Variant Architectures Base Image Docs / Tags
java amd64, arm64 eclipse-temurin:17-jdk Link
python amd64, arm64 nikolaik/python-nodejs:python3.13-nodejs22-slim Link
golang amd64, arm64 golang:1.21-bookworm Link

Pull (multi-arch manifest)

# Each variant is a multi-arch manifest supporting both amd64 and arm64
docker pull ghcr.io/openhands/agent-server:91ce99a-python

Run

docker run -it --rm \
  -p 8000:8000 \
  --name agent-server-91ce99a-python \
  ghcr.io/openhands/agent-server:91ce99a-python

All tags pushed for this build

ghcr.io/openhands/agent-server:91ce99a-golang-amd64
ghcr.io/openhands/agent-server:91ce99a-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:91ce99a-golang-arm64
ghcr.io/openhands/agent-server:91ce99a-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:91ce99a-java-amd64
ghcr.io/openhands/agent-server:91ce99a-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:91ce99a-java-arm64
ghcr.io/openhands/agent-server:91ce99a-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:91ce99a-python-amd64
ghcr.io/openhands/agent-server:91ce99a-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-amd64
ghcr.io/openhands/agent-server:91ce99a-python-arm64
ghcr.io/openhands/agent-server:91ce99a-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-arm64
ghcr.io/openhands/agent-server:91ce99a-golang
ghcr.io/openhands/agent-server:91ce99a-java
ghcr.io/openhands/agent-server:91ce99a-python

About Multi-Architecture Support

  • Each variant tag (e.g., 91ce99a-python) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., 91ce99a-python-amd64) are also available if needed

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 10, 2026

Python API breakage checks — ✅ PASSED

Result:PASSED

Action log

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 10, 2026

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 10, 2026

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-agent-server/openhands/agent_server
   conversation_router.py133993%286, 376–379, 391–394
TOTAL228781040254% 

all-hands-bot

This comment was marked as duplicate.

@VascoSch92 VascoSch92 linked an issue Apr 10, 2026 that may be closed by this pull request
@VascoSch92 VascoSch92 marked this pull request as ready for review April 10, 2026 08:00
@VascoSch92 VascoSch92 requested a review from neubig April 10, 2026 08:01
all-hands-bot

This comment was marked as outdated.

all-hands-bot

This comment was marked as duplicate.

Copy link
Copy Markdown
Collaborator

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 configured

CI & 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.36s

Functional 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:

  1. Error Handling - Properly maps Python exceptions to HTTP status codes:

    • FileNotFoundError → 404 (profile not found)
    • ValueError → 400 (corrupted profile)
    • Missing conversation → 404
  2. Consistency - Follows the same pattern as other conversation endpoints

  3. Testing - Comprehensive test coverage with all 4 scenarios

  4. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expose model switching (switch_profile) in agent-server REST API Support switching LLM models in the middle of conversation

2 participants