AI-assisted content enrichment on article save¶
Version
You can apply this model as a recipe (Drupal 10.3 or later) to your own Drupal site:
1 2 3 4 5 6 7 8 9 10 11 | |
Purpose¶
Purpose¶
Editors write the article, the site derives the metadata. When an article is saved, this model sends the article text to an AI chat model, asks for editorial metadata in a strict JSON schema, and writes the answer into real node fields. The prompt restricts the model to the supplied text, so nothing is invented.
The model demonstrates the structured output feature of the Chat action from AI Integration - ECA. Instead of parsing prose, the action is given a JSON schema, so the response is a predictable object that ECA can address with tokens.
What it produces¶
Five values come back from one AI call.
summarygoes into the summary property of the body field, but only when the editor left it empty.meta_descriptiongoes intofield_meta_descriptionand doubles as the marker that this article has already been enriched.reading_time_minutesgoes intofield_reading_timeas an integer.sentimentgoes intofield_sentiment, constrained by the schema to one of four values. A negative sentiment additionally clears the promote flag, so unhappy articles never reach the front page unreviewed.keywordsis a list. The model loops over it, reuses a term from the Tags vocabulary when the keyword already exists, creates the term when it does not, and references it on the article.
How it stays out of its own way¶
Three guards make the model safe to run on every save.
- The AI call only happens when the body holds text and when
field_meta_descriptionis still empty. The first successful run fills that field, so later saves skip the whole flow. Emptying the field asks for a fresh enrichment. - The event is Presave, not Update. Fields are changed on the entity that Drupal is already about to write, so the model never calls save on the article itself and can never trigger itself recursively. Only the newly created taxonomy terms are saved explicitly, and they belong to a different entity type, so they cannot re-enter this model either.
- The keyword loop is bounded by the list itself. Every pass removes one item with List remove item, and the only way back into the loop body is the gateway link that requires at least one remaining item. A list of six keywords therefore runs six times and stops.
Points worth copying¶
- The
schemasetting of the Chat action carries a strict JSON schema. Every property is listed inrequiredandadditionalPropertiesis false, which is what strict mode demands and what makes the response reliable enough to write into fields. - Strict mode does not accept the whole of JSON Schema, and the accepted
subset differs between providers. This model therefore restricts itself
to types,
enum,requiredandadditionalProperties, which every structured output provider handles, and states the keyword count and the reading time floor in the property descriptions and in the prompt. If your provider documents support forminItems,maxItemsorminimum, adding them makes the contract stricter still. - The response arrives as a plain string. Tamper: Encode/Decode in Json
Decode mode turns it into structured data under the token
enrichment. - The raw response keeps a token of its own. The Chat action writes
ai_responseand the decode step writesenrichment, so the failure branch can still log the string that did not parse. Decoding a token into its own name would discard it. - Nested values are then addressed directly as
[enrichment:summary]or[enrichment:sentiment], and the nested list as[enrichment:keywords]. - Nothing is written until the decoded response actually carries a meta
description. That single check covers a truncated answer, a refusal and a
provider that ignored the schema. The unusable case is logged to the
eca_ai_enrichmentchannel instead of writing garbage into the article. - The keyword list is copied into its own token before the loop, so list processing stays focused on the list rather than on the root response object.
- The keyword loop needs no custom event. A gateway is the join point, and the link out of it carries the "keywords remain" condition, the same pattern as the Multi value field loop model.
Before you run it¶
Select a chat model in the "Ask AI for structured enrichment data" action. The chosen provider must support structured output, otherwise the schema is ignored and the response will not decode. Expect the save request to take several seconds while the provider answers.
Re-enrichment appends to field_tags rather than replacing it, so an
article enriched twice keeps the tags of the first run.
Dependencies¶
- config
- field.field.node.article.body
- field.field.node.article.field_meta_description
- field.field.node.article.field_reading_time
- field.field.node.article.field_sentiment
- field.field.node.migrate_example_beer.body
- field.field.node.page.body
- field.field.node.simplenews_issue.body
- field.storage.node.body
- field.storage.node.field_meta_description
- field.storage.node.field_reading_time
- field.storage.node.field_sentiment
- node.type.article
- node.type.migrate_example_beer
- node.type.page
- node.type.simplenews_issue
- taxonomy.vocabulary.tags
- module
- ai_integration_eca
- ai_provider_amazeeio
- eca_base
- eca_content
- eca_log
- eca_tamper
- modeler_api
- node
- text
Used plugins¶
Events¶
Conditions¶
- Article has body text
- Enrichment target is still empty
- Response carries a meta description
- Response is empty or did not decode
- Sentiment is negative
- Tag already exists
- Tag does not exist yet
- Keywords remain
Actions¶
- AND, enrichment target still empty
- Turn the body into plain text
- Ask AI for structured enrichment data
- Decode the structured response
- Write the summary, keeping editor input
- Log an unusable AI response
- Write the SEO meta description
- Write the estimated reading time
- Write the sentiment value
- Extract the keyword list
- Keep negative articles off the front page
- Take the next keyword
- Load the existing tag
- Create a new tag
- Reference the tag on the article
- Save the new tag
Changelog¶
Initial version