AI Features in Textstem: From Automatic Metadata to the Orchestration Engine

AI Features in Textstem: From Automatic Metadata to the Orchestration Engine

AI assistance in Textstem is not a single feature — it is a set of opt-in capabilities that slot into the content workflow at different points. This post walks through everything that is available, how each piece works, and how to control it.

How AI is enabled

All AI features share a common gate. Set OPENAI_API_KEY in your .env and confirm OPENAI_ENABLED=true (the default). The model used across all features defaults to gpt-4o-mini and can be changed via OPENAI_MODEL. Individual capabilities are switched on or off independently in config/textstemapp.php under the openai key:

'openai' => [
    'enabled'              => env('OPENAI_ENABLED', true),
    'api_key'              => env('OPENAI_API_KEY', ''),
    'model'                => env('OPENAI_MODEL', 'gpt-4o-mini'),
    'use_queue'            => env('OPENAI_USE_QUEUE', true),
    'generate_tags'        => false,
    'generate_teaser'      => false,
    'generate_description' => false,
    'generate_keywords'    => false,
]

Nearly all operations run via the Laravel queue by default (use_queue = true), so they do not block the request and can be retried if they fail.

Automatic metadata on save

When content is saved, Textstem can automatically generate several metadata fields without any manual input. Each is individually gated by a config flag so you can enable only what you need:

  • Description (generate_description) — reads the rendered HTML of the page or post, strips navigation and aside elements to isolate the main content, and asks the model to write a concise meta description.
  • Tags (generate_tags) — analyses the content against an existing tag pool and suggests which tags apply, or proposes new ones.
  • Keywords (generate_keywords) — extracts a set of SEO keywords from the content.
  • Teaser (generate_teaser) — writes a short excerpt suitable for listing pages and social sharing.

The description and teaser generators read content_render (the fully rendered HTML output), not the raw stored content, so what the AI sees matches what a visitor would read. Content is trimmed to a safe token window before being sent to the API.

Image alt text generation

When an image asset is uploaded to the media library, Textstem can generate an accessibility-ready alt text description automatically. The GenerateAltText action resizes the image to a maximum of 1200px on its longest side, encodes it as WebP at 80% quality, and sends it to the model as a base64 data URL using the vision API. The resulting description is saved back to the asset record and a ModelDescriptionUpdated event is fired so the rest of the application can react.

This runs via its own queue job (GenerateAltTextJob) and is controlled by the generate_description flag.

Article generation

The GenerateArticle action takes a title and an optional prompt, combines them with the package's content.generate_article prompt, and asks the model to write a full article. The response is parsed as Markdown and converted to HTML before being stored. If a post ID is supplied, the generated content is written directly back to that post record and an ArticleGenerated event fires — which can be listened to from the host application to trigger notifications or further processing.

Like the other operations, this can run synchronously or be dispatched to the queue.

Structured data (JSON-LD) generation

The page editor in the admin includes a Structured Data panel with a Generate JSON-LD (AI)button. When clicked, Textstem sends the page title, description, content, and your organisation details (from config/structured-data.php) to the model and receives back a comprehensive @graph-based JSON-LD block. The graph includes Organisation, WebSite, WebPage, the primary content entity, and a BreadcrumbList, all linked via @id references.

This feature is covered in depth in the structured data post.

AI page analysis

Two Livewire components provide on-demand AI-powered auditing of page output:

  • AiAnalyzer — accepts any URL, fetches the page, and runs either an SEO audit or an accessibility review against the rendered HTML.
  • AiPageAnalyzer — works on a specific CMS page directly, using PageRenderer to generate the HTML internally rather than making an HTTP request.

Both support two analysis types:

  • Accessibility — audits against WCAG 2.1 Level A and AA, with particular attention to Australian web accessibility standards. Reports on alt text, keyboard access, colour contrast, form labels, heading structure, ARIA usage, and more.
  • SEO — checks title and description tags, heading structure, canonical URLs, internal linking, structured data presence, Open Graph tags, and keyword density. The output is formatted as a report suitable for presenting to a client or site owner.

The prompt management system

Every prompt used by the AI features is managed through a layered repository. When the package needs a prompt, it looks it up in this order:

  1. Database — a GlobalOption record with group prompts takes highest precedence, allowing live edits via the admin without a deployment.
  2. Published override fileresources/prompts/textstem.php in the host application, for project-level customisation checked into version control.
  3. Package defaults — the built-in prompts shipped with Textstem.

The admin Prompt Manager tool shows every known prompt key alongside which layer is currently providing it (database, file, or default) and lets you edit any of them in-browser. Variable substitution uses a simple :key syntax — for example, the alt text prompt receives a :language replacement to match the site language.

The orchestration engine

For more complex workflows, Textstem ships a PromptOrchestrator — a general-purpose LLM execution engine driven by database-stored OrchestrationAction records. Each action defines a prompt key, an output schema (the shape of JSON the model must return), an executor (what to do with the output), and optional metadata such as model, temperature, and max tokens.

The orchestrator handles context assembly, structured output parsing, schema validation, and automatic retries — if the model returns invalid JSON or output that does not match the expected schema, the error is appended to the prompt and the call is retried up to twice. Bulk actions are dispatched to the queue; single actions can run synchronously. A preview modeassembles the full prompt and calls the model but stops before executing any database changes — useful for reviewing what the AI would do before committing.

Every run is logged to an OrchestrationRun record: prompt tokens, completion tokens, estimated cost in USD (calculated from known model pricing), duration, raw output, validated output, and affected records are all stored for auditing and debugging.

Rate limiting and resilience

The OpenAiModelRequest job handles OpenAI rate limit errors gracefully: it parses the retry window from the error message (e.g. "try again in 244ms" or "try again in 2s"), enforces a minimum 10-second back-off, and releases itself back to the queue with the appropriate delay. The job allows up to 10 attempts before it is considered permanently failed.

Configuration summary

The main control points are:

  • config/textstemapp.phpopenai section: feature flags, model, queue toggle
  • config/structured-data.php — organisation details for JSON-LD generation (publish with php artisan vendor:publish --tag=structured-data)
  • config/textstem.phpaccessibility and seo sections: WCAG level, queue toggle, report retention
  • The Prompt Manager admin tool — live prompt overrides without re-deploying

Switch from OpenAI to Anthropic

NOTE - We are looking at switching from OpenAI to Anthropic AI in hte near future, most likely as a configurable option (depending on how much work is needed managing differences (such as Function/tool calling)