Observed since 21 Oct, 10:08 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
ai-character-engine
A modular open-source engine for building stateful LLM-powered characters.
A Python SDK for AI characters with memory, tools and ongoing conversations.
Build companions, game NPCs and virtual assistants with a shared character runtime.
English | 繁體中文 | 简体中文 | 日本語 | 한국어
Español | Français | Deutsch | Português (Brasil)
Documentation | Installation | Examples | Architecture | Agent Skill
AI Character Engine helps you build AI companions, game NPCs and virtual assistants. Give a character a personality, let it remember interactions and keep track of unfinished work, then connect tools so it can act inside your application.
The engine manages the character’s data: conversations and long-term memories, emotion and relationship state, reflections, beliefs and ongoing goals. The model generates responses. Character records live outside the model, where you can inspect, revise and persist them, and keep using them when you change models.
Start with text chat or connect the engine to an existing desktop character, voice interface or game. The core works independently of any particular model or renderer; the VRM adapter is an optional package. Supports Linux, macOS and Windows with Python 3.11–3.13.
Also included: event traces, persistence replay, cognitive evaluation, HTTP/SSE/WebSocket service interfaces and offline SFT/LoRA training tools. These modules are optional. Start with a text character and add the pieces you need.
CharacterRuntime is the entry point for one character. It assembles context, calls models and tools, and records the turn’s result. Profile, current state, memory, beliefs and goals are stored separately and included within their context budgets.
flowchart TD
App["Your app, game or character interface"] <--> Runtime["CharacterRuntime"]
Context["Profile · State · Memory · Beliefs · Goals"] --> Runtime
Runtime <--> Models["Models and cognitive-role routing"]
Runtime <--> Tools["Application tools"]
Runtime --> Record["Turn recording and state updates"]
Record --> Context
Runtime --> Snapshot["Read-only snapshot"]
Snapshot --> Workers["Background summaries, reflection and cognition"]
Workers --> Commit["Proposal validation and commit"]
Commit --> Context
Background workers operate on snapshots. Their proposals are checked for provenance and revision before being committed, so an old task cannot simply overwrite newer character state. Models, speech services and renderers connect through interfaces; the runtime and commit flow coordinate writes to character data.
The architecture guide covers foreground/background flows, character isolation, world perception and extension interfaces.
You need Python 3.11–3.13.
git clone https://github.com/weryk153/ai-character-engine.git
cd ai-character-engine
python -m venv .venv
Activate the environment with source .venv/bin/activate on macOS/Linux or .venv\Scripts\Activate.ps1 in Windows PowerShell, then install:
python -m pip install .
Start a local OpenAI-compatible model server, such as LM Studio. Replace your-loaded-model below with the loaded model’s identifier and adjust the URL to your server. Both calls use the same character, so the second receives the earlier exchange.
import asyncio
from ai_character_engine import CharacterProfile, CharacterRuntime
from ai_character_engine.llm.local import OpenAICompatibleChatClient
async def main():
llm = OpenAICompatibleChatClient(
base_url="http://127.0.0.1:1234/v1",
model="your-loaded-model",
)
character = CharacterRuntime(
character=CharacterProfile(
id="mei", name="Mei",
description="A friendly companion who gives concise answers.",
),
llm=llm,
)
try:
print((await character.run_turn("Call me Alex.")).text)
print((await character.run_turn("What name did I ask you to use?")).text)
finally:
await llm.client.close()
asyncio.run(main())
This example uses conversation history. For continuity across restarts, add session save/restore. Long-term memory, reflection and goals require their own configuration. See the installation guide for the full setup.
A host that talks to one character — a chat window, a voice application, a desktop avatar — can skip that configuration: CharacterCompanion is the engine assembled with memory, mood, goals, reflection and interruption already wired, behind a single reply(). Its context is written into the conversation as it grows, so a local model’s prompt cache stays usable from turn to turn. See examples/companion_chat.py.
.env before running.API reference · Configuration · Deployment and operations · Extensions
Current version: 1.5.1. CI covers Linux/macOS/Windows × Python 3.11/3.12/3.13, plus API compatibility, persistence replay, failure injection, same-environment performance and package installation. See CI, validation and compatibility.
Issues and fixes are welcome; see CONTRIBUTING. Licensed under Apache-2.0. Third-party models, voices and assets retain their own licenses.
Download counts and popularity will appear here as the collector receives them.
| Package / target | Requirement | Relationship |
|---|---|---|
openaiEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | openai>=1.0.0 | runtime |
python-dotenvEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | python-dotenv>=1.0.0 | runtime |
pytestEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | pytest>=8.0; extra == "dev" | runtime |
pytest-asyncioEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | pytest-asyncio>=0.23; extra == "dev" | runtime |
httpxEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | httpx<1,>=0.27; extra == "dev" | runtime |
tzdataEnvironment{
"extras": "",
"marker": "sys_platform == \"win32\" and extra == \"dev\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | tzdata>=2024.1; sys_platform == "win32" and extra == "dev" | runtime |
fastapiEnvironment{
"extras": "",
"marker": "extra == \"service\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | fastapi<1,>=0.115; extra == "service" | runtime |
uvicornEnvironment{
"extras": "",
"marker": "extra == \"service\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | uvicorn<1,>=0.30; extra == "service" | runtime |
pydanticEnvironment{
"extras": "",
"marker": "extra == \"service\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | pydantic<3,>=2.7; extra == "service" | runtime |
websocketsEnvironment{
"extras": "",
"marker": "extra == \"service\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | websockets<17,>=13; extra == "service" | runtime |
qdrant-clientEnvironment{
"extras": "",
"marker": "extra == \"qdrant\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | qdrant-client<2,>=1.10; extra == "qdrant" | runtime |
httpxEnvironment{
"extras": "",
"marker": "extra == \"local\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | httpx<1,>=0.27; extra == "local" | runtime |
sounddeviceEnvironment{
"extras": "",
"marker": "extra == \"voice-audio\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | sounddevice<0.6,>=0.5; extra == "voice-audio" | runtime |
httpxEnvironment{
"extras": "",
"marker": "extra == \"voice-audio\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | httpx<1,>=0.27; extra == "voice-audio" | runtime |
httpxEnvironment{
"extras": "",
"marker": "extra == \"voice-sherpa\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | httpx<1,>=0.27; extra == "voice-sherpa" | runtime |
sounddeviceEnvironment{
"extras": "",
"marker": "extra == \"voice-sherpa\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | sounddevice<0.6,>=0.5; extra == "voice-sherpa" | runtime |
sherpa-onnxEnvironment{
"extras": "",
"marker": "extra == \"voice-sherpa\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | sherpa-onnx==1.13.8; extra == "voice-sherpa" | runtime |
sherpa-onnx-coreEnvironment{
"extras": "",
"marker": "sys_platform == \"darwin\" and extra == \"voice-sherpa\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | sherpa-onnx-core==1.13.8; sys_platform == "darwin" and extra == "voice-sherpa" | runtime |
numpyEnvironment{
"extras": "",
"marker": "extra == \"voice-sherpa\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | numpy<3,>=1.26; extra == "voice-sherpa" | runtime |
httpxEnvironment{
"extras": "",
"marker": "extra == \"voice-mac\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | httpx<1,>=0.27; extra == "voice-mac" | runtime |
sounddeviceEnvironment{
"extras": "",
"marker": "extra == \"voice-mac\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | sounddevice<0.6,>=0.5; extra == "voice-mac" | runtime |
faster-whisperEnvironment{
"extras": "",
"marker": "extra == \"voice-mac\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | faster-whisper<2,>=1.1; extra == "voice-mac" | runtime |
numpyEnvironment{
"extras": "",
"marker": "extra == \"voice-mac\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | numpy<3,>=1.26; extra == "voice-mac" | runtime |
datasetsEnvironment{
"extras": "",
"marker": "extra == \"training\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | datasets<5,>=3; extra == "training" | runtime |
transformersEnvironment{
"extras": "",
"marker": "extra == \"training\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | transformers<6,>=4.48; extra == "training" | runtime |
peftEnvironment{
"extras": "",
"marker": "extra == \"training\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | peft<1,>=0.14; extra == "training" | runtime |
trlEnvironment{
"extras": "",
"marker": "extra == \"training\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | trl<2,>=0.15; extra == "training" | runtime |
accelerateEnvironment{
"extras": "",
"marker": "extra == \"training\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | accelerate<2,>=1; extra == "training" | runtime |
datasetsEnvironment{
"extras": "",
"marker": "extra == \"training-qlora\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | datasets<5,>=3; extra == "training-qlora" | runtime |
transformersEnvironment{
"extras": "",
"marker": "extra == \"training-qlora\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | transformers<6,>=4.48; extra == "training-qlora" | runtime |
peftEnvironment{
"extras": "",
"marker": "extra == \"training-qlora\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | peft<1,>=0.14; extra == "training-qlora" | runtime |
trlEnvironment{
"extras": "",
"marker": "extra == \"training-qlora\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | trl<2,>=0.15; extra == "training-qlora" | runtime |
accelerateEnvironment{
"extras": "",
"marker": "extra == \"training-qlora\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | accelerate<2,>=1; extra == "training-qlora" | runtime |
bitsandbytesEnvironment{
"extras": "",
"marker": "extra == \"training-qlora\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | bitsandbytes<1,>=0.45; extra == "training-qlora" | runtime |
bdist_wheel · 528.1 KB · Python py3
sdist · 467.8 KB · Python source
Shared repository data · GitHub
Observed since 21 Oct, 10:08 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package_version:404aa1f736e5b3594934cbb802c12316d94d4feafe17bf716347899db4069e7e