Most AI work is not a new product. It is a feature inside something you already run: a content tool in your CMS, an assistant in your SaaS app, a chatbot that deflects repetitive support tickets. The difficulty is rarely the API call — it is making an unpredictable, slow, per-token-billed dependency behave inside a codebase built on the assumption that functions return quickly and deterministically.

Where LLM features tend to pay off

The features that survive contact with real users are narrow and tied to something the user was already trying to do:

  • Content operations — drafting summaries, meta descriptions, alt text and taxonomy suggestions inside the editorial workflow, with a human approving before publish.
  • Support deflection — a chatbot grounded in your documentation and policies that hands over to a person cleanly when it is out of depth.
  • Structured extraction — pulling fields out of emails, forms, invoices or CVs into your existing schema.
  • Search that understands intent — natural-language queries over your catalogue or knowledge base.
  • In-product assistants — a feature inside your SaaS application that explains data or drafts something the user then edits.

A general "chat with our website" box, by contrast, is the feature most often built and least often used. Narrow beats broad almost every time.

Building it into Drupal and WordPress properly

Fourteen years in these platforms means the AI layer arrives as a citizen of the codebase rather than a bolt-on. In practice that means a real module or plugin with configuration in the admin UI, credentials in environment variables rather than the database, calls dispatched to the queue system instead of blocking a page render, results cached so identical requests are not paid for twice, and respect for the existing permission model, revision history and multilingual setup.

The failure mode I am most often called in to fix is an AI feature that calls an API synchronously during page build. It works fine in staging with one editor and falls over the moment real traffic and a slow provider response coincide.

Streaming, latency and the feel of the thing

A model can take several seconds to produce a full answer. A spinner for eight seconds feels broken; the same eight seconds with tokens streaming in feels fast. Streaming responses is usually the single highest-impact thing you can do for perceived quality, and it needs support end to end — server-sent events or equivalent, a front end that renders progressively, and infrastructure that will not buffer the whole response before flushing it.

Alongside that: timeouts with a sensible fallback, cancellation when the user navigates away, and never leaving the interface in a state where the user cannot tell whether anything is happening.

Cost control, because it is a running bill

Unlike most features, this one has a per-use cost that scales with traffic. Left unmanaged it produces an unpleasant invoice and no way to explain it. What I put in as standard:

  • Caching — identical or near-identical requests should not be paid for twice.
  • Prompt caching — where the provider supports it, the stable part of a long prompt is reused across calls at a fraction of the price.
  • Model routing — simple classification does not need your most expensive model; complex reasoning does.
  • Rate limiting and budgets — per user and overall, so one script cannot run up an unbounded bill.
  • Per-feature cost tracking — so the monthly number breaks down into things you can make decisions about.

Prompts as versioned code

Prompts belong in version control, reviewed like any other change, with a test set behind them. Treating them as configuration someone edits in production is how a working feature quietly degrades and nobody can say when or why.

The same applies to model upgrades. A new model version can change output format and break downstream parsing. Pinning versions deliberately, and having a test set to run before switching, turns that from an incident into a routine change.

Handling failure in the user interface

Providers have outages, rate limits and occasional refusals. A well-built feature degrades: it falls back to a non-AI path where one exists, shows an honest message rather than a raw stack trace, keeps whatever the user had typed, and logs enough context to diagnose the cause. The feature being temporarily unavailable should never take the page down with it.

Common questions

Can you add this to our existing site without rebuilding it?

Usually yes. On Drupal or WordPress it is a module or plugin alongside what you already run; on a custom application it is typically a service the app calls. A rebuild is only warranted where the existing architecture makes background processing or caching impossible, which is rare and would be flagged during discovery rather than discovered mid-build.

Which model should we use?

It depends on the task, your latency tolerance, your budget and any data residency requirements — and it will change, because the landscape moves quickly. I build behind an abstraction so the model is a configuration choice rather than a rewrite, and I pin versions so a provider update cannot silently change your output format.

How do we stop users abusing it?

Rate limiting per user and per IP, authentication on anything expensive, input length caps, output filtering, and spend budgets that cut off rather than escalate. For public-facing features these are requirements, not enhancements — an unauthenticated LLM endpoint is an open invitation to run up your bill.

Will an AI chatbot actually reduce our support load?

It can, if it is grounded in real documentation, honest about its limits, and hands over to a human cleanly. It will not if it is a generic model guessing at your policies — that generates a second wave of tickets from users correcting it. The deciding factor is the quality of the underlying content, which is why this often starts as a documentation exercise.

What ongoing maintenance does this need?

Realistically: watching cost and usage, re-running the test set when you change prompts or models, refreshing any grounding content as it changes, and reviewing conversations periodically to find where it is falling short. Budget for it as a small ongoing commitment rather than a project that finishes.

Want to talk this through?

Describe the problem in your own words — I will tell you what I would actually build, and what I would not.

Start a conversation