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->buildConfigurationForm() (line 167)</code></li>
<li><code>Warning: Undefined array key "llm_ajax_prefix" in RestrictToTopic->buildConfigurationForm() (line 170)</code></li>
<li><code>Warning: Trying to access array offset on null in RestrictToTopic->buildConfigurationForm() (line 170)</code></li>
<li><code>Warning: Undefined array key "llm_config" in RestrictToTopic->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->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). → 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?" → 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>— add null-safe fallbacks:</p>
<pre>$form_state->setValue('llm_ai_provider',<br> $this->getConfiguration()['llm_provider']<br> ?? $this->getConfiguration()['llm_ai_provider']<br> ?? NULL<br>);<br><br>$form_state->setValue('llm_ai_model',<br> $this->getConfiguration()['llm_model']<br> ?? ($this->getConfiguration()['llm_ajax_prefix']['llm_ai_model'] ?? NULL)<br>);<br><br>$llm_configs = $this->getConfiguration()['llm_config'] ?? [];</pre><p><code>processInput()</code> — fall back to the site's default provider:</p>
<pre>$provider = $this->configuration['llm_provider'] ?? '';<br>$model = $this->configuration['llm_model'] ?? '';<br><br>if (empty($provider)) {<br> $default = $this->getAiPluginManager()->getDefaultProviderForOperationType('chat');<br> if ($default === NULL) {<br> return new StopResult(<br> 'No AI provider configured for topic classification. Please configure a default chat provider in the AI module settings.',<br> $this<br> );<br> }<br> $provider = $default['provider_id'];<br> $model = $default['model_id'];<br>}<br>// ...<br>$ai_provider->setConfiguration($this->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