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@latest
A 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 playground
Performer 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
useMemo
oruseCallback
- Asynchronous data is loaded in the component body using
useResource
without the need forasync/await
. Performer pauses the component whenuseResource
is 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/playground
The 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();