Example config now:
uuid: 96d54c20-5996-4636-a13a-c0668c089ea9
langcode: en
status: true
dependencies: { }
id: content_type_agent_triage
label: 'Content Type Agent'
description: 'This is the initial agents for content type/node types, that can figure out if you are trying to create, edit, delete or ask questions about content types.'
orchestration_agent: false
triage_agent: true
system_prompt: |-
You are an Drupal 11 developer that specializes in content types/node types. You can create, edit, answer questions or delete content types using the tools to your disposal.
Thinks of the following instructions:
1. When editing a content type, make sure that this content type exists. Otherwise tell them that it doesn't exist.
2. If you are on your second run, you will see actual information from the tools that has been run, they might answer a question or make it possible for you to start using the editing tools.
3. If a question comes in that you think you can answer without any need to forward it, please do. Otherwise use one of the tools to gather more information.
4. If the instructions/questions have nothing to do with content types/nodes types, just answer that you are not the right agent to answer this.
5. If you will do something, never respond that you will do something, instead just go ahead and do it.
6. If you will be editing, make sure that the information exists about the node type first, so you can do a choice if you actually need to edit it. Do not explain why you do something, just return the tool.
7. If you create or edit, you do not have to verify after that everything is ok.
secured_system_prompt: '[ai_agent:agent_instructions]'
max_loops: 3
default_information_tools: |
node_types:
label: 'Node Types'
description: 'The existing node types on the system in YAML array with type (data name), label (name) and description'
tool: 'ai_agent:list_config_entities'
parameters:
entity_type: node_type
amount: 0
fields:
- type
- name
- description
structured_output_enabled: false
structured_output_schema: ''
tools:
-
tool_id: get_content_type_info
provider: ai_agent
active: true
tool_settings:
description_override: 'Description override test'
require_usage: true
use_artifact_storage: true
return_directly: true
progress_message: 'Custom progress message '
property_overrides:
-
property_id: node_type
overridden_description: 'Override node_type property description test'
restriction_type: ''
restriction_values: { }
restriction_hide_property: true
-
tool_id: edit_content_type
provider: ai_agent
active: true
tool_settings:
description_override: ''
require_usage: false
use_artifact_storage: false
return_directly: true
progress_message: ''
property_overrides: { }
-
tool_id: create_content_type
provider: ai_agent
active: true
tool_settings:
description_override: ''
require_usage: false
use_artifact_storage: false
return_directly: false
progress_message: ''
property_overrides: { }
Hi kristen pol
I have removed the post update hook and redundant EntityPublishedInterface implementation as iterface is already implemented by EditorialContentEntityBase
Hi @mxr576
This is one demo,where I have use the ai runtime which dependes upon the symfony message to run the agen aync.
https://www.linkedin.com/posts/harivansh-sharma-826719102_drupal-ai-drup...
Will get to the issue, Right now this isn't priority.
My changes are currently scattered, making it difficult to consolidate them. I’m currently working on building the foundational system for the workflow and need some time to architect it properly.
We can go ahead and close this issue.
Hi @marcus_johansson
The JSON schema example is good, but developers would benefit from seeing actual PHP code showing how to use it with ChatInput.
Somethng like this.
// Create your chat input
$chatInput = new ChatInput([
new ChatMessage('user', 'Extract user info: harivansh sharma, @harivansh, harivansh@example.com'),
]);
// Define your schema
$schema = [
'name' => 'user_data',
'strict' => TRUE,
'schema' => [
'type' => 'object',
'properties' => [
'name' => ['type' => 'string'],
'username' => ['type' => 'string'],
'email' => ['type' => 'string', 'format' => 'email'],
],
'required' => ['name', 'username', 'email'],
],
];
// Apply the schema
$chatInput->setChatStructuredJsonSchema($schema);
// Run inference
$response = $provider->chat($chatInput, 'model-id')->getNormalized();
// Parse the structured response
$userData = json_decode($response->getText(), TRUE);
// Result: [name => harivansh sharma, username => harivansh, email => harivansh@example.com]
I think we should consolidate all tool configuration into a single tools property on the entity. This would eliminate the separate enabled_tools, locked_tools, and tool_operations arrays.
[
'name' => 'tool_name', // Required: tool name
'description' => 'Tool description', // Required: tool description
'input_schema' => [...], // Required: MCP tool schema
// Configuration
'enabled' => true, // Boolean: is tool enabled
'locked' => false, // Boolean: is tool locked
'operation' => 'read', // String: read|write
// Locked definition (only if locked === true)
'locked_definition' => [
'name' => 'tool_name',
'description' => 'Tool description',
'input_schema' => [...],
],
]To make this more maintainable and type-safe, we should also create dedicated DTOs/Value Objects for this:
ToolCollection - represents the collection of all tools
Tool - represents a single tool with all its configuration
InputSchema - represents the tool's input schema
LockDefinition - represents the locked tool definition
This approach would give us a single source of truth, cleaner code, and better type safety throughout the codebase.
What your though on this.
@robertoperuzzo thank you for the feedback, I will work on your suggestion.
@jurgenhaas
Your solution works, but I think we should use named arguments instead
return new Collection(
fields: $fields + $this->extraConstraints,
allowExtraFields: false,
);
This is cleaner than passing NULL for unused parameters and aligns better with the #[HasNamedArguments] attribute on the Collection constructor. What do you think?
@marcus_johansson
I think we should add another column in the tool maintenance section
Something like this
@marcus_johansson
We can add the "looked_tools" property (new field in the MCP config form) to store the locked tool definitions.
On subsequent tool fetches, locked tools use the saved definition instead of the server's response. This prevents malicious updates from changing tool behavior or injecting prompts
Users can unlock tools to receive updates when they trust the source
harivansh → made their first commit to this issue’s fork.
harivansh → made their first commit to this issue’s fork.
So I discussed with @marcus_johansson and @a.dmitriiev. We discussed a few approaches
I have come up with a new approach to solve the issue with the template pattern.
Issue: Enforce the provider class to dispatch the pre- and post events.
New approach: Hybrid Template Method with safety net pattern.
- Move event dispatch into the provider base and operation traits (Template Method).
- Keep a reflection-based safety net in the base class to wrap legacy providers and plugin operation types that haven’t migrated yet.
- ProviderProxy no longer needs to own event dispatch; keep it temporarily (with @method docblocks) for IDE compatibility during migration and phase it out later.
New provider flow:
Provider uses trait and implements protected do*() methods.
Call path:
- ChatTrait::chat() (public wrapper) -> calls:
- dispatchOperation('chat', [$this, 'doChat'], $input, $model_id, $tags)
dispatchOperation():
- Dispatch: PreGenerateResponseEvent
- Call: OpenAiProvider::doChat() (protected implementation)
- Dispatch: PostGenerateResponseEvent
- Return OutputInterface to the caller
Old Provider :
Caller invokes a method on an unmigrated provider (e.g., $provider->chat()).
AiProviderClientBase::__call() intercepts the call and:
- Uses reflection to check the method’s return type.
- If the return type implements OutputInterface, it wraps the original call:
- dispatchOperation('chat', function(...) { return $provider->chat(...); }, ...)
- dispatchOperation() dispatches PreGenerateResponseEvent, calls the original OldProvider::chat() (public), dispatches PostGenerateResponseEvent, and returns the output.
- If the method is not an operation, __call() invokes it directly (no wrapping).
Implementation:
- AiProviderClientBase::dispatchOperation() — centralizes event dispatch and request ID handling.
- AiProviderClientBase::__call() — safety net that detects operation methods via reflection and wraps them.
- Operation base traits (Chat, Embeddings, TextToImage, TextToSpeech, SpeechToText, ImageToImage, Moderation):
- Provide typed public wrapper methods (e.g., chat()) that call dispatchOperation().
- Declare abstract protected function do*() methods providers must implement.
Apologies I made a bit of mess with MR, Took pull from 2.x branch by mistake.
I have these changes
- Removed `getCurrentThreadsKey()`, `setCurrentThreadsKey()`, and `removeCurrentThreadsKey()` methods
- Simplified `generateUniqueKey()` to always generate a new hash instead of checking server-side storage
- Updated `resetThread()` to return a new hash directly without server-side tracking
- Simplified `setThreadsKey()` by removing `session_one_thread` special handling
Thread ids were being stored in server-side sessions via PrivateTempStore, which caused session writes on every request. This negatively impacted page cache performance.
Threads are now fully managed client-side via localStorage already implemented in deepchat-init.js, and the server generates new ids via AJAX when needed DeepChatApi.php::setSession(). This eliminates unnecessary session writes and reduces page cache impact.
harivansh → made their first commit to this issue’s fork.
Hi @wouters_f
The AI Search module has been moved out of the AI core.
#3552884
✨
Move out AI Search
Active
https://www.drupal.org/project/issues/ai_search →
marcus_johansson
I have created the initial version of orchestration using Mermaid. I haven't added much styling yet, just experimenting. What do you think.
harivansh → created an issue. See original summary → .
harivansh → created an issue. See original summary → .