From 10563900a3b9c94c29abdeeb625a34885ddf7573 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 8 Jul 2025 07:35:48 -0700 Subject: [PATCH] Implement comprehensive LLM provider system with global cost protection - Add multi-provider LLM architecture supporting OpenRouter, OpenAI, Gemini, and custom providers - Implement global LLM on/off switch with default DISABLED state for cost protection - Add per-character LLM configuration with provider-specific models and settings - Create performance-optimized caching system for LLM enabled status checks - Add API key validation before enabling LLM providers to prevent broken configurations - Implement audit logging for all LLM enable/disable actions for cost accountability - Create comprehensive admin UI with prominent cost warnings and confirmation dialogs - Add visual indicators in character list for custom AI model configurations - Build character-specific LLM client system with global fallback mechanism - Add database schema support for per-character LLM settings - Implement graceful fallback responses when LLM is globally disabled - Create provider testing and validation system for reliable connections --- .env.docker | 4 +- .env.example | 25 +- Dockerfile.admin | 32 +- admin-frontend/package-simple.json | 32 + admin-frontend/package.json | 16 +- admin-frontend/public/favicon.ico | Bin 0 -> 127 bytes admin-frontend/src/App.tsx | 18 +- .../Character/CharacterCreationModal.tsx | 295 ++++++++ .../src/components/LLMProviderSettings.tsx | 181 +++++ .../src/components/LLMProviders.tsx | 474 +++++++++++++ .../src/components/Layout/Header.tsx | 6 +- .../src/components/Layout/Sidebar.tsx | 16 +- admin-frontend/src/contexts/AuthContext.tsx | 18 +- .../src/contexts/WebSocketContext.tsx | 12 +- admin-frontend/src/pages/AdminUtils.tsx | 107 +++ admin-frontend/src/pages/CharacterDetail.tsx | 645 ++++++++++-------- admin-frontend/src/pages/Characters.tsx | 411 +++++------ admin-frontend/src/pages/Guide.tsx | 217 ++++++ admin-frontend/src/pages/LiveChat.tsx | 180 +++++ admin-frontend/src/pages/Settings.tsx | 580 +++++++++++++++- admin-frontend/src/services/api.ts | 114 +++- admin_interface_updated.html | 57 +- check_character_data.py | 36 + config/characters.yaml | 64 +- config/llm_providers_example.yaml | 79 +++ docker-compose.yml | 26 +- fix_character_prompts.py | 65 ++ migrations/004_prompt_templates.sql | 71 ++ migrations/005_update_character_prompts.sql | 43 ++ migrations/006_add_character_llm_settings.sql | 18 + requirements-admin.txt | 3 +- requirements.txt | 3 +- scripts/test_llm_providers.py | 164 +++++ scripts/update_llm_config.py | 140 ++++ src/admin/app.py | 224 +++++- src/admin/auth.py | 14 +- src/admin/services/character_service.py | 128 +++- src/admin/services/conversation_service.py | 196 +++++- src/admin/services/system_service.py | 464 ++++++++++++- src/admin/services/websocket_manager.py | 6 +- src/bot/message_handler.py | 341 ++++++++- src/characters/character.py | 250 ++++++- src/characters/enhanced_character.py | 29 +- src/conversation/engine.py | 178 ++++- src/conversation/scheduler.py | 35 +- src/database/models.py | 34 + src/llm/llm_manager.py | 189 +++++ src/llm/multi_provider_client.py | 241 +++++++ src/llm/providers/__init__.py | 19 + src/llm/providers/base.py | 67 ++ src/llm/providers/custom_provider.py | 170 +++++ src/llm/providers/gemini_provider.py | 124 ++++ src/llm/providers/openai_provider.py | 110 +++ src/llm/providers/openrouter_provider.py | 122 ++++ src/main.py | 42 +- src/rag/vector_store.py | 178 +++-- src/utils/config.py | 16 +- sync_vectors.py | 73 ++ test_llm_current_provider.py | 75 ++ 59 files changed, 6686 insertions(+), 791 deletions(-) create mode 100644 admin-frontend/package-simple.json create mode 100644 admin-frontend/public/favicon.ico create mode 100644 admin-frontend/src/components/Character/CharacterCreationModal.tsx create mode 100644 admin-frontend/src/components/LLMProviderSettings.tsx create mode 100644 admin-frontend/src/components/LLMProviders.tsx create mode 100644 admin-frontend/src/pages/AdminUtils.tsx create mode 100644 admin-frontend/src/pages/Guide.tsx create mode 100644 admin-frontend/src/pages/LiveChat.tsx create mode 100644 check_character_data.py create mode 100644 config/llm_providers_example.yaml create mode 100644 fix_character_prompts.py create mode 100644 migrations/004_prompt_templates.sql create mode 100644 migrations/005_update_character_prompts.sql create mode 100644 migrations/006_add_character_llm_settings.sql create mode 100755 scripts/test_llm_providers.py create mode 100755 scripts/update_llm_config.py create mode 100644 src/llm/llm_manager.py create mode 100644 src/llm/multi_provider_client.py create mode 100644 src/llm/providers/__init__.py create mode 100644 src/llm/providers/base.py create mode 100644 src/llm/providers/custom_provider.py create mode 100644 src/llm/providers/gemini_provider.py create mode 100644 src/llm/providers/openai_provider.py create mode 100644 src/llm/providers/openrouter_provider.py create mode 100644 sync_vectors.py create mode 100644 test_llm_current_provider.py diff --git a/.env.docker b/.env.docker index bb1ac14..0176d8a 100644 --- a/.env.docker +++ b/.env.docker @@ -18,13 +18,13 @@ LLM_MODEL=koboldcpp/Broken-Tutu-24B-Transgression-v2.0.i1-Q4_K_M LLM_TIMEOUT=300 LLM_MAX_TOKENS=2000 LLM_TEMPERATURE=0.8 -LLM_MAX_PROMPT_LENGTH=6000 +LLM_MAX_PROMPT_LENGTH=16000 LLM_MAX_HISTORY_MESSAGES=5 LLM_MAX_MEMORIES=5 # Admin Interface ADMIN_PORT=8294 -SECRET_KEY=your-secret-key-here +SECRET_KEY=stable-secret-key-for-jwt-tokens-fishbowl-2025 ADMIN_USERNAME=admin ADMIN_PASSWORD=FIre!@34 diff --git a/.env.example b/.env.example index 14e5c66..9055416 100644 --- a/.env.example +++ b/.env.example @@ -35,7 +35,7 @@ LLM_API_KEY=x LLM_TIMEOUT=300 LLM_MAX_TOKENS=2000 LLM_TEMPERATURE=0.8 -LLM_MAX_PROMPT_LENGTH=6000 +LLM_MAX_PROMPT_LENGTH=16000 LLM_MAX_HISTORY_MESSAGES=5 LLM_MAX_MEMORIES=5 @@ -46,6 +46,29 @@ ADMIN_USERNAME=admin ADMIN_PASSWORD=FIre!@34 SECRET_KEY=CAKUZ5ds49B1PUEWDWt07TdgxjTtDvvxOOkvOOfbnDE +# LLM Provider Configuration +# OpenRouter (supports Claude, GPT, Llama, etc.) +OPENROUTER_ENABLED=false +OPENROUTER_API_KEY= +OPENROUTER_MODEL=anthropic/claude-3-sonnet + +# OpenAI +OPENAI_ENABLED=false +OPENAI_API_KEY= +OPENAI_MODEL=gpt-4o-mini + +# Google Gemini +GEMINI_ENABLED=false +GEMINI_API_KEY= +GEMINI_MODEL=gemini-1.5-flash + +# Custom/Local LLM (current setup) +CUSTOM_LLM_ENABLED=true + +# Ollama +OLLAMA_ENABLED=false +OLLAMA_MODEL=llama3 + # System Configuration CONVERSATION_FREQUENCY=0.5 RESPONSE_DELAY_MIN=1.0 diff --git a/Dockerfile.admin b/Dockerfile.admin index d9b4005..45b43c3 100644 --- a/Dockerfile.admin +++ b/Dockerfile.admin @@ -9,7 +9,7 @@ RUN apt-get update && apt-get install -y \ && rm -rf /var/lib/apt/lists/* # Install Node.js for frontend build -RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \ +RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash - \ && apt-get install -y nodejs # Copy requirements first for better caching @@ -26,20 +26,30 @@ COPY migrations/ ./migrations/ COPY alembic.ini ./ # Build frontend -COPY admin-frontend/ ./admin-frontend/ +COPY admin-frontend/package*.json ./admin-frontend/ WORKDIR /app/admin-frontend -# Clear any existing node_modules and lock files -RUN rm -rf node_modules package-lock.json yarn.lock +# Install dependencies first (better caching) +RUN npm install --silent -# Install dependencies with npm (using .npmrc config) -RUN npm install +# Copy frontend source code +COPY admin-frontend/ ./ -# Build with increased memory for Node.js +# Build with increased memory for Node.js and disable optimization ENV NODE_OPTIONS="--max-old-space-size=4096" -# Build React app or create fallback -RUN npm run build || mkdir -p build -RUN test -f build/index.html || echo "

