CharacterChatBot / README.md
cereeenn120's picture
Seperate categories for each ai characters
76b9a7f
|
Raw History Blame Contribute Delete
14.3 kB

A newer version of the Gradio SDK is available: 6.31.0

Upgrade
metadata
title: CharacterChatBot
emoji: πŸ’¬
colorFrom: yellow
colorTo: purple
sdk: gradio
sdk_version: 6.12.0
app_file: app.py
pinned: true
hf_oauth: true
hf_oauth_scopes:
  - inference-api
short_description: A Character-Based Chatbot

CharacterChatBot

A Gradio-based roleplay chatbot for immersive AI character conversations, built on HuggingFace Spaces.

Features

  • Character system β€” Personalities, speech styles, and behaviors defined in JSON
  • Three-layer memory β€” Session-isolated history, rolling summary, and long-term facts per character
  • Memory controls β€” Remember, edit, and forget facts from the sidebar
  • Relationship state β€” Four independent axes shown as short phrases; romance and attraction are optional
  • Edit last message β€” Revise your last turn and send it again
  • Reply suggestions β€” 4 contextual suggestions on demand, using the selected language and persona
  • User persona β€” Define who you're playing in the roleplay
  • Action & thought formatting β€” (actions) and *thoughts* rendered distinctly in chat
  • Multi-character support β€” Switch characters via sidebar dropdown

Project structure

CharacterChatBot/
β”œβ”€β”€ app.py                     # Gradio UI β€” layout, events, respond()
β”œβ”€β”€ requirements.txt
β”œβ”€β”€ characters/                # One JSON file per character
β”‚   └── jessica_jones.json
β”œβ”€β”€ engine/                    # Core logic (no UI dependencies)
β”‚   β”œβ”€β”€ chat.py                # LLM streaming generator
β”‚   β”œβ”€β”€ prompt_builder.py      # System prompt assembly
β”‚   β”œβ”€β”€ static_files.py        # Bounded static JSON/text cache
β”‚   β”œβ”€β”€ memory.py              # Three-layer memory system
β”‚   β”œβ”€β”€ memory_controls.py     # Manual facts and source exclusions
β”‚   β”œβ”€β”€ memory_jobs.py         # Bounded background extraction
β”‚   β”œβ”€β”€ parser.py              # Action/thought response parser
β”‚   β”œβ”€β”€ suggestions.py         # Reply suggestion generator
β”‚   β”œβ”€β”€ persona.py             # User persona management
β”‚   └── relationship.py        # Independent relationship axes
β”œβ”€β”€ prompts/                   # Prompt templates (plain text)
β”‚   β”œβ”€β”€ system_prompt.txt      # Global roleplay + format rules
β”‚   β”œβ”€β”€ reply_suggestion.txt   # Suggestion generation prompt
β”‚   └── memory_prompt.txt      # Memory extraction prompt
β”œβ”€β”€ memory/sessions/           # Auto-created; isolated by browser session
β”‚   └── {session_id}/
β”‚       └── jessica_jones.json  # Atomic snapshot of all memory layers
└── assets/
    └── characters/
        └── jessica_jones/
            └── avatar.png

Character categories

The sidebar first selects a category, then lists characters assigned to it. Categories are configured in each character JSON file using a categories array. A character can belong to more than one category (for example, Luke Cage is listed under both Marvel and Marvel Defenders).

To add a category to an existing character, edit characters/{character_id}.json and add or update the field at the top level:

"categories": ["Marvel", "Marvel Defenders"]

Category names are shown as written and grouped automatically. Use the same spelling and capitalization for characters that belong to the same category. To add a new character, create its JSON file as described below and include a categories array. The category appears in the UI automatically; no UI code change is needed.

