AI girlfriend creator: how the settings turn into a character
AI application developer working with language models since 2022. Built several chatbots with memory and personality.
A character builder is a form: you toggle some chips, press create, and get a companion. Underneath it is much simpler than the interface implies and considerably more honest than these interfaces usually are. Almost everything you pick turns into three text fields that later feed the chat system prompt and the image prompt.
This is written by the developer who wrote the code, so there is no claim here that cannot be checked against the source. Where the pipeline cannot do something, that is what I will say: it cannot.
What the builder lets you set, and what it does not
The set of settings is fixed and narrower than a first glance suggests. Everything you choose collapses into eight groups of fields:
- Identity — gender and a name of up to 32 characters. The name is sanitised: angle brackets, braces, square brackets, slashes, pipe and a set of service characters are stripped, whitespace collapses, the result is truncated to 32. The reason is simple — the name is rendered on the site and fed into the prompt, which makes it a vector for prompt-tag injection.
- Style — realistic or anime. Not cosmetic: style picks the image model, the negative prompt set, the art direction, and the entire mechanism that keeps the face stable.
- Appearance — ethnicity (7), hair colour (7), hair length (3), eye colour (5), body type (6 for a female character, 5 for a male one), height (3) and, for a female character only, bust size (3), plus 600 characters of free text.
- Personality — 12 trait presets (caring, playful, dominant, submissive, shy, confident, intellectual, mysterious, romantic, possessive, funny, nurturing) plus 600 characters of free text.
- Opening line — a greeting of up to 400 characters. Optional, but with a practical consequence: leave it empty and the character opens the chat in silence.
- Intimacy style — an explicitness slider from 0 to 100 (default 34) and 8 preference chips (romance, domination, submission, roleplay, dirty talk, teasing, possessiveness, praise), plus free text.
- Privacy — private, or available by link.
- Preview image — a technical field, covered separately below.
Now the more important list: what is not there.
- Age. There is no field at all. Age is not an appearance parameter, it is a legal requirement: every character on the service is an adult, enforced at the moderation and negative-prompt level rather than handed to the user as a setting.
- Clothing. There is no clothing chip, and that is deliberate. Clothing is described in the scene prompt on every generation — the model picks the level of undress from context.
- Voice. The API still accepts a voice_id field, but it is forcefully set to None — voice selection is temporarily out of the interface.
- Bio and description. The model has a description column, but the builder does not fill it: catalog bios come from personality_prompt, and user characters do not reach the public catalog at all.
- Editing after creation. There is no update endpoint. Create, list, delete. A typo in the personality means deleting the character and building it again — taking the entire conversation history with it.
How the settings become a prompt
Here is the mechanism with no marketing wrapper. A backend function assembles the visual profile. First comes the subject — “beautiful woman” or “handsome man”, also selected by gender. Then the tags are appended in a fixed order: ethnicity, hair colour, hair length, eye colour, body type, height, and bust if the character is female. Each chip maps to a short English phrase: “brunette hair”, “medium bust”, “Slavic features”.
The order is not accidental; it is fixed in code. Then photo traits, if any, then your free text, all joined with periods. If nothing at all was selected, a neutral fallback — “attractive adult character” — keeps the prompt from ever being empty.
Personality is assembled differently. The 12 presets are not decorative labels but ready-made phrases with gender: “caring, attentive to the feelings of the person she talks to” and “caring, attentive to the feelings of the person he talks to” are two rows under one key. Select nothing and you get “a lively, emotional personality” instead. Your free text is appended after the presets rather than replacing them, and that is deliberate: the presets supply the grammatical frame, the free text enriches it. If free text replaced presets, an empty field would leave personality_prompt blank and the model would get a character with no personality at all.
Intimacy style is built from two parts. The 0–100 slider maps to one of four anchor phrases at thresholds: 0 — “romantic and reserved…”, 34 — “warms up gradually, comfortable with flirting…”, 67 — “openly flirtatious and sexually confident…”, 85 — “very open and explicit…”. Four steps rather than a hundred, because a smooth gradient phrase sounds noticeably worse in conversation than four meaningful states the model switches between by context. On top of the anchor go the selected preference chips and your free text.
Last mechanical detail. The three fields go into a system prompt rebuilt in the user's language. personality_prompt and intimacy_profile stay in Russian, because the system prompt is built from Russian phrasing anyway; translating them per message would be a wasted model call. visual_profile is translated once, at creation, because image models are trained on English tags.
How appearance is set: seed, preview, and the uploaded photo
First, the seed. Every character has a reference_seed field, assigned on the very first photo request using (id * 7919 + 104729) mod 2^31 — not a random number and not a generated UUID, but a deterministic function of the character's database id. The point is that the same character always starts generation from the same point: with an unchanged prompt you do not get the identical frame, but a very close one in composition and colour. At creation time the field is None; the seed is assigned lazily.
Second, the preview. The trial-generation button creates a temporary, non-persisted character object and asks the image model for one full-body frame with a neutral scene prompt. That object's seed comes from the user id rather than the character id, so repeated previews are stable within one account. Then there is a fork: if you press create and the frontend sends that same image back in the preview field, it is saved as the character's master portrait and immediately becomes the avatar.
Third, the uploaded photo. The photo is not saved: the bytes go into a single vision call and go out of scope, and the model returns not a face but general traits — hair colour and length, eye colour, build and height, skin tone, style cues such as glasses or makeup. The limit is 40 words. That call's system prompt requires describing “a DIFFERENT, fictional character inspired by the style” and forbids naming or implying who the person is, and a separate check rejects photos of minors, recognisable public figures, and frames without a clear human face. The feature is “inspire”, not “clone”.
Now the honest part about identity nodes. Nodes that transfer facial traits from a reference do exist here — in the fallback pipeline on RunningHub, where the master portrait is passed into the workflow's image node. But the main pipeline runs on Venice and is pure text-to-image: neither the master portrait nor an identity node takes part, and everything is held together by anchor tags plus the seed. The reason is measured: a full anime frame through the edit pipeline took about 30 seconds against roughly 5 for text-to-image, in exchange for a face stability gain the tags covered well enough in practice.
What the builder does not do, and why
The main thing it does not do is let you “just upload a photo and get that same person”. Not for ethical reasons and not out of laziness, but for three technical ones.
- A vision call cannot return a parametric face vector. To generate “the same” person you need a face embedding or a reference image in a node. We get neither — 40 words of tags, and a similar fictional character built from them.
- A copy of a real face is identifying a real person. The code forbids it: the vision prompt requires describing a different character, and that prohibition is baked into the very string that forms the description.
- Predictability is not recognisability. Even with a perfect identity node the result is a repainting, not the same person. Promising “upload a photo, get them back” would be a promise the pipeline cannot keep.
What happens after you save
This works differently from what “publish to the catalog” would suggest. User characters do not reach the public catalog at all. That is deliberate: the catalog is built from system characters plus approved public user characters, but the builder accepts only two privacy values — private and shared-by-link — and any other value, public included, is coerced to private. Nothing you build can leak into the public showcase by accident.
There is no pre-creation moderation queue either — the character is created approved immediately. But the name, the final appearance (already in English) and the intimacy profile all pass through a blocklist, and a hit means the character is not created at all.
The count limit is 30 characters per account; exceeding it returns a 429. Access is as stated — private means only you, shared-by-link means anyone holding the link, which cannot be guessed because it is a random token, and whose page is excluded from indexing.
Site and Telegram
On the site, a button opens the same chat widget used for system characters, only carrying your character id and share token; access is checked as “catalog character of an allowed style, or owner, or presented share link”, and yours passes on the second branch.
Telegram is slightly more involved, and that is a decision rather than an oversight. After creation you get two links, and they are not interchangeable. The first is personal, carrying an account-link token: opening it links your Telegram to the web account and grants access to the character. It must not be forwarded, because someone else's Telegram opening it would bind to your account. The token lives 30 days. The second is based on the share token: it can be sent to anyone, works for a private character too, and binds nothing.
The personal token is written to three Redis databases at once — the Russian, English and Portuguese bots — because each reads it only from its own. A small detail in the code, and a non-obvious reason the English version of the link would otherwise “just work by itself”. The bots themselves are one per language, on purpose: language affects the system prompt, the register and the form of address, and switching mid-conversation breaks the character, so the bot is chosen once at entry.
What it costs and how long it takes
Creating the character itself is free. Exactly one step is paid: the trial generation in the preview, priced as an ordinary photo — 14 crystals.
Photo upload for inspiration is the only builder feature that requires an actual paid subscription, any of the three tiers: on the free tier the server answers 402 and the frontend shows an upgrade prompt.
On timing: submitting the form is three string assemblies, a pre-filter and a database write — fractions of a second. Generating the greeting, if you did not write one, is a synchronous single model call, so the character will not appear instantly.