MuK AI Assistant
Native AI Chat & Agent Runtime for Odoo
MuK IT GmbH - www.mukit.at
Overview
A complete agentic AI assistant inside Odoo. MuK AI
ships a native OWL chat client, a session-based agent runtime, and
three first-class LLM providers (OpenAI,
Anthropic, Google Gemini) with live
token and reasoning streaming. The assistant talks to your data through
the same muk_mcp tool registry your external AI clients
already use — one source of truth, one permission model, one
audit trail.
Includes human-in-the-loop ask_user support, a
session-scoped approval gate for risky writes, per-agent tool
filters, read-only scope enforcement, multimodal attachments
(images, PDFs, text files), lazy tool loading with a per-agent
essentials list, agent suggestion prompts, field history
versioning, and a prebuilt catalog of current GPT-5.x /
Claude 4.x–5 / Gemini 2.5–3.x models with input,
output and cache (read + write) pricing and per-model
reasoning-effort tiers.
MuK AI is also the foundation for the rest of the MuK AI suite:
adding a new provider is a single-file drop-in via the providers
REGISTRY, and downstream add-ons plug extra tools,
agents and UI extensions onto the same runtime.
Chat Client
A full OWL-based chat action lives under MuK AI > Chat
(also reachable at /odoo/ai). Sessions auto-save to
the database, stream replies live via the Odoo bus, and render
tool calls inline as collapsible cards with live-filling arguments
so users always see what the model is about to run. Markdown
rendering covers headings, lists, tables, inline code, and fenced
code blocks with syntax highlighting. A systray icon surfaces
running sessions across the UI, and a floating chat window
(Discuss-style) lets users keep talking while navigating other
views.
Keyboard-first by design: Enter sends, Shift+Enter adds a newline, and a single Send/Stop toggle handles mid-stream cancellation. Auto-scroll pauses when the user scrolls up, so reading past replies is not interrupted by the next delta.
Spaces
Chats pile up fast. Spaces are folders in the
sidebar: create one with the + next to
Spaces, then drag chats into it to file them and drag
them back out to loosen them again. Drag a space by its grip to
reorder the list. Each space carries a name, a Font Awesome
icon picked from a searchable grid, and an optional
default agent that is preselected for every
chat started inside it — so a “Finance” space
can always open with your read-only analyst. Everything is
editable from the chat itself, no backend menu required.
Unread chats are counted per space; click the badge to narrow a
space to just its unread conversations. Modules can also ship
system spaces whose membership is derived from
a stored domain instead of manual filing —
muk_ai_schedule ships a Scheduled space
that collects every chat started by a schedule. One record
serves the whole database: nothing is duplicated per user, and
new matching chats appear in it retroactively.
Read-Only Sharing
A chat belongs to whoever started it — but it can be handed around to be read. The line above the composer names who can read the current chat and opens a picker of colleagues to add or drop. Everyone you pick sees the whole transcript, every tool call and every answer exactly as it ran, and nothing else: the composer says the chat is read only instead of offering an input, and the agent, the approval mode, rewind, branch and regenerate are all withheld. The same holds in the popped-out window, and add-ons inherit it, so a shared chat cannot be steered from anywhere. A reader watches the answer arrive as it is written — a chat streams on a channel of its own, which the server hands out only to the people allowed to read it, so their transcript and their chat list keep up on their own.
Reading is granted by a record rule of its own, so an administrator keeps their usual access and nobody else gains any. A system space, Shared with Me, collects everything shared with you. Hand a chat over to someone else and you stay on the list as a reader, so work you started never drops out of sight.
Live Reasoning Stream
When the underlying model exposes a reasoning channel (OpenAI o-series and GPT-5 family, Anthropic Opus 4.x and Sonnet 4.x with extended or adaptive thinking), the chat replaces the static “Thinking…” placeholder with the model's latest reasoning sentence as it streams. The line rotates as new thoughts arrive and disappears the moment the answer text starts — you see what the agent is currently working through, never a stale block of text. Models without a reasoning channel (Gemini, GPT-4 family, Haiku) keep the original animated dots fallback automatically.
Source Citations
Answers stay grounded in your data. Whenever the assistant
reads records to build a reply, the records it relied on are
collected as sources and surfaced as a
compact chip under the message — expand it to see each
cited record with its model and a one-click link that opens
the record in Odoo. The same list is mirrored in the
Artifacts side rail alongside the turn's
attachments, so it is always clear where a number or
a claim came from — no more guessing whether the model
invented a figure or pulled it from an actual
sale.order.
Reasoning Effort
Dial how hard a model thinks before it answers. Each agent
carries a Reasoning Effort setting —
Minimal, Low, Medium,
High, Extra High or
Maximum — and only the tiers the chosen
model actually supports are offered; the field disappears
entirely for models with no thinking knob. The catalog stores
each model's supported efforts, so a request above what a model
allows is clamped to the nearest tier, and a provider that
still refuses is retried without the effort so the answer is
always served. Leave it on Model Default to let the
model decide.
Multi-Provider Architecture
The provider layer is a thin Python registry plus stored
configuration records (muk_ai.provider). Each
provider class declares
name / label / default_model / default_url / supports_*,
implements headers() and request(), and
inherits shared HTTP + SSE streaming from
ProviderBase. Settings, default-model lookup,
capability probes, and dispatch are all derived from the same
REGISTRY — new providers ship as one Python
file and one mutate-line in the addon's
__init__.py, no fork of muk_ai
required.
| Provider | API surface | Streaming | Reasoning |
|---|---|---|---|
openai |
Responses API | SSE | Summary stream on o-series & GPT-5; encrypted carry for stateless mode |
anthropic |
Messages API | SSE | Extended thinking (legacy budget) and adaptive thinking (4.7+) |
google |
Gemini API | SSE | Falls back to the static thinking indicator |
The shipped catalog includes the current GPT-5.x family (5, 5.1, 5.2, 5.4 incl. Pro variants, 5.5 + Pro, 5.6), GPT-4.1 family, GPT-4o, the o-series; Claude Opus 4 / 4.1 / 4.5 / 4.6 / 4.7 / 4.8, Sonnet 4.x / 5, Haiku, Fable 5; Gemini 2.5 Pro/Flash/Flash-Lite/Flash-Image and the 3.x previews — each with context window and per-1M-token pricing for live cost display.
Agents & Tool Filters
An Agent is a named preset: system prompt
(rendered with Odoo's inline-template engine for placeholders
like {{ user.name }} or {{ today }}),
model override, tool filter, an Essential Tools
list that controls which schemas ship eagerly (the rest stay
name-only and lazy-load on demand), native-tool toggles (web
search, image generation, code interpreter — auto-hidden
when the provider doesn't support them), read-only flag,
approval mode, and starter suggestion prompts. Every chat
session inherits from an agent — ship a "Sales Assistant"
with write access to leads alongside a "Read-only Analyst"
that cannot mutate anything. Read-only is enforced server-side
through the MCP scope check, not just by prompt instruction.
System prompts are versioned: every edit captures the previous body into a Prompt History revision so you can audit and roll back. Per-user rate limits throttle session creation, and record rules keep each user's sessions private (own-only for internal users, full access for system).
Multiple Agents & Handoff
One chat can be served by several agents. Flip on
Allow Handoff to make an agent a delegation
target, and the built-in Router reads the
opening request, calls list_agents, and hands the
conversation to the best specialist with
switch_agent — from the next turn that
agent's prompt, tools and model take over the same
session (the switch is logged inline as an
A → B marker). Ship a “Sales
Assistant” with write access alongside a
“Read-only Analyst”, and let the Router pick.
Type /agent to switch yourself, or hand the whole
live chat to a colleague with the share button (or
/handover) — they get the session, an inbox
notification and an unread badge.
Session-Scoped Approval Gate
Risky write calls pause the session and ask before running.
An update to a mail-tracked field, a workflow method like
action_post on an invoice, a deletion, or a
create on a high-impact model triggers an approval card inline
in the chat. The card explains why it fired
("approving because state is tracked on
account.move"), shows the proposed JSON arguments,
and offers three buttons: Approve once,
Allow for Session (auto-approves matching
calls for the rest of this session only), and
Reject (returns a
rejected_by_user tool output so the model can
recover). Every decision is logged under Reports >
Approval Log with proposed vs. executed arguments.
Floating Window & View Context
A floating chat window (Discuss-style) lets the assistant stay
reachable while users navigate other views. When open, the
session sticks to the Odoo view the user is looking at —
form record, list, kanban, pivot, graph — and a header
pill shows the pinned context (e.g.
sale.order · SO-00042) that opens that
view on click. Navigation fired by the agent
(open_record, open_view,
open_action) updates the pin automatically. The
context is injected at request time as a short
<ui_ctx> tag, so the model can resolve
references like "this order" or "the current list" without an
extra tool call. Type /unpin to clear it.
Reshape Views from Chat
Ask the assistant to change the list, kanban, pivot or graph
you are looking at — “group these orders by
salesperson”, “only show drafts”,
“switch to a bar chart” — and it
reshapes that view in place instead of opening
a new one. The adjust_search tool is a
client tool: rather than running on the server it runs
in your browser tab and drives the live search view —
activating filters and group-bys (with
date_order:month-style intervals), applying field
searches and custom-domain facets, removing facets, switching
view type, and setting pivot/graph measures, chart mode,
ordering and stacking. It reports back exactly what it changed,
and — as in the shot below — will pause to check
with you first when a change would hide data.
Conversation Controls
The transcript is fully in your hands:
- Tool groups — when a turn fires several tool calls they collapse into one Used N tools card with the tool chips and a green ✓ / red × success-and-error badge; expand it for the individual calls. Each card fills its arguments live and then drops in its result in place.
- Rewind, branch & regenerate — hover any message to rewind (remove it and everything after), branch (fork the history into a brand-new session), or regenerate the last answer.
- Artifacts panel — a side rail that gathers every uploaded file, every AI-generated image (open full-size in the file viewer) and every cited source under Attachments and Sources tabs.
- Find in conversation — a search bar highlights matches with a current/total counter and jumps between them.
- Attention & notifications — the systray icon carries a live running dot and a red count of sessions awaiting you; unread sessions are marked and sorted to the top, and completion notices can arrive as web and Enterprise mobile push.
Tool Dispatch via muk_mcp
Agents call exactly the same tool surface as your external MCP
clients — no duplicate catalog, no divergence. The MuK
AI agent registry pulls tools from muk_mcp
filtered by registry, and a handful of UI-specific tools
(open_record, open_view,
open_action, show_notification,
ask_user, the view-reshaping
adjust_search) are scoped to the in-Odoo agent. The
chat UI auto-dispatches any returned ir.actions.*
descriptor, so the model can navigate the user through the UI
as part of a reply.
When the LLM calls ask_user the session pauses in
waiting state and surfaces the question in the chat.
The user's answer is fed back as a
function_call_output so the model resumes the
turn exactly where it left off. Beyond the UI tools, a built-in
web_fetch tool reads a public web page as clean
Markdown (boilerplate stripped, links kept) behind an SSRF
guard that re-validates every redirect, and the fetched page
joins the reply's Sources as a citable web link.
Lazy Tool Loading
Shipping every MCP tool's JSON schema in the
tools array of every turn is expensive on
context. Each agent declares a small
Essential Tools set that loads eagerly;
every other catalog tool is advertised by name only inside an
<available_tools> block appended to the
system prompt. The model fetches full schemas on demand
through a built-in meta-tool:
-
tool_load(names=[...])— pulls one or more schemas from the catalog. The names are appended to the session's loaded-tools list and stay available for the rest of the session. -
tool_load(names=[...], call={name, arguments})— load and execute one of the just-loaded tools in the same round-trip; the schemas plus the inline tool result come back in a singlefunction_call_output, no follow-up turn needed. Preferred shape for one-shot lookups.
Unknown names come back under an unknown key so
the model can recover gracefully. ask_user is
auto-injected when approvals are on, regardless of the
essentials list. Leaving Essential Tools
empty enables the default lazy mode (read-side primitives + UI
helpers + ask_user); populate it to override.
Runtime Context Block
Alongside the agent's system prompt and
<available_tools> list, each session
injects a short <runtime> block stating the
current Odoo version, today's date, the user (id, timezone),
the company (id), the active approval mode, and — when
relevant — the list of companies the user can access
with a hint to pass allowed_company_ids for
cross-company searches. The model never has to spend a turn
calling whoami or asking for the date to ground
its first reply.
Multimodal Attachments
Drop a file onto the composer, paste a screenshot, or click
the clip icon. Images render inline, PDFs and text files show
as pills. On send, every attachment is streamed to the active
provider as a native content block —
input_image / input_file for OpenAI
Responses, image / document blocks
for Anthropic Messages, inline base64 for Gemini, inline text
for small .txt / .csv /
.md files. Accepted: PNG, JPEG, WebP, GIF, PDF,
plain text, CSV, Markdown. Capped at 128 MiB per file with
oversize and unknown-type uploads rejected server-side.
Context-Window Meter & Slash Commands
The chat header shows a colour-coded pill with the share of the
current model's context window consumed by the last turn's
input. Below 85 % is idle, 85–95 % flags a warning and
offers to auto-compact, 95 %+ triggers auto-compaction
silently — the conversation is replaced by a
≤500-token summary so the session can keep going without
losing continuity. Context sizes come from the
muk_ai.model record for the agent's active model.
Start a message with / to open a popover of
available commands: /help,
/clear (wipe conversation, keep agent),
/compact (manual compaction),
/unpin (clear view-context pin),
/agent (switch the active agent), and
/handover (transfer the chat to another user).
Streaming & Bus Events
Replies stream over the Odoo bus as they arrive from the
provider. Each session inherits
bus.listener.mixin and routes events through the
owner's partner channel — not a guessable
string — so sessions cannot be eavesdropped by other
authenticated users. Text is delivered as
text_delta events; reasoning summaries as
reasoning_delta; tool calls stream as
tool_call_start + tool_call_args_delta
so the chat UI shows the tool card with live-filling arguments
before the tool even fires.
A per-stream idle watchdog aborts dead connections and marks
the session as errored. Runaway loops are capped per turn,
and the ask_user-at-cap edge is handled
gracefully so resumed sessions do not get stuck.
Security & Audit
Internal User opens the chat and manages
their own sessions; System manages every
session, edits agents and providers, reviews approvals and
tool logs. Record rules scope sessions to their owner.
open_action honours per-action
groups_id so the model cannot hand a user a
descriptor they would not be allowed to reach through the
menu.
Every session carries an audit-ready tool log: user messages,
tool calls, tool results, assistant text, ask_user prompts,
answers, approval decisions — all JSON-serialised.
Combined with muk_mcp's own log, any AI-driven
write on your data is traceable end to end.
Settings
Two one-line defaults under Settings > General Settings > MuK AI: Default Provider (falls back to the first active provider when an agent doesn't pin one) and Default Agent (used for new chat sessions when the user hasn't picked one). Everything else is configured per-record on the provider, model, and agent forms. A Test Connection button on the provider form sends a tiny probe and reports success or failure without leaving the page. The same page holds the runtime guard-rails — Iteration Limit (rounds per slice), Turn Runtime and Slice Runtime wall-clock budgets, and a per-turn Cost Limit — and lists the installable MuK AI extensions (MCP, Compatible Providers, Mistral, Schedule, Skills, Voice, Workflows) as one-click toggles.
Want more?
Are you having troubles with your Odoo integration? Or do you feel
your system lacks of essential features?
If your answer is YES
to one of the above questions, feel free to contact us at anytime
with your inquiry.
We are looking forward to discuss your
needs and plan the next steps with you.
Our Services
Odoo
Development
Odoo
Integration
Odoo
Infrastructure
Odoo
Training
Odoo
Support
| Availability |
Odoo Online
Odoo.sh
On Premise
|
| Odoo Apps Dependencies |
Discuss (mail)
|
| Community Apps Dependencies | Show |
| Lines of code | 31434 |
| Technical Name |
muk_ai |
| License | LGPL-3 |
| Website | http://www.mukit.at |
MuK AI Assistant
A complete agentic AI assistant inside Odoo. MuK AI ships a native OWL chat client, a session-based agent runtime, and three first-class LLM providers (OpenAI, Anthropic, Google Gemini) with live token and reasoning streaming. The assistant talks to your data through the same muk_mcp tool registry your external AI clients already use — one source of truth, one permission model, one audit trail.
Includes human-in-the-loop ask_user support, a session-scoped approval gate for risky writes, per-agent tool filters, read-only scope enforcement, multimodal attachments (images, PDFs, text files), lazy tool loading with a per-agent essentials list, agent suggestion prompts, field history versioning, and a prebuilt catalog of current GPT-5.x / Claude 4.x–5 / Gemini 2.5–3.x models with input, output and cache (read + write) pricing and per-model reasoning-effort tiers.
It is also the foundation for the rest of the MuK AI suite: the REGISTRY in providers/__init__.py drives both the in-memory dispatcher and the stored muk_ai.provider records, so adding a new provider is a single-file drop-in, and downstream add-ons plug extra tools, agents and UI extensions onto the same runtime.
Installation
To install this module, you need to:
Download the module and add it to your Odoo addons folder. Afterward, log on to your Odoo server and go to the Apps menu. Trigger the debug mode and update the list by clicking on the "Update Apps List" link. Now install the module by clicking on the install button.
Upgrade
To upgrade this module, you need to:
Download the module and add it to your Odoo addons folder. Restart the server and log on to your Odoo server. Select the Apps menu and upgrade the module by clicking on the upgrade button.
Configuration
Settings — Settings > General Settings > MuK AI
- Default Provider — falls back to the first active provider when an agent does not pin one.
- Default Agent — used for new chat sessions when the user hasn't picked one.
- Iteration Limit — max tool-calling rounds per worker slice (default 20).
- Turn Runtime — wall-clock budget for a whole user turn across all slices (default 3600 s).
- Slice Runtime — wall-clock budget for one worker slice (default 600 s); a slice that exceeds it is checkpointed and resumed by a fresh worker.
- Cost Limit — maximum spend per user turn in the model's price currency (0 disables).
The same page lists the installable MuK AI extensions (MCP, Compatible Providers, Mistral, Schedule, Skills, Voice, Workflows) as one-click toggles.
Everything else is configured per-record, not globally.
Providers — MuK AI > Configuration > Providers
One record per provider implementation (openai, anthropic, google). Fields:
- API Key — authentication token for this provider.
- Default Model — muk_ai.model used when an agent does not specify one.
- Max Tokens — completion-token cap per request (default 4096).
- Request Timeout — HTTP timeout in seconds (default 60).
- Idle Timeout — seconds without a streamed chunk before the stream is aborted and the session is marked errored (default 45).
- Rate Limit (per minute) — max sessions a single user may create per minute. 0 disables the cap.
A Test Connection button on the provider form sends a tiny probe (Reply with a single word.) and reports success or failure without leaving the page.
Models — MuK AI > Configuration > Models
The model catalog ships prefilled with current API-available SKUs — the GPT-5.x family (5, 5.1, 5.2, 5.4 + Pro, 5.5 + Pro, and the 5.6 Sol / Terra / Luna tiers), GPT-4.1 family, GPT-4o and the o-series; Claude (Opus 4.x through 4.8, Sonnet 4.x and Sonnet 5, Haiku, Fable 5); and Gemini (2.5 Pro/Flash/Flash-Lite/Flash-Image plus the 3.x previews). Each record carries:
- Technical Name — API id (gpt-5.2, claude-sonnet-5, gemini-3.5-flash, …).
- Provider — the parent muk_ai.provider.
- Context Window — used for the colour-coded meter in the chat.
- Input / Output Rate and Cache Read / Cache Write Rate — USD per 1M tokens. Input is split into fresh, cache-read and cache-write tokens so prompt-cached turns are billed at their real (much lower) cost; the rates drive the per-session cost display and the usage pivot.
- Supported Reasoning Efforts / Default Reasoning Effort — the thinking tiers this model accepts (see Reasoning effort under Agents); models without a thinking knob leave these empty.
Access Groups
- Internal User (base.group_user) — opens the chat, manages their own sessions, reads agents and their suggestions.
- System (base.group_system) — manages every session, edits agents and providers, reviews approvals and tool logs.
Record rules keep each user's sessions private.
Usage
Chatting
Open MuK AI > Chat (or /odoo/ai). Create a session from the sidebar, pick an agent (optional), type a message and hit Enter. The reply streams in live via the Odoo bus; tool calls render as collapsible cards with live-filling arguments so you can see exactly what the model is about to run. Shift+Enter inserts a newline. A single Send/Stop toggle cancels an in-flight stream.
A systray icon in the top bar surfaces running sessions and lets you pop out a floating chat window so the assistant stays reachable while you navigate other views.
Spaces
Chats pile up fast, so the sidebar groups them into spaces. Create one with the + next to Spaces, drag chats into it to file them and drag them out to loosen them again, and drag a space by its grip to reorder the list. The pencil opens an editor for the space name, its Font Awesome icon (searchable grid) and an optional default agent preselected for every chat started inside it — the + on a space row starts such a chat. Unread chats are counted per space; click the badge to narrow that space to its unread conversations. Every branch paginates on its own, so opening a space with hundreds of chats costs one page.
Modules can ship system spaces whose membership comes from a stored domain instead of manual filing — muk_ai_schedule ships a Scheduled space collecting every chat a schedule started. They belong to nobody, are visible to everyone, and cannot be renamed, reordered or dropped into; one record serves the whole database, so nothing is duplicated per user and matching chats appear retroactively. Administrators manage every space under MuK AI > Configuration > Spaces.
Read-only sharing
A chat belongs to the person who started it, but it can be handed around to be read. The line above the composer names who can read the current chat and opens a picker of colleagues to add or drop. Everyone you pick sees the whole transcript — every tool call, every answer, exactly as it ran — and nothing else: the composer states that the chat is read only instead of offering an input, and the agent, the approval mode, rewind, branch and regenerate are all withheld. The same holds in the popped-out window, and add-ons inherit it, so a shared chat cannot be steered from anywhere. A reader watches the answer arrive as it is written: a chat streams on a channel of its own, which the server hands out only to the people allowed to read it, so their transcript and their chat list keep up on their own. Reading is granted by a record rule of its own, so an administrator keeps their usual access and nobody else gains any. A system space, Shared with Me, collects everything shared with you. Handing a chat over to someone else leaves you on the list as a reader, so work you started stays visible.
Source citations
When the assistant reads data to answer, what it relied on is collected as sources and shown as a chip under the reply. Expand it to see each source — an Odoo record (with its model and a one-click link that opens it) or, when the agent used web_fetch, the external web page it read. The same list is mirrored in the Artifacts side rail, so every figure is traceable to its origin.
Tool groups
When a turn fires several tool calls back to back, they collapse into a single Used N tools card instead of stacking many. The header shows the tool-name chips and aggregate status — a green check with the success count and a red × with the error count — and expands to reveal each individual call. Each card fills its arguments live as the model streams them and then drops in the result in place, so you watch the work happen without waiting for the whole turn to finish.
Rewind, branch & regenerate
Hover any message for its actions. Rewind removes that message and everything after it (after a confirmation that names how many events drop). Branch forks the conversation into a brand-new session seeded with the history up to that point, leaving the original intact. Regenerate, offered on the last answer, re-runs the turn. Rewind and Branch are disabled while a reply is streaming and on messages already folded into compacted history.
Artifacts panel
A collapsible Artifacts rail (paperclip icon in the header) gathers everything a conversation produced. An Attachments tab lists every uploaded file and every AI-generated image as a card you can open full-size in Odoo's file viewer; a Sources tab lists the cited records and web pages. Tabs appear only when they have content.
In-chat search
The search icon opens a find-in-conversation bar. Matches across user and assistant text are highlighted with a current/total counter; Enter / Shift+Enter jump to the next / previous hit and scroll it into view, Esc closes.
Notifications
Sessions that finish or need you while you are elsewhere signal you. The MuK AI systray icon carries a live running dot whenever a session is running or waiting, plus a red count of sessions awaiting your attention; unread sessions are marked and sorted to the top in both the systray dropdown and the chat sidebar. Users on the inbox notification preference also get completion notices as web push and Enterprise mobile (OCN) push. Opening the session clears the flag.
Write-tool safety (session-scoped approvals)
Risky write calls pause the session and ask before running. An update to a mail-tracked field, a workflow method like action_post on an invoice, a deletion, or a create on a high-impact model triggers an approval card inline in the chat. The card shows why it fired ("approving because state is tracked on account.move"), the proposed JSON arguments, and three buttons:
- Approve once — dispatches this call and logs the decision.
- Allow for Session — same, plus adds a signature to the session so matching calls (same tool, same model, same sensitive fields) auto-approve for the rest of this session.
- Reject — returns a rejected_by_user tool output so the model can recover (clarify, propose a safer alternative).
Every decision is recorded under MuK AI > Reports > Approval Log with the proposed vs. executed arguments. Per-agent Approval Mode lets unattended agents skip the prompt (Never ask). Approvals do not persist across sessions — each new session starts with an empty allow-list.
View context
When the floating chat window is open, the session sticks to the Odoo view the user is looking at (form record, list, kanban, pivot, graph). A header pill shows the pinned context (e.g. sale.order · SO-00042) and opens that view on click. Navigation fired by the agent (open_record, open_view, open_action) updates the pin automatically. The context is injected at request time as a short <ui_ctx> tag at the tail of the conversation, so the model can resolve references like "this order" or "the current list" without an extra tool call. Type /unpin to clear it.
Reshape views from chat
Ask the assistant to change the list, kanban, pivot or graph you are looking at — "group these orders by salesperson", "only show drafts", "switch to a bar chart" — and it reshapes that view in place rather than opening a new one. The adjust_search tool is a client tool: instead of running on the server it runs in your browser tab and drives the live search view — activating filters and group-bys (with date_order:month-style intervals), applying field searches and custom-domain facets, removing facets, switching view type, and setting pivot/graph measures, chart mode, ordering, stacking and cumulation. It reports back exactly what it changed, and the available filter/group-by names when you ask for one it doesn't recognise.
Attachments
Drop a file onto the composer, paste a screenshot, or click the clip icon. Images render inline, PDFs and text files show as pills. On send, every attachment is streamed to the active provider as a native content block — input_image / input_file for OpenAI Responses, image / document blocks for Anthropic Messages, inline base64 for Gemini, inline text for small .txt / .csv / .md files. Accepted types: PNG, JPEG, WebP, GIF, PDF, plain text, CSV, Markdown. Capped at 128 MiB per file with oversize and unknown-type uploads rejected server-side. Text files are inlined and truncated past 256 KiB.
Human-in-the-loop
When the LLM calls the ask_user tool, the session pauses in waiting state and surfaces the question in the chat. Your answer is fed back as a function_call_output so the model resumes the turn exactly where it left off.
Context-window meter
The chat header shows a colour-coded pill with the share of the current model's context window consumed by the last turn's input. Below 85 % is idle, 85–95 % flags a warning and offers to auto- compact, 95 %+ triggers auto-compaction silently. Context sizes come from the muk_ai.model record for the agent's active model.
Slash commands
Start a message with / to open a pop-up of available commands:
- /help — inline cheat sheet.
- /clear — confirm and wipe the current conversation and tool log, keeping the session record and agent.
- /compact — ask the provider for a ≤500-token summary and replace the conversation with it, freeing context without losing continuity.
- /unpin — clear the view-context pin.
- /agent — switch the active agent for this session.
- /handover — transfer this chat to another user.
Agents
Open MuK AI > Agents. An agent is a named preset:
- System Prompt — rendered with inline-template placeholders ({{ user.name }}, {{ company.name }}, {{ today }}, {{ approval_mode }}) at session start and on /compact.
- Model — optional override; blank falls back to the provider default.
- Reasoning Effort — how hard the model thinks before answering: Minimal, Low, Medium, High, Extra High or Maximum; blank uses the model's default. Only the tiers the model supports are offered, and the field disappears for models without a thinking knob. A requested tier above what the model allows is clamped to the nearest supported one, and a provider that still rejects it is retried without the effort so the answer is always served.
- Provider-native toggles — enable the active provider's web search, image generation and code interpreter tools when the provider supports them.
- Read-only — enforced server-side through the MCP scope check, not just a prompt instruction.
- Tool Filter — whitelist of MCP tool names the agent may call (empty = all tools allowed).
- Essential Tools — names that ship with full schemas at session start. Empty falls back to a sensible default (read-side primitives + UI helpers + ask_user) and turns lazy loading on; populate it to opt into a custom essentials set, or set it to every catalog name to ship the full eager-mode tool array. Names outside the tool filter are silently dropped.
- Approval Mode — Ask on writes (default) or Never ask for unattended agents.
- Allow Handoff — expose this agent as a delegation target so the Router (and other agents) can hand a conversation to it mid-session (see Agent handoff below).
- Suggestions — starter prompts shown in the empty chat, editable as a one2many kanban inside the agent form.
Every system-prompt edit snapshots the prior value into the agent's prompt_history JSON column (via the reusable muk_ai.revision.mixin). The Prompt History stat button on the agent form opens a side-by-side dialog that lists all prior revisions with author + timestamp and lets you restore any of them.
Agent handoff (Router)
A single chat can be served by more than one agent. Turn on Allow Handoff to make an agent a delegation target; the built-in Router agent then reads the opening request, calls list_agents, and hands the conversation to the best specialist with switch_agent. From the next turn that agent's prompt, tools and model take over the same session, and a specialist can hand back or across later on its own. The switch is recorded inline in the transcript as an A → B marker. Use /agent to switch the active agent yourself at any time.
Session handover
Hand a live chat to a colleague. The share icon in the chat header (or /handover) opens a user picker — each internal user shown with avatar, name and email, searchable — and reassigns the session to them. The recipient gets the session marked unread, an inbox notification and a systray badge. Only the session owner or an administrator can hand over, and not while the session is running.
Tool vision
Tools that return images — a screenshot grabber, a chart generator — feed those images straight back to the model when the active provider supports vision. Providers without vision get a note that images were produced but cannot be shown, so the turn still completes cleanly.
Lazy tool loading
Shipping every MCP tool's JSON schema in the tools array of every turn is expensive on context. MuK AI lets each agent declare a small Essential Tools set that is loaded eagerly; every other catalog tool is advertised by name only inside an <available_tools> block appended to the system prompt. The model fetches full schemas on demand through a built-in meta-tool:
- tool_load(names=[...]) — pulls one or more schemas from the catalog. The names are appended to the session's expanded_tool_names list and stay loaded for the rest of the session.
- tool_load(names=[...], call={name, arguments}) — load and execute one of the just-loaded tools in the same round-trip; the schemas plus the inline tool result come back in a single function_call_output, no follow-up turn needed. This is the preferred shape for one-shot lookups. The inline call passes through the same write-approval gate as any other call — a risky write pauses for approval instead of running.
Names are resolved tolerating a namespace prefix (functions.foo → foo). A load with some misses succeeds and returns the misses under an unknown key so the model can recover; a load where every name is unknown comes back as an explicit error. ask_user is auto-injected when approvals are enabled, regardless of the essentials list. Leaving Essential Tools empty enables the default lazy mode (read-side primitives + UI helpers + ask_user); populate it to override.
Runtime context block
Alongside the agent's system prompt and <available_tools> list, each session injects a short <runtime> block stating the current Odoo version, today's date, the user (id, timezone), the company (id), the active approval mode, and — when relevant — the list of companies the user can access (with a hint to pass allowed_company_ids for cross-company searches). This means the model never has to spend a turn calling whoami or today to ground its first reply.
Providers
The provider layer is a Python registry (providers/__init__.py) plus stored configuration records (muk_ai.provider). Each provider class declares name / label / default_model / default_url / supports_*, implements headers() and request(), and inherits shared HTTP + SSE streaming plumbing from ProviderBase. A client is constructed from the provider record itself — API key, timeouts, max tokens, environment and default model are all read off the record — so there is one source of truth and no config drift.
| Provider | Streaming | Notes |
|---|---|---|
| openai | SSE | OpenAI Responses API; skips temperature for reasoning models |
| anthropic | SSE | Messages API; in-dispatcher adapter for tool_use / tool_result |
| SSE | Gemini streamGenerateContent; native image generation and grounding |
All three providers emit the same on-delta events (text, tool_start, tool_args), so the chat UI feels identical regardless of which provider is active.
Performance. Long conversations exploit provider prompt caching — Anthropic cache_control breakpoints on the stable prompt prefix (system block, tools, and a conversation anchor placed before the per-round volatile trailers) and OpenAI's prompt_cache_key keyed on the session — so the reused prefix is billed at the much cheaper cache-read rate instead of full price each round. Outbound calls run over a pooled keep-alive connection shared across rounds (with connect-only retry, so a tool-calling POST is never replayed), cutting per-round latency. Requested reasoning effort is clamped to each model's supported tiers, with a transparent retry-without-effort backstop if a provider still refuses.
Tool Dispatch via muk_mcp
Agents call exactly the same tool surface as your external MCP clients — no duplicate catalog, no divergence. The MuK AI agent pulls tools from muk_mcp filtered by the odoo registry, and a handful of UI-specific tools are registered there for the in-Odoo agent only:
- open_record — navigate the user to a specific record.
- open_view — open a filtered list/kanban/pivot view of a model.
- open_action — launch an existing Odoo action by xmlid or id (honouring the action's groups_id).
- show_notification — toast a message in the Odoo web client.
- ask_user — pause the session for clarification.
- adjust_search — reshape the list/kanban/pivot/graph the user is currently looking at (see Reshape views from chat). This is a client tool: it runs in the user's browser tab, not on the server.
- web_fetch — fetch a public web page and return its main content as clean Markdown (or plain text / raw HTML), with an SSRF guard that re-validates every redirect. The fetched page is added to the reply's Sources as a citable web link.
The chat UI auto-dispatches any returned ir.actions.* descriptor, so the model can navigate the user through the UI as part of a reply.
Extending the Provider Set
Create a new addon (do not modify muk_ai). Subclass ProviderBase in your addon:
# muk_ai_mistral/providers/mistral.py from odoo.addons.muk_ai.providers.base import ProviderBase class MistralProvider(ProviderBase): name = 'mistral' label = "Mistral" default_model = 'mistral-large-latest' default_url = 'https://api.mistral.ai/v1' def headers(self): return { 'Authorization': f'Bearer {self.api_key}', 'Content-Type': 'application/json', } def request( self, inputs, tools_schema=None, text_schema=None, on_delta=None, model=None, enable_web_search=False, enable_image_generation=False, enable_code_interpreter=False, extra=None, ): ...
Register it by mutating the imported REGISTRY from your addon's __init__.py chain (runs at import time, before any session needs it):
# muk_ai_mistral/providers/__init__.py from odoo.addons.muk_ai.providers import REGISTRY from .mistral import MistralProvider REGISTRY[MistralProvider.name] = MistralProvider
Add <record id="provider_mistral" model="muk_ai.provider"> to data/provider.xml and matching muk_ai.model seeds in data/model.xml. Depend on muk_ai in your __manifest__.py. Selection, default-model lookup, capability probes, and dispatch all pick it up automatically.
Extending the Tool Set
Agents inherit every tool registered via the muk_mcp pattern (see muk_mcp's index for the full guide). To scope a tool to the in-Odoo agent only, set the decorator's registry='odoo'; external MCP clients keep seeing the default mcp surface only.
Security & Audit
Every session carries a JSON-serialised tool log covering user messages, tool calls, tool results, assistant text, ask_user prompts, and answers. Combined with muk_mcp's own audit log, every AI-driven read or write on your data is traceable end-to-end.
The per-provider idle watchdog aborts dead connections. Runaway loops are capped by MAX_ITERATIONS (default 20) per slice and MAX_TOOL_CALLS_PER_ROUND = 10; an ask_user-at-cap edge is handled gracefully so resumed sessions do not get stuck.
Long turns are sliced across cron ticks instead of dying. Each worker run gets a wallclock budget bounded below the cron process limit (limit_time_real_cron, falling back to limit_time_real) so it yields before Odoo kills the worker; at the boundary the session stays running and re-triggers a worker, resuming from its persisted conversation on the next tick. A per-turn wallclock budget and an optional per-turn cost limit bound total work. All four caps are set from Settings > General Settings > MuK AI (Iteration Limit, Turn Runtime, Slice Runtime, Cost Limit) and stored as the muk_ai.max_iterations, muk_ai.turn_wallclock_seconds, muk_ai.slice_wallclock_seconds and muk_ai.turn_cost_limit system parameters.
Turn dispatch is automatic: a turn queued during a web request starts as soon as that request's response is sent, instead of waiting for the next AI session worker cron. It falls back to the cron whenever the server runs its own cron threads (they pick the turn up immediately anyway) and never runs in-request on multi-worker (prefork) servers. The muk_ai.dispatch_mode system parameter overrides that detection: inline forces in-request dispatch, cron forces the worker crons. Leave it unset unless you are diagnosing a dispatch problem.
Sessions inherit bus.listener.mixin and route streaming events through the owner's partner channel — not a guessable string — so one user cannot eavesdrop on another.
Credits
Contributors
- Mathias Markl <mathias.markl@mukit.at>
- Kerrim Abd E-Hamed <kerrim.adbelhamed@mukit.at>
Author & Maintainer
This module is maintained by the MuK IT GmbH.
MuK IT is an Austrian company specialized in customizing and extending Odoo. We develop custom solutions for your individual needs to help you focus on your strength and expertise to grow your business.
If you want to get in touch please contact us via mail (sale@mukit.at) or visit our website (https://mukit.at).
Please log in to comment on this module
Error when using
Hi , i got JS error when click open ai provider record "Odoo Client Error
UncaughtPromiseError > OwlError
Uncaught Promise > An error occured in the owl lifecycle (see this Error's "cause" property)
Occured on odoo19.local on 2026-04-29 06:34:58 GMT".
And have an other error:, when open ai Chat, it load too slow, i check muk_ai.session/get_snapshot take 40s to load ?
Error when using
Thank you for the feedback. The slow loading is fixed by 19.0.1.6.14
Take a look at https://github.com/muk-it/odoo-modules/commit/b3f7cfe23a17d835ce8ec66b98b60ae2c96e9ff7
There are no comments yet!