FIFO Evals Client - Migration Guide
Replaces a synchronous evaluation script that times out on large jobs with an async FIFO queue client that handles up to 10,000 prompts without failure.
What this file does
Replaces a synchronous evaluation script that times out on large jobs with an async FIFO queue client that handles up to 10,000 prompts without failure.
When to use it
- Migrating from bulk_evals.py to avoid 60-second timeouts
- Running evaluations with 990+ prompts that previously failed
- Needing real-time progress tracking and job cancellation
- Adding client-side CSV validation before submission
Assumes this stack
FIFO Evals Client - Migration Guide
What Changed?
OLD: bulk_evals.py (Synchronous)
- Problem: Times out after 60 seconds
- Failure Rate: 100% on jobs with 990+ prompts
- Flow: CSV → external-apis (synchronous) → ❌ Timeout
NEW: fifo_client.py (Async Job Queue + Enhanced Validation)
- Solution: Async job submission with FIFO queue
- Success Rate: 0% failure rate (no timeouts)
- Flow: CSV → Submit (<1s) → FIFO Queue → Background Processing → Results
- New Features:
- ✅ Client-side CSV validation (encoding, columns, structure)
- ✅ List all jobs with status filtering
- ✅ Cancel running jobs
- ✅ Comprehensive error messages
- ✅ Progress tracking with conversation stats
Quick Start
Installation
No additional dependencies needed! Uses httpx which is already in the project.
Basic Usage
from AdoptXchange.evals import FIFOEvalsClient
# Create client
client = FIFOEvalsClient(api_key="your_api_key", org_id="your_org_id")
# Submit and wait (simplest)
result_file = client.submit_and_wait(
csv_file="evaluation_test_cases.csv",
config={
"action_id": "action_rallyup_assistant",
"base_url": "https://go.rallyup.com",
"security_params": {"cookie": "session=..."},
"batch_size": 5
},
output_file="results.csv"
)
print(f"Results: {result_file}")
Output:
Submitting evaluation job: evaluation_test_cases.csv
⚠️ CSV Validation Warnings:
• Mixed conversation structure: 5 multi-turn, 45 single-turn conversations.
📊 CSV Analysis:
• 55 total rows
• 50 conversations (5 multi-turn, 45 single-turn)
• Input column: 'input'
• ID column: 'input_id'
Job submitted: abc-123
Progress: 10.0% (5 of 50 conversations)
Progress: 20.0% (10 of 50 conversations)
Progress: 30.0% (15 of 50 conversations)
...
✅ Job completed in 5.2 minutes
Downloaded: results.csv (12.5 KB)
Migration Examples
Example 1: Replace Synchronous Call
Before (bulk_evals.py):
from AdoptXchange.evals.bulk_evals import run_bulk_evaluation
# Times out on 990 prompts ❌
result = run_bulk_evaluation(
csv_file="test.csv",
action_id="action_rallyup_assistant",
# ... fails
)
After (fifo_client.py):
from AdoptXchange.evals import run_fifo_evaluation
# Succeeds on 990 prompts ✅
result_file = run_fifo_evaluation(
csv_file="test.csv",
action_id="action_rallyup_assistant",
base_url="https://go.rallyup.com",
security_params={"cookie": "session=..."},
api_key="your_api_key",
output_file="results.csv"
)
Example 2: Step-by-Step Control
When you need more control:
from AdoptXchange.evals import FIFOEvalsClient
client = FIFOEvalsClient(api_key="your_api_key", org_id="your_org_id")
# 1. Submit job (returns immediately)
job_id = client.submit_evaluation(
csv_file="test.csv",
config={
"action_id": "action_rallyup_assistant",
"base_url": "https://go.rallyup.com",
"security_params": {"cookie": "session=..."}
}
)
print(f"Job ID: {job_id}")
# 2. Check status anytime
status = client.get_status(job_id)
print(f"Progress: {status['progress']['percent_complete']:.1f}%")
# 3. Wait for completion (optional)
result = client.wait_for_completion(job_id, poll_interval=30)
# 4. Download results
client.download_results(result, "results.csv")
Example 3: List and Manage Jobs
List all your evaluation jobs:
from AdoptXchange.evals import FIFOEvalsClient
client = FIFOEvalsClient(api_key="your_api_key", org_id="your_org_id")
# List all jobs
all_jobs = client.list_jobs()
for job in all_jobs:
print(f"{job['job_id']}: {job['status']} - {job['created_at']}")
# List only completed jobs
completed = client.list_jobs(status_filter="completed")
# List only in-progress jobs
active = client.list_jobs(status_filter="processing")
Example 4: Cancel a Long-Running Job
Cancel jobs that are taking too long:
from AdoptXchange.evals import FIFOEvalsClient
client = FIFOEvalsClient(api_key="your_api_key", org_id="your_org_id")
# Submit large evaluation (990 prompts = 85 minutes)
job_id = client.submit_evaluation("large_eval.csv", config={...})
# Check progress
status = client.get_status(job_id)
print(f"Progress: {status['progress']['percent_complete']}%")
# User realizes wrong file - cancel it!
if status['status'] in ['pending', 'processing']:
result = client.cancel_job(job_id)
print(f"Cancelled at: {result['cancelled_at']}")
Example 5: Context Manager
For automatic cleanup:
from AdoptXchange.evals import FIFOEvalsClient
with FIFOEvalsClient(api_key="your_api_key", org_id="your_org_id") as client:
result_file = client.submit_and_wait(
csv_file="test.csv",
config={...},
output_file="results.csv"
)
# Client automatically closed
Configuration Options
Required Fields
config = {
"action_id": "action_rallyup_assistant", # ProjectA3 action ID
"base_url": "https://go.rallyup.com", # Customer API URL
"security_params": { # Auth credentials
"cookie": "session=abc123" # or api_key, etc.
}
}
Optional Fields
config = {
# ... required fields ...
"batch_size": 5, # Conversations per batch (default: 5)
"skip_maxim": False, # Skip Maxim evaluation (default: False)
"maxim_workspace_id": "...", # Maxim workspace (optional)
"workflow_params": {}, # Additional action params (default: {})
}
CSV Format
Input CSV (Flexible Column Names!)
The CSV format is backward compatible with bulk_evals.py, plus new flexibility:
input_id,input,expected_output
1,I want to view raffle tickets,Perfect! Here's the link...
11,I want to see ticket registrations,I found 2 campaigns. Which one?
11,Discovered Water Gala 2026,Here's the link to view registrations...
Column Name Flexibility:
- Input column:
input,prompt,user_message, ormessage - ID column:
input_id,conversation_id, orid - Output column:
expected_outputorresponse - All case-insensitive matching
Example Alternative Format:
conversation_id,prompt,response
conv1,Hello,Hi there
conv1,How are you?,I'm doing well
Multi-Turn Detection:
- Same
input_id= same conversation - Rows executed in order
- Conversation history preserved
No ID Column?
- All rows treated as separate single-turn conversations
- Server auto-generates IDs
CSV Validation: The client now validates your CSV before submission:
- ✅ Encoding support (UTF-8, UTF-8-BOM, Latin-1, Windows-1252)
- ✅ File size limit (100 MB max)
- ✅ Row count limit (10,000 rows max)
- ✅ Required columns check
- ✅ Conversation structure analysis
Output CSV (Enhanced!)
input_id,turn,input,expected_output,actual_output,status,execution_time_ms
1,1,I want to view raffle tickets,...,Perfect!...,completed,4823
11,1,I want to see ticket registrations,...,I found 2...,completed,4201
11,2,Discovered Water Gala 2026,...,Here's the link...,completed,5567
New columns:
turn: Turn number within conversationstatus:completed,failed, orerrorexecution_time_ms: Per-turn execution time
API Methods
FIFOEvalsClient(api_key, org_id, base_url="https://api.adopt.ai")
Initialize client.
Parameters:
api_key(str): Bearer token for authenticationbase_url(str): API base URL (default: production)
submit_evaluation(csv_file, config) -> str
Submit evaluation job (returns immediately in <1 second).
Parameters:
csv_file(str): Path to CSV fileconfig(dict): Job configuration (see Configuration Options)
Returns:
job_id(str): Job identifier for status tracking
Example:
job_id = client.submit_evaluation("test.csv", config={...})
get_status(job_id) -> dict
Get current job status and progress.
Parameters:
job_id(str): Job identifier
Returns:
{
"job_id": "abc-123",
"status": "processing", # pending, processing, completed, failed, cancelled
"error_message": None, # Error description if failed
"started_at": "2026-02-10T19:00:05Z",
"completed_at": None,
"progress": {
"total_conversations": 50,
"completed_conversations": 10,
"total_turns": 55,
"completed_turns": 12,
"percent_complete": 21.8
},
"metrics": {
"successful_conversations": 9,
"failed_conversations": 1,
"successful_turns": 11,
"failed_turns": 1,
"average_latency_ms": 1234.5
}
}
Status Values:
pending- Job queued, not started yetprocessing- Worker actively executingcompleted- Successfully finishedfailed- Execution errorcancelled- User cancelled the job
wait_for_completion(job_id, poll_interval=30) -> dict
Wait for job to complete (blocks until done).
Parameters:
job_id(str): Job identifierpoll_interval(int): Seconds between status checks
Returns:
- Result dictionary with download URLs
Example:
result = client.wait_for_completion(job_id, poll_interval=30)
get_result(job_id) -> dict
Get job result with download URLs.
Parameters:
job_id(str): Job identifier
Returns:
{
"job_id": "abc-123",
"org_id": "org-456",
"status": "completed",
"csv_s3_key": "fifo-evals/data/abc-123/output.csv",
"json_s3_key": "fifo-evals/data/abc-123/report.json",
"csv_download_url": "https://s3.../output.csv?X-Amz-...",
"json_download_url": "https://s3.../report.json?X-Amz-...",
"message": "Results available for download (URLs expire in 15 minutes)"
}
list_jobs(limit=50, status_filter=None) -> list
List all evaluation jobs for current user.
Parameters:
limit(int): Maximum number of jobs to return (1-100, default: 50)status_filter(str): Filter by status (pending, processing, completed, failed, cancelled)
Returns:
- List of job summaries
Example:
# List all jobs
all_jobs = client.list_jobs()
# List only completed jobs
completed = client.list_jobs(status_filter="completed")
# List recent 20 jobs
recent = client.list_jobs(limit=20)
# Each job contains:
# {
# "job_id": "abc-123",
# "status": "completed",
# "created_at": "2026-02-10T19:00:00Z",
# "started_at": "2026-02-10T19:00:05Z",
# "completed_at": "2026-02-10T20:25:00Z"
# }
cancel_job(job_id) -> dict
Cancel a running or pending evaluation job.
Parameters:
job_id(str): Job identifier to cancel
Returns:
- Updated job status with cancellation details
Example:
result = client.cancel_job(job_id)
print(f"Cancelled at: {result['cancelled_at']}")
# Returns:
# {
# "job_id": "abc-123",
# "org_id": "org-456",
# "status": "cancelled",
# "message": "Eval job cancelled successfully",
# "cancelled_at": "2026-02-10T20:30:00Z"
# }
Note: Cannot cancel jobs with status completed or failed.
download_results(result, output_file) -> str
Download result CSV from presigned URL.
Parameters:
result(dict): Result dictionary fromget_result()(must containcsv_download_url)output_file(str): Local path to save CSV
Returns:
- Path to downloaded file
submit_and_wait(csv_file, config, output_file="results.csv") -> str
Convenience method: submit, wait, and download in one call.
Parameters:
csv_file(str): Input CSV pathconfig(dict): Job configurationoutput_file(str): Output CSV path
Returns:
- Path to downloaded results file
This is the simplest migration path from bulk_evals.py!
Performance Comparison
| Metric | bulk_evals.py (OLD) | fifo_client.py (NEW) |
|---|---|---|
| Submission Time | Blocks until complete | <1 second |
| Max Prompts | ~10 (timeout at 60s) | 10,000 per job |
| 990 Prompts | ❌ 100% failure rate | ✅ 0% failure rate |
| Execution Time (990) | Timeout | ~85 minutes |
| Progress Tracking | ❌ None | ✅ Real-time |
| Multi-Turn | ✅ Supported | ✅ Supported |
| Maxim Integration | ✅ Supported | ✅ Supported |
Troubleshooting
Error: "Job failed"
Check the error message:
try:
result = client.wait_for_completion(job_id)
except Exception as e:
print(f"Error: {e}")
# Check last status for details
status = client.get_status(job_id)
print(status)
Error: "Results not ready"
Wait for job to complete before calling get_result():
status = client.get_status(job_id)
if status["status"] == "completed":
result = client.get_result(job_id)
client.download_results(result, "results.csv")
else:
progress = status["progress"]
print(f"Still processing: {progress['percent_complete']:.1f}%")
Error: "CSV file not found"
Use absolute paths:
from pathlib import Path
csv_file = Path("test.csv").resolve()
job_id = client.submit_evaluation(str(csv_file), config={...})
Error: "CSV missing required column"
Your CSV must have at least one of these input columns:
input,prompt,user_message, ormessage
Example fix:
# ❌ Wrong - no input column
id,query,answer
1,Test,Response
# ✅ Correct - has 'input' column
id,input,answer
1,Test,Response
Error: "CSV has too many rows"
Maximum 10,000 rows per job. Split large files:
# Split into chunks of 10,000 rows each
import pandas as pd
df = pd.read_csv("large_file.csv")
chunk_size = 10000
for i, chunk_df in enumerate(df.groupby(df.index // chunk_size)):
chunk_df[1].to_csv(f"chunk_{i}.csv", index=False)
# Submit each chunk
job_id = client.submit_evaluation(f"chunk_{i}.csv", config={...})
print(f"Submitted chunk {i}: {job_id}")
Warning: "Mixed conversation structure"
This is informational - your CSV has both multi-turn and single-turn conversations:
⚠️ CSV Validation Warnings:
• Mixed conversation structure: 5 multi-turn, 45 single-turn conversations.
This is normal! The client will handle both correctly.
FAQ
Q: Do I need to update my CSV format?
No! The CSV format is the same. The client handles everything transparently.
Q: Can I cancel a running job?
Yes! Use client.cancel_job(job_id) to cancel pending or processing jobs:
result = client.cancel_job(job_id)
print(f"Cancelled: {result['status']}")
Note: Cannot cancel jobs that are already completed or failed.
Q: How long do result URLs last?
Presigned URLs expire after 15 minutes. Download promptly after get_result().
Q: Can I submit multiple jobs?
Yes! Each job gets a unique job_id. They'll be processed in FIFO order.
Use client.list_jobs() to see all your jobs:
jobs = client.list_jobs()
for job in jobs:
print(f"{job['job_id']}: {job['status']}")
Q: How do I see all my evaluation jobs?
Use client.list_jobs() to list all jobs, with optional filtering:
# All jobs
all_jobs = client.list_jobs()
# Only completed
completed = client.list_jobs(status_filter="completed")
# Only in-progress
active = client.list_jobs(status_filter="processing")
Q: What happened to bulk_evals.py?
It's still there for backward compatibility but deprecated. Migrate to fifo_client.py.
What's inside
10 API methods, 5 migration examples, configuration reference, CSV format spec, performance comparison table, troubleshooting FAQ
Change this for your project
- Replace
api_key="your_api_key"with your actual API key - Replace
org_id="your_org_id"with your organization ID - Replace
action_id="action_rallyup_assistant"with your action ID - Replace
base_url="https://go.rallyup.com"with your customer API URL
Where it goes
Keep alongside your test suite. Used to define and score model evaluations.
Worth borrowing
- Client-side CSV validation before submission catches encoding, column, and structure issues early
- FIFO queue with async submission separates job submission from execution, eliminating timeouts
- Convenience method
submit_and_waitwraps submit, poll, and download into one call for simple migration
Related Documents
AI Tools for Developers
Curates a personal reference of AI coding tools, models, and setup instructions for VS Code, Xcode, and Cursor.
Evaluating AI Agent Systems: Metrics, Benchmarks, and Quality Assurance (2024-2026)
Surveys 2024-2026 metrics, benchmarks, and monitoring tools for evaluating AI agent systems, with recommendations for a self-improving coding agent.
IATA BCBP Standard Compliance
Documents which IATA BCBP fields and barcode formats a Swift library implements, including Version 8 gender code support.
Voice AI Leaderboards, Benchmarks, and Evaluation Gaps (Jan 2025 -- Feb 2026)
Surveys 20+ voice AI benchmarks from Jan 2025, Feb 2026, identifies evaluation gaps, and provides leaderboard data for STT, TTS, and end-to-end voice agents.