Add FilterableRagTool to augment RagTool supporting dynamic metadata filtering via additional context definitions
>>> [!note] Migrated issue <!-- Drupal.org comment --> <!-- Migrated from issue #3584010. --> Reported by: [m4olivei](https://www.drupal.org/user/410384) Related to !1476 !38 !37 >>> <p>---<br> [Tracker]<br> <strong>Update Summary: </strong>Initial implementation pushed, seeking review<br> <strong>Short Description: </strong>RagTool::execute() ignores additional context_definitions added via alter hook, preventing dynamic metadata filtering<br> <strong>Check-in Date: </strong>04/09/2026<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>For sites making use of the RagTool FunctionCall plugin, it would be useful if we could extend the FunctionCall to advertise to the LLM additional filters that are then applied to the underlying VDB query. For instance, imagine a user query like "What are some themes that can be derived over sales data for 2025", it would be nice if the LLM could understand that the RAG tool call has an optional <code>program_year</code> parameter (context value) available to it that it can use. The RagTool would then subsequently have access to that dynamically set value in the <code>execute()</code> method, and apply that filter to the Search API query.</p> <p>RagTool's <code>execute()</code> method currently hardcodes the four context values it reads: <code>index</code>, <code>search_string</code>, <code>amount</code>, and <code>min_score</code>. Any additional context_definitions added to the plugin &mdash; whether via <code>hook_ai_function_call_info_alter()</code> or by extending the class &mdash; are accepted by <code>populateValues()</code> and stored as context, but silently ignored during execution because <code>execute()</code> never reads them.</p> <p>This matters because VDB providers already support attribute-based filtering. The Pinecone provider gained metadata filtering in <span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-7"><a href="https://www.drupal.org/project/ai/issues/3477266" title="Status: Closed (fixed)">#3477266: Allow Pinecone VDB Provider to filter on meta data</a></span>, and the PostgreSQL provider's <code>prepareFilters()</code>, provided by the ai_provider_amazeeio module, correctly translates Search API <code>addCondition()</code> calls into WHERE clauses. The infrastructure for filtering exists at the query level &mdash; the gap is that RagTool provides no way to pass LLM-provided tool arguments through to the query as conditions.</p> <p>Here is a more fulsome example of a project I'm working on where this would be useful:</p> <p><strong>Use case:</strong> An AI agent searches a vector index of interview writeups. The index stores <code>field_program_year</code> as a filterable attribute. We want the LLM to receive <code>program_year</code> as a tool parameter (with an enum of allowed values) so that when a user asks "What did respondents say about pricing in 2025?", the VDB query is filtered to only 2025 records rather than relying on semantic similarity alone to surface the right year.</p> <p>Today this requires creating an entirely new FunctionCall plugin that duplicates RagTool's <code>execute()</code> body &mdash; a 48-line copy &mdash; just to insert one <code>addCondition()</code> call.</p> <h3 id="summary-proposed-resolution">Proposed resolution</h3> <p>Add a new <code>FilterableRagTool</code> FunctionCall plugin backed by a deriver (<code>FilterableRagToolDeriver</code>) that produces one derivative per configured <code>ai_search.index.*</code> config. Each derivative is scoped to a single Search API index and automatically exposes that index's filterable attribute fields (fields with <code>indexing_option: attributes</code>) as optional context_definitions. The existing <code>RagTool</code> remains untouched for backwards compatibility.</p> <p>The key design choices: </p> <ul> <li><strong>Per-index derivatives.</strong> Rather than a single generic tool that accepts any index, each index gets its own purpose-built tool with a name like "RAG Search: VDB Index". The LLM sees a tool scoped to a specific index with exactly the right filter parameters for that index's data.</li> <li><strong>Config-driven context_definitions.</strong> The deriver reads each index's <code>ai_search.index.*</code> config to find fields marked as <code>attributes</code>, then builds context_definitions keyed by the Search API field identifier (which matches the VDB metadata key). No alter hook is required to get basic filtering &mdash; it "just works" for any index with attribute fields configured.</li> <li>Attribute context_definitions are exposed as free-form strings by default. Modules can implement <code>hook_ai_function_call_info_alter()</code> to add <code>Choice</code> constraints or other refinements when the set of allowed values is known and bounded.</li> <li><strong>Query alter hook.</strong> A new <code>hook_ai_search_filterable_rag_query_alter()</code> fires after attribute filters are applied but before query execution, letting modules add complex condition groups, sorts, or provider-specific options without subclassing. The hook receives both the query and the plugin instance.</li> </ul> <p>In <code>execute()</code>, after applying attribute filters and firing the hook, the remaining logic (execute query, filter by min_score, format output) mirrors the existing <code>RagTool</code>.</p> <p>Extension points summary:</p> <table> <tr> <th>Extension point</th> <th>Use case</th> </tr> <tr> <td><code>hook_ai_function_call_info_alter()</code></td> <td>Add <code>Choice</code> constraints to attribute context_definitions, adjust descriptions, disable derivatives</td> </tr> <tr> <td><code>hook_ai_search_filterable_rag_query_alter()</code></td> <td>Modify the query before execution (complex conditions, sorts, provider options)</td> </tr> </table> <h3 id="summary-remaining-tasks">Remaining tasks</h3> <ul> <li>Review of the proposed approach illustrated by the initial implementation on the MR branch</li> <li><del>There may be cases of users' not wanting only a subset of their Filterable Attributes exposed. Determine how the Tool configuration can mitigate this.</del></li> <li><del>Tests (unit/kernel coverage for the deriver and execute logic)</del></li> </ul> <h3>Optional: Other details as applicable</h3> <p><strong>API addition (fully backwards compatible):</strong> The existing <code>ai_search:rag_search</code> plugin is unchanged. The new <code>ai_search:filterable_rag_search</code> plugin with its derivatives is purely additive. A new hook <code>hook_ai_search_filterable_rag_query_alter()</code> is introduced, documented in <code>ai_search.api.php</code>.</p> <h3 id="testing-instructions">Testing Instructions</h3> <ul> <li>Setup an AI Search backend Search API Server and Index</li> <li>Add a Main Content field</li> <li>Add one or more Filterable attribute fields</li> <li>Configure an agent with the "Filterable RAG Search" tool</li> <li>Verify that any Filterable Attributes from the index show up as tool properties</li> <li>Use the Agent Explorer (from <code>ai_agents_explorer</code> module) at <code>/admin/config/ai/agents/explore?agent_id=agent_id<code> experiment with some user queries</code></code></li> <li>Verify the LLM can set values against Filterable attributes</li> </ul> <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>[x] AI Generated Code<br> This code was mainly generated by an AI with human guidance, and reviewed, tested, and refined by a human.</p> > Related issue: [Issue #3471759](https://www.drupal.org/node/3471759) > Related issue: [Issue #3477266](https://www.drupal.org/node/3477266) > Related issue: [Issue #3547016](https://www.drupal.org/node/3547016)
issue