Skip to content

[WIP, MAJOR] Introduce AI persona framework #1324

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: main
Choose a base branch
from

Conversation

dlqqq
Copy link
Member

@dlqqq dlqqq commented Apr 15, 2025

Description

This is a major feature PR introduces an AI persona framework to Jupyter AI and allows multiple AI personas to be added per chat. Personas are analogous to "bot accounts" in other chat applications in Slack or Discord.

High-level summary of changes:

  • "Jupyternaut" has been redefined as a default AI persona provided by Jupyter AI. Jupyternaut will use the same interface as every other AI persona.

  • This PR changes Jupyternaut to only reply when @-mentioned in the chat. All AI personas, including Jupyternaut, will only reply when @-mentioned. This allows users to precisely control when & which AI personas respond.

    • This also enables future work surrounding multi-agent collaboration (MAC). AI personas are able to @-mention each other to dispatch tasks or use agentic tools only available through another persona.
  • This PR allows other packages to add fully-custom AI personas. Developers can define the name, avatar, and how new messages are handled. Custom AI personas can use any AI model or AI library of their choice, and now have full control over how new messages are handled.

Demo

Screen.Recording.2025-04-13.at.6.10.26.PM.mov

Technical summary

Jupyter AI v2 had a concept of "personas", but these were essentially just names & avatars that could only appear when custom model providers were in use. This caused several issues for developers:

  • In v2, a custom persona only appeared in the chat after the user manually switched to a custom model provider and sent a new message.
  • In v2, developers were forced to use LangChain to build a custom model provider to provide a custom persona.
  • In v2, developers could not change any details about "how" their custom model provider was being called. That was managed exclusively by DefaultChatHandler in Jupyter AI.

This PR completely re-defines the concept of a persona in Jupyter AI v3 to address these issues.

Personas are now each defined in a class extending BasePersona, an abstract base class provided by Jupyter AI. A summary of the interface is as follows:

class BasePersona(ABC):
    ychat: YChat
    manager: 'PersonaManager'
    config: ConfigManager
    log: Logger
    awareness: PersonaAwareness

    @property
    @abstractmethod
    def defaults(self) -> PersonaDefaults:
        pass
    
    @abstractmethod
    async def process_message(self, message: Message) -> None
        pass
  • Each persona instance is scoped to a single chat under self.ychat.
  • Each persona can define its own name under self.defaults. PersonaDefaults is a Pydantic data model that allows a persona to define its own name and avatar.
    • We want to eventually offer users persona-specific configuration to allow users to change the name & avatar of each persona at runtime through the Jupyter AI settings. That's why the persona name & avatar are defined under self.defaults.
  • Each persona fully defines how new messages are handled in self.process_message(). This is just a plain async function which can do anything. You can define a provider to use any AI model or AI libraries of your choice, as long as they are installed in your Python environment.
    • Writing/streaming replies should be done through the methods available on self.ychat, e.g. self.ychat.add_message().

To help orchestrate personas for each chat, this PR also defines a new PersonaManager class. A new PersonaManager is initialized for each new YChat instance automatically. This class helps initialize the set of BasePersona instances for each chat.

To allow other packages to install personas, persona classes are loaded from the "jupyter_ai.personas" entry point group (EPG) when the server extension starts. Any third-party package can define a persona class and provide it to this EPG to add a custom persona to Jupyter AI.

Architecture changes

  • The chat handlers in v2 no longer do anything in this branch.
  • The JupyternautPersona implementation now fully defines how Jupyternaut handles new messages, superseding DefaultChatHandler.

Related issues

Other details

  • This PR is still a WIP.
  • We may add demo implementations of custom personas to the jupyter_ai_test package. That way, others can switch to this branch and test it via jlpm dev-reinstall.

@dlqqq dlqqq added the enhancement New feature or request label Apr 15, 2025

def set_local_state_field(self, field: str, value: Any) -> None:
with self.as_custom_client():
return self.awareness.set_local_state_field(field, value)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove return ?

f" - Unable to load AI persona from entry point `{persona_ep.name}` due to an exception printed below."
)
self.log.exception(e)
continue

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

swallowing all the exceptions? Perhaps that's okay, but consider adding inline comment to explain why this is better than failing outright.

def personas(self) -> dict[str, BasePersona]:
return self._personas

def get_mentioned_personas(self, new_message: Message) -> list[BasePersona]:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: docstring

mentioned_persona_names = [persona.name for persona in mentioned_personas]
self.log.info(
f"Received new user message mentioning the following personas: {mentioned_persona_names}."
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional: isn't this a lot of stdout, maybe debug ?

if mentioned_id in self.personas:
persona_list.append(self.personas[mentioned_id])

return persona_list

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: any risk of duplication here?

otherwise you can dedup by using a set to buffer the mentioned_id or with:

return list(set(persona_list))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[v3.0.0ax] Jupyternaut always replies
2 participants