Skip to content

Add AI personas: an opt-in catalog of reusable characters with their own instructions, name, avatar and voice - #270

Merged
MikeAlhayek merged 8 commits into
mainfrom
claude/ai-persona
Oct 16, 2026
Merged

MikeAlhayek merged 8 commits into
mainfrom
claude/ai-persona

Conversation

@MikeAlhayek

@MikeAlhayek MikeAlhayek commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

What

A new, opt-in AI persona feature describes reusable characters: who the assistant is, the name it uses, its language, its avatar and the voice it speaks with. Greetings stay with the profile's welcome message, so one persona can serve profiles that open differently. An AI profile, a chat interaction or a profile template links to one optional persona by its technical name (AIPersonaMetadata.PersonaId). The persona is linked, not copied, so editing it changes everything that uses it from the next message.

A persona answers who; the system message and prompt templates still answer what.

Its own catalog, not an AI template

A template is a blueprint that is copied when it is used; a persona stays linked. So personas have their own catalog instead of being AI templates with a Persona source:

  • AIPersona is a named catalog item (instructions, assistant name, language, voice and voice deployment, avatar URL). IAIPersonaManager merges saved personas with every IAIPersonaProvider: persona files in the content root's Templates/Personas, or embedded ones registered with AddEmbeddedPersonas(assembly). A saved persona replaces a file with the same name. The catalog handler requires a unique name and instructions, and an update never changes the name.
  • Opt-in: .AddPersonas(personas => personas.AddYesSqlStores()) (or .AddEntityCoreStores()) on the AI suite builder. YesSql hosts create the index table with CreateAIPersonaIndexSchemaAsync. Without the feature, persona links are ignored and chats run with the profile's own instructions and voice.

How it works

  • IAIPersonaResolver is the single place that turns a link into a ResolvedAIPersona (DefaultAIPersonaResolver). A missing persona is logged and the request carries on without one. A Liquid error drops the instructions but keeps the rest.
  • Text on every request. AIPersonaCompletionContextBuilderHandler runs once the profile or interaction has built its system message and places the persona block first, then the prompt templates, then the system message, so business rules win a conflict. When the persona has an assistant name, the block starts with "Your name is …", and the instructions can use it as the Liquid variable assistant_name. The persona is also exposed under AICompletionContextKeys.Persona. Realtime sessions read their instructions from the same completion context.
  • Voice when a session opens. SelectVoiceAsync chooses the explicit voice (caller, profile or interaction), then the persona's voice, then the deployment default. The persona's voice is used only when the speaking deployment has the same provider as the deployment it was picked from. The chat hubs go through IServiceProvider.ResolvePersonaAsync / SelectPersonaVoiceAsync, so a host without personas keeps working.
  • Name and avatar in chat. @crestapps/ai-chat-ui takes a new assistantAvatarUrl setting (data-coreai-chat-assistant-avatar-url) beside assistantLabel. Assistant messages and the typing indicator show the persona's name and a round avatar in place of the robot icon; without them the chat looks as before. Hosts get the values from IServiceProvider.ResolveChatAssistantDisplayAsync(resource), which returns the default look when personas are not enabled.
  • Saving. PersonaChatInteractionSettingsHandler reads personaId; a client that omits the key keeps the current link. Profile JSON accepts a top-level PersonaId. AIChatSession.PersonaId records the persona a session started with.

Sample hosts

Both the MVC (YesSql) and Blazor (EF Core) hosts add personas:

  • a Personas admin page and dashboard card to list, create, edit and delete personas (a file-based persona opens as a copy that replaces the file once saved);
  • an AI persona picker on AI profiles and Profile templates, hidden when personas are not enabled;
  • the persona's name and avatar on assistant messages in the chat page and the widget.

Both hosts were checked live: personas created through each store, linked to profiles, and chats answered in character with the persona's name and avatar.

New public types

AIPersona, ResolvedAIPersona, IAIPersonaManager / DefaultAIPersonaManager, IAIPersonaProvider with AIPersonaFileSystemProvider and EmbeddedResourceAIPersonaProvider, AIPersonaFileParser, IAIPersonaResolver / DefaultAIPersonaResolver, AIPersonaMetadata, AIPersonaExtensions, AIPersonaServiceProviderExtensions, AIPersonaResolverExtensions, AIChatAssistantDisplay, CrestAppsAIPersonasBuilder, PersonaChatInteractionSettingsHandler, AICompletionContextKeys.Persona, AIChatSession.PersonaId, and the YesSql AIPersonaIndex.

Tests and docs

  • Tests cover the resolver, the persona handler, the manager's merge rules, catalog validation, file parsing and providers, the opt-in registration, the chat display mapping, and both sample view models.
  • Full suite passes locally (4,722). The solution builds in Release with -warnaserror. npm run lint, npm run check and test:unit pass, and the rebuilt wwwroot assets are committed.
  • Docs: AI Personas page, an Assistant name and avatar section on the Chat page, and 2.0.0 changelog entries.

The Orchard Core side (persona editor, pickers, built-in persona files, recipe step, chat views) follows on a CrestApps.OrchardCore branch once this ships in a preview.

🤖 Generated with Claude Code

…and voice

A persona describes who the assistant is: rendered Liquid instructions, the name it uses,
an optional greeting, its language and the voice it speaks with. An AI profile, chat
interaction or profile template links to one optional persona by its technical name
through AIPersonaMetadata.

