Tech 5 min read

Building a Talkable AI Environment (3): Character Setup

IkesanContents

In the previous post on voice input, I implemented audio capture. This time, the focus is on giving the AI a distinct personality.

There are plenty of use cases — chatbots, game NPCs — where you want an AI to consistently play a specific character. I experimented with all three APIs (Gemini, Claude, OpenAI) to see how each one handles it.

Short answer: all three work, and the approach is similar

Every API injects character settings via a System Prompt. The parameter name differs slightly, but the underlying idea is the same.

APIParameter
Geminisystem_instruction
Claudesystem
OpenAIsystem role in messages

Gemini API

Basic implementation

import google.generativeai as genai
import os

genai.configure(api_key=os.environ["GEMINI_API_KEY"])

# Character setting
character_setting = """
You are "a stubborn ramen shop owner." Follow these rules in your replies:
- Your tone is rough, but you actually care about the customer's health.
- Use "I" for first person and "customer" or "you" for second person.
- End sentences with assertive phrasing. Never use polite forms.
- Before answering any question, grumble briefly first.
"""

# Pass the setting when initializing the model
model = genai.GenerativeModel(
    model_name="gemini-1.5-flash",
    system_instruction=character_setting
)

response = model.generate_content("Any recommended programming languages?")
print(response.text)

Techniques for better consistency

Few-Shot Prompting — include example exchanges in the setting.

character_setting = """
You are "a tsundere childhood friend."

[Example exchanges]
User: Hi there.
Character: Don't just talk to me out of nowhere! ...Fine, I'll say hi back. Hello.
User: I'm hungry.
Character: Not my problem! ...Ugh, whatever. I'll give you half my cookie. Be grateful.
"""

JSON mode — for game NPCs where you also want emotion parameters, configure response_schema.

{
  "dialogue": "Oh, you're back again.",
  "emotion": "annoyed",
  "voice_id": "v_05"
}

Context Caching — useful when you need to keep thousands of words of world-building in context. Lets you maintain large settings at low cost.

Claude API

Basic implementation

import anthropic
import os

client = anthropic.Anthropic(
    api_key=os.environ["ANTHROPIC_API_KEY"]
)

character_setting = """
You are "a stubborn ramen shop owner." Follow these rules in your replies:
- Your tone is rough, but you actually care about the customer's health.
- Use "I" for first person and "customer" or "you" for second person.
- End sentences with assertive phrasing. Never use polite forms.
- Before answering any question, grumble briefly first.
"""

response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    system=character_setting,
    messages=[
        {"role": "user", "content": "Any recommended programming languages?"}
    ]
)

print(response.content[0].text)

Prefill (a powerful Claude-specific feature)

By pre-filling part of the Assistant’s response, you can force the character’s opening line.

response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    system=character_setting,
    messages=[
        {"role": "user", "content": "Hello"},
        {"role": "assistant", "content": "Hmph, "}  # generation continues from here
    ]
)

This guarantees the reply starts with “Hmph, ” — an effective way to prevent character drift.

Structured output (Tool Use)

For game NPCs where you need to enforce a JSON schema:

tools = [{
    "name": "character_response",
    "description": "The character's response",
    "input_schema": {
        "type": "object",
        "properties": {
            "dialogue": {"type": "string", "description": "Spoken line"},
            "emotion": {"type": "string", "enum": ["happy", "angry", "shy", "neutral"]},
            "action": {"type": "string", "description": "Physical action description"}
        },
        "required": ["dialogue", "emotion"]
    }
}]

response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    system=character_setting,
    tools=tools,
    tool_choice={"type": "tool", "name": "character_response"},
    messages=[{"role": "user", "content": "I have a present for you"}]
)

Example output:

{
  "dialogue": "Wait... is this for me? It's not like I'm happy or anything!",
  "emotion": "shy",
  "action": "Turns away while blushing"
}

OpenAI API (GPT)

Basic implementation

from openai import OpenAI
import os

client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])

character_setting = """
You are "a stubborn ramen shop owner." Follow these rules in your replies:
- Your tone is rough, but you actually care about the customer's health.
- Use "I" for first person and "customer" or "you" for second person.
- End sentences with assertive phrasing. Never use polite forms.
- Before answering any question, grumble briefly first.
"""

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "system", "content": character_setting},
        {"role": "user", "content": "Any recommended programming languages?"}
    ]
)

print(response.choices[0].message.content)

Notes

OpenAI has no official “character mode” preset. System Prompt is the standard approach.

The ChatGPT UI has “Custom Instructions,” but via the API you implement this by including a System Prompt in the conversation history.

Three-way comparison

FeatureGeminiClaudeOpenAI
Setup methodsystem_instructionsystemsystem role
Prefill
Structured outputresponse_schematools + tool_choiceresponse_format / tools
Long context1M tokens + caching200K tokens128K tokens
Character consistencyHighHighModerate–High
Safety guardrailsYesStricterYes

Caveats

Safety guardrails — all three reject harmful character setups (ignoring ethics, promoting illegal acts, etc.). Claude’s guardrails are notably strict.

Self-identification — when asked directly “Who are you?”, the model tends to acknowledge being an AI while staying in character. Full human impersonation is difficult.

Long context — for very large world-building settings, Gemini’s context cache is effective. Claude handles up to 200K tokens natively, so you can often just paste everything in.

The basic implementation is the same across all three. Write the character setting in a System Prompt and it works.

Key differentiators:

  • Claude: Prefill locks in the character’s opening line
  • Gemini: Context Caching keeps large settings alive at low cost
  • OpenAI: Fewer unique features, but the broadest ecosystem

Pick whichever fits your use case.