POST /api/arena/dualchat
Send a prompt to two AI models simultaneously for blind comparison. Supports random, topper, and manual selection modes.
POST
Dual Chat (Arena Mode) Stable
Send a prompt to two AI models simultaneously. Models respond in parallel, and responses are returned anonymized for blind comparison.Authentication Required
JWT Claims Extraction (Lines 338-350):Request Body
User message sent to both modelsValidation (Line 200):
Model selection strategyValues:
"random": Two different random active models (Lines 246-249)"topper": Top-performing model + random model (Lines 237-242)- Default if manual models provided:
"manual"(Line 233)
First model name (manual selection)Validation (Lines 218-229):Required: Only if
model2 also providedSecond model name (manual selection)Required: Only if
model1 also providedSystem prompt applied to both modelsDefault: Implementation-defined and not guaranteed (provider-specific)
Maximum tokens per model responseApplies: To both models independently
Sampling temperature (0.0-2.0) for both modelsDefault: Not specified by API contract (provider-defined)
Session tracking identifierAuto-generation (Line 197):
Thread UUID to associate comparison with conversationValidation (Lines 354-360):
Response
Always
true on success (Line 392)First model response (same structure as single chat)
Second model response (same structure as agent1)
UUID identifying this comparison (Line 198, 395)Used for: Voting via
/api/arena/model-voteISO8601 UTC timestamp (Line 415)
Total request duration (Lines 265, 416)Note: Due to parallel execution, approximately equal to slowest model time
Side Effects
Database Mutations (Lines 333-360):-
message_logs table (Lines 333-334):
- Two separate log entries (one per model)
-
users table UPSERT (Lines 336-350):
- Executes on every authenticated request
- Idempotent operation
-
comparisons table (Line 352):
- Stores comparison data for voting/leaderboard
- Links to both models
-
thread_messages table (conditional, Lines 354-360):
- Only if
threadIdprovided and valid GUID - Links message to comparison via
comparisonId
- Only if
Behavior
Parallel Execution (Lines 254-257):- Each model has independent 45s timeout
- Each model has independent fallback chain
- One model failure doesn’t block the other
- Measures total elapsed time
- Due to parallel execution:
max(model1_time, model2_time) + overhead
| Mode | Logic | Line Range |
|---|---|---|
| Manual | !string.IsNullOrWhiteSpace(request.Model1 || Model2) | 218-233 |
| Topper | request.SelectionMode == "topper" | 237-242 |
| Random | Default | 244-250 |
- Queries
model_votestable for highest win rate - Pairs top model with random model
- Ensures diverse comparison
Error Conditions
| Code | HTTP | Cause | Controller Line |
|---|---|---|---|
INVALID_REQUEST | 400 | Prompt null/whitespace | 202-208 |
INVALID_REQUEST | 400 | Manual mode missing model1 or model2 | 222-228 |
API_ERROR | 500 | Inner exception (provider failure) | 424-430 |
API_ERROR | 500 | Outer exception (unexpected error) | 436-442 |
Edge Cases
- Same model selected twice: Not prevented by code, allowed in random selection
- Topper mode with insufficient vote data: Behavior not enforced by server contract (assumed fallback to random selection)
- Invalid threadId GUID: Silently skipped, no error (Line 356 guard)
- Model fallback changes model names:
finalModel1andfinalModel2may differ from requested models - Null usage stats: Handled with null-coalescing (Lines 366-367)
Comparison ID Usage
Generated at request start (Line 198):- Logging comparison to
comparisonstable (Line 352) - Linking to thread message in
thread_messagestable (Line 358) - Returned in response for voting (Line 395)
- Voting endpoint requires this ID:
POST /api/arena/model-vote
Rate Limits
No explicit rate limiting in controller. Provider-level limits apply:- Groq free tier: 30 req/min, 14,400 tokens/min
- Dual chat consumes 2× tokens (both models)
- Effective limit: ~15 dual-chat requests/min on free tier