Configuration

AI configuration

AI behaviour is configured in config/ai.php and consumed by laravel/ai plus Externa’s AppAssistant agent. Defaults target a local OpenAI-compatible gateway (LM Studio, vLLM, etc.).

Related

Env reference: Environment variables. HTTP routes: AI API. Product overview: AI assistant.

Default provider

Config keyEnvDefault
defaultAI_DEFAULT_PROVIDERlocal
default_for_images(hard-coded)gemini
default_for_audio(hard-coded)openai
default_for_transcription(hard-coded)openai
default_for_embeddings(hard-coded)openai
default_for_reranking(hard-coded)cohere

AppAssistant is annotated with #[Provider('local')], so chat uses the local provider entry unless you change that attribute / default.

AI_DEFAULT_PROVIDER=local

Local provider (LOCAL_AI_*)

Under providers.local:

SettingEnvDefault
Driver(fixed)openai (OpenAI-compatible API shape)
URLLOCAL_AI_URLhttp://127.0.0.1:1234/v1
API keyLOCAL_AI_API_KEYempty string
Default text modelLOCAL_AI_MODELconfig default local-model; .env.example sets openai/gpt-oss-20b
LOCAL_AI_URL=http://127.0.0.1:1234/v1
LOCAL_AI_API_KEY=
LOCAL_AI_MODEL=openai/gpt-oss-20b

Tool calling

Prefer a model that supports tool calling reliably. Tiny models often hallucinate successful tool results without calling tools — Externa’s instructions tell the agent never to invent success, but a weak model still wastes time.

AppAssistant sets a 300s HTTP timeout and a high maxSteps() budget (field-type count + 16) for long tool loops.

Local gateway / Herd nginx

LM Studio may emit response.reasoning_text.* SSE events; Externa remaps them to OpenAI-shaped response.reasoning_summary_text.* so laravel/ai keeps the stream alive. If the browser still shows a mid-stream “network error”, raise nginx fastcgi_read_timeout (Herd default is often 60s) to at least 600s while the model thinks without emitting tokens.

Cloud / other providers

config/ai.php registers many providers. Set the matching env keys when you switch AI_DEFAULT_PROVIDER or call a named provider:

Provider keyTypical env vars
anthropicANTHROPIC_API_KEY, ANTHROPIC_URL
azureAZURE_OPENAI_API_KEY, AZURE_OPENAI_URL, AZURE_OPENAI_API_VERSION, deployment names
bedrockAWS_BEDROCK_REGION, AWS_BEARER_TOKEN_BEDROCK, AWS credentials / AWS_USE_DEFAULT_CREDENTIALS
cohereCOHERE_API_KEY
deepseekDEEPSEEK_API_KEY
elevenELEVENLABS_API_KEY
geminiGEMINI_API_KEY, GEMINI_URL
groqGROQ_API_KEY
jinaJINA_API_KEY
mistralMISTRAL_API_KEY
ollamaOLLAMA_API_KEY, OLLAMA_URL (default http://localhost:11434)
openaiOPENAI_API_KEY, OPENAI_URL
openrouterOPENROUTER_API_KEY
voyageaiVOYAGEAI_API_KEY
xaiXAI_API_KEY

These are not all listed in .env.example; they are read when present.

Remote import hosts

'remote_import_hosts' => array_values(array_filter(array_map(
    'trim',
    explode(',', (string) env('AI_REMOTE_IMPORT_HOSTS', '')),
))),
EnvDefaultMeaning
AI_REMOTE_IMPORT_HOSTSemptyComma-separated host allowlist for ImportRemoteJson / related remote fetches. Empty → no remote hosts allowed.
AI_REMOTE_IMPORT_HOSTS=api.example.com,data.partner.test

Security

Never leave remote import open to arbitrary hosts in production. Pair allowlists with short-lived Bearer tokens when the remote API requires auth.

Webhook token

ConfigEnvPurpose
webhook_tokenAI_WEBHOOK_TOKENShared secret for POST /ai/webhooks/collection-import

Auth accepts either:

  • Authorization: Bearer {token}, or
  • X-AI-Webhook-Token: {token}

Compared with hash_equals against the configured value. If the env is empty, the webhook always returns 403.

CSRF is excluded for this path in bootstrap/app.php. Details: AI API.

AI_WEBHOOK_TOKEN=generate-a-long-random-secret

Daily prompt limit

ConfigEnvDefault
daily_prompt_limitAI_DAILY_PROMPT_LIMIT0

0 means unlimited. When set to a positive integer, AiChatController enforces a per-user daily cap.

AI_DAILY_PROMPT_LIMIT=0

Embeddings and MCP

ConfigEnvDefaultRole
embeddings.enabledAI_EMBEDDINGS_ENABLEDfalseToggles embeddings-related features (e.g. similar-item search tooling when wired).
mcp.enabledAI_MCP_ENABLEDfalseToggles MCP-related features.
AI_EMBEDDINGS_ENABLED=false
AI_MCP_ENABLED=false

Embedding caching block (separate from the feature flag):

'caching' => [
    'embeddings' => [
        'cache' => false,
        'store' => env('CACHE_STORE', 'database'),
    ],
],

Conversations tables

'conversations' => [
    'connection' => env('DB_CONNECTION'),
    'tables' => [
        'conversations' => 'agent_conversations',
        'messages' => 'agent_conversation_messages',
    ],
    'generate_title' => false, // titles from first prompt via Str::limit
],
SettingMeaning
TablesLaravel AI conversation persistence uses agent_conversations and agent_conversation_messages on the default DB connection.
generate_titlefalse — Externa skips an extra LM call for titles; the first prompt is truncated instead.

HTTP CRUD for conversations is under /ai/conversations/* (session auth + can-use-ai). See AI API.

Attachments cleanup

Expired chat attachments are purged by ai:cleanup-attachments (scheduled daily). Configure retention via attachment expires_at in application code; the command deletes expired AiChatAttachment rows and their files.

Minimal local AI .env

AI_DEFAULT_PROVIDER=local
LOCAL_AI_URL=http://127.0.0.1:1234/v1
LOCAL_AI_API_KEY=
LOCAL_AI_MODEL=openai/gpt-oss-20b
AI_REMOTE_IMPORT_HOSTS=
AI_WEBHOOK_TOKEN=
AI_DAILY_PROMPT_LIMIT=0
AI_EMBEDDINGS_ENABLED=false
AI_MCP_ENABLED=false
Previous
Files & storage