Rename package to aibitat

This commit is contained in:
Wlad Paiva
2023-10-16 09:37:24 -03:00
parent 3c3e9bf7d1
commit d4cde5fc0b
15 changed files with 148 additions and 128 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
---
'chatflow': patch
'aibitat': patch
---
- **Automated reply with loop prevention.** Chats are kept alive until the
+8
View File
@@ -0,0 +1,8 @@
---
'aibitat': patch
---
Rename `ChatFlow` to `AIbitat` because npm complained about it being too similar
to the package `chat-flow` and forced me using my username as scope... since I
don't want go down that road I prefered changing the package name to something
more unique
+1 -1
View File
@@ -1,5 +1,5 @@
---
'@wladiston/chatflow': patch
'aibitat': patch
---
add dist files in the exported module
+2 -2
View File
@@ -2,11 +2,11 @@
"mode": "pre",
"tag": "beta",
"initialVersions": {
"chatflow": "0.0.0",
"@wladiston/chatflow": "0.0.1-beta.0"
"aibitat": "0.0.0"
},
"changesets": [
"afraid-gifts-move",
"afraid-terms-cover",
"friendly-needles-smile",
"soft-brooms-develop"
]
+1 -1
View File
@@ -1,5 +1,5 @@
---
'@wladiston/chatflow': patch
'aibitat': patch
---
fix module exports
+10 -1
View File
@@ -1,4 +1,13 @@
# chatflow
# AIbitat
## 0.0.1-beta.3
### Patch Changes
- cabb37f: Rename `ChatFlow` to `AIbitat` because npm complained about it being
too similar to the package `chat-flow` and forced me using my username as
scope... since I don't want go down that road I prefered changing the package
name to something more unique
## 0.0.1-beta.2
+19 -15
View File
@@ -1,9 +1,9 @@
This project is a fork from the original
[autogen](https://github.com/microsoft/autogen) but done in TypeScript.
# ChatFlow - Multi-Agent Conversation framework
# AIbitat - Multi-Agent Conversation framework
ChatFlow enables the next-gen LLM applications with a generic multi-agent
AIbitat enables the next-gen LLM applications with a generic multi-agent
conversation framework. It offers customizable and conversable agents that
integrate LLMs, tools, and humans. By automating chat among multiple capable
agents, one can easily make them collectively perform tasks autonomously or with
@@ -15,18 +15,18 @@ provider agnostic and can be used with any provider that implements the
environment.
By default, it uses **OpenAI** and **GPT-3.5-TURBO** as the provider but you can
change it by passing `provider` and `model` to the `ChatFlow` constructor or by
change it by passing `provider` and `model` to the `AIbitat` constructor or by
setting them on the node config.
### Features
- **Multi-agent conversations:** ChatFlow agents can communicate with each other
- **Multi-agent conversations:** AIbitat agents can communicate with each other
to solve tasks. This allows for more complex and sophisticated applications
than would be possible with a single LLM.
- **Customization:** ChatFlow agents can be customized to meet the specific
needs of an application. This includes the ability to choose the LLMs to use,
the types of human input to allow, and the tools to employ.
- **Human participation:** ChatFlow seamlessly allows human participation. This
- **Customization:** AIbitat agents can be customized to meet the specific needs
of an application. This includes the ability to choose the LLMs to use, the
types of human input to allow, and the tools to employ.
- **Human participation:** AIbitat seamlessly allows human participation. This
means that humans can provide input and feedback to the agents as needed.
## Roadmap
@@ -58,16 +58,16 @@ setting them on the node config.
You can install the package:
```bash
npm install @wladiston/chatflow
npm install aibitat
```
add you `OPEN_AI_API_KEY` to your environment variables and then use it like
this:
```ts
import {ChatFlow} from '@wladiston/chatflow'
import {AIbitat} from 'aibitat'
const flow = new ChatFlow({
const aibitat = new AIbitat({
nodes: {
'🧑': '🤖',
'🤖': ['🐭', '🦁', '🐶'],
@@ -88,19 +88,21 @@ const flow = new ChatFlow({
},
})
flow.on('message', ({from, to, content}) => console.log(`${from}: ${content}`))
aibitat.on('message', ({from, to, content}) =>
console.log(`${from}: ${content}`),
)
// 🧑: How much is 2 + 2?
// 🐭: The sum of 2 + 2 is 4.
// 🦁: That is correct.
// 🐶: TERMINATE
await flow.start({
await aibitat.start({
from: '🧑',
to: '🤖',
content: 'How much is 2 + 2?',
})
console.log('saving chats... ', flow.chats)
console.log('saving chats... ', aibitat.chats)
// saving chats... [
// {
// from: "🧑",
@@ -146,7 +148,9 @@ to each other. The `config` object is used to configure each node.
You can listen to events using the `on` method:
```ts
flow.on('message', ({from, to, content}) => console.log(`${from}: ${content}`))
aibitat.on('message', ({from, to, content}) =>
console.log(`${from}: ${content}`),
)
```
The following events are available:
+5 -5
View File
@@ -1,10 +1,10 @@
import {ChatFlow} from '../src'
import {AIbitat} from '../src'
import {terminal} from '../src/utils'
console.log('🚀 starting chat\n')
console.time('🚀 chat finished')
const flow = new ChatFlow({
const aibitat = new AIbitat({
nodes: {
'🧑': '🤖',
},
@@ -18,10 +18,10 @@ const flow = new ChatFlow({
},
})
flow.on('message', terminal.print)
flow.on('terminate', terminal.terminate)
aibitat.on('message', terminal.print)
aibitat.on('terminate', terminal.terminate)
await flow.start({
await aibitat.start({
from: '🧑',
to: '🤖',
content: '2 + 2 = 4?',
+5 -5
View File
@@ -1,10 +1,10 @@
import {ChatFlow} from '../src'
import {AIbitat} from '../src'
import {terminal} from '../src/utils'
console.log('🚀 starting chat\n')
console.time('🚀 chat finished')
const flow = new ChatFlow({
const aibitat = new AIbitat({
nodes: {
client: 'manager',
manager: ['mathematician', 'reviewer', 'client'],
@@ -27,10 +27,10 @@ const flow = new ChatFlow({
},
})
flow.on('message', terminal.print)
flow.on('terminate', terminal.terminate)
aibitat.on('message', terminal.print)
aibitat.on('terminate', terminal.terminate)
await flow.start({
await aibitat.start({
from: 'client',
to: 'manager',
content: '2 + 2 = ?',
+7 -7
View File
@@ -1,10 +1,10 @@
import {ChatFlow} from '../src'
import {AIbitat} from '../src'
import {terminal} from '../src/utils'
console.log('🚀 starting chat\n')
console.time('🚀 chat finished')
const flow = new ChatFlow({
const aibitat = new AIbitat({
nodes: {
client: 'manager',
manager: ['mathematician', 'reviewer', 'client'],
@@ -25,15 +25,15 @@ const flow = new ChatFlow({
},
})
flow.on('message', terminal.print)
flow.on('terminate', terminal.terminate)
aibitat.on('message', terminal.print)
aibitat.on('terminate', terminal.terminate)
flow.on('interrupt', async node => {
aibitat.on('interrupt', async node => {
const feedback = await terminal.askForFeedback(node)
await flow.continue(feedback)
await aibitat.continue(feedback)
})
await flow.start({
await aibitat.start({
from: 'client',
to: 'manager',
content: '2 + 2 = ?',
+4 -4
View File
@@ -1,10 +1,10 @@
{
"name": "@wladiston/chatflow",
"version": "0.0.1-beta.2",
"description": "A chat flow organizer for AI agents",
"name": "aibitat",
"version": "0.0.1-beta.3",
"description": "An extensible, stateless and customizable framework for multi-agents conversation",
"repository": {
"type": "git",
"url": "https://github.com/wladiston/chatflow.git"
"url": "https://github.com/wladiston/abitat.git"
},
"author": {
"name": "Wlad Paiva",
-1
View File
@@ -1 +0,0 @@
export * from './chat-flow.ts'
@@ -1,9 +1,9 @@
import {beforeEach, describe, expect, mock, test} from 'bun:test'
import OpenAI from 'openai'
import {AIProvider} from '../providers'
import {type Message} from '../types.ts'
import {ChatFlow, type ChatFlowProps} from './chat-flow.ts'
import {AIbitat, type AIbitatProps} from './aibitat.ts'
import {AIProvider} from './providers/index.ts'
import {type Message} from './types.ts'
// HACK: Mock the AI provider.
// This is still needed because Bun doesn't support mocking modules yet.
@@ -18,7 +18,7 @@ beforeEach(() => {
ai.create.mockImplementation(() => Promise.resolve('TERMINATE'))
})
const defaultFlow: ChatFlowProps = {
const defaultaibitat: AIbitatProps = {
provider,
nodes: {
'🧑': '🤖',
@@ -37,12 +37,12 @@ const defaultStart = {
describe('direct message', () => {
test('should reply a chat', async () => {
const flow = new ChatFlow(defaultFlow)
await flow.start(defaultStart)
const aibitat = new AIbitat(defaultaibitat)
await aibitat.start(defaultStart)
expect(flow.chats).toHaveLength(2)
expect(aibitat.chats).toHaveLength(2)
// expect human has the TERMINATE from the bot
expect(flow.chats.at(-1)).toEqual({
expect(aibitat.chats.at(-1)).toEqual({
from: '🤖',
to: '🧑',
content: 'TERMINATE',
@@ -53,15 +53,15 @@ describe('direct message', () => {
test('should have a system message', async () => {
const role = 'You are a 🤖.'
const flow = new ChatFlow({
...defaultFlow,
const aibitat = new AIbitat({
...defaultaibitat,
config: {
...defaultFlow.config,
...defaultaibitat.config,
'🤖': {type: 'agent', role},
},
})
await flow.start(defaultStart)
await aibitat.start(defaultStart)
expect(ai.create).toHaveBeenCalledTimes(1)
expect(ai.create.mock.calls[0][0][0].content).toEqual(role)
@@ -73,40 +73,40 @@ describe('direct message', () => {
Promise.resolve(i >= 10 ? 'TERMINATE' : `... ${i++}`),
)
const flow = new ChatFlow({
...defaultFlow,
const aibitat = new AIbitat({
...defaultaibitat,
config: {
...defaultFlow.config,
...defaultaibitat.config,
'🧑': {type: 'assistant', interrupt: 'NEVER'},
},
})
await flow.start(defaultStart)
await aibitat.start(defaultStart)
// the chat gets in a loop if the bot doesn't terminate
expect(flow.chats).toHaveLength(12)
expect(aibitat.chats).toHaveLength(12)
})
test('should not engage in infinity conversations', async () => {
ai.create.mockImplementation(() => Promise.resolve('...'))
const flow = new ChatFlow({
...defaultFlow,
const aibitat = new AIbitat({
...defaultaibitat,
maxRounds: 4,
config: {
...defaultFlow.config,
...defaultaibitat.config,
'🧑': {type: 'assistant', interrupt: 'NEVER'},
},
})
await flow.start(defaultStart)
await aibitat.start(defaultStart)
expect(ai.create).toHaveBeenCalledTimes(3)
})
test('should have initial messages', async () => {
const flow = new ChatFlow({
...defaultFlow,
const aibitat = new AIbitat({
...defaultaibitat,
maxRounds: 1,
chats: [
{
@@ -118,14 +118,14 @@ describe('direct message', () => {
],
})
await flow.start({
await aibitat.start({
from: '🤖',
to: '🧑',
content: '4',
})
expect(flow.chats).toHaveLength(3)
expect(flow.chats.at(0)).toEqual({
expect(aibitat.chats).toHaveLength(3)
expect(aibitat.chats.at(0)).toEqual({
from: '🧑',
to: '🤖',
content: '2 + 2 = 4?',
@@ -134,12 +134,12 @@ describe('direct message', () => {
})
test('should trigger an event when a reply is received', async () => {
const flow = new ChatFlow(defaultFlow)
const aibitat = new AIbitat(defaultaibitat)
const callback = mock(() => {})
flow.on('message', callback)
aibitat.on('message', callback)
await flow.start(defaultStart)
await aibitat.start(defaultStart)
expect(callback).toHaveBeenCalledTimes(2)
})
@@ -147,15 +147,15 @@ describe('direct message', () => {
test('should always interrupt interaction after each reply', async () => {
ai.create.mockImplementation(() => Promise.resolve('...'))
const flow = new ChatFlow({
...defaultFlow,
const aibitat = new AIbitat({
...defaultaibitat,
interrupt: 'ALWAYS',
})
const callback = mock(() => {})
flow.on('interrupt', callback)
aibitat.on('interrupt', callback)
await flow.start(defaultStart)
await aibitat.start(defaultStart)
expect(callback).toHaveBeenCalledTimes(1)
})
@@ -163,53 +163,53 @@ describe('direct message', () => {
test('should trigger an event when a interaction is needed', async () => {
ai.create.mockImplementation(() => Promise.resolve('...'))
const flow = new ChatFlow({
...defaultFlow,
const aibitat = new AIbitat({
...defaultaibitat,
config: {
...defaultFlow.config,
...defaultaibitat.config,
'🤖': {type: 'agent', interrupt: 'ALWAYS'},
},
})
const callback = mock(() => {})
flow.on('interrupt', callback)
aibitat.on('interrupt', callback)
await flow.start(defaultStart)
await aibitat.start(defaultStart)
expect(flow.chats).toHaveLength(3)
expect(aibitat.chats).toHaveLength(3)
})
test('should auto-reply only when user skip engaging', async () => {
ai.create.mockImplementation(() => Promise.resolve('...'))
const flow = new ChatFlow(defaultFlow)
const aibitat = new AIbitat(defaultaibitat)
// HACK: we should use `expect.assertions(1)` here but
// bun has not implemented it yet.
// so I have to work around it.
// https://github.com/oven-sh/bun/issues/1825
const p = new Promise(async resolve => {
flow.on('interrupt', async () => {
if (flow.chats.length < 4) {
await flow.continue()
aibitat.on('interrupt', async () => {
if (aibitat.chats.length < 4) {
await aibitat.continue()
} else {
resolve(true)
}
})
await flow.start(defaultStart)
await aibitat.start(defaultStart)
})
expect(p).resolves.toBeTrue()
expect(flow.chats[3].content).toBe('...')
expect(flow.chats[3].state).toBe('success')
expect(flow.chats).toHaveLength(5)
expect(aibitat.chats[3].content).toBe('...')
expect(aibitat.chats[3].state).toBe('success')
expect(aibitat.chats).toHaveLength(5)
})
test('should continue conversation with user`s feedback', async () => {
ai.create.mockImplementation(() => Promise.resolve('...'))
const flow = new ChatFlow({
...defaultFlow,
const aibitat = new AIbitat({
...defaultaibitat,
maxRounds: 10,
})
@@ -218,27 +218,27 @@ describe('direct message', () => {
// so I have to work around it.
// https://github.com/oven-sh/bun/issues/1825
const p = new Promise(async resolve => {
flow.on('interrupt', a => {
if (flow.chats.length < 4) {
flow.continue('my feedback')
aibitat.on('interrupt', a => {
if (aibitat.chats.length < 4) {
aibitat.continue('my feedback')
} else {
resolve(true)
}
})
await flow.start(defaultStart)
await aibitat.start(defaultStart)
})
expect(p).resolves.toBeTrue()
expect(flow.chats[2].from).toBe('🧑')
expect(flow.chats[2].to).toBe('🤖')
expect(flow.chats[2].content).toBe('my feedback')
expect(aibitat.chats[2].from).toBe('🧑')
expect(aibitat.chats[2].to).toBe('🤖')
expect(aibitat.chats[2].content).toBe('my feedback')
})
})
describe('as a group', () => {
const groupFlow: ChatFlowProps = {
...defaultFlow,
const groupaibitat: AIbitatProps = {
...defaultaibitat,
nodes: {
'🧑': '🤖',
'🤖': ['🐶', '😸', '🐭'],
@@ -257,8 +257,8 @@ describe('as a group', () => {
const roleMessage = x.find(y => y.content?.includes('next role'))
if (roleMessage) {
// pick a random node from groupFlow.nodes
const nodes = groupFlow.nodes['🤖']
// pick a random node from groupaibitat.nodes
const nodes = groupaibitat.nodes['🤖']
const nextRole = nodes[Math.floor(Math.random() * nodes.length)]
return Promise.resolve(nextRole)
}
@@ -268,35 +268,35 @@ describe('as a group', () => {
})
test('should chat to members of the group', async () => {
const flow = new ChatFlow(groupFlow)
await flow.start(defaultStart)
const aibitat = new AIbitat(groupaibitat)
await aibitat.start(defaultStart)
expect(flow.chats).toHaveLength(11)
expect(aibitat.chats).toHaveLength(11)
})
test.todo('should infer the next speaker', async () => {})
test('should chat only a specific amount of rounds', async () => {
const flow = new ChatFlow({
...groupFlow,
const aibitat = new AIbitat({
...groupaibitat,
config: {
...groupFlow.config,
...groupaibitat.config,
'🤖': {type: 'manager', provider, maxRounds: 4},
},
})
await flow.start(defaultStart)
await aibitat.start(defaultStart)
expect(flow.chats).toHaveLength(5)
expect(aibitat.chats).toHaveLength(5)
})
})
test.todo('should call a function', async () => {
const myFunc = mock((props: {x: number; y: number}) => {})
const flow = new ChatFlow({
...defaultFlow,
const aibitat = new AIbitat({
...defaultaibitat,
})
await flow.start(defaultStart)
await aibitat.start(defaultStart)
expect(myFunc).toHaveBeenCalledTimes(1)
expect(myFunc.mock.calls[0][0]).toEqual({x: 1, y: 2})
+13 -13
View File
@@ -6,10 +6,10 @@ import {
AIProvider,
OpenAIProvider,
type OpenAIModel,
} from '../providers/index.ts'
import {Message} from '../types.ts'
} from './providers/index.ts'
import {Message} from './types.ts'
const log = debug('autogen:chat-flow')
const log = debug('autogen:chat-aibitat')
export type ProviderConfig =
| {
@@ -24,7 +24,7 @@ export type ProviderConfig =
}
/**
* Base config for ChatFlow nodes.
* Base config for AIbitat nodes.
*/
export type BaseNodeConfig = ProviderConfig & {
/** The role this node will play in the conversation */
@@ -68,12 +68,12 @@ export type Assistant = BaseNodeConfig & {
}
/**
* A Node config for ChatFlow.
* A Node config for AIbitat.
*/
export type NodeConfig = Agent | Manager | Assistant
/**
* Configuration for all Nodes in ChatFlow.
* Configuration for all Nodes in AIbitat.
*/
export type Config = {
// TODO: Add types for this
@@ -114,9 +114,9 @@ type ChatState = Omit<Chat, 'content'> & {
type History = Array<ChatState>
/**
* ChatFlow props.
* AIbitat props.
*/
export type ChatFlowProps = ProviderConfig & {
export type AIbitatProps = ProviderConfig & {
nodes: Nodes
config: Config
@@ -140,23 +140,23 @@ export type ChatFlowProps = ProviderConfig & {
}
/**
* ChatFlow is a class that manages the flow of a chat.
* AIbitat is a class that manages the aibitat of a chat.
* It is designed to solve a task with LLM.
*
* Guiding the chat through a flow of nodes.
* Guiding the chat through a aibitat of nodes.
*/
export class ChatFlow {
export class AIbitat {
private emitter = new EventEmitter()
private defaultProvider: AIProvider<unknown>
private defaultInterrupt: ChatFlowProps['interrupt']
private defaultInterrupt: AIbitatProps['interrupt']
private maxRounds: number
private _chats: History
private nodes: Nodes
private config: Config
constructor(props: ChatFlowProps) {
constructor(props: AIbitatProps) {
const {
nodes,
config,
+1 -1
View File
@@ -1,3 +1,3 @@
export * from './agents'
export * from './aibitat.ts'
export * from './providers'
export * from './types.ts'