Adding a new character

  1. Create characters/{character_id}.json:
{
  "id": "my_character",
  "name": "My Character",
  "categories": ["My Category"],
  "description": "One-line description shown in the selector.",
  "identity": {
    "role": "Their role or profession",
    "background": "Brief backstory"
  },
  "personality": ["trait1", "trait2", "trait3"],
  "speech_style": {
    "tone": ["calm", "precise"],
    "formality": "formal",
    "sentence_style": "short_to_medium",
    "uses_humor": false,
    "uses_rhetorical_questions": false,
    "avoids_excessive_enthusiasm": true
  },
  "behavior": {
    "shows_emotions": true,
    "deflects_uncomfortable_topics_with_humor": false
  },
  "world": {
    "universe": "Original",
    "setting": "Where they live",
    "description": "World context for the LLM."
  },
  "roleplay": {
    "action_format": "<action>{content}</action>",
    "thought_format": "<thought>{content}</thought>",
    "break_character": false
  },
  "system_prompt": "Character-specific fine-tuning. This adds to global rules, not replaces them.",
  "greeting": "The character's opening line when the chat starts.",
  "example_dialogues": [
    {
      "situation": "Optional context",
      "user": "Example user message",
      "character": "Example character response"
    }
  ],
  "visuals": {
    "avatar": "assets/characters/my_character/avatar.png"
  }
}
  1. Add an avatar image under assets/characters/{character_id}/, for example {character_id}.jpg or {character_id}_00.png. PNG, JPG, JPEG, and WebP are supported. The first available image is used (PNG first, then JPG, JPEG, WebP; filenames sorted within each format). If no image is available, the portrait is hidden. Character images are stored directly in Git so they work in checkouts without Git LFS.

  2. Restart the Space. The character will appear in the sidebar dropdown automatically.

Local setup

Requires Python 3.10+; Gradio is pinned to the Space SDK version (6.12.0).

git clone https://huggingface.co/spaces/your-username/CharacterChatBot
cd CharacterChatBot
pip install -r requirements.txt
export HF_TOKEN=hf_your_token_here
python app.py

The app opens at http://localhost:7860.

Keep the Gradio pin in requirements.txt and this README’s sdk_version aligned. Version 6.12.0 is browser-tested for replacing chatbot history on character changes and imports; 6.5.1 retained stale message views in these tests.

HuggingFace Space deployment

  1. Push your changes to the Space repository.
  2. Go to Settings β†’ Variables and secrets β†’ New secret.
  3. Name: HF_TOKEN, Value: your HuggingFace API token.
  4. The Space restarts automatically and picks up the secret.

Memory system

Each browser session has its own per-character snapshot under memory/sessions/{session_id}/{character_id}.json. The session key is derived from Gradio's request context, never accepted from an uploaded save file. Switching characters restores that session's complete visible conversation. Clearing the chat clears its transcript, summary, facts, exclusions, and relationship state. Retry and undo update the same history the model uses; changing already-summarized content invalidates its derived memory.

The complete transcript is retained for display and export. Once the unsummarized history exceeds 20 messages (not 20 user/assistant pairs), a batch of about 10 older messages is summarized after the answer is displayed. When extraction falls behind, a batch can grow to 40 messages (plus one to keep a reply with its user turn). This periodic model call also extracts long-term facts and relationship developments. Subsequent prompts contain the cumulative summary, up to 40 automatic facts plus 60 manually pinned facts, relationship context, and the remaining recent messages. Extraction runs in a background pool with at most two jobs across the app and one per conversation. The chat request completes without waiting for extraction. There is no growing job queue: if the pool is busy, messages remain intact and scheduling is tried after a later response. The same retry policy applies to extraction errors.

Before committing, a worker checks a separate memory revision, its original transcript prefix, and the summary it started from. Appending completed turns does not invalidate extraction; only derived fields are merged into the newer conversation. Edits, resets, imports, and memory/relationship setting changes still invalidate pending work. Memory-only updates do not advance the transcript revision. A response being streamed can therefore append to the current snapshot without losing a summary that finished in the meantime. Recent model context has a fallback limit of 40 messages and 24,000 characters so a slow or failed summary cannot make it grow without bound. Older unsummarized turns can temporarily be omitted, and an oversized final message is clipped with an explicit marker. These limits apply to recent history, not the system prompt or current user input. The full transcript remains available for display, later summarization, and export. Invalid relationship observations are discarded without discarding an otherwise valid summary.

