-
Notifications
You must be signed in to change notification settings - Fork 15
AI Providers
FreeITSM's AI features share a single, provider-agnostic building block. Any module can offer a provider / model / key settings panel and call out to an LLM without re-implementing the plumbing. Three providers are supported β Anthropic (Claude), OpenAI (GPT) and OpenRouter β and each AI feature is configured independently with its own key.
This page documents the shared layer added in June 2026. For the per-module behaviour see the individual module pages (Knowledge, CMDB, Workflows, Forms, Tickets, Contracts).
Five pieces, of which a module only ever touches the first:
| File | Responsibility |
|---|---|
includes/ai_settings_panel.php |
renderAiSettingsPanel('<ns>') β renders the provider/model/key panel. One line per settings page. |
assets/js/ai-settings.js |
Drives every [data-ai-panel] on the page β provider switching, the model dropdown, save/test. |
includes/ai_settings.php |
Namespace registry + load/save/forUi. Reads config from system_settings, decrypts keys. |
includes/ai_provider.php |
Provider-agnostic aiProviderChat() β the only code that knows each vendor's wire format. Plus the OpenRouter model catalogue. |
api/system/ai/{get_settings,save_settings,test_connection,openrouter_models}.php |
Shared endpoints behind the panel. No per-module logic. |
The split matters: ai_provider.php is storage-agnostic (it takes a plain config array and never reads settings itself), so it can be reused anywhere, while ai_settings.php is the part that knows about system_settings.
aiProviderChat(array $cfg, array $opts) normalises all three into one return shape (content, tokens_in, tokens_out, provider, model, duration_ms).
| Provider | Endpoint | Wire format |
|---|---|---|
anthropic |
https://api.anthropic.com/v1/messages |
Anthropic Messages API (x-api-key, anthropic-version: 2023-06-01) |
openai |
https://api.openai.com/v1/chat/completions |
OpenAI chat-completions (Authorization: Bearer) |
openrouter |
https://openrouter.ai/api/v1/chat/completions |
OpenAI-compatible β same code path as openai
|
OpenAI and OpenRouter share one implementation (aiProviderCallOpenAICompatible()) because OpenRouter speaks the OpenAI wire format. All outbound POSTs go through aiProviderHttpPost(), which retries on 429 / 5xx / network errors with exponential backoff (3 attempts, 2s base).
-
One key, hundreds of models. OpenRouter is an aggregator β a single key reaches Claude, GPT, Gemini, Llama, Mistral, DeepSeek and more. Models are addressed by namespaced id, e.g.
anthropic/claude-3.5-sonnet,google/gemini-2.0-flash. -
Live model catalogue.
aiProviderListOpenRouterModels()fetcheshttps://openrouter.ai/api/v1/models(no key required β public) and caches it insystem_settings(openrouter_models_cache,openrouter_models_cached_at) for 24h. On a failed refresh it falls back to the stale cache rather than returning an empty list. The panel renders this as a searchable dropdown showing each model's id and per-million-token pricing (in / out). -
Attribution headers. OpenRouter calls send
HTTP-Referer: https://freeitsm.co.ukandX-Title: FreeITSMso usage is attributed on the OpenRouter dashboard. - Per-key spend limits. OpenRouter supports multiple keys with per-key spend limits, so per-module granular billing is preserved under a single OpenRouter account.
Each AI feature registers a namespace in aiSettingsRegistry() (includes/ai_settings.php) with a default provider and model:
| Namespace | Feature | Settings location | Default model |
|---|---|---|---|
knowledge_ai |
Knowledge β Ask AI | Knowledge β Settings β AI | claude-haiku-4-5-20251001 |
cmdb_ai |
CMDB β summaries + Suggest Properties | CMDB β Settings β AI Integration | claude-haiku-4-5-20251001 |
workflow_ai |
Workflows β AI co-author | Workflows β Settings β AI | claude-sonnet-4-6 |
forms_ai |
Forms β AI Assist | Forms β Settings β AI | claude-sonnet-4-6 |
tickets_reply_cleanup |
Tickets β Reply Cleanup | Tickets β Settings β Reply Cleanup | claude-haiku-4-5-20251001 |
problem_ai |
Problem Management β AI analysis | Problems β Settings β AI | claude-sonnet-4-6 |
lms_ai |
LMS β course authoring helpers | Learning β Settings β AI | claude-sonnet-4-6 |
knowledge_writeup |
Knowledge Assistant β gap judgement + drafting | Knowledge β Settings β AI | claude-sonnet-4-6 |
RFP Builder (Contracts) is deliberately deferred from the shared panel β its long streaming generations need a dedicated OpenRouter SSE path first, so it keeps its existing Anthropic streaming for now.
The registry is also a security boundary β see below.
Config for a namespace <ns> lives in four system_settings rows:
| Key | Contents |
|---|---|
<ns>_provider |
anthropic | openai | openrouter
|
<ns>_model |
Model id |
<ns>_api_key |
Encrypted at rest (AES-256-GCM). Must be listed in ENCRYPTED_SETTING_KEYS + MASKED_SETTING_KEYS in includes/encryption.php. |
<ns>_verify_ssl |
'1' | '0' (absent β falls back to the global SSL_VERIFY_PEER) |
Security properties:
-
The registry is an allowlist. The generic
api/system/ai/*endpoints only ever read or write keys derived from a registered namespace, so they can't be abused to read or overwrite arbitrarysystem_settings(e.g. other secrets). An unregistered namespace is rejected. -
Keys never leave the server.
aiSettingsForUi()returns only a mask (****<last4>) and ahas_keyflag β the plaintext key is never sent to the browser. It exists in plaintext only for the moment a request is made to the provider. -
Masked / no-change saves.
aiSettingsSave()treats a masked or empty key submission as "leave unchanged", so re-saving provider + model never wipes the stored key. You only overwrite it by typing a new one. -
SSL verify dial. The effective verify flag is the global
SSL_VERIFY_PEERconstant ANDed with the per-namespace toggle. On a dev box where the global is off (no CA bundle), outbound HTTPS skips verification regardless; in production set the global on and use the toggle to opt out per feature.
No schema change was required β everything lives in system_settings.
Two features stream their output live (claude.ai-style SSE). The rollout deliberately left the proven streaming engine (includes/rfp_ai.php) untouched:
-
Provider =
anthropicβ existing native streaming path (live tokens), unchanged. -
Provider =
openai/openrouterβ one-shotaiProviderChat()emitted as a single SSE event (no token-by-token streaming for these providers yet).
This applies to Forms AI Assist and Tickets Reply Cleanup.
- Add a namespace to
aiSettingsRegistry()inincludes/ai_settings.php(label + default provider + default model). - Add
<ns>_api_keytoENCRYPTED_SETTING_KEYSandMASKED_SETTING_KEYSinincludes/encryption.php. - Drop
<?php renderAiSettingsPanel('<ns>'); ?>on the module's settings page (ensureai-settings.js, thecommoni18n namespace, andshowToast()are available). - In the backend, load config with
aiSettingsLoad($conn, '<ns>')and pass it toaiProviderChat().
That's the whole adoption cost β no per-module provider plumbing.
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
- Architecture
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- Theming & Dark Mode
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
- MobileβFriendly
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π’ Ticket numbering
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- Problem Management
- Tasks
- Assets
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
- Forms
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)