Configuration#

Learn how to configure EngiAI for your environment.

Environment Variables#

The assistant uses environment variables for configuration. These are stored in a .env file in the project root.

Creating Your Configuration#

  1. Copy the example file:

    cp .env.example .env
    
  2. Edit .env with your settings:

    nano .env  # or use your preferred editor
    

Required Variables#

These must be set for the assistant to function:

# OpenAI API Key (Required for all LLM operations)
OPENAI_API_KEY=sk-your-openai-api-key-here

# Web search (DuckDuckGo is keyless and is the default)
SEARCH_PROVIDER=duckduckgo
SEARCH_REGION=us-en
SEARCH_TIMEOUT_SECONDS=10

# Optional Tavily backend
# SEARCH_PROVIDER=tavily
# TAVILY_API_KEY=tvly-your-tavily-api-key-here

# Google API Key (Required for all LLM operations)
GOOGLE_API_KEY=sk-your-google-api-key-here

# MMORE RAG Service (Required for document retrieval)
MMORE_RAG_URL=http://localhost:8000

Where to get keys:

External Services:

  • MMORE: runs from the official upstream image as part of the Docker stack, through a small local layer (services/mmore/Dockerfile). No clone, no manual setup.

MMORE variables:

Variable

Default

Purpose

MMORE_RAG_URL

http://localhost:8000

Where the app and scripts reach the service. Docker overrides it to http://mmore-rag-service:8000 for the chatbot container; evaluations use port 8001.

MMORE_IMAGE

pinned sha-* tag

Which upstream image to run. Read services/mmore/README.md before changing. Never use an edge-* tag: they move on every upstream push.

SKIP_MMORE

false

Disable RAG entirely — the RAG tools are not created at all.

MMORE_EMPTY_RAG

false

Benchmark control condition: the tools exist but the index answers nothing. Set automatically by evaluate_agent.py --empty-rag.

MMORE_INDEX_TIMEOUT

900

Seconds to wait for a background indexing job. MMORE v2 indexes asynchronously, so uploads return a job id that the client polls.

MMORE_MIN_SIMILARITY

0.3

Score floor for the search_documents tool. Retune if an upstream bump changes the score scale.

Note

MMORE_PATH is obsolete — it pointed at a local MMORE checkout that no longer exists. Remove it from your .env.

Optional Variables#

These enhance functionality but aren’t required:

# LLM Configuration
LLM_MODEL=openai:gpt-4.1  # Default model
LLM_MODEL_OPTIONS=openai:gpt-4.1,ollama:qwen3.5:9b-mxfp8
LLM_MODEL_LABELS={"openai:gpt-4.1":"Cloud","ollama:qwen3.5:9b-mxfp8":"Local 9B"}
LLM_ROUTER_MODEL=ollama:qwen3.5:4b-mxfp8
LLM_ROUTER_TEMPERATURE=0.1
LLM_TEMPERATURE=0.7       # Temperature for responses (0.0-2.0)

# HPC Cluster Configuration
HPC_HOST_ALIAS=euler              # SSH alias for your HPC cluster
HPC_HOSTNAME=euler.ethz.ch        # Cluster hostname
HPC_USERNAME=username              # Your cluster username

# Weights & Biases (for ML tracking)
WANDB_API_KEY=your-wandb-key
WANDB_ENTITY=your-team

# Prusa 3D Printer Integration (optional)
SKIP_MCP=true                           # Set to false to enable Prusa integration
PRUSA_MCP_URL=http://localhost:8765     # URL of a running Prusa MCP server
PRUSA_MCP_HOST=0.0.0.0                  # Host for the standalone MCP server
PRUSA_MCP_PORT=8765                     # Port for the standalone MCP server

# Database (optional, uses SQLite by default)
DATABASE_URL=sqlite:///data/conversations.db

