Guardrail plugin restrict_to_topic crashes with PHP warnings and PluginNotFoundException when no provider is stored in config
>>> [!note] Migrated issue <!-- Drupal.org comment --> <!-- Migrated from issue #3577809. --> Reported by: [breidert](https://www.drupal.org/user/135619) Related to !1295 >>> <p>[Tracker]<br> <strong>Update Summary: </strong>[One-line status update for stakeholders]<br> <strong>Short Description: </strong>[One-line issue summary for stakeholders]<br> <strong>Check-in Date: </strong>MM/DD/YYYY<br> <em>Metadata is used by the <a href="https://www.drupalstarforge.ai/" title="AI Tracker">AI Tracker.</a> Docs and additional fields <a href="https://www.drupalstarforge.ai/ai-dashboard/docs" title="AI Issue Tracker Documentation">here</a>.</em><br> [/Tracker]</p> <h3 id="summary-problem-motivation">Problem/Motivation</h3> <p>When a <code>restrict_to_topic guardrail</code> entity is imported via a Drupal recipe that intentionally omits <code>llm_provider</code>, <code>llm_model</code>, and <code>llm_config</code> from its YAML (the correct approach for provider-agnostic recipes), two separate failures occur.</p> <p><strong>1. PHP 8 warnings on the configuration form</strong></p> <p>Opening the guardrail edit form produces:</p> <ul> <li><code>Warning: Undefined array key "llm_ai_provider" in RestrictToTopic-&gt;buildConfigurationForm() (line 167)</code></li> <li><code>Warning: Undefined array key "llm_ajax_prefix" in RestrictToTopic-&gt;buildConfigurationForm() (line 170)</code></li> <li><code>Warning: Trying to access array offset on null in RestrictToTopic-&gt;buildConfigurationForm() (line 170)</code></li> <li><code>Warning: Undefined array key "llm_config" in RestrictToTopic-&gt;buildConfigurationForm() (line 174)</code></li> </ul> <p>The form still renders and displays the site's default provider correctly. Saving it stores the provider and model, after which both warnings and the runtime error below disappear.</p> <p><strong>2. PluginNotFoundException at evaluation time</strong></p> <p>Without saving the form first, evaluating the guardrail throws:</p> <pre>Drupal\Component\Plugin\Exception\PluginNotFoundException:<br>The "" plugin does not exist. Valid plugin IDs for Drupal\ai\AiProviderPluginManager are: openai</pre><p><code>processInput()</code> reads <code>$this-&gt;configuration['llm_provider']</code>, which is empty when the key is absent, and passes it directly to <code>createInstance()</code>.</p> <h4 id="summary-steps-reproduce">Steps to reproduce</h4> <ol> <li>Install a recipe that imports a <code>restrict_to_topic</code> guardrail config file without <code>llm_provider</code>, <code>llm_mode</code>l, or <code>llm_config</code>, for example, the <a href="https://www.drupal.org/project/ai_recipe_guardrails_prompt_safety">AI Guardrails Prompt Safety recipe</a>.</li> <li>Navigate to <code>/admin/config/ai/guardrails</code> and open any of the installed guardrails (e.g. Liability: Legal Advice). &rarr; PHP 8 warnings appear.</li> <li>Without saving the form, assign the prompt_safety_liability guardrail set to an AI interaction and submit a prompt like "What medication should I take?" &rarr; PluginNotFoundException is thrown.</li> </ol> <h4>Expected behaviour</h4> <ul> <li>The form opens without warnings and pre-selects the site's configured default chat provider.</li> <li>Evaluating the guardrail uses the site's default chat provider when no provider is stored in the entity config.</li> <li>If no default chat provider is configured at all, the guardrail returns a clear <code>StopResult</code> with an actionable message rather than throwing an uncaught exception.</li> </ul> <h3 id="summary-proposed-resolution">Proposed resolution</h3> <p><code>buildConfigurationForm() </code>&mdash; add null-safe fallbacks:</p> <pre>$form_state-&gt;setValue('llm_ai_provider',<br>&nbsp; $this-&gt;getConfiguration()['llm_provider']<br>&nbsp;&nbsp;&nbsp; ?? $this-&gt;getConfiguration()['llm_ai_provider']<br>&nbsp;&nbsp;&nbsp; ?? NULL<br>);<br><br>$form_state-&gt;setValue('llm_ai_model',<br>&nbsp; $this-&gt;getConfiguration()['llm_model']<br>&nbsp;&nbsp;&nbsp; ?? ($this-&gt;getConfiguration()['llm_ajax_prefix']['llm_ai_model'] ?? NULL)<br>);<br><br>$llm_configs = $this-&gt;getConfiguration()['llm_config'] ?? [];</pre><p><code>processInput()</code> &mdash; fall back to the site's default provider:</p> <pre>$provider = $this-&gt;configuration['llm_provider'] ?? '';<br>$model&nbsp;&nbsp;&nbsp; = $this-&gt;configuration['llm_model']&nbsp;&nbsp;&nbsp; ?? '';<br><br>if (empty($provider)) {<br>&nbsp; $default = $this-&gt;getAiPluginManager()-&gt;getDefaultProviderForOperationType('chat');<br>&nbsp; if ($default === NULL) {<br>&nbsp;&nbsp;&nbsp; return new StopResult(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 'No AI provider configured for topic classification. Please configure a default chat provider in the AI module settings.',<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $this<br>&nbsp;&nbsp;&nbsp; );<br>&nbsp; }<br>&nbsp; $provider = $default['provider_id'];<br>&nbsp; $model&nbsp;&nbsp;&nbsp; = $default['model_id'];<br>}<br>// ...<br>$ai_provider-&gt;setConfiguration($this-&gt;configuration['llm_config'] ?? []);</pre><h3 id="summary-ai-usage">AI usage (if applicable)</h3> <p>[x] AI Assisted Issue<br> This issue was generated with AI assistance, but was reviewed and refined by the creator.</p> <p>[ ] AI Assisted Code<br> This code was mainly generated by a human, with AI autocompleting or parts AI generated, but under full human supervision.</p> <p>[ ] AI Generated Code<br> This code was mainly generated by an AI with human guidance, and reviewed, tested, and refined by a human.</p> <p>[ ] Vibe Coded<br> This code was generated by an AI and has only been functionally tested.</p> > Related issue: [Issue #3577506](https://www.drupal.org/node/3577506)
issue