A declarative component framework for building AI assistants.
Performer is a Javascript component framework for building programmable and modular AI assistants like chatbots and agents. Performer is inspired by frameworks like React and uses JSX and components. However, Performer is for building conversational AI backends not user interfaces.
To get started locally you should have a few things:
- Node.js installed.
- npm, yarn or pnpm. We recommend
pnpm.
Use the create command in your terminal to create a new Performer project.
npm create performer@latestA simple chat application.
import { Assistant, createTool, Repeat, User, useState } from "@performer/core";
import { z } from "zod";
export const name = "Chat until goodbye";
const ByeSchema = z
.object({})
.describe(
"When the the conversation naturally ends, for example when the user says goodbye or asks to end the chat",
);
export function App() {
const stopped = useState(false);
const tool = createTool("endOfConversation", ByeSchema, () => {
stopped.value = true;
});
return () => (
<>
<system>Greet the user.</system>
<Assistant />
<Repeat stop={stopped}>
<User />
<Assistant tools={[tool]} model="gpt-4" />
</Repeat>
<system>
Say goodbye to the user with a joke related to the conversation. Use
emojis.
</system>
<Assistant />
</>
);
}Start the playground to chat with your app.
pnpm run playgroundPerformer uses JSX, but instead of HTML tags like body and div, tags are based on OpenAI chat message roles: system, assistant, user, tool.
<system>Greet the user, be funny!</system>
<assistant>Hello there! I'd tell you a joke about time travel, but you didn't like it.</assistant>
<user>That's a good one! I see what you did there.</user>Message elements don't have any functionality, they simply represent a list of messages.
Components give your app functionality. Components are the building blocks that make it easy to build complex conversational AI.
A Performer component is a JavaScript function that encapsulates logic and messages. Performer component names must always start with a capital letter, while message tags must be lowercase.
Here is a component that fetches a list of repositories for a user and creates a system message:
async function fetchRepos(user: string) {
const response = await fetch(
`https://api.github.com/users/${user}/repos?sort=updated`,
);
return response.json();
}
function Repos({ user }: { user: string }) {
const repos = useResource(fetchRepos, user);
return () => (
<system>
Answer questions about the {user}'s GitHub respositories
{user}'s GitHub Repositories:
{repos.map(
(repo: any) => `
${repo.full_name}
${repo.description}
\n`,
)}
</system>
);
}Some key differences from React here to take notice of:
- The component returns a function that wraps JSX instead of returning JSX directly. In Performer a component only runs once, if changes only the return function is re-executed to update the JSX. This means you don't have to worry about memoization, no need for hooks like
useMemooruseCallback - Asynchronous data is loaded in the component body using
useResourcewithout the need forasync/await. Performer pauses the component whenuseResourceis running and resumes when the data is available.
Performer comes with a few built-in components that you can use in your JSX.
Assistant
The built-in Assistant component requests a completion from an OpenAI compatible language model and returns an assistant message.
Now we have the repos we can ask a question. The Assistant component will send a request with the system message from Repos and the user message containing the question. Once the language model responds it will add an assistant message with the answer as a response.
import { Assistant } from "@performer/core";
function RecentWork ({ user }) {
return () => (
<>
<Repos user={user} />
<user>What has {user} been working on recently?</user>
<Assistant />
</>
)
}User
The built-in User component waits for external user input and returns a user messages.
The previous example used a static user message to ask a question. We can replace it with the User component to wait for external user input.
import { Assistant, User } from "@performer/core";
function RecentWork ({ user }) {
return () => (
<>
<Repos user={user} />
<User />
<Assistant />
</>
)
}We now have a component which can answer questions about a users GitHub repos. This can be used as an API endpoint, or part of a larger chatbot or agent.
Repeat
Repeat is a built-in component, that duplicates its children with every iteration.
Repeat can be used to create a back and forth chat between a user and an assistant:
import { Assistant, User, Repeat } from "@performer/core";
function App () {
return () => (
<>
<system>Help the user. Be funny</system>
<Repeat>
<User />
<Assistant />
</Repeat>
</>
)
}The initial system message sets the tone, then the chat alternates between user input and assistant response.
The Performer Playground is a React app for testing and chatting with your performers.
To use playground with your apps install the package:
npm install @performer/playgroundThe playground package comes with a CLI named playground.
Add a script to your package.json pointing at your source directory:
"scripts": {
...
"playground": "playground ./src"
}App modules
The playground imports all modules in the source directory. Modules that export the following are considered Performer apps:
App function required. Any module that export an App function is considered a Performer app.
export function App () {
...
}name string optional. Name used to identify the app in the sidebar. Filename is used if name not exported.
export const name = 'My chatbot'slug string optional. Slug used in the URL path to your app. Slugified name is used if slug not exported.
export const slug = 'chatbot'target browser|node optional. Control which environment the app is loaded.
export const target = 'browser'Note: Only client side browser loading as been implement, if set to node the app will not appear in the playground.
Use Performer class to run and interact with performer apps.
import { Performer } from '@performer/core'
function App () {
// ...
}
const performer = new Performer(<App />);
performer.start();
