A lightweight, functional, and composable framework for building AI agents that work together to solve complex tasks.
Built with TypeScript and designed to be serverless-ready.
- Getting Started
- Why Another AI Agent Framework?
- Core Concepts
- Agents
- Workflows
- Workflow States
- Providers
- Tools
- Execution
- Test framework
- Contributors
- Made with ❤️ at Callstack
It is very easy to get started. All you have to do is to create a file with your agents and workflow, then run it.
Use our creator tool to quickly create a new AI agent project.
npx create-fabrice-ai
You can choose from a few templates. You can see a full list of them here.
npm install fabrice-ai
Here is a simple example of a workflow that researches and plans a trip to Wrocław, Poland:
import { agent } from 'fabrice-ai/agent'
import { teamwork } from 'fabrice-ai/teamwork'
import { solution, workflow } from 'fabrice-ai/workflow'
import { lookupWikipedia } from './tools/wikipedia.js'
const activityPlanner = agent({
description: `You are skilled at creating personalized itineraries...`,
})
const landmarkScout = agent({
description: `You research interesting landmarks...`,
tools: { lookupWikipedia },
})
const workflow = workflow({
team: { activityPlanner, landmarkScout },
description: `Plan a trip to Wrocław, Poland...`,
})
const result = await teamwork(workflow)
console.log(solution(result))
Finally, you can run the example by simply executing the file.
Using bun
bun your_file.ts
Using node
node --import=tsx your_file.ts
Most existing AI agent frameworks are either too complex, heavily object-oriented, or tightly coupled to specific infrastructure.
We wanted something different - a framework that embraces functional programming principles, remains stateless, and stays laser-focused on composability.
Now, English + Typescript is your tech stack.
Here are the core concepts of Fabrice:
Teamwork should be easy and fun, just like in real life. It should not require you to learn a new framework and mental model to put your AI team together.
There should be no assumptions about the infrastructure you're using. You should be able to use any provider and any tools, in any environment.
No classes, no side effects. Every operation should be a function that returns a new state.
We should provide you with all tools and features needed to build your AI team, locally and in the cloud.
Agents are specialized workers with specific roles and capabilities. Agents can call available tools and complete assigned tasks. Depending on the task complexity, it can be done in a single step, or multiple steps.
To create a custom agent, you can use our agent
helper function or implement the Agent
interface manually.
import { agent } from 'fabrice-ai/agent'
const myAgent = agent({
role: '<< your role >>',
description: '<< your description >>',
})
Additionally, you can give it access to tools by passing a tools
property to the agent. You can learn more about tools here. You can also set custom provider
for each agent. You can learn more about providers here.
Fabrice comes with a few built-in agents that help it run your workflows out of the box.
Supervisor, supervisor
, is responsible for coordinating the workflow.
It splits your workflow into smaller, more manageable parts, and coordinates the execution.
Resource Planner, resourcePlanner
, is responsible for assigning tasks to available agents, based on their capabilities.
Final Boss, finalBoss
, is responsible for wrapping up the workflow and providing a final output,
in case total number of iterations exeeceds available threshold.
You can overwrite built-in agents by setting it in the workflow.
For example, to replace built-in supervisor
agent, you can do it like this:
import { supervisor } from './my-supervisor.js'
workflow({
team: { supervisor },
})
Workflows define how agents collaborate to achieve a goal. They specify:
- Team members
- Task description
- Expected output
- Optional configuration
Workflow state is a representation of the current state of the workflow. It is a tree of states, where each state represents a single agent's work.
At each level, we have the following properties:
agent
: name of the agent that is working on the taskstatus
: status of the agentmessages
: message historychildren
: child states
First element of the messages
array is always a request to the agent, typically a user message. Everything that follows is a message history, including all the messages exchanged with the provider.
Workflow can have multiple states:
idle
: no work has been started yetrunning
: work is in progresspaused
: work is paused and there are tools that must be called to resumefinished
: work is completefailed
: work has failed due to an error
When you run teamwork(workflow)
, initial state is automatically created for you by calling rootState(workflow)
behind the scenes.
Note
You can also provide your own initial state (for example, to resume a workflow from a previous state). You can learn more about it in the server-side usage section.
Root state is a special state that contains an initial request based on the workflow and points to the supervisor
agent, which is responsible for splitting the work into smaller, more manageable parts.
You can learn more about the supervisor
agent here.
Child state is like root state, but it points to any agent, such as one from your team.
You can create it manually, or use childState
function.
const child = childState({
agent: '<< agent name >>',
messages: user('<< task description >>'),
})
Tip
Fabrice exposes a few helpers to facilitate creating messages, such as user
and assistant
. You can use them to create messages in a more readable way, although it is not required.
To delegate the task, just add a new child state to your agent's state.
const state = {
...state,
children: [
...state.children,
childState({
/** agent to work on the task */
agent: '<< agent name >>',
/** task description */
messages: [
{
role: 'user',
content: '<< task description >>',
}
],
})
]
}
To make it easier, you can use delegate
function to delegate the task.
const state = delegate(state, [agent, '<< task description >>'])
To hand off the task, you can replace your agent's state with a new state, that points to a different agent.
const state = childState({
agent: '<< new agent name >>',
messages: state.messages,
})
In the example above, we're passing the entire message history to the new agent, including the original request and all the work done by any previous agent. It is up to you to decide how much of the history to pass to the new agent.
Providers are responsible for sending requests to the LLM and handling the responses.
Fabrice comes with a few built-in providers:
- OpenAI (structured output)
- OpenAI (using tools as response format)
- Groq
You can learn more about them here.
If you're working with an OpenAI compatible provider, you can use the openai
provider with a different base URL and API key, such as:
openai({
model: '<< your model >>',
options: {
apiKey: '<< your_api_key >>',
baseURL: '<< your_base_url >>',
},
})
By default, Fabrice uses OpenAI gpt-4o model. You can change the default model or provider either for the entire system, or for specific agent.
To do it for the entire workflow:
import { grok } from 'fabrice-ai/providers/grok'
workflow({
/** other options go here */
provider: grok()
})
To change it for specific agent:
import { grok } from 'fabrice-ai/providers/grok'
agent({
/** other options go here */
provider: grok()
})
Note that an agent's provider always takes precedence over a workflow's provider. Tools always receive the provider from the agent that triggered their execution.
To create a custom provider, you need to implement the Provider
interface.
const myProvider = (options: ProviderOptions): Provider => {
return {
chat: async () => {
/** your implementation goes here */
},
}
}
You can learn more about the Provider
interface here.
Tools extend agent capabilities by providing concrete actions they can perform.
Fabrice comes with a few built-in tools via @fabrice-ai/tools
package. For most up-to-date list, please refer to the README.
To create a custom tool, you can use our tool
helper function or implement the Tool
interface manually.
import { tool } from 'fabrice-ai/tools'
const myTool = tool({
description: 'My tool description',
parameters: z.object({
/** your Zod schema goes here */
}),
execute: async (parameters, context) => {
/** your implementation goes here */
},
})
Tools will use the same provider as the agent that triggered them. Additionally, you can access the context
object, which gives you access to the provider, as well as current message history.
To give an agent access to a tool, you need to add it to the agent's tools
property.
agent({
role: '<< your role >>',
tools: { searchWikipedia },
})
Since tools are passed to an LLM and referred by their key, you should use meaningful names for them, for increased effectiveness.
Execution is the process of running the workflow to completion. A completed workflow is a workflow with state "finished" at its root.
The easiest way to complete the workflow is to call teamwork(workflow)
function. It will run the workflow to completion and return the final state.
const state = await teamwork(workflow)
console.log(solution(state))
Calling solution(state)
will return the final output of the workflow, which is its last message.
If you are running workflows in the cloud, or any other environment where you want to handle tool execution manually, you can call teamwork the following way:
/** read state from the cache */
/** run the workflow */
const state = await teamwork(workflow, prevState, false)
/** save state to the cache */
Passing second argument to teamwork
is optional. If you don't provide it, root state will be created automatically. Otherwise, it will be used as a starting point for the next iteration.
Last argument is a boolean flag that determines if tools should be executed. If you set it to false
, you are responsible for calling tools manually. Teamwork will stop iterating over the workflow and return the current state with paused
status.
If you want to handle tool execution manually, you can use iterate
function to build up your own recursive iteration logic over the workflow state.
Have a look at how teamwork
is implemented here to understand how it works.
There's a packaged called fabrice-ai/bdd
dedicated to unit testing - actually to Behavioral Driven Development. Check the docs.
Mike 💻 |
Piotr Karwatka 💻 |
Fabrice is an open source project and will always remain free to use. If you think it's cool, please star it 🌟. Callstack is a group of React and React Native geeks, contact us at hello@callstack.com if you need any help with these or just want to say hi!
Like the project? ⚛️ Join the team who does amazing stuff for clients and drives React Native Open Source! 🔥