Discord Fishbowl Admin

Interface loading...

" > build/index.html +ENV GENERATE_SOURCEMAP=false +ENV DISABLE_ESLINT_PLUGIN=true +ENV CI=false +ENV REACT_APP_API_URL="" +ENV PUBLIC_URL="/admin" +ENV TSC_COMPILE_ON_ERROR=true +ENV ESLINT_NO_DEV_ERRORS=true + +# Build React app +RUN npm run build + +# Verify build output +RUN ls -la build/ && test -f build/index.html # Back to main directory WORKDIR /app @@ -51,7 +61,7 @@ RUN mkdir -p logs ENV PYTHONPATH=/app/src # Expose admin port -EXPOSE 8000 +EXPOSE 8294 # Run the admin interface CMD ["python", "-m", "src.admin.app"] \ No newline at end of file diff --git a/admin-frontend/package-simple.json b/admin-frontend/package-simple.json new file mode 100644 index 0000000..2650e05 --- /dev/null +++ b/admin-frontend/package-simple.json @@ -0,0 +1,32 @@ +{ + "name": "discord-fishbowl-admin", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.8.0", + "axios": "^1.6.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "devDependencies": { + "react-scripts": "5.0.1" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} \ No newline at end of file diff --git a/admin-frontend/package.json b/admin-frontend/package.json index e1e81e1..f1c69ee 100644 --- a/admin-frontend/package.json +++ b/admin-frontend/package.json @@ -2,6 +2,7 @@ "name": "discord-fishbowl-admin", "version": "1.0.0", "private": true, + "homepage": "/admin", "dependencies": { "@types/node": "^20.0.0", "@types/react": "^18.2.0", @@ -9,7 +10,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.8.0", - "react-scripts": "5.0.1", + "react-scripts": "^5.0.1", "typescript": "^4.9.5", "web-vitals": "^3.0.0", "@tailwindcss/forms": "^0.5.0", @@ -53,15 +54,8 @@ ] }, "devDependencies": { - "@types/jest": "^29.0.0" + "@types/jest": "^29.0.0", + "react-scripts": "5.0.1" }, - "resolutions": { - "schema-utils": "^3.3.0", - "fork-ts-checker-webpack-plugin": "^6.5.3" - }, - "overrides": { - "schema-utils": "^3.3.0", - "fork-ts-checker-webpack-plugin": "^6.5.3" - }, - "proxy": "http://localhost:8294" + "proxy": "http://localhost:8000" } \ No newline at end of file diff --git a/admin-frontend/public/favicon.ico b/admin-frontend/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..b6fdd272a0e5b42895f781333443bb6d15676f62 GIT binary patch literal 127 zcmYL - } /> - } /> + } /> } /> } /> - } /> - } /> - } /> - } /> } /> - } /> + } /> + } /> + } /> + } /> ); diff --git a/admin-frontend/src/components/Character/CharacterCreationModal.tsx b/admin-frontend/src/components/Character/CharacterCreationModal.tsx new file mode 100644 index 0000000..adfb5da --- /dev/null +++ b/admin-frontend/src/components/Character/CharacterCreationModal.tsx @@ -0,0 +1,295 @@ +import React, { useState } from 'react'; +import { X, Save, User, Brain, FileText } from 'lucide-react'; +import { apiClient } from '../../services/api'; +import LoadingSpinner from '../Common/LoadingSpinner'; +import toast from 'react-hot-toast'; + +interface Character { + name: string; + status: 'active' | 'idle' | 'reflecting' | 'offline'; + is_active: boolean; + last_active?: string; + personality?: string; + system_prompt?: string; + interests?: string[]; + speaking_style?: string; + background?: string; +} + +interface CharacterCreationModalProps { + isOpen: boolean; + onClose: () => void; + onCharacterCreated: (character: Character) => void; +} + +const CharacterCreationModal: React.FC = ({ + isOpen, + onClose, + onCharacterCreated +}) => { + const [formData, setFormData] = useState({ + name: '', + personality: '', + system_prompt: `You are a character named {{name}}. You have the following personality: {{personality}} + +Your speaking style is {{speaking_style}}. You are interested in {{interests}}. + +Background: {{background}} + +When responding to messages: +1. Stay in character at all times +2. Reference your personality and interests naturally +3. Engage authentically with other characters +4. Show growth and development over time + +Remember to be consistent with your established personality while allowing for natural character development through interactions.`, + interests: '', + speaking_style: '', + background: '', + is_active: true + }); + const [saving, setSaving] = useState(false); + + const handleInputChange = (field: keyof typeof formData, value: any) => { + setFormData(prev => ({ ...prev, [field]: value })); + }; + + const handleInterestsChange = (interestsText: string) => { + handleInputChange('interests', interestsText); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!formData.name.trim()) { + toast.error('Character name is required'); + return; + } + + try { + setSaving(true); + + const characterData = { + name: formData.name.trim(), + personality: formData.personality, + system_prompt: formData.system_prompt.replace('{{name}}', formData.name.trim()), + interests: formData.interests.split(',').map(s => s.trim()).filter(s => s.length > 0), + speaking_style: formData.speaking_style, + background: formData.background, + is_active: formData.is_active + }; + + const response = await apiClient.createCharacter(characterData); + + // Create character object for local state + const newCharacter: Character = { + name: characterData.name, + status: characterData.is_active ? 'active' : 'offline', + is_active: characterData.is_active, + personality: characterData.personality, + system_prompt: characterData.system_prompt, + interests: characterData.interests, + speaking_style: characterData.speaking_style, + background: characterData.background, + last_active: new Date().toISOString() + }; + + onCharacterCreated(newCharacter); + toast.success(`Character ${characterData.name} created successfully!`); + + // Reset form + setFormData({ + name: '', + personality: '', + system_prompt: `You are a character named {{name}}. You have the following personality: {{personality}} + +Your speaking style is {{speaking_style}}. You are interested in {{interests}}. + +Background: {{background}} + +When responding to messages: +1. Stay in character at all times +2. Reference your personality and interests naturally +3. Engage authentically with other characters +4. Show growth and development over time + +Remember to be consistent with your established personality while allowing for natural character development through interactions.`, + interests: '', + speaking_style: '', + background: '', + is_active: true + }); + + } catch (error: any) { + console.error('Failed to create character:', error); + toast.error(error.response?.data?.detail || 'Failed to create character'); + } finally { + setSaving(false); + } + }; + + if (!isOpen) return null; + + return ( +
+
+ {/* Header */} +
+

Create New Character

+ +
+ + {/* Form */} +
+
+ {/* Basic Info */} +
+
+
+ +

Basic Information

+
+ +
+ + handleInputChange('name', e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-1 focus:ring-primary-500" + placeholder="Enter character name..." + required + /> +
+ +
+ +