For Prusa 3D printer integration:

  • The Prusa MCP server is installed as a pip dependency from git (prusa-mcp @ git+https://github.com/gioelemo/prusa-mcp.git), declared in services/prusa_mcp_server/requirements-mcp.txt.

  • It is installed automatically by the Prusa MCP Dockerfile or by ./services/prusa_mcp_server/run.sh. No submodule initialization is required.

  • See services/prusa_mcp_server/README.md for setup and credential handling.

  • Set SKIP_MCP=false to enable. This also enables the whiteboard canvas — see Whiteboard Canvas.

Voice Integration#

The assistant supports voice interaction (speech-to-text and text-to-speech) using three providers:

# Voice Provider Selection
# An unrecognised value falls back to "elevenlabs" with a warning.
VOICE_PROVIDER=elevenlabs  # Options: "elevenlabs", "openai" or "google"

# ElevenLabs Configuration (default provider)
ELEVENLABS_API_KEY=your-elevenlabs-api-key
ELEVENLABS_STT_MODEL=scribe_v2
ELEVENLABS_TTS_MODEL=eleven_multilingual_v2
ELEVENLABS_DEFAULT_VOICE=George
ELEVENLABS_VOICES=Rachel:21m00Tcm4TlvDq8ikWAM,Domi:AZnzlk1XvdvUeBnXmlld,Bella:EXAVITQu4vr4xnSDxMaL,Antoni:ErXwobaYiN019PkySvjV,Josh:TxGEqnHWrfWFTfGW9XjX,George:JBFqnCBsd6RMkjVDRZzb

# OpenAI Voice Configuration (alternative provider)
OPENAI_TTS_MODEL=gpt-4o-mini-tts  # Options: "gpt-4o-mini-tts", "tts-1", "tts-1-hd"
OPENAI_TTS_VOICE=alloy            # See the voice/model note below
OPENAI_STT_MODEL=whisper-1        # OpenAI Whisper for speech-to-text

# Google Gemini Voice Configuration (text-to-speech only, uses GOOGLE_API_KEY)
GOOGLE_TTS_MODEL=gemini-3.1-flash-tts-preview
GOOGLE_TTS_VOICE=Kore

Voice Provider Options:

ElevenLabs (Recommended for quality):

  • High-quality, natural-sounding voices

  • Supports 6 preset voices (Rachel, Domi, Bella, Antoni, Josh, George)

  • Multilingual support with eleven_multilingual_v2 model

  • Get API key at elevenlabs.io

OpenAI (Good for simplicity):

  • Uses OpenAI Whisper for accurate speech-to-text

  • Three TTS models:

    • gpt-4o-mini-tts: the default, and the only model that accepts every voice

    • tts-1: standard quality, faster, cheaper

    • tts-1-hd: high definition quality

  • 13 voices: alloy, ash, coral, echo, fable, nova, onyx, sage and shimmer work everywhere; ballad, verse, marin and cedar are gpt-4o-mini-tts only and are rejected by tts-1/tts-1-hd. The Settings page narrows the voice list to match the selected model.

  • Requires OpenAI API key (same as OPENAI_API_KEY)

Google Gemini (text-to-speech only):

  • Reuses GOOGLE_API_KEY — no separate credential or GCP project needed

  • 30 prebuilt voices (Zephyr, Puck, Charon, Kore, Fenrir, Leda, Orus, Aoede, …)

  • No speech-to-text. Selecting Google for voice output leaves microphone input to OpenAI Whisper, or ElevenLabs when no OpenAI key is configured. The Settings page and the chat input both say which provider will transcribe.

  • Default model is gemini-3.1-flash-tts-preview for a measured reason: the 2.5 models return an empty response most of the time on short inputs (1 success in 5 for gemini-2.5-flash-preview-tts, against 5/5 for 3.1-flash), and short assistant replies are common.

  • Gemini returns raw PCM, which the app wraps in a WAV container. WAV is roughly three times the size of MP3 and audio is stored base64-encoded in the message history, so long replies are truncated (see below).

Input length: each provider rejects over-long input outright rather than truncating it, so the app truncates first — at a sentence boundary — and shows a caption saying how much was read. The caps are 4096 characters for OpenAI and 10000 for ElevenLabs, both set by the API.

Google’s cap is the tightest at 2000, and it bounds stored bytes rather than the request: Gemini’s own limit is the loosest of the three, but its WAV output is 48 KB/s and gets base64-encoded into both the session history and the messages.audio column, so ~2000 characters is already ~3.5 minutes of speech and ~13 MB in a single row.

Usage:

  • Select provider in Settings UI (⚙️ Settings > Voice Interaction)

  • Provider, voice and per-provider TTS model persist per conversation

  • Switch providers anytime without losing conversation history

Document Processing Variables#

For PDF and document processing features:

# Paper Import Configuration
PAPERS_SOURCE_DIR=/path/to/papers  # Directory containing PDFs to import
PAPERS_STATE_FILE=data/local_import_state.json  # Tracks imported papers

Paper Import:

  • PAPERS_SOURCE_DIR: Point to your local papers directory (or network share)

  • Papers are automatically uploaded to MMORE service

  • Progress tracked in PAPERS_STATE_FILE

  • See Paper Import Guide for details

Observability & Debugging#

For monitoring and debugging:

# LangSmith Tracing (optional)
LANGCHAIN_TRACING=false  # Set to true to enable
LANGCHAIN_ENDPOINT=https://api.smith.langchain.com
LANGSMITH_API_KEY=your-langsmith-key
LANGCHAIN_PROJECT=engiai

# Host Service (Docker GUI integration)
HOST_SERVICE_PORT=9999  # Port for opening GUI apps from Docker

LangSmith Setup:

  1. Create account at smith.langchain.com

  2. Get API key from settings

  3. Monitor LLM calls, costs, and performance

  4. Debug conversation flows and tool usage

Weave and log level:

Variable

Default

Purpose

USE_WEAVE_CHATBOT

false

Trace the interactive Streamlit app in Weave (benchmark runs are governed by USE_WEAVE). Read once per session in src/ui/streamlit_app.py.

ENGIAI_LOG_LEVEL

INFO

Root log level for the benchmark harness only — read in evaluate_agent.py’s __main__, not by the app or UI. Case-insensitive; an unrecognised value warns and falls back to INFO rather than aborting.

Warning

USE_WEAVE_CHATBOT silently requires USE_WEAVE. It only decides whether init_weave() is called; setup_weave_tracing() returns immediately when USE_WEAVE is false (the guard is in config.py; weave_integration.init_weave just delegates to it), and the return value is discarded, so setting USE_WEAVE_CHATBOT=true on its own traces nothing and reports no error.

Agent availability flags#

Each disables one routing target on the supervisor. The benchmark harness sets them per run; set them here only to disable a route permanently.

Variable

Default

Purpose

SKIP_SEARCH

false

Disables web search. The SearchAgent is not constructed at all, and the routing prompt is told not to route to it (SupervisorAgent._build_routing_prompt).

SEARCH_PROVIDER

duckduckgo

Web-search backend: keyless duckduckgo, or tavily with TAVILY_API_KEY.

SEARCH_REGION

us-en

DuckDuckGo result region.

SEARCH_TIMEOUT_SECONDS

10

Timeout for each DuckDuckGo request. One retry is attempted after a transient failure.

SKIP_ARXIV

false

Disables the ArXiv route. Unlike SKIP_SEARCH the agent is still constructed; _arxiv_node returns an “unavailable” message and the routing prompt redirects paper lookups to RAG (SupervisorAgent._arxiv_node).

SKIP_MCP

false

Master switch over both MCP integrations — the Prusa printer and the whiteboard canvas. true disables the canvas regardless of SKIP_CANVAS, which is what keeps the eval harness and CI unchanged. Note docker-compose.yml sets this to false for the chatbot regardless of .env.

SKIP_CANVAS

false

Disables the whiteboard agent on its own, leaving the printer integration alone. .env.example ships True, since a fresh checkout has no canvas server running.

CANVAS_MCP_URL

http://localhost:8766

Where the agent reaches the canvas server. Docker sets http://excalidraw-mcp-server:8766. Do not append /sse — the client adds it.

CANVAS_MCP_HOST / CANVAS_MCP_PORT

0.0.0.0 / 8766

Bind address and port for the standalone canvas server.

CANVAS_MCP_ALLOWED_HOSTS

see Whiteboard Canvas

Host-header allowlist for the canvas SSE transport. The un-namespaced MCP_ALLOWED_HOSTS is deliberately not read by that server.

CANVAS_STATE_DIR

unset locally, /app/data/canvas in Docker

Where the canvas server persists scenes. Unset means memory-only. The durable per-conversation copy lives in the EngiAI database regardless.

CANVAS_MAX_SCENES / CANVAS_MAX_ELEMENTS / CANVAS_MAX_BYTES

64 / 2000 / 2000000

Resource bounds on the canvas server.

Hugging Face Integration#

For ML model downloads:

# Hugging Face
HF_TOKEN=your-huggingface-token
HF_HOME=$HOME/.cache/huggingface/hub
HF_DATASETS_CACHE=$HOME/.cache/huggingface/datasets

Getting HF Token:

  1. Create account at huggingface.co

  2. Go to Settings → Access Tokens

  3. Create token with “Read” permission

Advanced SLURM Configuration#

For HPC job submission (advanced users):

# SLURM Job Defaults
SLURM_TIME=00:45:00           # Max job duration
SLURM_NTASKS=1                # Number of tasks
SLURM_CPUS_PER_TASK=4         # CPUs per task
SLURM_MEM_PER_CPU=7GB         # Memory per CPU
SLURM_GPUS=rtx_4090:1         # GPU specification

# Module Loading
SLURM_STACK_MODULE=stack/2024-06
SLURM_GCC_MODULE=gcc/12.2.0
SLURM_PYTHON_MODULE=python_cuda/3.11.6
SLURM_CUDA_MODULE=cuda/12.8.0

Note: Many SLURM settings can be configured via the Settings UI (⚙️ Settings > SLURM Cluster Configuration). Environment variables serve as fallback defaults.

Guardrails. These are caps, not defaults: they never change what the agent asks for, they reject a request that exceeds them. All four are checked together in _check_hard_limits() in src/tools/engiopt.py, and the comparison is strictly greater-than, so a request equal to the cap passes.

Variable

Default

Purpose

SLURM_MAX_GPUS

4

Maximum GPUs the agent may request

SLURM_MAX_TIME_HOURS

24

Maximum wall-clock hours

SLURM_MAX_CPUS

16

Maximum CPUs

SLURM_MAX_MEM_PER_CPU_GB

16

Maximum memory per CPU, in GB

SKIP_SLURM_EMAIL

false

Omit --mail-type/--mail-user from generated job scripts. The harness forces this on for hpc_train_* and training_code_* runs so a grid does not send hundreds of emails.

Warning

All four caps are parsed with a bare int(os.getenv(...)). A non-integer value raises ValueError when a job is validated, not at startup.

LLM Models#

You can configure which language model to use:

Supported Models#

# OpenAI Models
LLM_MODEL=openai:gpt-4.1         # GPT-4.1 (recommended)
LLM_MODEL=openai:gpt-4o          # GPT-4o
LLM_MODEL=openai:gpt-4o-mini     # GPT-4o Mini (faster, cheaper)

# Google Models
LLM_MODEL=google_genai:gemini-2.5-flash  # Gemini 2.5 Flash

# Ollama (local models)
LLM_MODEL=ollama:qwen3.5:4b      # Qwen 3.5 4B (local)

To let each browser session switch between a small set of deployed models, provide a comma-separated allow-list. LLM_MODEL remains the default:

LLM_MODEL=ollama:qwen3.5:4b-mxfp8
LLM_MODEL_OPTIONS=ollama:qwen3.5:4b-mxfp8,ollama:qwen3.5:9b-mxfp8,ollama:qwen3.8:27b-mxfp8
LLM_ROUTER_MODEL=ollama:qwen3.5:4b-mxfp8

Changing the mode in the sidebar rebuilds only that browser session’s agent on its next message. It does not change the model selected by other users. LLM_ROUTER_MODEL can keep the small structured routing step on a fast model while the selected model handles the actual work and response.

Model Parameters#

# Temperature: Controls randomness in responses (0.0 to 2.0)
LLM_TEMPERATURE=0.0   # Deterministic, focused (recommended for engineering)
LLM_TEMPERATURE=0.7   # Balanced creativity (default)
LLM_TEMPERATURE=1.0   # More creative and varied responses

# Note: Higher temperature increases creativity but may reduce accuracy
# For engineering tasks, use 0.0-0.3 for precision
# For brainstorming, use 0.7-1.0 for variety

HPC Configuration#

For cluster computing features:

SSH Setup#

  1. Configure SSH access to your cluster:

    # In ~/.ssh/config
    Host euler
        HostName euler.ethz.ch
        User your-username
        IdentityFile ~/.ssh/id_rsa
    
  2. Test connection:

    ssh euler
    
  3. Set in .env:

    HPC_HOST_ALIAS=euler
    HPC_HOSTNAME=euler.ethz.ch
    HPC_USERNAME=your-username
    

Fabric Configuration#

The assistant uses Fabric for HPC interactions. Configuration is automatic based on your .env settings.

Weights & Biases#

For experiment tracking:

  1. Create account at wandb.ai

  2. Get API key from https://wandb.ai/authorize

  3. Add to .env:

    WANDB_API_KEY=your-key-here
    WANDB_ENTITY=your-username-or-team
    

Variable

Default

Purpose

WANDB_PROJECT

engiopt

Project that HPC training runs log to

TRAINING_CODE_WANDB_PROJECT

unset

Pins the project for the training-code-generation track, on both the submit and the evaluate side. Set it to isolate a grid: without it, seed_{seed} artifact aliases from different runs collide in one project. When set it also overrides the value the agent supplies.

Prusa MCP#

Variable

Default

Purpose

PRUSA_MCP_URL

http://localhost:8765

Where the agent reaches the MCP server. Docker sets http://prusa-mcp-server:8765. Do not append /sse — the client adds it.

PRUSA_MCP_HOST / PRUSA_MCP_PORT

0.0.0.0 / 8765

Bind address and port for the standalone server. 8765 is what run.sh, the Dockerfile and compose all use, so both can be left unset. server.py’s bare fallback is 8000 — the port MMORE occupies — and applies whenever the server is started without run.sh or Docker. Note run.sh sources services/.env, not the repo-root .env.

PRUSA_MCP_ALLOWED_HOSTS

see below

Host-header allowlist for the SSE transport. The un-namespaced MCP_ALLOWED_HOSTS is still honoured but deprecated — it is read by every MCP server in the stack, so a single value would be applied to both this server and the canvas server, and each would reject the other’s hostname with HTTP 421.

PRUSA_TOKEN_FILE

~/.config/prusa-mcp/tokens.json

OAuth2 token file, written by make prusa-login. Docker sets /app/data/prusa_tokens.json and mounts ./data, so the host login is reused by the container.

Warning

PRUSA_MCP_ALLOWED_HOSTS replaces the default list rather than extending it. The default is localhost, 127.0.0.1 and prusa-mcp-server, each in bare and :PORT form. Since the server binds 0.0.0.0, this allowlist is what stops other machines reaching the printer-control tools — widen it only to a host you control, and list hosts explicitly rather than using a wildcard. See Transport security.

Database Setup#

See the Database Setup Guide for detailed instructions on:

  • Setting up PostgreSQL or SQLite for conversation history

  • Configuring the MMORE RAG service for document retrieval

Variable

Default

Purpose

POSTGRES_PASSWORD

change_me

Consumed by docker-compose.yml only — no Python reads it. It sets the container’s superuser password and is substituted into the fallback DATABASE_URL. Change it before deploying.

OLLAMA_NUM_CTX

32768

Context window passed as num_ctx to Ollama-backed models. Applied only when LLM_MODEL starts with ollama:. Parsed at import time, so a non-integer value fails immediately.

LLM_MODEL_OPTIONS

selected model

Comma-separated allow-list shown in the per-session model selector. The default model is inserted automatically if omitted.

LLM_MODEL_LABELS

{}

Optional JSON object mapping model identifiers to friendly selector labels. Unknown models fall back to their identifier.

LLM_ROUTER_MODEL

selected model

Optional model used only for supervisor routing. A small local model reduces latency while the selected profile still performs the actual task.

LLM_ROUTER_TEMPERATURE

0.1

Temperature used only for structured supervisor routing. Kept separate from response creativity because some small local models fail to emit structured output at exactly zero.

VS Code Settings#

Recommended settings are in .vscode/settings_template.json:

{
    "python.defaultInterpreterPath": "./venv/bin/python",
    "python.analysis.extraPaths": [
        "${workspaceFolder}/benchmarks/evaluations/plots"
    ],
    "python.linting.enabled": true,
    "python.linting.ruffEnabled": true,
    "python.linting.mypyEnabled": true,
    "mypy-type-checker.importStrategy": "fromEnvironment",
    "python.formatting.provider": "none",
    "ruff.importStrategy": "fromEnvironment",
    "ruff.organizeImports": true,
    "ruff.fixAll": true,
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
        "source.fixAll.ruff": "explicit",
        "source.organizeImports.ruff": "explicit"
    },
    "editor.rulers": [
        88
    ],
    "files.trimTrailingWhitespace": true,
    "files.insertFinalNewline": true,
    "files.trimFinalNewlines": true,
    "[python]": {
        "editor.defaultFormatter": "charliermarsh.ruff",
        "editor.formatOnSave": true,
        "editor.codeActionsOnSave": {
            "source.fixAll.ruff": "explicit",
            "source.organizeImports.ruff": "explicit"
        }
    }
}

Validating Configuration#

Check your configuration:

# Test API keys
python -c "from config import config; print('OpenAI Key:', config.openai_api_key[:10])"

# Test database connection
python -c "from src.ui.database import DatabaseManager; db = DatabaseManager(); print('DB OK')"

# Run all tests
pytest

Troubleshooting#

API Key Issues#

Error: openai.AuthenticationError

  • Check your API key in .env

  • Ensure no extra spaces or quotes

  • Verify key is active in OpenAI dashboard

HPC Connection Issues#

Error: Connection refused

  • Verify SSH config

  • Test manual SSH connection

  • Check firewall/VPN requirements

Database Issues#

Error: database is locked

  • Close other applications using the database

  • Use PostgreSQL for concurrent access

Security Best Practices#

  1. Never commit .env - It’s in .gitignore for a reason

  2. Use environment-specific keys - Different keys for dev/prod

  3. Rotate keys regularly - Change API keys periodically

  4. Limit key permissions - Use minimal required scopes

  5. Use secrets manager - For production deployments (AWS Secrets Manager, etc.)

Next Steps#