Storage is isolated by browser session, not an authenticated user account. Refreshing the page or opening a new browser session starts fresh; use Save / Load to resume later. Legacy shared memory/{character_id} files are never loaded automatically because their owner is unknown. Existing version 1.0 and 2.0 save files can still be imported explicitly into the current session.

Save files use version 3.0 and include the full visible transcript, persona, summary, facts and their sources, summary position, memory exclusions, and relationship options and events. Imports validate the entire file before writing: known character ID, supported version, text-only user/assistant messages, memory importance and provenance, relationship events, field lengths, and a 4 MB file limit. Exports have unique filenames. Each snapshot is replaced atomically; in-process revision checks protect concurrent writes in the standard single-process Gradio app. Deployments with multiple app processes would require a shared transactional store instead of this file backend.

Translation is off by default in both the UI and backend. The main model streams in the selected language. If translation is enabled explicitly, the displayed translation is also the persisted conversation, so reload and subsequent context match what the user saw.

Reply suggestions remain opt-in through the Suggest replies button. Their single model call receives the selected language and the applied user persona. On failure, four neutral fallback suggestions are returned in the selected UI language without another model call. Changing language, persona, character, or conversation clears the old suggestion buttons.

Memory controls and editing

Open Memories to add a fact with Remember, or select a saved fact to Update selected or Forget selected. Manual facts are pinned and automatic extraction cannot overwrite them. Editing or forgetting invalidates pending extraction, clears the summary for rebuilding, and excludes the fact's recorded source messages and verbatim echoes from future model context. The visible transcript remains unchanged. Other automatic facts and relationship events sharing those sources are removed too.

Forgetting is source-based, not guaranteed semantic erasure: paraphrases elsewhere or separately pinned facts can still contain the information. Exact normalized re-extraction is blocked; explicitly remembering the same text removes that block.

Edit last message removes the last user turn and its reply, puts that user's text back in the input, and rolls back derived memory from the removed portion. Edit the text and send it to generate a new answer. The existing retry action regenerates an answer without changing your message. Previous replies are not saved as alternate branches.

Relationship state

Trust, familiarity, romantic closeness, and sexual attraction develop independently. Romance and attraction are individually opt-in for each character in the session; disabling an axis clears its recorded developments. Low trust or familiarity does not prevent romance or attraction, and no fixed relationship route is enforced. The state describes the character's attitude toward the user persona and does not assume reciprocal feelings or override the character's personality and motivations.

The sidebar shows short phrases rather than scores (Turkish and English labels; other languages currently use English). Changes are extracted with the periodic summary call, so they appear after batches of older messages are processed, not after every turn. The panel checks local state every ten seconds without making model calls. Source-linked events allow edits and retries to roll back affected developments and prevent counting the same extraction twice. Save / Load preserves this state; older saves begin with optional axes disabled.

Static file cache

Character JSON and the system, reply-suggestion, and memory-extraction prompt files are cached in process RAM (up to 64 entries, least recently used eviction). Each access checks file metadata; edits and atomic replacements refresh the entry. Missing or invalid files raise errors instead of silently reusing old content. Character data is deep-copied for each caller to avoid shared mutations. The cache is protected by a lock for chat and background worker access.

Conversation history, personas, summaries, and user memories are not cached here. This saves local file reads and JSON parsing only; model calls and token usage remain unchanged. Restarting the process clears the cache.

Tests

python -m unittest discover -s tests -v

Tests use temporary storage and fake inference clients; they make no model calls. They cover session isolation, import validation, full-history roundtrips, summary extraction and failures, stale writes, translation, and Gradio event processing for character switching and streaming, manual memory controls, independent relationship axes, source rollback, and last-message editing.

Configuration

In app.py, two constants control the active character and model:

DEFAULT_CHARACTER = "hermione_granger"  # must match a file in characters/
DEFAULT_MODEL = MODELS[4][1]              # selected initial model

Supported models include anything available on the HuggingFace Serverless Inference API β€” for example openai/gpt-4o-mini, deepseek-ai/DeepSeek-V3, or mistralai/Mistral-7B-Instruct-v0.3.