diff --git a/RAGKI-BotPGVector.json b/RAGKI-BotPGVector.json new file mode 100644 index 0000000..6299c57 --- /dev/null +++ b/RAGKI-BotPGVector.json @@ -0,0 +1,323 @@ +{ + "name": "RAG KI-Bot (PGVector)", + "nodes": [ + { + "parameters": { + "public": true, + "initialMessages": "Hallo! 👋\nMein Name ist Clara (Customer Learning & Answering Reference Assistant)\nWie kann ich behilflich sein?", + "options": { + "inputPlaceholder": "Hier die Frage eingeben...", + "showWelcomeScreen": true, + "subtitle": "Die Antworten der AI können fehlerhaft sein.", + "title": "Support-Chat 👋", + "customCss": ":root {\n /* Colors */\n --chat--color-primary: #e74266;\n --chat--color-primary-shade-50: #db4061;\n --chat--color-primary-shade-100: #cf3c5c;\n --chat--color-secondary: #20b69e;\n --chat--color-secondary-shade-50: #1ca08a;\n --chat--color-white: #ffffff;\n --chat--color-light: #f2f4f8;\n --chat--color-light-shade-50: #e6e9f1;\n --chat--color-light-shade-100: #c2c5cc;\n --chat--color-medium: #d2d4d9;\n --chat--color-dark: #101330;\n --chat--color-disabled: #d2d4d9;\n --chat--color-typing: #404040;\n\n /* Base Layout */\n --chat--spacing: 1rem;\n --chat--border-radius: 0.25rem;\n --chat--transition-duration: 0.15s;\n --chat--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;\n\n /* Window Dimensions */\n --chat--window--width: 400px;\n --chat--window--height: 600px;\n --chat--window--bottom: var(--chat--spacing);\n --chat--window--right: var(--chat--spacing);\n --chat--window--z-index: 9999;\n --chat--window--border: 1px solid var(--chat--color-light-shade-50);\n --chat--window--border-radius: var(--chat--border-radius);\n --chat--window--margin-bottom: var(--chat--spacing);\n\n /* Header Styles */\n --chat--header-height: auto;\n --chat--header--padding: var(--chat--spacing);\n --chat--header--background: var(--chat--color-dark);\n --chat--header--color: var(--chat--color-light);\n --chat--header--border-top: none;\n --chat--header--border-bottom: none;\n --chat--header--border-left: none;\n --chat--header--border-right: none;\n --chat--heading--font-size: 2em;\n --chat--subtitle--font-size: inherit;\n --chat--subtitle--line-height: 1.8;\n\n /* Message Styles */\n --chat--message--font-size: 1rem;\n --chat--message--padding: var(--chat--spacing);\n --chat--message--border-radius: var(--chat--border-radius);\n --chat--message-line-height: 1.5;\n --chat--message--margin-bottom: calc(var(--chat--spacing) * 1);\n --chat--message--bot--background: var(--chat--color-white);\n --chat--message--bot--color: var(--chat--color-dark);\n --chat--message--bot--border: none;\n --chat--message--user--background: var(--chat--color-secondary);\n --chat--message--user--color: var(--chat--color-white);\n --chat--message--user--border: none;\n --chat--message--pre--background: rgba(0, 0, 0, 0.05);\n --chat--messages-list--padding: var(--chat--spacing);\n\n /* Toggle Button */\n --chat--toggle--size: 64px;\n --chat--toggle--width: var(--chat--toggle--size);\n --chat--toggle--height: var(--chat--toggle--size);\n --chat--toggle--border-radius: 50%;\n --chat--toggle--background: var(--chat--color-primary);\n --chat--toggle--hover--background: var(--chat--color-primary-shade-50);\n --chat--toggle--active--background: var(--chat--color-primary-shade-100);\n --chat--toggle--color: var(--chat--color-white);\n\n /* Input Area */\n --chat--textarea--height: 50px;\n --chat--textarea--max-height: 30rem;\n --chat--input--font-size: inherit;\n --chat--input--border: 0;\n --chat--input--border-radius: 0;\n --chat--input--padding: 0.8rem;\n --chat--input--background: var(--chat--color-white);\n --chat--input--text-color: initial;\n --chat--input--line-height: 1.5;\n --chat--input--placeholder--font-size: var(--chat--input--font-size);\n --chat--input--border-active: 0;\n --chat--input--left--panel--width: 2rem;\n\n /* Button Styles */\n --chat--button--color: var(--chat--color-light);\n --chat--button--background: var(--chat--color-primary);\n --chat--button--padding: calc(var(--chat--spacing) * 1 / 2) var(--chat--spacing);\n --chat--button--border-radius: var(--chat--border-radius);\n --chat--button--hover--color: var(--chat--color-light);\n --chat--button--hover--background: var(--chat--color-primary-shade-50);\n --chat--close--button--color-hover: var(--chat--color-primary);\n\n /* Send and File Buttons */\n --chat--input--send--button--background: var(--chat--color-white);\n --chat--input--send--button--color: var(--chat--color-secondary);\n --chat--input--send--button--background-hover: var(--chat--color-primary-shade-50);\n --chat--input--send--button--color-hover: var(--chat--color-secondary-shade-50);\n --chat--input--file--button--background: var(--chat--color-white);\n --chat--input--file--button--color: var(--chat--color-secondary);\n --chat--input--file--button--background-hover: var(--chat--input--file--button--background);\n --chat--input--file--button--color-hover: var(--chat--color-secondary-shade-50);\n --chat--files-spacing: 0.25rem;\n\n /* Body and Footer */\n --chat--body--background: var(--chat--color-light);\n --chat--footer--background: var(--chat--color-light);\n --chat--footer--color: var(--chat--color-dark);\n}\n\n\n/* You can override any class styles, too. Right-click inspect in Chat UI to find class to override. */\n.chat-message {\n\tmax-width: 50%;\n}", + "responseMode": "lastNode" + } + }, + "type": "@n8n/n8n-nodes-langchain.chatTrigger", + "typeVersion": 1.3, + "position": [ + 0, + 0 + ], + "id": "chat-trigger-001", + "name": "When chat message received", + "webhookId": "rag-chat-webhook", + "notesInFlow": true, + "notes": "Chat URL: /webhook/rag-chat-webhook/chat" + }, + { + "parameters": { + "promptType": "define", + "text": "={{ $json.chatInput }}\nAntworte ausschliesslich auf Deutsch und nutze zuerst die Wissensdatenbank.", + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.agent", + "typeVersion": 2.2, + "position": [ + 208, + 0 + ], + "id": "ai-agent-001", + "name": "AI Agent" + }, + { + "parameters": { + "model": "ministral-3:3b", + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.lmChatOllama", + "typeVersion": 1, + "position": [ + 64, + 208 + ], + "id": "ollama-chat-001", + "name": "Ollama Chat Model", + "credentials": { + "ollamaApi": { + "id": "ZmMYzkrY4zMFYJ1J", + "name": "Ollama (local)" + } + } + }, + { + "parameters": {}, + "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow", + "typeVersion": 1.3, + "position": [ + 224, + 208 + ], + "id": "memory-001", + "name": "Simple Memory" + }, + { + "parameters": { + "mode": "retrieve-as-tool", + "toolName": "knowledge_base", + "toolDescription": "Verwende dieses Tool für Infos die der Benutzer fragt. Sucht in der Wissensdatenbank nach relevanten Dokumenten.", + "tableName": "documents", + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.vectorStorePGVector", + "typeVersion": 1, + "position": [ + 432, + 128 + ], + "id": "pgvector-retrieve-001", + "name": "PGVector Store", + "credentials": { + "postgres": { + "id": "1VVtY5ei866suQdA", + "name": "PostgreSQL (local)" + } + } + }, + { + "parameters": { + "model": "nomic-embed-text:latest" + }, + "type": "@n8n/n8n-nodes-langchain.embeddingsOllama", + "typeVersion": 1, + "position": [ + 416, + 288 + ], + "id": "embeddings-retrieve-001", + "name": "Embeddings Ollama", + "credentials": { + "ollamaApi": { + "id": "ZmMYzkrY4zMFYJ1J", + "name": "Ollama (local)" + } + } + }, + { + "parameters": { + "formTitle": "Dokument hochladen", + "formDescription": "Laden Sie ein PDF-Dokument hoch, um es in die Wissensdatenbank aufzunehmen.", + "formFields": { + "values": [ + { + "fieldLabel": "Dokument", + "fieldType": "file", + "acceptFileTypes": ".pdf" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.formTrigger", + "typeVersion": 2.3, + "position": [ + 768, + 0 + ], + "id": "form-trigger-001", + "name": "On form submission", + "webhookId": "rag-upload-form" + }, + { + "parameters": { + "operation": "pdf", + "binaryPropertyName": "Dokument", + "options": {} + }, + "type": "n8n-nodes-base.extractFromFile", + "typeVersion": 1, + "position": [ + 976, + 0 + ], + "id": "extract-file-001", + "name": "Extract from File" + }, + { + "parameters": { + "mode": "insert", + "tableName": "documents", + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.vectorStorePGVector", + "typeVersion": 1, + "position": [ + 1184, + 0 + ], + "id": "pgvector-insert-001", + "name": "PGVector Store Insert", + "credentials": { + "postgres": { + "id": "1VVtY5ei866suQdA", + "name": "PostgreSQL (local)" + } + } + }, + { + "parameters": { + "model": "nomic-embed-text:latest" + }, + "type": "@n8n/n8n-nodes-langchain.embeddingsOllama", + "typeVersion": 1, + "position": [ + 1168, + 240 + ], + "id": "embeddings-insert-001", + "name": "Embeddings Ollama1", + "credentials": { + "ollamaApi": { + "id": "ZmMYzkrY4zMFYJ1J", + "name": "Ollama (local)" + } + } + }, + { + "parameters": { + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader", + "typeVersion": 1.1, + "position": [ + 1392, + 240 + ], + "id": "data-loader-001", + "name": "Default Data Loader" + } + ], + "pinData": {}, + "connections": { + "When chat message received": { + "main": [ + [ + { + "node": "AI Agent", + "type": "main", + "index": 0 + } + ] + ] + }, + "Ollama Chat Model": { + "ai_languageModel": [ + [ + { + "node": "AI Agent", + "type": "ai_languageModel", + "index": 0 + } + ] + ] + }, + "Simple Memory": { + "ai_memory": [ + [ + { + "node": "AI Agent", + "type": "ai_memory", + "index": 0 + } + ] + ] + }, + "PGVector Store": { + "ai_tool": [ + [ + { + "node": "AI Agent", + "type": "ai_tool", + "index": 0 + } + ] + ] + }, + "Embeddings Ollama": { + "ai_embedding": [ + [ + { + "node": "PGVector Store", + "type": "ai_embedding", + "index": 0 + } + ] + ] + }, + "On form submission": { + "main": [ + [ + { + "node": "Extract from File", + "type": "main", + "index": 0 + } + ] + ] + }, + "Extract from File": { + "main": [ + [ + { + "node": "PGVector Store Insert", + "type": "main", + "index": 0 + } + ] + ] + }, + "Embeddings Ollama1": { + "ai_embedding": [ + [ + { + "node": "PGVector Store Insert", + "type": "ai_embedding", + "index": 0 + } + ] + ] + }, + "Default Data Loader": { + "ai_document": [ + [ + { + "node": "PGVector Store Insert", + "type": "ai_document", + "index": 0 + } + ] + ] + } + }, + "active": true, + "settings": { + "executionOrder": "v1" + }, + "versionId": "6ebf0ac8-b8ab-49ee-b6f1-df0b606b3a33", + "meta": { + "instanceId": "a2179cec0884855b4d650fea20868c0dbbb03f0d0054c803c700fff052afc74c" + }, + "id": "Q9Bm63B9ae8rAj95", + "tags": [] +} \ No newline at end of file diff --git a/install.sh b/install.sh index bd3f0c3..bd41846 100755 --- a/install.sh +++ b/install.sh @@ -61,6 +61,9 @@ Domain / n8n options: --base-domain (default: userman.de) -> FQDN becomes sb-.domain --n8n-owner-email (default: admin@) --n8n-owner-pass Optional. If omitted, generated (policy compliant). + --workflow-file Path to n8n workflow JSON file (default: RAGKI-BotPGVector.json) + --ollama-model Ollama chat model (default: ministral-3:3b) + --embedding-model Ollama embedding model (default: nomic-embed-text:latest) --debug Enable debug mode (show logs on stderr) --help Show help @@ -95,11 +98,18 @@ N8N_OWNER_EMAIL="" N8N_OWNER_PASS="" POSTGREST_PORT="3000" +# Workflow file (default: RAGKI-BotPGVector.json in script directory) +WORKFLOW_FILE="${SCRIPT_DIR}/RAGKI-BotPGVector.json" + # Ollama API settings (hardcoded for local setup) OLLAMA_HOST="192.168.45.3" OLLAMA_PORT="11434" OLLAMA_URL="http://${OLLAMA_HOST}:${OLLAMA_PORT}" +# Ollama models (can be overridden via CLI) +OLLAMA_MODEL="ministral-3:3b" +EMBEDDING_MODEL="nomic-embed-text:latest" + # --------------------------- # Arg parsing # --------------------------- @@ -119,6 +129,9 @@ while [[ $# -gt 0 ]]; do --base-domain) BASE_DOMAIN="${2:-}"; shift 2 ;; --n8n-owner-email) N8N_OWNER_EMAIL="${2:-}"; shift 2 ;; --n8n-owner-pass) N8N_OWNER_PASS="${2:-}"; shift 2 ;; + --workflow-file) WORKFLOW_FILE="${2:-}"; shift 2 ;; + --ollama-model) OLLAMA_MODEL="${2:-}"; shift 2 ;; + --embedding-model) EMBEDDING_MODEL="${2:-}"; shift 2 ;; --postgrest-port) POSTGREST_PORT="${2:-}"; shift 2 ;; --debug) DEBUG="1"; export DEBUG; shift 1 ;; --help|-h) usage; exit 0 ;; @@ -145,8 +158,15 @@ if [[ -n "${APT_PROXY}" ]]; then [[ "${APT_PROXY}" =~ ^http://[^/]+:[0-9]+$ ]] || die "--apt-proxy must look like http://IP:PORT (example: http://192.168.45.2:3142)" fi +# Validate workflow file exists +if [[ ! -f "${WORKFLOW_FILE}" ]]; then + die "Workflow file not found: ${WORKFLOW_FILE}" +fi info "Argument-Parsing OK" +info "Workflow file: ${WORKFLOW_FILE}" +info "Ollama model: ${OLLAMA_MODEL}" +info "Embedding model: ${EMBEDDING_MODEL}" if [[ -n "${APT_PROXY}" ]]; then info "APT proxy enabled: ${APT_PROXY}" @@ -602,10 +622,10 @@ info "Chat Webhook URL (intern): ${CHAT_INTERNAL_URL}" info "Step 10: Setting up n8n credentials and importing RAG workflow..." # Use the new robust n8n setup function from libsupabase.sh -# Parameters: ctid, email, password, pg_host, pg_port, pg_db, pg_user, pg_pass, ollama_url, ollama_model, embedding_model +# Parameters: ctid, email, password, pg_host, pg_port, pg_db, pg_user, pg_pass, ollama_url, ollama_model, embedding_model, workflow_file if n8n_setup_rag_workflow "${CTID}" "${N8N_OWNER_EMAIL}" "${N8N_OWNER_PASS}" \ "postgres" "5432" "${PG_DB}" "${PG_USER}" "${PG_PASSWORD}" \ - "${OLLAMA_URL}" "llama3.2:3b" "nomic-embed-text:v1.5"; then + "${OLLAMA_URL}" "${OLLAMA_MODEL}" "${EMBEDDING_MODEL}" "${WORKFLOW_FILE}"; then info "Step 10 OK: n8n RAG workflow setup completed successfully" else warn "Step 10: n8n workflow setup failed - manual setup may be required" diff --git a/libsupabase.sh b/libsupabase.sh index 0fc89ee..9d3530b 100755 --- a/libsupabase.sh +++ b/libsupabase.sh @@ -618,9 +618,9 @@ n8n_api_cleanup() { pct exec "$ctid" -- bash -c "rm -f /tmp/n8n_cookies.txt /tmp/rag_workflow.json" 2>/dev/null || true } -# Full n8n setup: Create credentials, import workflow, activate +# Full n8n setup: Create credentials, import workflow from file, activate # This version runs all API calls in a single shell session to preserve cookies -# Usage: n8n_setup_rag_workflow [ollama_model] [embedding_model] +# Usage: n8n_setup_rag_workflow # Returns: 0 on success, 1 on failure n8n_setup_rag_workflow() { local ctid="$1" @@ -632,11 +632,23 @@ n8n_setup_rag_workflow() { local pg_user="$7" local pg_pass="$8" local ollama_url="$9" - local ollama_model="${10:-llama3.2:3b}" - local embedding_model="${11:-nomic-embed-text:v1.5}" + local ollama_model="${10:-ministral-3:3b}" + local embedding_model="${11:-nomic-embed-text:latest}" + local workflow_file="${12:-}" info "n8n Setup: Starting RAG workflow setup..." + # Validate workflow file + if [[ -z "$workflow_file" ]]; then + warn "n8n Setup: No workflow file specified, using built-in template" + workflow_file="" + elif [[ ! -f "$workflow_file" ]]; then + warn "n8n Setup: Workflow file not found: $workflow_file" + return 1 + else + info "n8n Setup: Using workflow file: $workflow_file" + fi + # Wait for n8n to be ready info "n8n Setup: Waiting for n8n to be ready..." local i @@ -654,12 +666,20 @@ n8n_setup_rag_workflow() { local escaped_pg_pass escaped_pg_pass=$(echo "$pg_pass" | sed 's/\\/\\\\/g; s/"/\\"/g') - # Generate workflow JSON with placeholder credential IDs (will be replaced in container) - info "n8n Setup: Generating workflow JSON..." + # Read workflow from file or generate from template + info "n8n Setup: Preparing workflow JSON..." local workflow_json - workflow_json=$(n8n_generate_rag_workflow_json "POSTGRES_CRED_ID" "PostgreSQL (local)" "OLLAMA_CRED_ID" "Ollama (local)" "$ollama_model" "$embedding_model") + if [[ -n "$workflow_file" && -f "$workflow_file" ]]; then + # Read workflow from external file + workflow_json=$(cat "$workflow_file") + info "n8n Setup: Loaded workflow from file: $workflow_file" + else + # Generate workflow from built-in template + workflow_json=$(n8n_generate_rag_workflow_json "POSTGRES_CRED_ID" "PostgreSQL (local)" "OLLAMA_CRED_ID" "Ollama (local)" "$ollama_model" "$embedding_model") + info "n8n Setup: Generated workflow from built-in template" + fi - # Push workflow JSON to container + # Push workflow JSON to container (will be processed by setup script) pct_push_text "$ctid" "/tmp/rag_workflow_template.json" "$workflow_json" # Create a setup script that runs all API calls in one session @@ -731,9 +751,54 @@ if [ -z "\$OLLAMA_CRED_ID" ]; then fi echo "Ollama credential created: \$OLLAMA_CRED_ID" -# Replace placeholder IDs in workflow JSON +# Process workflow JSON: replace credential IDs and clean up echo "Preparing workflow JSON..." -sed -e "s/POSTGRES_CRED_ID/\$PG_CRED_ID/g" -e "s/OLLAMA_CRED_ID/\$OLLAMA_CRED_ID/g" /tmp/rag_workflow_template.json > /tmp/rag_workflow.json + +# Create a Python script to process the workflow JSON +cat > /tmp/process_workflow.py << 'PYTHON_SCRIPT' +import json +import sys + +# Read the workflow template +with open('/tmp/rag_workflow_template.json', 'r') as f: + workflow = json.load(f) + +# Get credential IDs from environment/arguments +pg_cred_id = sys.argv[1] +ollama_cred_id = sys.argv[2] + +# Remove fields that should not be in the import +fields_to_remove = ['id', 'versionId', 'meta', 'tags', 'active', 'pinData'] +for field in fields_to_remove: + workflow.pop(field, None) + +# Process all nodes and replace credential IDs +for node in workflow.get('nodes', []): + credentials = node.get('credentials', {}) + + # Replace PostgreSQL credential + if 'postgres' in credentials: + credentials['postgres'] = { + 'id': pg_cred_id, + 'name': 'PostgreSQL (local)' + } + + # Replace Ollama credential + if 'ollamaApi' in credentials: + credentials['ollamaApi'] = { + 'id': ollama_cred_id, + 'name': 'Ollama (local)' + } + +# Write the processed workflow +with open('/tmp/rag_workflow.json', 'w') as f: + json.dump(workflow, f) + +print("Workflow processed successfully") +PYTHON_SCRIPT + +# Run the Python script to process the workflow +python3 /tmp/process_workflow.py "\$PG_CRED_ID" "\$OLLAMA_CRED_ID" # Import workflow echo "Importing workflow..."