-
Notifications
You must be signed in to change notification settings - Fork 68
Configuration
FreeCAD AI stores all settings in a single JSON file and provides a GUI dialog for editing them. No environment variables or .env files are needed.
Open the settings dialog in any of these ways:
- Menu: FreeCAD AI > AI Settings
- Toolbar: click the "AI Settings" button in the FreeCAD AI toolbar
- Chat panel footer: click the gear icon at the bottom of the chat dock widget
The dialog has sections for: LLM Provider, Model Parameters, Behavior, System Prompt, Tool Reranking, MCP Servers, Editor, User Tools, Skills, Hooks, and a Test Connection button.



| Field | Description |
|---|---|
| Provider | Dropdown to select the LLM backend. Changing this auto-fills Base URL and Model with defaults. |
| API Key | Your provider's API key. Can also be file:/path/to/token (re-read each call) or cmd:command (run command, use stdout). Leave empty for Ollama. See Dynamic API Keys. |
| Base URL | The API endpoint. Auto-filled from provider presets but can be overridden. |
| Model | The model name to use. Auto-filled with the provider's recommended default. |
This section combines fixed fields and a freeform parameter table.
Fixed fields:
| Field | Description | Default |
|---|---|---|
| Max Output Tokens | Maximum number of tokens the LLM can generate per response. Range: 256--262,144. This controls output length only, not the context window. | 4096 |
| Context Window | Context window size in tokens. Older messages are automatically compacted when the conversation exceeds this limit. Set to your model's context limit, or lower to control API costs (e.g., 200,000 for Claude to avoid the expensive >200k tier). Range: 4,000--1,000,000. | 20,000 |
Sampling parameters table:
Below the fixed fields is a key-value table for arbitrary sampling parameters (temperature, top_p, top_k, etc.) that are sent with each API request. Parameters are saved per model name — when you switch models, the saved parameters for that model are loaded automatically.
| Button | Description |
|---|---|
| Add | Add a new parameter row |
| Remove | Remove the selected parameter |
| Load Defaults | Reset to the provider's recommended values |
Common parameters: temperature, top_p, top_k, n, presence_penalty, frequency_penalty, repetition_penalty.
Why per-model parameters? Different models have different optimal settings. For example, Gemma 4 recommends temperature=1.0, top_p=0.95, top_k=64, while most other models work well with temperature=0.3. Instead of a single global temperature, you can configure each model independently. When you switch between models, the parameters follow automatically.
Providers can ship recommended defaults (e.g., Moonshot ships with temperature=0.6, top_p=0.95). Click Load Defaults to reset to these values.
| Field | Description | Default |
|---|---|---|
| Model supports tool calling | When checked, the assistant uses structured tool calls in Act mode. When unchecked, falls back to generating Python code directly. Uncheck this for models that don't support OpenAI-style function calling. | On |
| Auto-execute code in Act mode | When checked, the assistant executes tool calls immediately without asking for confirmation. When unchecked, each tool call shows a confirmation dialog. | Off |
| Thinking | Controls LLM reasoning chains. See the Thinking Mode section below. | Off |
| Strip thinking from conversation history | Tristate checkbox that controls whether thinking/reasoning content is removed from previous turns before sending to the API. See Strip Thinking History below. | Auto-detect |
| Viewport capture | Automatically attach viewport screenshots to chat messages. Off = disabled, Every Message = always, After Changes = only when tools modify the document. |
Off |
| Capture resolution | Resolution preset for viewport screenshots: Low (400x300), Medium (800x600), High (1600x1200). |
Medium |
| Model supports vision | Whether the LLM can process images natively. Auto-detected via Test Connection probe, or set manually. See Vision Routing below. | Not tested |
The full system prompt (static instructions) is shown in an editable text field. You can customize the instructions sent to the LLM — for example, adding domain-specific rules or removing sections you don't need.
- Text field — shows the current system prompt. Edit freely.
- Reset to Default — regenerates the default prompt from code. Use this if your edits cause problems.
Dynamic sections (document state, available skills, AGENTS.md) are always appended automatically and are not shown in this field.
Note: The system prompt override is stored in config.json as system_prompt_override. An empty value means "use the default."
A list of configured Model Context Protocol servers. See MCP Integration for details.
- Add... -- opens a dialog to add a new MCP server (name, command, args, deferred, enabled).
- Remove -- removes the selected server from the list.
Each server in the list shows its status tags (e.g., (deferred), (disabled)).
Controls which editor opens when you click Edit... or New... on a hook or user-tool file.
- Use external editor -- when checked, files open via the OS file association (xdg-open / Launch Services) so the Settings dialog can stay open. When unchecked (default), files open in FreeCAD's docked Python editor — which requires closing the Settings dialog first since the editor is an MDI sub-window of the FreeCAD main window and can't be reached while a modal dialog is up.
The setting is read live from the checkbox at the moment you click Edit/New, so toggling it applies to the current action even if you later cancel the dialog (no Save required for this toggle to take effect for the current edit).
When the FreeCAD-editor path is taken, clicking Edit/New first prompts:
- Save -- persists pending Settings changes, closes the dialog, opens the file in the docked editor.
- Discard -- discards pending Settings changes, closes the dialog, opens the file.
- Cancel -- keeps the dialog open, no file is opened (and for New, no file is created — the prompt fires before the template is written, so cancelling leaves no debris).
When the external-editor path is taken, no prompt fires — the Settings dialog stays open, the new file shows up in the list immediately, and the file opens in your OS-default .py handler.
Register your own Python functions as LLM-callable tools. Files in <FreeCADAI dir>/tools/ are automatically discovered and validated.
-
Tool list -- shows all discovered tool files with status indicators:
-
✓valid (green) -- tool loaded successfully -
⚠warning (yellow) -- loaded with warnings (e.g., missing docstring) -
✗error (red) -- validation failed (e.g., syntax error, no typed functions)
-
- New... -- prompts for a function name, writes a starter template (typed example function with a docstring — passes validation cleanly out of the box), and opens it in the configured editor. The function name is used for both the filename and the function inside it; it must be a valid Python identifier.
-
Add... -- opens a file picker to copy an existing
.pyor.FCMacrofile into the tools directory. - Edit... -- opens the selected tool file in the configured editor (FreeCAD's docked Python editor, or your OS-default editor — see Editor Section).
- Remove -- deletes the selected file from the tools directory.
- Reload -- re-scans and re-validates all tool files.
-
Also scan FreeCAD macro directory -- when checked, also scans
~/.config/FreeCAD/Macro/for compatible tool functions.
See Creating Custom Tools for the function convention and examples.
Shows all installed skills (built-in and user-created) with status indicators:
-
✓built-in -- using the repo's version (or user copy matches built-in) -
⚠modified -- user copy in<FreeCADAI dir>/skills/differs from the built-in version. This can happen when the workbench is updated but the user copy is stale. -
☆user -- user-created skill with no built-in equivalent -
Reset to Built-in -- deletes the user copy and reverts to the built-in version. Only available for skills that have both a user copy and a built-in version.
-
Refresh -- re-scans both skill directories and updates the list.
Click Test Connection to verify your provider settings. The workbench sends a small test request to the configured endpoint and displays the result:
- Green text -- connection successful, shows a snippet of the response.
- Red text -- connection failed, shows the error message.
After a successful connection test, a capability probe runs automatically:
-
Ollama: capabilities are read directly from
/api/show(authoritative), reportingvision,tools, andthinkingsupport. - All other providers: a small image containing a random 3-digit number is sent to the LLM. If the LLM reads the number correctly, vision is marked as supported. Tool/thinking support comes from the provider's static capability flag.
Results appear below the connection status:
-
"Vision: supported" / "Vision: not supported" -- whether the model can process images natively. If unsupported and an MCP server provides a
describe_imagetool, images will be auto-described via that tool instead. -
"Capabilities: tools: yes, thinking: no" (Ollama only) -- shows whether the specific model supports OpenAI-style tool calling and reasoning chains. The
enable_toolssetting is gated on this — Ollama models that lack tool support (e.g., embedding or reranker models accidentally selected as the main model) will not have tool schemas sent to them.
Detection results are persisted in config.json (vision_detected, tools_detected, thinking_detected). You only need to re-test when you change the provider, model, or base URL — the workbench resets these flags automatically when those fields change.
The test temporarily applies whatever values are currently in the dialog (you do not need to save first).
After verifying, click Save to persist settings or Cancel to discard changes.
Starting in v0.12.0-alpha, FreeCAD AI also registers a native preferences page at Edit → Preferences → FreeCAD AI, following the convention used by every workbench listed in the FreeCAD Addon Manager. This is in addition to (not a replacement for) the AI Settings dialog described above.
The preferences page shows 8 fields — the basics that map cleanly to FreeCAD's flat parameter store:
| Field | Type | Notes |
|---|---|---|
| Provider | dropdown | Same provider list as the AI Settings dialog |
| Base URL | text | API endpoint |
| Model | text | Model identifier |
| API Key | password | Supports file: and cmd: prefixes (see Dynamic API Keys) |
| Max Output Tokens | spin box | 256–262144 |
| Mode | dropdown |
plan / act
|
| Thinking | dropdown |
off / on / extended
|
| Enable Tools | checkbox | Disable to force code-generation fallback |
Everything else — MCP servers, skills, hooks, model parameters, system prompt, viewport capture, tool reranking — stays in the AI Settings dialog. Those fields use nested data structures (lists of dicts, base64-encoded dock state) that don't flatten naturally to FreeCAD's GetInt / GetString / GetBool parameter store.
FreeCAD AI keeps <FreeCADAI dir>/config.json as the primary config surface and mirrors the 8 preference-page fields into FreeCAD's parameter store at BaseApp/Preferences/Mod/FreeCADAI:
- On config load: any values present in the parameter store override the JSON values for the 8 mirrored fields. This means changes you make in Edit → Preferences are picked up the next time the workbench loads its config.
- On config save: the 8 mirrored fields are written back to the parameter store. Changes you make in the AI Settings dialog automatically propagate to the preferences page.
If you only ever use one of the two UIs, you don't need to think about the bridge — both surfaces stay consistent. If you set a value via the parameter file directly (e.g., editing ~/.config/FreeCAD/FreeCAD.cfg by hand) that's out of range for an enum field, the bridge ignores it defensively and falls back to the JSON value.
The Addon Manager workflow assumes every workbench has an Edit → Preferences entry — that's what users look for first. The AI Settings dialog predates the Addon Manager work and exposes the full surface (including features that have no flat-key equivalent), so removing it would be a regression. Both stay.
| Provider | API Key Required | Default Model | Tool Calling | Notes |
|---|---|---|---|---|
| Ollama | No | llama3 |
Yes | Local, free, no data leaves your machine |
| Anthropic | Yes | claude-sonnet-4-20250514 |
Yes | Best tool calling quality. Native API format. |
| OpenAI | Yes | gpt-4o |
Yes | Wide model selection |
| Gemini | Yes | gemini-2.0-flash |
Yes | Google AI, generous free tier |
| OpenRouter | Yes | anthropic/claude-sonnet-4-20250514 |
Yes | Multi-provider gateway, pay-per-token |
| Moonshot | Yes | kimi-k2.5 |
Yes | Kimi models. Default params pre-configured. |
| DeepSeek | Yes | deepseek-chat |
Yes | DeepSeek-V3 |
| Qwen | Yes | qwen-plus |
Yes | Alibaba DashScope (international endpoint) |
| Groq | Yes | llama-3.3-70b-versatile |
Yes | Ultra-fast inference |
| Mistral | Yes | mistral-large-latest |
Yes | Parallel tool calling |
| Together | Yes | meta-llama/Llama-3.3-70B-Instruct-Turbo |
Yes | Open model hosting |
| Fireworks | Yes | accounts/fireworks/models/llama-v3p3-70b-instruct |
Yes | Fast inference |
| xAI | Yes | grok-3 |
Yes | Grok models |
| Cohere | Yes | command-a-03-2025 |
Yes | OpenAI-compatible endpoint |
| SambaNova | Yes | Meta-Llama-3.3-70B-Instruct |
Yes | Fast inference |
| MiniMax | Yes | MiniMax-M1 |
Yes | MiniMax models |
| Llama | Yes | Llama-4-Maverick-17B-128E-Instruct |
Yes | Meta's official Llama API |
| GitHub | Yes (PAT) | gpt-4o |
Yes | GitHub Models marketplace |
| HuggingFace | Yes (hf_...) |
Qwen/Qwen2.5-72B-Instruct |
Yes | Serverless inference API |
| Zhipu | Yes | glm-5 |
Yes | GLM models (international endpoint at z.ai) |
| Custom | Varies | (none) | No* | Any OpenAI-compatible endpoint |
* Custom providers have tool calling disabled by default. The assistant will fall back to code generation.
Provider-specific defaults:
-
Moonshot (Kimi-K2.5): Ships with recommended defaults (
temperature=0.6, top_p=0.95, n=1). These are loaded when selecting Moonshot and can be edited in the Model Parameters table.
No API key needed. Install Ollama and pull a model (see Installation#Ollama Setup (Local Models)).
- Set Provider to Ollama.
- Leave API Key empty.
-
Base URL:
http://localhost:11434/v1(default) — orhttp://<host>:11434/v1for a remote Ollama. -
Model: enter the model name you pulled, e.g.,
qwen3,llama3,qwen2.5-coder. - Click Test Connection, then Save.
Important: always use the
/v1path, not/api/. Ollama exposes two HTTP interfaces on the same port:
/v1/*— OpenAI-compatible endpoints (/v1/chat/completions). This is what FreeCAD AI uses./api/*— Ollama's native endpoints (/api/chat,/api/generate). FreeCAD AI does not use this.A Base URL ending in
/api/will return HTTP 404 because the workbench will try to POST to/api/chat/completions, which doesn't exist. Always end the Base URL with/v1.
Recommended models for FreeCAD AI:
-
qwen3-- good tool calling, 8B parameters, runs on most hardware -
qwen2.5-coder-- strong at code generation, 7B parameters -
llama3-- fast general-purpose model, 8B parameters
- Go to console.anthropic.com and create an account.
- Navigate to API Keys and create a new key.
- In FreeCAD AI settings:
- Provider: Anthropic
-
API Key: paste your key (starts with
sk-ant-) -
Base URL:
https://api.anthropic.com(default) -
Model:
claude-sonnet-4-20250514(default, recommended)
- Click Test Connection, then Save.
Anthropic uses its own native API format (not OpenAI-compatible). The workbench handles this automatically.
Available models: claude-sonnet-4-20250514, claude-haiku-3-20250414, claude-opus-4-20250514
- Go to platform.openai.com and create an account.
- Navigate to API Keys and create a new key.
- In FreeCAD AI settings:
- Provider: OpenAI
-
API Key: paste your key (starts with
sk-) -
Base URL:
https://api.openai.com/v1(default) -
Model:
gpt-4o(default, recommended)
- Click Test Connection, then Save.
Available models: gpt-4o, gpt-4o-mini, gpt-4-turbo, o1, o1-mini
- Go to aistudio.google.dev and sign in with your Google account.
- Click Get API Key and create a new key.
- In FreeCAD AI settings:
- Provider: Gemini
- API Key: paste your key
-
Base URL:
https://generativelanguage.googleapis.com/v1beta/openai(default) -
Model:
gemini-2.0-flash(default, recommended)
- Click Test Connection, then Save.
Gemini uses an OpenAI-compatible endpoint provided by Google, so no special handling is needed.
Available models: gemini-2.0-flash, gemini-2.0-flash-lite, gemini-1.5-pro
OpenRouter is a gateway that routes requests to many providers (Anthropic, OpenAI, Google, Meta, Mistral, and more). You get a single API key and pay per token.
- Go to openrouter.ai and create an account.
- Navigate to Keys and create a new key.
- In FreeCAD AI settings:
- Provider: OpenRouter
- API Key: paste your key
-
Base URL:
https://openrouter.ai/api/v1(default) -
Model:
anthropic/claude-sonnet-4-20250514(default) or any model from the OpenRouter catalog
- Click Test Connection, then Save.
Model names on OpenRouter use the provider/model format. Browse the model list to find alternatives.
Use this for self-hosted models (vLLM, text-generation-inference, LM Studio, etc.) or any endpoint that implements the OpenAI /chat/completions API.
- Set Provider to Custom.
- Enter the Base URL of your endpoint (e.g.,
http://localhost:8000/v1). - Enter an API Key if your endpoint requires one, or leave it empty.
- Enter the Model name your endpoint expects.
- Click Test Connection, then Save.
Note: tool calling is disabled for custom providers by default. The assistant will fall back to generating Python code directly instead of using structured tool calls.
Settings are stored at:
<FreeCADAI dir>/config.json
This file is created automatically on first launch. You can edit it by hand, but using the settings dialog is recommended.
<FreeCADAI dir> is resolved on workbench load using the order below (highest precedence first):
-
$FREECAD_AI_CONFIG_DIRenvironment variable, if set. Used verbatim. Useful for isolated profiles, sync-friendly locations (Syncthing, Dropbox), or pinning a fixed path during migration testing. -
<FreeCAD user config dir>/FreeCADAI/when the workbench runs inside FreeCAD. On FreeCAD 1.1+ Linux this is~/.config/FreeCAD/v1-1/FreeCADAI/— version-scoped under XDG_CONFIG_HOME, top-level alongside FreeCAD's ownFreeCAD.conf/user.cfg/system.cfg. The path is obtained viaFreeCAD.getUserConfigDir()when available, otherwise derived fromFreeCAD.Version()plus$XDG_CONFIG_HOME(default~/.config):-
Linux:
~/.config/FreeCAD/v1-1/FreeCADAI/ -
macOS (untested by maintainer):
~/Library/Preferences/FreeCAD/v1-1/FreeCADAI/ -
Windows (untested):
%APPDATA%\FreeCAD\v1-1\FreeCADAI\On FreeCAD <1.1 the result is the unversioned~/.config/FreeCAD/FreeCADAI/(or platform equivalent).
-
Linux:
-
~/.config/FreeCAD/FreeCADAI/legacy fallback when FreeCAD is not importable — pytest, console scripts, plain Python REPL.
Why under
.config/FreeCAD/v1-1/? FreeCAD 1.1 introduced version-scoped user dirs so different FreeCAD installs don't share settings. The workbench's data is config-shaped (settings, secrets, conversation logs) so it belongs underXDG_CONFIG_HOME(default~/.config/), notXDG_DATA_HOME(~/.local/share/, whereMod/andMacro/live). v0.13.0-alpha aligns with that XDG split. Earlier workbench versions hardcoded~/.config/FreeCAD/FreeCADAI/(right XDG namespace, but unversioned), leaving users on FreeCAD 1.1+ with two side-by-sideFreeCADAI/trees: the live unversioned one, plus a stale snapshot inside~/.config/FreeCAD/v1-1/that FreeCAD's own version-bump migration created. See issue #9.
Existing v0.12.x and earlier installs are auto-migrated on first import of freecad_ai.config under v0.13.0-alpha+. Migration is rename-then-move with a two-stage candidate search and an automatic sweep.
| # | Action |
|---|---|
| 1 | If <target>/.freecad_ai_active_marker already exists, skip migration entirely (marker is the "done" signal) and jump to step 5 (sweep). |
| 2 | If <target> already exists without a marker (e.g. FreeCAD 1.1's own first-launch migration created a stale FreeCADAI/ snapshot at ~/.config/FreeCAD/v1-1/FreeCADAI/), rename it to <target>.pre-v0.13-snapshot/ — frees the name without overwriting. |
| 3 | Pick the first historical candidate that has user content as migration source. Order: (a) <FreeCAD user data dir>/FreeCADAI/ — the v0.13.0-alpha pre-release wrote here briefly under XDG_DATA_HOME, only relevant on the maintainer's machine; (b) ~/.config/FreeCAD/FreeCADAI/ — every released build before v0.13.0-alpha. |
| 4 |
Move the chosen source to <target> (shutil.move — atomic same-filesystem rename when possible; copy-then-remove fallback for cross-device moves). The source ceases to exist; no copy is left behind. If no candidate has content, create an empty <target> (fresh install). |
| 5 |
Sweep: rename any remaining historical candidates that still have content to <candidate>.duplicate-cleanup/ (timestamped if collision). Catches duplicates left by an aborted/copy-based prior migration. Sweep runs on every launch, not just first migration. |
| 6 | Drop the marker file <target>/.freecad_ai_active_marker. |
- Idempotent — once the marker exists, subsequent runs skip migration. Removing the marker forces a re-migration. The sweep still runs every launch (cheap if no candidates exist).
- Best-effort — if any step raises (permissions, full disk, exotic filesystem) the workbench logs the error to stderr and falls back to a candidate path that still exists, so the workbench still loads. Better degraded than dead.
-
Collision-safe — pre-existing
FreeCADAI.pre-v0.13-snapshot/orFreeCADAI.duplicate-cleanup/from a previously-aborted migration is preserved by appending a Unix timestamp to the new backup name.
After migration, two kinds of backup may exist:
-
<target>.pre-v0.13-snapshot/(in<FreeCAD user config dir>/) — the staleFreeCADAI/snapshot FreeCAD's own migration created at the new target path. Pre-existed; never the source of truth. -
<candidate>.duplicate-cleanup/(typically next to~/.config/FreeCAD/) — historical candidate paths that still had content after the chosen source was moved. From a partially-completed prior migration; data already lives at the active location.
Both are untouched by the workbench going forward — safe to delete after verifying the new active location works:
# Linux. Adjust paths if you're on a different platform.
ls -la ~/.config/FreeCAD/v1-1/FreeCADAI/ # active config dir (FreeCAD 1.1+)
ls -la ~/.config/FreeCAD/v1-1/FreeCADAI.pre-v0.13-snapshot/ # FreeCAD's stale snapshot
ls -la ~/.config/FreeCAD/FreeCADAI.duplicate-cleanup/ # legacy duplicate
rm -rf ~/.config/FreeCAD/v1-1/FreeCADAI.pre-v0.13-snapshot/
rm -rf ~/.config/FreeCAD/FreeCADAI.duplicate-cleanup/Use the workbench for at least one session before deleting — that confirms the new location is working.
FREECAD_AI_CONFIG_DIR=~/work/freecad-ai-profile-1 freecadThe path is created if it doesn't exist. No migration runs in this mode — you start with an empty profile (or whatever's already in that directory).
{
"provider": {
"name": "anthropic",
"api_key": "sk-ant-...",
"base_url": "https://api.anthropic.com",
"model": "claude-sonnet-4-20250514"
},
"mode": "plan",
"max_tokens": 4096,
"context_window": 20000,
"temperature": 0.3,
"model_params": {
"gemma4:27b": {"temperature": 1.0, "top_p": 0.95, "top_k": 64},
"kimi-k2.5": {"temperature": 0.6, "top_p": 0.95}
},
"auto_execute": false,
"max_retries": 3,
"enable_tools": true,
"thinking": "off",
"strip_thinking_history": null,
"viewport_capture": "off",
"viewport_resolution": "medium",
"mcp_servers": [],
"user_tools_disabled": [],
"scan_freecad_macros": false,
"hooks_disabled": [],
"use_external_editor": false,
"system_prompt_override": "",
"vision_detected": null,
"vision_override": null,
"tools_detected": null,
"thinking_detected": null,
"chat_dock_geometry": null,
"chat_dock_mw_state": null
}| Field | Type | Default | Description |
|---|---|---|---|
provider.name |
string | "anthropic" |
Provider identifier. One of: anthropic, openai, ollama, gemini, openrouter, moonshot, deepseek, qwen, groq, mistral, together, fireworks, xai, cohere, sambanova, minimax, llama, github, huggingface, zhipu, custom. |
provider.api_key |
string | "" |
API key for the provider. Supports file: and cmd: prefixes for dynamic tokens (see Dynamic API Keys). Leave empty for Ollama. |
provider.base_url |
string | "https://api.anthropic.com" |
API endpoint URL. Auto-filled from provider presets. |
provider.model |
string | "claude-sonnet-4-20250514" |
Model name to use for completions. |
mode |
string | "plan" |
Operating mode. "plan" shows generated code for review before execution. "act" executes tool calls directly (with optional confirmation dialog). |
max_tokens |
integer | 4096 |
Maximum output tokens per LLM response. Does not affect context window size. Range: 256--262,144. |
context_window |
integer | 20000 |
Context window size in tokens. Conversation is automatically compacted when it exceeds this limit. Set to model's limit or lower to control costs. Range: 4,000--1,000,000. |
temperature |
float | 0.3 |
Default sampling temperature. Used when no model-specific params are set. Range: 0.0--2.0. |
model_params |
object | {} |
Per-model sampling parameters, keyed by model name. Each value is a dict of parameter names to values (e.g., {"temperature": 1.0, "top_p": 0.95}). See Model Parameters Section above. |
auto_execute |
boolean | false |
When true and mode is "act", tool calls execute without a confirmation dialog. |
max_retries |
integer | 3 |
Number of times to retry a failed tool call before giving up. |
enable_tools |
boolean | true |
When true, the assistant uses structured tool calls. When false, it falls back to generating raw Python code. |
thinking |
string | "off" |
LLM reasoning mode. "off" = no reasoning (fastest). "on" = standard thinking. "extended" = extended thinking with higher token budget. See Thinking Mode below. |
strip_thinking_history |
boolean or null | null |
Whether to strip thinking/reasoning content from conversation history. null = auto-detect by model name (Gemma strips), true = always strip, false = never strip. See Strip Thinking History below. |
viewport_capture |
string | "off" |
Auto-capture viewport screenshots. "off" = disabled. "every_message" = attach screenshot with every message. "after_changes" = attach after tool calls modify the document. Can be overridden per-session via the Capture button. |
viewport_resolution |
string | "medium" |
Resolution preset for viewport screenshots. "low" = 400x300. "medium" = 800x600. "high" = 1600x1200. |
mcp_servers |
array | [] |
List of MCP server configurations. See MCP Servers below. |
user_tools_disabled |
array of strings | [] |
Filenames of user tool files to skip when loading (e.g., ["broken_tool.py"]). |
scan_freecad_macros |
boolean | false |
Also scan FreeCAD's macro directory (~/.config/FreeCAD/Macro/) for compatible tool functions. |
hooks_disabled |
array of strings | [] |
Hook directory names to skip when loading (e.g., ["log-tool-calls"]). |
use_external_editor |
boolean | false |
When true, the Edit... and New... buttons in Settings > Hooks and Settings > User Tools open files via the OS file association (xdg-open / Launch Services). When false (default), files open in FreeCAD's docked Python editor — which requires closing the Settings dialog first (Save / Discard / Cancel prompt) since the editor is an MDI sub-window of the main window. See Editor Section. |
system_prompt_override |
string | "" |
Custom system prompt text. When non-empty, replaces the default static instructions. Dynamic sections (document state, skills, AGENTS.md) are still appended. Empty = use default. |
vision_detected |
boolean or null | null |
Result of the vision probe. null = not tested, true = vision supported, false = vision not supported. Set automatically by Test Connection. |
vision_override |
boolean or null | null |
Manual override for vision support. When set, takes precedence over vision_detected. Set via the "Model supports vision" checkbox in Settings. |
tools_detected |
boolean or null | null |
Result of the tool-calling probe. null = not tested or non-Ollama provider, true/false = detected via Ollama /api/show. When true or false, this overrides the provider's static capability flag for supports_tools. |
thinking_detected |
boolean or null | null |
Result of the thinking probe. null = not tested or non-Ollama provider, true/false = detected via Ollama /api/show. Informational; thinking mode is still configured via the thinking field. |
chat_dock_geometry |
array or null | null |
Saved chat dock window geometry (FreeCAD main-window byte array, base64-encoded). Restored automatically on workbench activation. Reset by closing/reopening the dock. |
chat_dock_mw_state |
string or null | null |
Saved FreeCAD main-window state (dock area, tab positions, base64-encoded). Used together with chat_dock_geometry to restore tabification across sessions. |
Each entry in the mcp_servers array has this structure:
{
"name": "filesystem",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
"env": {},
"enabled": true,
"deferred": true
}| Field | Type | Default | Description |
|---|---|---|---|
name |
string | Display name for the server. | |
command |
string | Executable to launch (e.g., npx, python3, node). |
|
args |
array of strings | [] |
Command-line arguments. |
env |
object | {} |
Additional environment variables to set when spawning the process. |
enabled |
boolean | true |
Whether to connect to this server on startup. |
deferred |
boolean | true |
Load tool schemas lazily on first use. Set to false to load all schemas eagerly on connect. |
See MCP Integration for full documentation on MCP support.
Thinking mode enables the LLM to show its reasoning process before producing a final answer. This is useful for complex multi-step modeling tasks.
| Setting | Behavior |
|---|---|
| Off | No reasoning output. Fastest response time. Recommended for simple tasks. |
| On | Standard thinking/reasoning. The LLM produces a reasoning chain before its response. |
| Extended | Extended thinking with a higher token budget. Best for complex multi-step tasks but slower and more expensive. |
How thinking is implemented depends on the provider:
-
Anthropic: uses the
thinkingblock withbudget_tokensand the beta header. -
OpenAI: uses the
reasoning_effortparameter. -
Ollama (qwen3): uses
/thinkand/no_thinktags in the system prompt.
Not all models support thinking. If your model does not support it, the setting is silently ignored.
Some models require thinking/reasoning content to be preserved in conversation history for multi-turn tool chaining (e.g., Moonshot/Kimi-K2.5), while others explicitly reject it (e.g., Gemma 3/4 — "No Thinking Content in History").
The "Strip thinking from conversation history" checkbox in Settings is a tristate control:
| State | Behavior |
|---|---|
| Partially checked (default) | Auto-detect by model name. Gemma models strip automatically; all others preserve. |
| Checked | Always strip thinking content from history. |
| Unchecked | Never strip — preserve thinking content in all previous turns. |
When to change this:
- If you use a model that crashes or produces errors on multi-turn conversations with thinking enabled, try checking this box.
- If you use a Gemma model but want to keep thinking history (e.g., for debugging), uncheck the box.
- For most models, the default auto-detect works correctly.
Technical detail: When stripping is active, the reasoning_content field is removed from assistant messages in the conversation history before sending to the API. The thinking content is still displayed in the chat UI — only the API payload is affected.
FreeCAD AI can attach images (viewport screenshots, pasted images, drag-and-drop) to chat messages. How these images are handled depends on whether the LLM supports vision:
Vision support is detected automatically when you click Test Connection in Settings. The detection path depends on the provider:
-
Ollama: the workbench calls
/api/showfor the configured model and reads itscapabilitiesarray (vision,tools,thinking). This is authoritative — no probe image is sent. - All other providers: a small image containing a random 3-digit number (default 128×64, 32pt font) is sent to the LLM. If the LLM reads the number correctly, vision is marked as supported.
Probe image size note (v0.11.1+): the OCR probe was bumped from 64×32 / 16pt to 128×64 / 32pt because some vision-capable Ollama models (notably
qwen3-vl:32b) had a preprocessing cliff at the smaller size: the image was rejected before inference, producing a false "vision not supported" result. The larger probe gives 4× area headroom and works across the models tested. The Ollama/api/showpath bypasses this entirely.
You can also manually set vision support via the "Model supports vision" checkbox in the Behavior section of Settings. A Reset button appears when you have set a manual override, allowing you to return to the auto-detected value.
Vision detection resets when you change the provider or model (since different models have different capabilities). The manual override is preserved across provider/model changes.
For Ollama models, the same /api/show call also reports tool-calling and thinking support, persisted as tools_detected and thinking_detected in the config. The chat path then gates enable_tools on the detected value, so picking an Ollama embedding or reranker model as the main model won't push tool schemas to a model that can't handle them.
For non-Ollama providers, tool support comes from the provider's static capability flag (e.g., Anthropic, OpenAI, OpenRouter all support tools by default; "Custom" defaults to off). tools_detected and thinking_detected stay null for those providers — the static flag is authoritative.
| Scenario | Behavior |
|---|---|
| Vision supported | Images are sent inline to the LLM as base64 content blocks (native vision). |
| Vision not supported, MCP fallback available | Images are automatically described by an MCP tool (e.g., describe_image from llm-vision-mcp) and the text description is sent to the LLM instead. A note appears in the chat for each described image. |
| Vision not supported, no MCP fallback | Image controls (Capture, Attach, drag-drop, paste) are disabled. A tooltip explains how to enable vision. |
| Vision not tested | Image controls remain enabled (optimistic). On first image use, a hint suggests running Test Connection to enable auto-detection. |
If your LLM does not support vision, you can configure an MCP server that provides a describe_image tool. The workbench automatically searches registered MCP tools for one with describe_image in the name. When found, images are routed through that tool transparently — you do not need to invoke it manually.
A compatible MCP vision server is available at ghbalf/llm-vision-mcp. It routes images to a vision-capable model (OpenAI, Anthropic, Google, Ollama, or any OpenAI-compatible endpoint) and returns text descriptions.
git clone https://github.com/ghbalf/llm-vision-mcp.git
cd llm-vision-mcp
npm install
npm run buildOpen Settings > MCP Servers and click Add.... Fill in:
| Field | Value |
|---|---|
| Name | llm-vision-mcp |
| Command | node |
| Args | dist/index.js --provider openai --openai-api-key sk-... |
| Deferred | checked (recommended) |
Adjust the args for your vision provider. Examples:
# OpenAI (default model: gpt-4o)
dist/index.js --provider openai --openai-api-key sk-...
# Anthropic (default model: claude-sonnet-4-latest)
dist/index.js --provider anthropic --anthropic-api-key sk-ant-...
# Google Gemini (default model: gemini-2.0-flash)
dist/index.js --provider google --google-api-key AIza...
# Ollama local (default model: llava)
dist/index.js --provider ollama
The --provider and API key can also be set via environment variables (VISION_DEFAULT_PROVIDER, OPENAI_API_KEY, etc.) or a .env file in the llm-vision-mcp directory.
Important: The dist/index.js path must be absolute (e.g., /home/you/llm-vision-mcp/dist/index.js) or relative to your working directory.
After adding the server, click Test Connection. If the vision probe detects that your main LLM does not support vision, the workbench will automatically find the describe_image tool from the MCP server and use it to describe images before sending them to the LLM.
See MCP Integration for general MCP server documentation.
The /optimize-skill command automatically improves a skill's SKILL.md instructions by iteratively running test cases, scoring results, and using the LLM to fix errors.
- Type
/optimize-skillin the chat (Act mode) - Select a skill from the dropdown and add test cases (e.g.,
100x60x40mm, 2mm walls, snap-fit lid) - Configure iterations, runs per test, and strategy
- Click Start Optimization
The optimizer runs all iterations automatically inside a single tool call. Each iteration:
- Evaluates the SKILL.md against all test cases
- Scores: completion, error rate, geometric correctness, efficiency
- Asks the LLM to fix errors in the SKILL.md
- Keeps improved versions, discards regressions
| Field | Default | Description |
|---|---|---|
| Skill | -- | Skill to optimize |
| Test cases | -- | Arguments to test with (at least one required) |
| Iterations | 10 | Number of evaluate-modify cycles |
| Runs per test | 2 | Runs per test case (averaged for noise reduction) |
| Strategy | Balanced | Conservative (targeted fixes only), Balanced (+ periodic restructuring), Aggressive (frequent restructuring) |
Advanced settings:
| Field | Default | Description |
|---|---|---|
| Tool call budget | 30 | Max tool calls per evaluation run |
| Run timeout | 300s | Max seconds per evaluation run |
| Keep tolerance | 0.05 | Score margin for keeping lateral moves |
| Network retries | 2 | Extra retry attempts on network/timeout errors (exponential backoff: 5s, 10s, 20s...) |
The optimizer saves all versions under <FreeCADAI dir>/skills/<name>/.optimize/:
-
SKILL.md.original-- backup of the original (never overwritten) -
v1.md,v2.md, ... -- each iteration's SKILL.md -
history.json-- score progression, kept/discarded status, model info
The best version is automatically written to SKILL.md. You can always restore the original from SKILL.md.original.
Hooks are user-defined Python scripts that fire on lifecycle events. They can block dangerous operations, modify user input, or log activity.
Each hook is a named directory under <FreeCADAI dir>/hooks/ containing a hook.py file:
<FreeCADAI dir>/hooks/
├── safety-guard/
│ └── hook.py
└── my-logger/
└── hook.py
Built-in hooks ship with the workbench (in the repo's hooks/ directory) and are discovered automatically.
Define functions named on_<event> in hook.py:
def on_pre_tool_use(context):
"""Block dangerous operations."""
if context["tool_name"] == "execute_code":
code = context["arguments"].get("code", "")
if "removeObject" in code:
return {"block": True, "reason": "Blocked: removeObject is dangerous"}
return {}
def on_post_tool_use(context):
"""Log every tool call."""
with open("/tmp/tool_log.txt", "a") as f:
f.write(f"{context['tool_name']}: {context['success']}\n")| Event | When | Can block? | Thread |
|---|---|---|---|
| pre_tool_use | Before a tool executes | Yes | Worker |
| post_tool_use | After a tool completes | No | Worker |
| user_prompt_submit | Before user message sent to LLM | Yes (block or modify) | Main |
| post_response | After LLM response processed | No | Main |
| file_attach | When a binary file is attached (not image/text) | Yes (block) or return text | Main |
Worker thread hooks (pre_tool_use, post_tool_use) must NOT call FreeCAD GUI APIs (App.ActiveDocument, FreeCADGui). They can read/write files, log, or make HTTP requests.
Main thread hooks (user_prompt_submit, post_response, file_attach) can safely access the FreeCAD API.
| Return | Effect | Events |
|---|---|---|
{"block": True, "reason": "..."} |
Block the action |
pre_tool_use, user_prompt_submit, file_attach
|
{"modify": "new text"} |
Replace the user's message text | user_prompt_submit |
{"text": "..."} |
Converted file content (included as text in the message) | file_attach |
{} or None
|
No effect | All |
Multiple hooks on the same event run in alphabetical order by directory name. If any hook blocks, the action is blocked. Modifications chain (each hook sees the previous hook's output).
pre_tool_use: tool_name (str), arguments (dict), turn (int)
post_tool_use: tool_name, arguments, success (bool), output (str), error (str), turn
user_prompt_submit: text (str), images (list), mode ("plan"/"act")
post_response: response_text (str), tool_calls_count (int), mode
file_attach: path (str), filename (str), extension (str), mime_type (str)
In Settings > Hooks:
-
New... -- prompts for a hook name, creates a new directory under
<FreeCADAI dir>/hooks/, writes a starterhook.pytemplate containing one stub handler per supported event (registry-sourced fromVALID_EVENTS), and opens it in the configured editor. Delete the handlers you don't need. -
Add... -- copy an existing
hook.pyfile into a new hook directory. -
Edit... -- open the selected hook's
hook.pyin the configured editor (FreeCAD's docked Python editor, or your OS-default editor — see Editor Section). - Remove -- delete the hook directory (built-in hooks can only be disabled).
- Reload -- re-scan and reload all hooks without restarting FreeCAD.
Disable a hook by adding its name to hooks_disabled in config.json, or by unchecking it in Settings.
| Hook | Event | Description |
|---|---|---|
log-tool-calls |
post_tool_use |
Logs tool calls to the FreeCAD Report View |
FreeCAD AI has a strict zero-external-dependencies policy — it uses only Python stdlib and runs inside FreeCAD's bundled Python. PDF/DOCX parsing requires external C libraries (poppler, libxml2) that cannot be bundled portably across platforms.
Instead, binary file conversion is handled through two extensibility paths:
-
Hooks (lightweight) — call CLI tools like
pdftotextorpandoc. Text-only output, but easy to set up. - MCP servers (full-featured) — servers like markdownify-mcp provide rich conversion with embedded images and structure preservation.
A ready-to-use example hook is included in the repository at docs/hooks/file-attach-example/. To install:
# Replace <FreeCADAI dir> with your actual path — see "Configuration paths" above.
# On FreeCAD 1.1+ Linux this is ~/.config/FreeCAD/v1-1/FreeCADAI/.
cp -r docs/hooks/file-attach-example/ "<FreeCADAI dir>/hooks/file-attach/"It supports PDF (via pdftotext or pandoc), DOCX/ODT/RTF (via pandoc or LibreOffice), and XLSX (via ssconvert or LibreOffice). Install the tools you need:
sudo apt install poppler-utils # pdftotext for PDF
sudo apt install pandoc # universal document converterA minimal PDF-only hook:
import subprocess
def on_file_attach(context):
if context["extension"] != "pdf":
return {}
result = subprocess.run(
["pdftotext", "-layout", context["path"], "-"],
capture_output=True, text=True, timeout=30,
)
if result.returncode == 0 and result.stdout.strip():
return {"text": result.stdout}
return {"block": True, "reason": f"pdftotext failed: {result.stderr}"}FreeCAD AI creates several directories under <FreeCADAI dir>/ (see Configuration paths for what <FreeCADAI dir> resolves to):
| Directory | Purpose |
|---|---|
<FreeCADAI dir>/ |
Root config directory. Contains config.json. |
<FreeCADAI dir>/conversations/ |
Auto-saved chat sessions. The last 20 sessions are available for reload via the "Load" button in the chat footer. |
<FreeCADAI dir>/skills/ |
User-created skills. Each skill is a subdirectory containing a SKILL.md file. See Skills. |
<FreeCADAI dir>/tools/ |
User extension tools. .py and .FCMacro files with typed functions. See Creating Custom Tools. |
<FreeCADAI dir>/hooks/ |
User-defined hooks. Each hook is a subdirectory containing a hook.py file. See Hooks section above. |
<FreeCADAI dir>/logs/ |
Session logs. latest_session.json contains the most recent session's tool call log. |
Instead of pasting a literal API key, you can use dynamic token resolution. This keeps secrets out of config.json and supports OAuth tokens that expire and need periodic refresh.
Note on platform coverage: the workbench is developed and tested on Linux. The macOS and Windows examples below follow the documented behavior of the underlying tools (
security,CredentialManager,expanduser/expandvars,subprocess.run(shell=True)) but have not been verified on those OSes by the maintainer. If you run into a platform-specific issue, please open an issue — patches and corrections welcome.
| Format | Description | Example |
|---|---|---|
sk-abc123 |
Literal key (default behavior) | sk-ant-api03-... |
file:/path/to/token |
Read token from file on each LLM call | file:~/.config/gcloud/access_token |
cmd:command |
Run command, use stdout as token | cmd:gcloud auth print-access-token |
The token is resolved on every request, so refreshed tokens are picked up automatically.
The path supports ~ expansion (file:~/token) and OS environment variables ($HOME on Unix, %APPDATA% on Windows). Both are evaluated on every request.
| OS | Example |
|---|---|
| Linux/macOS | file:~/.config/anthropic-key |
| Windows | file:%APPDATA%\freecad-ai\anthropic-key.txt |
Lock the file down with filesystem permissions: chmod 600 on Unix, file properties → security → restrict to your user account on Windows.
Lets you delegate to the OS's native secret store via that store's CLI. The command runs in a shell (/bin/sh -c on Unix, cmd.exe /c on Windows), so pipes, subcommands, and shell variables work.
| OS | Native store | Example |
|---|---|---|
| Linux | libsecret (GNOME Keyring, KWallet) via libsecret-tools
|
cmd:secret-tool lookup service freecad-ai account anthropic |
| macOS | Keychain | cmd:security find-generic-password -a anthropic -s freecad-ai -w |
| Windows | Credential Manager (needs the CredentialManager PowerShell module) |
cmd:powershell -Command "(Get-StoredCredential -Target 'freecad-ai-anthropic').GetNetworkCredential().Password" |
| Any | GPG | cmd:gpg --decrypt ~/keys/anthropic.gpg |
To populate the stores once:
-
Linux:
secret-tool store --label="freecad-ai anthropic" service freecad-ai account anthropic -
macOS:
security add-generic-password -a anthropic -s freecad-ai -w 'sk-ant-…' -
Windows (PowerShell):
New-StoredCredential -Target 'freecad-ai-anthropic' -UserName anthropic -Password (Read-Host -AsSecureString)
Google Cloud / Vertex AI (token refreshed by gcloud):
cmd:gcloud auth print-access-token
Azure OpenAI (token refreshed by az):
cmd:az account get-access-token --query accessToken -o tsv
External refresh script (cron job writes token to a file):
file:~/.config/freecad-ai/oauth_token
With a cron job like:
*/30 * * * * your-refresh-script > ~/.config/freecad-ai/oauth_token(Use any path you control — ~/.config/freecad-ai/ is just a convention. Don't write into <FreeCADAI dir>/ itself; on FreeCAD 1.1+ that path moves between version-scoped subdirs and the migration would shuffle your token alongside config files.)
-
file:supports~expansion (e.g.,file:~/token.txt) and environment-variable expansion ($VAR,%VAR%) -
cmd:has a 10-second timeout to prevent hanging -
cmd:runs in a shell, so pipes and subcommands work (e.g.,cmd:cat ~/.token | tr -d '\n') - If a
file:path doesn't exist or acmd:command fails, the request proceeds with an empty key (the LLM provider will return a 401 error)
-
API keys are stored in plaintext in
config.json. When usingfile:orcmd:prefixes, only the prefix string is stored — the actual token is resolved at runtime. Protect your config file and token files with appropriate file permissions (chmod 600). -
cmd:runs shell commands as your user. Only use commands you trust. - Auto-execute mode lets the LLM run arbitrary Python code in your FreeCAD session without confirmation. Use with caution, especially with cloud providers.
- MCP servers are spawned as child processes. Only configure servers you trust.
To verify your configuration is working:
- Open FreeCAD AI > AI Settings.
- Fill in your provider details.
- Click Test Connection.
- If successful, you will see a green "Connected!" message with a snippet of the LLM's response.
- If it fails, the red error message will indicate the problem:
- Connection refused -- the server is not running (common with Ollama).
- 401 Unauthorized -- invalid or missing API key.
- 404 Not Found -- wrong base URL or model name.
- Timeout -- the server took too long to respond. For Ollama, this can happen on first request while the model loads into memory; try again.
When many tools are registered (built-in + user tools + MCP servers), their schemas consume significant prompt tokens on every turn. Tool Reranking is an opt-in feature that sends only the most relevant subset for each user message. See Tool Reranking for the full explanation, configuration reference, and tuning guide.
After configuring your provider, proceed to Getting Started to learn how to use the chat interface, switch between Plan and Act modes, and build your first 3D model.