Skip to content

Commit e31f956

Browse files
chriscrosstalkclaude
authored andcommitted
fix(benchmark): Fix AI benchmark connectivity and improve error handling
- Add OLLAMA_API_URL environment variable for Docker networking - Use host.docker.internal to reach Ollama from NOMAD container - Add extra_hosts config in compose for Linux compatibility - Add downloading_ai_model status with clear progress indicator - Show model download progress on first AI benchmark run - Fail AI-only benchmarks with clear error if AI unavailable - Display benchmark errors to users via Alert component - Improve error messages with error codes for debugging Fixes issue where AI benchmark silently failed due to NOMAD container being unable to reach Ollama at localhost:11434. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 565abca commit e31f956

File tree

4 files changed

+47
-15
lines changed

4 files changed

+47
-15
lines changed

admin/app/services/benchmark_service.ts

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ const BENCHMARK_CHANNEL = 'benchmark-progress'
4444
const AI_BENCHMARK_MODEL = 'llama3.2:1b'
4545
const AI_BENCHMARK_PROMPT = 'Explain recursion in programming in exactly 100 words.'
4646

47+
// Ollama API URL - configurable for Docker environments where localhost doesn't reach the host
48+
// In Docker, use host.docker.internal (Docker Desktop) or the host gateway IP (Linux)
49+
const OLLAMA_API_URL = process.env.OLLAMA_API_URL || 'http://host.docker.internal:11434'
50+
4751
// Reference scores for normalization (calibrated to 0-100 scale)
4852
// These represent "expected" scores for a mid-range system (score ~50)
4953
const REFERENCE_SCORES = {
@@ -280,8 +284,12 @@ export class BenchmarkService {
280284
try {
281285
aiScores = await this._runAIBenchmark()
282286
} catch (error) {
287+
// For AI-only benchmarks, failing is fatal - don't save useless results with all zeros
288+
if (type === 'ai') {
289+
throw new Error(`AI benchmark failed: ${error.message}. Make sure AI Assistant is installed and running.`)
290+
}
291+
// For full benchmarks, AI is optional - continue without it
283292
logger.warn(`AI benchmark skipped: ${error.message}`)
284-
// AI benchmark is optional, continue without it
285293
}
286294
}
287295

@@ -362,31 +370,36 @@ export class BenchmarkService {
362370

363371
// Check if Ollama is available
364372
try {
365-
await axios.get('http://localhost:11434/api/tags', { timeout: 5000 })
366-
} catch {
367-
throw new Error('Ollama is not running or not accessible')
373+
await axios.get(`${OLLAMA_API_URL}/api/tags`, { timeout: 5000 })
374+
} catch (error) {
375+
const errorCode = error.code || error.response?.status || 'unknown'
376+
throw new Error(`Ollama is not running or not accessible (${errorCode}). Ensure AI Assistant is installed and running.`)
368377
}
369378

370379
// Check if the benchmark model is available, pull if not
371-
try {
372-
const modelsResponse = await axios.get('http://localhost:11434/api/tags')
373-
const models = modelsResponse.data.models || []
374-
const hasModel = models.some((m: any) => m.name === AI_BENCHMARK_MODEL || m.name.startsWith(AI_BENCHMARK_MODEL.split(':')[0]))
375-
376-
if (!hasModel) {
377-
this._updateStatus('running_ai', `Pulling benchmark model ${AI_BENCHMARK_MODEL}...`)
378-
await axios.post('http://localhost:11434/api/pull', { name: AI_BENCHMARK_MODEL })
380+
const modelsResponse = await axios.get(`${OLLAMA_API_URL}/api/tags`)
381+
const models = modelsResponse.data.models || []
382+
const hasModel = models.some((m: any) => m.name === AI_BENCHMARK_MODEL || m.name.startsWith(AI_BENCHMARK_MODEL.split(':')[0]))
383+
384+
if (!hasModel) {
385+
this._updateStatus('downloading_ai_model', `Downloading AI benchmark model (${AI_BENCHMARK_MODEL})... This may take a few minutes on first run.`)
386+
logger.info(`[BenchmarkService] Model ${AI_BENCHMARK_MODEL} not found, downloading...`)
387+
388+
try {
389+
// Model pull can take several minutes, use longer timeout
390+
await axios.post(`${OLLAMA_API_URL}/api/pull`, { name: AI_BENCHMARK_MODEL }, { timeout: 600000 })
391+
logger.info(`[BenchmarkService] Model ${AI_BENCHMARK_MODEL} downloaded successfully`)
392+
} catch (pullError) {
393+
throw new Error(`Failed to download AI benchmark model (${AI_BENCHMARK_MODEL}): ${pullError.message}`)
379394
}
380-
} catch (error) {
381-
logger.warn(`Could not check/pull model: ${error.message}`)
382395
}
383396

384397
// Run inference benchmark
385398
const startTime = Date.now()
386399

387400
try {
388401
const response = await axios.post(
389-
'http://localhost:11434/api/generate',
402+
`${OLLAMA_API_URL}/api/generate`,
390403
{
391404
model: AI_BENCHMARK_MODEL,
392405
prompt: AI_BENCHMARK_PROMPT,
@@ -694,6 +707,7 @@ export class BenchmarkService {
694707
running_memory: 40,
695708
running_disk_read: 55,
696709
running_disk_write: 70,
710+
downloading_ai_model: 80,
697711
running_ai: 85,
698712
calculating_score: 95,
699713
completed: 100,
@@ -714,6 +728,7 @@ export class BenchmarkService {
714728
running_memory: 'Memory Benchmark',
715729
running_disk_read: 'Disk Read Test',
716730
running_disk_write: 'Disk Write Test',
731+
downloading_ai_model: 'Downloading AI Model',
717732
running_ai: 'AI Inference Test',
718733
calculating_score: 'Calculating Score',
719734
completed: 'Complete',

admin/inertia/pages/settings/benchmark.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ export default function BenchmarkPage(props: {
134134
{ status: 'running_memory', progress: 40, message: 'Running memory benchmark...', label: 'Memory Benchmark', duration: 8000 },
135135
{ status: 'running_disk_read', progress: 55, message: 'Running disk read benchmark (30s)...', label: 'Disk Read Test', duration: 35000 },
136136
{ status: 'running_disk_write', progress: 70, message: 'Running disk write benchmark (30s)...', label: 'Disk Write Test', duration: 35000 },
137+
{ status: 'downloading_ai_model', progress: 80, message: 'Downloading AI benchmark model (first run only)...', label: 'Downloading AI Model', duration: 5000 },
138+
{ status: 'running_ai', progress: 85, message: 'Running AI inference benchmark...', label: 'AI Inference Test', duration: 15000 },
137139
{ status: 'calculating_score', progress: 95, message: 'Calculating NOMAD score...', label: 'Calculating Score', duration: 2000 },
138140
]
139141

@@ -202,6 +204,7 @@ export default function BenchmarkPage(props: {
202204
running_memory: 40,
203205
running_disk_read: 55,
204206
running_disk_write: 70,
207+
downloading_ai_model: 80,
205208
running_ai: 85,
206209
calculating_score: 95,
207210
completed: 100,
@@ -256,6 +259,16 @@ export default function BenchmarkPage(props: {
256259
</div>
257260
) : (
258261
<div className="space-y-6">
262+
{progress?.status === 'error' && (
263+
<Alert
264+
type="error"
265+
title="Benchmark Failed"
266+
message={progress.message}
267+
variant="bordered"
268+
dismissible
269+
onDismiss={() => setProgress(null)}
270+
/>
271+
)}
259272
<p className="text-desert-stone-dark">
260273
Run a benchmark to measure your system's CPU, memory, disk, and AI inference performance.
261274
The benchmark takes approximately 2-5 minutes to complete.

admin/types/benchmark.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export type BenchmarkStatus =
1212
| 'running_memory'
1313
| 'running_disk_read'
1414
| 'running_disk_write'
15+
| 'downloading_ai_model'
1516
| 'running_ai'
1617
| 'calculating_score'
1718
| 'completed'

install/management_compose.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ services:
55
pull_policy: always
66
container_name: nomad_admin
77
restart: unless-stopped
8+
extra_hosts:
9+
- "host.docker.internal:host-gateway" # Enables host.docker.internal on Linux
810
ports:
911
- "8080:8080"
1012
volumes:
@@ -30,6 +32,7 @@ services:
3032
- DB_SSL=false
3133
- REDIS_HOST=redis
3234
- REDIS_PORT=6379
35+
- OLLAMA_API_URL=http://host.docker.internal:11434
3336
depends_on:
3437
mysql:
3538
condition: service_healthy

0 commit comments

Comments
 (0)