- AITemplateSources.Persona, PersonaTemplateMetadata, AIPersonaMetadata and AIPersona.
- IAIPersonaResolver / DefaultAIPersonaResolver resolve the persona (missing personas and
  Liquid errors degrade quietly) and choose the voice: explicit, then the persona's when the
  speaking deployment belongs to the same provider, then the deployment default.
- The profile and chat interaction completion context builders place the persona's
  instructions before the prompt templates and the system message, and expose it under
  AICompletionContextKeys.Persona. Realtime sessions get it through the same context.
- The chat hubs pick voices through the resolver and use the persona's language as the
  speech language when none is given.
- PersonaChatInteractionSettingsHandler saves the personaId setting; profile JSON accepts PersonaId.
- AIChatSession.PersonaId records the persona a session started with.
- Persona files use Source: Persona front matter; profile templates may name a PersonaId.
- Docs: AI Personas page and 2.0.0 changelog entry.
A greeting belongs to the conversation a profile starts, and the profile's welcome
message already covers it. Keeping it off the persona lets one persona serve profiles
that open differently.
Both sample hosts can now create AI Persona templates (instructions, assistant name,
language, a voice picked from a realtime or text-to-speech deployment, avatar) and link
a persona to an AI profile or a Profile template. Applying a Profile template copies its
persona link to the new profile. When a profile leaves its voice empty and its persona has
one, the voice picker reads "Use persona voice (...)".
A template is a blueprint that is copied when it is used; a persona stays
linked, so an edit reaches everything that uses it. Personas now have their
own catalog instead of being AI templates with Source = Persona.

- AIPersona is a named catalog item; the runtime result is ResolvedAIPersona.
  PersonaTemplateMetadata and AITemplateSources.Persona are gone.
- IAIPersonaManager merges saved personas with IAIPersonaProvider ones: files
  in the content root's Templates/Personas, or embedded with
  AddEmbeddedPersonas. A saved persona replaces a file with the same name.
- Opt-in: .AddPersonas(p => p.AddYesSqlStores() / AddEntityCoreStores()).
  It registers the manager, the resolver, the catalog handler (unique name,
  instructions required) and a completion handler that places the persona's
  instructions at the top of the system message once the profile or
  interaction has built it. Without the feature, links are ignored.
- The chat hubs resolve personas through IServiceProvider extensions that
  treat a missing feature as no persona.
- YesSql: AIPersonaIndex and CreateAIPersonaIndexSchemaAsync. EF Core: a
  named document catalog.
- MVC and Blazor samples: a Personas admin page and gated persona pickers;
  the Persona source is removed from the template editors.
- No migration: personas had not shipped.
A persona's assistant name was only used for display, so a model asked "Who
are you?" did not know it unless the instructions happened to say it.

- The persona block now starts with "Your name is X." when the persona has an
  assistant name (AIPersonaExtensions.GetPersonaBlock, used by PrependTo).
- The instructions are rendered with the name as the Liquid variable
  assistant_name; a link parameter with that name wins.
Both sample hosts list Personas on the dashboard next to Templates, shown only
when the host adds the persona feature.
AI Chat sessions, the chat widget and Chat Interactions now show the persona a
profile or interaction links to.

- ai-chat-ui: a new assistantAvatarUrl setting beside assistantLabel. Assistant
  messages and the default typing indicator show the name and a round avatar in
  place of the robot icon, pulsing while a reply streams; without them the chat
  looks as before. Custom templates call getAssistantAvatarUrl(message), and a
  typing indicator can use the {{ assistantIcon }} token.
- AIChatAssistantDisplay, AIPersonaExtensions.ToChatAssistantDisplay and
  IAIPersonaResolver.ResolveChatAssistantDisplayAsync map a persona to those
  settings. The MVC and Blazor sample hosts pass them through.
Rebased onto the persona catalog: the display maps a ResolvedAIPersona, and
the new IServiceProvider.ResolveChatAssistantDisplayAsync returns the default
look when the host did not add personas. The MVC and Blazor chat pages and
widgets use it instead of requiring IAIPersonaResolver.
@MikeAlhayek MikeAlhayek changed the title Add AI personas: reusable characters with their own instructions and voice Add AI personas: an opt-in catalog of reusable characters with their own instructions, name, avatar and voice Oct 16, 2026
@MikeAlhayek
MikeAlhayek merged commit c068b2c into main Oct 16, 2026
9 checks passed
@MikeAlhayek
MikeAlhayek deleted the claude/ai-persona branch October 16, 2026 18:38
MikeAlhayek added a commit to CrestApps/CrestApps.OrchardCore that referenced this pull request Oct 16, 2026
…to 2.0.0-preview.226

- CrestAppsCoreVersion moves to 2.0.0-preview.326, which carries the
  system prompt template provider (CrestApps/CrestApps.Core#266) this
  branch adopts, and the AI persona catalog (CrestApps/CrestApps.Core#270).
- @crestapps/ai-chat-ui moves to 2.0.0-preview.226 in AI Chat, Chat
  Interactions and Resources: vendored assets rebuilt, CDN URLs and SRI
  hashes recomputed. The package no longer ships CSS source maps, so the
  two vendored .css.map files and their Assets.json entries are removed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant