How to Solve AI Character Consistency: Building a “Character Bible” Generator with Claude, GPT, and Gemini

How to Solve AI Character Consistency: Building a “Character Bible” Generator with Claude, GPT, and Gemini

If you’ve ever tried to generate a recurring character across multiple AI image panels, you’ve almost certainly run into the same wall: the character’s face, outfit, or color palette subtly (or drastically) changes from one generation to the next. Solving AI character consistency is one of the hardest unsolved problems in automated comic and illustration pipelines — and it’s exactly what a NORA Custom Script Agent called the Character Bible Builder was designed to fix.

This post walks through how that tool actually works under the hood: how it takes a rough character concept and expands it into a structured “character bible” via an LLM call, and why specific engineering decisions — schema-first prompting, provider-agnostic AI dispatch, retry logic, dual-write outputs — were made the way they were. If you’re building AI-driven comic pipelines on NORA, or just want a working blueprint for solving character drift in your own tooling, this is a real, production-grounded example rather than a generic pattern.

What Is Character Drift and Why Does It Break Comic Art Pipelines?

Character drift happens when the same character concept produces visually different results across separate AI image generations. One panel gives your hero blue eyes, the next gives them green. The jacket has three buttons in one render and a zipper in the next. Individually these are minor, but stacked across a full comic page — or a full webcomic series — they destroy the illusion of a consistent, believable character.

The root cause is almost always the same: vague prompts. If your prompt says “a woman with dark hair and a red jacket,” the image model has enormous creative latitude to interpret “dark hair” and “red jacket” differently every single time. There’s no anchor, no committed detail, nothing forcing the model toward the same visual choices twice.

This is a structural problem, not a model-quality problem — even the best image generation models will drift if the input prompt itself is ambiguous. That means the fix has to happen upstream, before the image generation step, at the prompt-engineering and data-structuring layer.

This is precisely the gap the Character Bible Builder targets. It’s built for exactly the audience that runs into this daily: developers building AI-driven comic/illustration content pipelines, webcomic creators automating panel generation, and NORA CSA developers who need a character to appear consistently across dozens or hundreds of renders. Rather than trying to patch consistency at the image-generation stage, the tool solves it earlier — by generating an exhaustively specific, machine-checkable description of the character before a single image prompt is ever built.

How Does a Character Bible Enforce Visual Consistency Across AI-Generated Panels?

A “character bible” in this context is a structured JSON document that expands a rough concept — just freeform text describing a character idea — into a rigid set of visual facts: physical traits, costume details, color values, personality-driven visual cues, and explicit style notes. The Character Bible Builder generates this document once per character, then every downstream image-generation call references the same bible instead of re-deriving details from scratch.

The pipeline is straightforward at a high level:

  1. NORA supplies a concept.json file via its memoryFiles array, containing a character_id and raw_input (the freeform character concept).
  2. The script calls an LLM (Claude, GPT, or Gemini) with a strict schema and prompt template.
  3. The LLM’s JSON output is validated, repaired if necessary, and the character_id is force-overwritten to guarantee ID integrity.
  4. The final character_bible.json is written to disk, ready to feed every future image-generation prompt for that character.

The critical design insight here is that consistency isn’t achieved by hoping the LLM remembers earlier choices — it’s achieved by generating a single source of truth once, then reusing it deterministically. Every subsequent panel prompt pulls from the same bible, so the “same” red jacket is always described with the same hex code and the same cut, because it’s literally the same JSON field being read, not regenerated.

This also decouples creative generation from consistency enforcement. The LLM gets one creative pass to invent the character; everything after that is mechanical reuse, which is exactly where consistency should live.

Building a Provider-Agnostic AI Dispatch Layer for Claude, GPT, and Gemini

One of the more practical engineering decisions in this tool is that it doesn’t lock into a single AI provider. Instead, it implements a dispatch layer — call_ai — that routes to call_anthropic, call_openai, or call_gemini depending on what’s specified in run_config. Operators can swap providers without touching a single line of code.

Why does this matter for a character-consistency tool specifically? Because pricing, rate limits, and output quality across Claude, GPT, and Gemini all shift over time, and a production comic pipeline shouldn’t be hostage to one vendor’s API changes or outages. Provider-agnosticism here is a pragmatic hedge against vendor lock-in, not an architectural flex.

It also surfaces an interesting real-world detail: each provider’s SDK has different conventions for system prompts. Anthropic and Gemini use a distinct system_instruction/system parameter, while OpenAI expects a system role message inside the messages array. The dispatch layer has to account for these differences per-provider rather than pretending they’re identical:

def call_ai(provider, system_prompt, user_prompt, run_config):
    if provider == "anthropic":
        return call_anthropic(system_prompt, user_prompt, run_config)
    elif provider == "openai":
        return call_openai(system_prompt, user_prompt, run_config)
    elif provider == "gemini":
        return call_gemini(system_prompt, user_prompt, run_config)
    else:
        raise ValueError(f"Unsupported provider: {provider}")

Worth noting honestly: cost tracking isn’t uniform across providers yet. Anthropic cost is computed manually from a hardcoded per-token rate (e.g., $0.000005 in / $0.000025 out based on Opus 4.8 pricing), while OpenAI cost reporting is currently left at 0.0 — a real gap in cost parity that’s worth being aware of if you’re adapting this pattern for budget-sensitive pipelines.

Schema-First Prompting: Consistency Anchors, Hex Colors, and Negative Prompts

The single most important design decision in this tool is schema-first prompting. Instead of asking the LLM to “describe a character” in prose, the script sends a strict target JSON schema (BIBLE_SCHEMA) alongside the concept, forcing the model to make committed, checkable creative choices rather than vague, restateable descriptions.

Two schema design choices do most of the consistency-enforcing work:

Every color field requires both a human-readable description and a hex code. This directly targets the “vague descriptions cause inconsistent AI-generated images” failure mode. “Red jacket” is subjective and re-interpretable; #B0231C is not. By forcing the LLM to commit to a hex value at bible-generation time, every downstream prompt can reference the exact same color, every time.

consistency_anchors and negative_prompt_anchors are explicit, dedicated fields. Consistency anchors capture the non-negotiable visual details that must appear in every render — a specific scar, a signature accessory, an exact eye color. Negative prompt anchors capture what should explicitly be excluded, which is just as important for consistency as what’s included — image models drift not only by adding details incorrectly but by omitting ones that were previously present.

This schema-first approach is a deliberate rejection of prose-based prompting for anything that needs to be machine-checkable downstream. Once the schema forces the LLM into structured, explicit choices, the resulting bible becomes something you can programmatically inject into every future image prompt with zero ambiguity.

Handling Failure Gracefully: Retry Logic, Dual-Write Outputs, and Session Fallbacks in NORA

LLMs don’t always return clean JSON — markdown fences, trailing commentary, or malformed structures are common failure modes. Rather than building a full JSON-repair library, the script uses a pragmatic retry-once-with-stricter-prompt pattern: if parsing fails, it re-sends the request with an appended “CRITICAL” instruction reinforcing raw-JSON-only output.

def clean_json_response(text):
    # Strips markdown code fences (```json ... ```) before parsing
    text = text.strip()
    if text.startswith("```"):
        text = text.split("```")[1]
        if text.startswith("json"):
            text = text[4:]
    return text.strip()

This is a low-cost resilience mechanism: it costs one extra API call in the rare failure case, instead of maintaining brittle repair logic for every conceivable malformed-JSON shape.

Two other defensive patterns stand out. First, dual-write output: the final bible is written both to the session-scoped directory and as a flat copy at data/sessions/character_bible.json. This isn’t architectural elegance — it’s a direct workaround for NORA’s non-recursive memoryFolderPath scanning behavior, prioritizing compatibility with how the host system actually discovers files over theoretical cleanliness.

Second, fallback session directory resolution: the script first checks active_session.txt, then falls back to guessing a directory named after the character_id if that file is missing or stale. This kind of defensive coding matters a lot in agent sandboxes, where the assumption “the session file will always be there” is exactly the kind of assumption that eventually breaks a pipeline in production.

Getting Started

If you’re building a NORA-based comic or illustration pipeline, the Character Bible Builder is a solid template for solving AI character consistency the right way: generate a single, structured, schema-enforced source of truth once, then reuse it deterministically across every downstream image prompt. The core building blocks — a provider-agnostic call_ai dispatch layer, a strict schema with hex-coded colors and consistency anchors, a cheap retry pattern for JSON failures, and defensive session-path handling — are all patterns you can adapt even outside NORA.

Start small: define your own BIBLE_SCHEMA with the fields your image pipeline actually needs, wire up one provider first (Claude or GPT), and get the retry-and-validate loop working before adding multi-provider support. Consistency is a data-structuring problem before it’s a prompting problem — solve that first, and the rest of your pipeline gets dramatically easier to maintain.

Leave a Comment