Filament Copilot: Embedding an AI Assistant into a Laravel Compliance Platform
James Manager
Modern compliance management is data-intensive and time-sensitive. Compliance officers, risk managers, and auditors routinely need to query records across multiple modules β risk assessments, audits, CAPAs, nonconformances, frameworks, and documents β often under time pressure. This article documents how we integrated the Filament Copilot plugin into Regcom, a compliance management platform built on Laravel 13 and Filament v5, and how we extended it with domain-specific tools that allow users to interact with their compliance data through natural language.
What is Filament Copilot?
Filament Copilot eslam-reda-div/filament-copilot) is a first-party-quality Filament plugin that embeds a full AI chat assistant directly into any Filament panel. It is built on top of laravel/ai, Laravel's official AI abstraction layer, which means it supports multiple AI providers (OpenAI, Anthropic, and others) with a consistent API.
The plugin is more than a chat widget. It ships with:
A floating chat button rendered in the Filament top bar (next to global search)
A slide-over chat modal with conversation history and a sidebar
A pluggable tool system backed by the
laravel/aitool-call protocolBuilt-in memory (the agent remembers facts across conversations)
Rate limiting per user or tenant
Token budget tracking
A full audit log of every AI interaction
A management dashboard (conversations, audit logs, rate limit controls) for administrators
Quick actions (canned prompts) for common workflows
Everything persists to your own database β no third-party SaaS required.
The plugin's core is CopilotAgent, a laravel/ai agent class that wires together all the moving parts. It implements four laravel/ai contracts:
Agentβ provides instructions (the system prompt built byContextBuilder)Conversationalβ passes the full conversation message history to the AIHasToolsβ exposes the registered tool instances to the AIHasMiddlewareβ runsRateLimitMiddlewareandAuditMiddlewarearound every inference call
The agent is configured with a temperature of 0.3 (precise, low creativity) and a max token output of 4096. These defaults are intentional for a compliance tool β you want accurate, structured responses, not creative ones.
The System Prompt
The system prompt is the most important configuration in any AI integration. It defines the agent's identity, domain knowledge, and behavioural constraints. For Regcom, the system prompt is embedded in config/filament-copilot.php as a heredoc:
```
You are Regcom Copilot, an AI assistant embedded in the Regcom compliance management platform. You help compliance officers, risk managers, and auditors manage their work efficiently.
Regcom covers the following modules:
**Risk Management**
- Risk assessments use reference numbers like R-2026-0001
- Likelihood and impact are scored 1β5; inherent_score = likelihood Γ impact (max 25)
- Categories: data_privacy, operational, legal, financial, regulatory, technical, reputational, other
- Statuses: identified β under_review β mitigating β mitigated β accepted β closed
**Compliance Audits**
- Types: internal, external, surveillance
- Statuses: scheduled β in_progress β completed β archived
...
Guidelines:
- Always use reference numbers (R-, CAPA-, NC-) when referring to specific records
- Proactively flag overdue items, high-severity issues, and approaching review dates
- Ask for confirmation before creating, editing, or deleting any record
- For user IDs, ask the user for the correct ID or look it up if possible
```This prompt gives the model complete domain context β the scoring system, status lifecycles, reference number formats, and behavioural rules (always confirm destructive actions). Without this, the agent would produce generic responses; with it, responses are immediately operational.
Key Design Decisions
Tools return plain text, not JSON
All tool handle() methods return formatted plain text strings. This is intentional β the AI model interprets plain text more reliably than raw JSON when composing a user-facing response. JSON is appropriate for structured output to a frontend; for AI-to-AI communication within a tool loop, readable text works better.
Pagination caps on all list tools
Every list tool enforces min(50, max(1, $perPage)). Without this, an AI could request thousands of rows in a single tool call, causing performance problems. The cap is not documented to the AI β it simply clamps silently.
The system prompt lives in config, not code
Keeping the system prompt in config/filament-copilot.php (not hardcoded in a provider) means it can be updated without touching any PHP class. In a deployed environment, changing the system prompt only requires a config cache refresh.
Admin panel uses management mode, app panel uses chat mode
The two panels serve different purposes. Compliance users /) get the chat widget. Administrators /admin) get the management dashboard β conversations, audit logs, rate limits β with no chat widget. This separation ensures administrators govern AI usage rather than use it for compliance workflows.
Real-World Usage Examples
With all tools registered and the system prompt in place, Regcom Copilot supports natural language interactions like:
"Show me all open risks in the operational category with an inherent score above 12"
β Agent calls ListRiskAssessmentsTool with status=identified, category=operational, then filters results
"Create a CAPA for nonconformance NC-2026-0042 β title: 'Update document retention policy', priority: high, due in 30 days, assign to user 15"
β Agent calls CreateCapaTool with all parameters, returns CAPA-2026-0XXX reference number
"Which compliance documents are overdue for review?"
β Agent calls ListComplianceDocumentsTool, filters by next_review_date < today, returns formatted list
"Find all audits led by Jane Smith"
β Agent searches for the user ID, then calls ListAuditsTool with lead_auditor_id
"What's the status of R-2026-0015?"
β Agent calls SearchRiskAssessmentsTool with query R-2026-0015, returns full record details
What the Documentation Didn't Warn Us About
The plugin is well-built, but several things didn't work the way the documentation implied. These are the real-world problems we hit and how we worked around them.
1. OpenAI 400 Errors From strict: true Tool Mode
The problem: As soon as the plugin sent its first request to OpenAI with any tool that had no parameters (such as the built-in ListResourcesTool, ListPagesTool, ListWidgetsTool), we got an HTTP 400 response from the OpenAI API. The error was cryptic β OpenAI rejected the tool definition entirely.
Root cause: In vendor/laravel/ai/src/Gateway/OpenAi/Concerns/MapsTools.php, the mapTool() method hardcodes 'strict' => true in every tool definition sent to OpenAI:
return [
'type' => 'function',
'name' => ToolNameResolver::resolve($tool),
'description' => (string) $tool->description(),
'strict' => true, // <-- this causes the 400
'parameters' => [...],
];OpenAI's strict tool mode requires that every property in the schema appear in required, and that additionalProperties: false is set. Tools with an empty schema() (no parameters) cannot satisfy these constraints β OpenAI simply refuses them. The same problem affected tools with optional parameters like RunToolTool, because optional parameters are illegal under strict mode.
The temporary fix: While the bug existed, we manually patched line 53 of that file, changing 'strict' => true to 'strict' => false. This disables strict mode for all tool calls, which OpenAI accepts without issues. The patch lived in vendor/ and had to be re-applied after every composer update laravel/ai.
Current status: fixed upstream. The fix was submitted as PR #340 and has since been merged into laravel/ai. Running composer update laravel/ai will pull in the patched release and the manual workaround is no longer needed. If you are on v0.6.6 or earlier and Copilot is throwing 400 errors from OpenAI, update the package to resolve it.
2. The System Prompt Was Being Injected Twice
The problem: With the full system prompt defined in config/filament-copilot.php, it appeared in the AI's context twice on every request. This wasted tokens and caused the agent to behave erratically, as if it received conflicting or redundant instructions.
Root cause: The documentation suggests setting your system prompt via ->systemPrompt() on the plugin registration, and it also reads the same config key internally.
3. The Default get_tools / run_tool Pattern Was Too Slow for Production
The problem: Out of the box, the plugin uses a two-step tool discovery workflow. The default system prompt instructs the agent to first call get_tools on a resource to discover what tools exist, and then call run_tool to execute one. In testing, this meant every user query required at minimum two round-trips to the AI β one to discover tools and one to use them β before any data was returned. For a compliance officer checking overdue CAPAs, this latency was unacceptable.
The fix: We implemented the CopilotResource interface on every Filament Resource and exposed tools directly via copilotTools(). The ResourceInspector discovery mechanism collects these tools at boot time, and the ToolRegistry exposes them to the agent in a single flat list.
4. Duplicate User Messages in Every API Request
The problem: Early in testing we noticed every outgoing request to OpenAI contained the user's latest message twice in the conversation history. This caused the agent to sometimes echo back the question or produce confused responses, and it wasted tokens on every single turn.
Root cause: A subtle interaction between the plugin's conversation persistence and laravel/ai's streaming API. The ConversationManager persists the user's new message to the database before the agent is invoked. Then getMessagesForAgent() loads the full conversation history from the database β which now includes the just-saved message as the last entry. Finally, laravel/ai's Promptable::stream() takes a prompt: argument and internally appends it as a new user message on top of whatever withMessages() provides. The net result is the same user message in both withMessages() and the prompt: slot.
Filament Copilot transforms a standard Filament panel into an AI-powered workspace without requiring any custom Livewire components, route definitions, or frontend code. The plugin handles the UI, conversation persistence, memory, rate limiting, and audit logging out of the box.
The key to a successful integration is the tool layer. The plugin itself cannot know your domain β it provides the scaffold, and you fill it with tools that speak your business language. For Regcom, that meant 47 tools covering every compliance operation a user might want to perform through natural language, each scoped to the exact Eloquent model layer the Filament Resources already use.
The result is an AI assistant that behaves as a knowledgeable compliance colleague: it knows your reference number formats, your status lifecycles, your scoring systems, and your data β and it asks for confirmation before changing anything.
Share this article
Related Articles
Custom Login & Logout Redirects in Filament v4
Learn how to set custom login and logout redirects in Filament v4, including intended URLs and panel-specific flows.
Embedding Livewire Components in Filament 4 Schemas
How Filament 4 makes it easier to build flexible, reusable interfaces with Livewire
Custom Uniqueness Validation with Spatie Tags in Laravel
Using Spatieβs multilingual tagging system? Here's how to write a Laravel validation rule that ensures tags remain unique across locales