fix: remove hardcoded localhost fallbacks, require explicit config

Critical fix for Docker deployment where AI features were trying to connect
to localhost:11434 instead of using configured provider (Ollama Docker service
or OpenAI).

Problems fixed:
1. Reformulation (clarify/shorten/improve) failing with ECONNREFUSED 127.0.0.1:11434
2. Auto-labels failing with same error
3. Notebook summaries failing
4. Could not switch from Ollama to OpenAI in admin

Root cause:
- Code had hardcoded fallback to 'http://localhost:11434' in multiple places
- .env.docker file not created on server (gitignore'd)
- No validation that required environment variables were set

Changes:

1. lib/ai/factory.ts:
   - Remove hardcoded 'http://localhost:11434' fallback
   - Only use localhost for local development (NODE_ENV !== 'production')
   - Throw error if OLLAMA_BASE_URL not set in production

2. lib/ai/providers/ollama.ts:
   - Remove default parameter 'http://localhost:11434' from constructor
   - Require baseUrl to be explicitly passed
   - Throw error if baseUrl is missing

3. lib/ai/services/paragraph-refactor.service.ts:
   - Remove 'http://localhost:11434' fallback (2 locations)
   - Require OLLAMA_BASE_URL to be set
   - Throw clear error if not configured

4. app/(main)/admin/settings/admin-settings-form.tsx:
   - Add debug info showing current provider state
   - Display database config value for transparency
   - Help troubleshoot provider selection issues

5. DOCKER-SETUP.md:
   - Complete guide for Docker configuration
   - Instructions for .env.docker setup
   - Examples for Ollama Docker, OpenAI, and external Ollama
   - Troubleshooting common issues

Usage:
On server, create .env.docker with proper provider configuration:
- Ollama in Docker: OLLAMA_BASE_URL="http://ollama:11434"
- OpenAI: OPENAI_API_KEY="sk-..."
- External Ollama: OLLAMA_BASE_URL="http://SERVER_IP:11434"

Then in admin interface, users can independently configure:
- Tags Provider (for auto-labels, AI features)
- Embeddings Provider (for semantic search)

Result:
✓ Clear errors if Ollama not configured
✓ Can switch to OpenAI freely in admin
✓ No more hardcoded localhost in production
✓ Proper separation between local dev and Docker production

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
sepehr 2026-01-12 22:28:39 +01:00
parent 76c7cd97e9
commit e6bcdea641
4 changed files with 35 additions and 5 deletions

View File

@ -300,7 +300,12 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
<p className="text-xs text-muted-foreground">AI provider for semantic search embeddings. Recommended: OpenAI (best quality).</p>
<div className="space-y-2">
<Label htmlFor="AI_PROVIDER_EMBEDDING">Provider</Label>
<Label htmlFor="AI_PROVIDER_EMBEDDING">
Provider
<span className="ml-2 text-xs text-muted-foreground">
(Current: {embeddingsProvider})
</span>
</Label>
<select
id="AI_PROVIDER_EMBEDDING"
name="AI_PROVIDER_EMBEDDING"
@ -312,6 +317,9 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
<option value="openai">🤖 OpenAI (text-embedding-4)</option>
<option value="custom">🔧 Custom OpenAI-Compatible</option>
</select>
<p className="text-xs text-muted-foreground">
Config value: {config.AI_PROVIDER_EMBEDDING || 'Not set (defaults to ollama)'}
</p>
</div>
{/* Ollama Embeddings Config */}

View File

@ -6,7 +6,16 @@ import { AIProvider } from './types';
type ProviderType = 'ollama' | 'openai' | 'custom';
function createOllamaProvider(config: Record<string, string>, modelName: string, embeddingModelName: string): OllamaProvider {
let baseUrl = config?.OLLAMA_BASE_URL || process.env.OLLAMA_BASE_URL || 'http://localhost:11434';
let baseUrl = config?.OLLAMA_BASE_URL || process.env.OLLAMA_BASE_URL
// Only use localhost as fallback for local development (not in Docker)
if (!baseUrl && process.env.NODE_ENV !== 'production') {
baseUrl = 'http://localhost:11434'
}
if (!baseUrl) {
throw new Error('OLLAMA_BASE_URL is required when using Ollama provider')
}
// Ensure baseUrl doesn't end with /api, we'll add it in OllamaProvider
if (baseUrl.endsWith('/api')) {

View File

@ -5,7 +5,10 @@ export class OllamaProvider implements AIProvider {
private modelName: string;
private embeddingModelName: string;
constructor(baseUrl: string = 'http://localhost:11434', modelName: string = 'llama3', embeddingModelName?: string) {
constructor(baseUrl: string, modelName: string = 'llama3', embeddingModelName?: string) {
if (!baseUrl) {
throw new Error('baseUrl is required for OllamaProvider')
}
// Ensure baseUrl ends with /api for Ollama API
this.baseUrl = baseUrl.endsWith('/api') ? baseUrl : `${baseUrl}/api`;
this.modelName = modelName;

View File

@ -84,7 +84,12 @@ export class ParagraphRefactorService {
const userPrompt = this.getUserPrompt(mode, content, language)
// Get AI provider response using fetch
let baseUrl = process.env.OLLAMA_BASE_URL || 'http://localhost:11434'
let baseUrl = process.env.OLLAMA_BASE_URL
if (!baseUrl) {
throw new Error('OLLAMA_BASE_URL environment variable is required')
}
// Remove /api suffix if present to avoid double /api/api/...
if (baseUrl.endsWith('/api')) {
baseUrl = baseUrl.slice(0, -4)
@ -185,7 +190,12 @@ Original language: ${language}
IMPORTANT: Provide all 3 versions in ${language}. No English, no explanations.`
// Get AI provider response using fetch
let baseUrl = process.env.OLLAMA_BASE_URL || 'http://localhost:11434'
let baseUrl = process.env.OLLAMA_BASE_URL
if (!baseUrl) {
throw new Error('OLLAMA_BASE_URL environment variable is required')
}
// Remove /api suffix if present to avoid double /api/api/...
if (baseUrl.endsWith('/api')) {
baseUrl = baseUrl.slice(0, -4)