mirror of
https://github.com/Mintplex-Labs/abitat.git
synced 2026-07-01 10:05:27 -04:00
added functions
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
---
|
||||
'aibitat': patch
|
||||
---
|
||||
|
||||
### 🎉 Function calling is here!!!
|
||||
|
||||
You can now call functions from the conversation. This is a huge step forward in
|
||||
making Aibitat more powerful and flexible.
|
||||
|
||||
To have your agents calling functions, use the `function` method to register a
|
||||
function:
|
||||
|
||||
```ts
|
||||
const aibitat = new AIbitat().function({
|
||||
name: 'unique-function-name',
|
||||
description: 'List of releases of AIbitat and the notes for each release.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
},
|
||||
handler: async () => {
|
||||
const response = await fetch(
|
||||
'https://github.com/wladiston/aibitat/releases',
|
||||
)
|
||||
const html = await response.text()
|
||||
const text = cheerio.load(html).text()
|
||||
return text
|
||||
},
|
||||
})
|
||||
```
|
||||
@@ -37,7 +37,7 @@ by setting them on the specific node config.
|
||||
- [x] **Group chats.** Agents chat with multiple other agents at the same time
|
||||
as if they were in a slack channel. The next agent to reply is the most
|
||||
likely to reply based on the conversation.
|
||||
- [ ] **Function execution.** Agents can execute functions and return the result
|
||||
- [x] **Function execution.** Agents can execute functions and return the result
|
||||
to the conversation.
|
||||
- [ ] **Cache**. Store conversation history in a cache to improve performance
|
||||
and reduce the number of API calls.
|
||||
@@ -61,14 +61,21 @@ by setting them on the specific node config.
|
||||
You can install the package:
|
||||
|
||||
```bash
|
||||
npm install aibitat
|
||||
# to install bun go to https://bun.sh and follow the instructions
|
||||
bun install aibitat
|
||||
```
|
||||
|
||||
add you `OPEN_AI_API_KEY` to your environment variables and then use it like
|
||||
this:
|
||||
Create an `.env` file and add your `OPEN_AI_API_KEY`:
|
||||
|
||||
```bash
|
||||
OPEN_AI_API_KEY=...
|
||||
```
|
||||
|
||||
Then create a file called `index.ts` and add the following:
|
||||
|
||||
```ts
|
||||
import {AIbitat} from 'aibitat'
|
||||
import {terminal} from 'aibitat/plugins'
|
||||
|
||||
const aibitat = new AIbitat({
|
||||
nodes: {
|
||||
@@ -89,44 +96,19 @@ const aibitat = new AIbitat({
|
||||
role: 'You reply "TERMINATE" if theres`s a confirmation',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
aibitat.onMessage(({from, to, content}) => console.log(`${from}: ${content}`))
|
||||
// 🧑: How much is 2 + 2?
|
||||
// 🐭: The sum of 2 + 2 is 4.
|
||||
// 🦁: That is correct.
|
||||
// 🐶: TERMINATE
|
||||
}).use(terminal())
|
||||
|
||||
await aibitat.start({
|
||||
from: '🧑',
|
||||
to: '🤖',
|
||||
content: 'How much is 2 + 2?',
|
||||
})
|
||||
```
|
||||
|
||||
console.log('saving chats... ', aibitat.chats)
|
||||
// saving chats... [
|
||||
// {
|
||||
// from: "🧑",
|
||||
// to: "🤖",
|
||||
// content: "How much is 2 + 2?",
|
||||
// state: "success"
|
||||
// }, {
|
||||
// from: "🐭",
|
||||
// to: "🤖",
|
||||
// state: "success",
|
||||
// content: "The sum of 2 + 2 is 4."
|
||||
// }, {
|
||||
// from: "🦁",
|
||||
// to: "🤖",
|
||||
// state: "success",
|
||||
// content: "That is correct."
|
||||
// }, {
|
||||
// from: "🐶",
|
||||
// to: "🤖",
|
||||
// state: "success",
|
||||
// content: "TERMINATE"
|
||||
// }
|
||||
// ]
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
Nodes are the agents that will be used in the conversation and how they connect
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import * as cheerio from 'cheerio'
|
||||
|
||||
import {AIbitat} from '../src'
|
||||
import {terminal} from '../src/plugins'
|
||||
|
||||
const aibitat = new AIbitat({
|
||||
nodes: {
|
||||
'🧑': '🤖',
|
||||
},
|
||||
config: {
|
||||
'🧑': {type: 'assistant'},
|
||||
'🤖': {type: 'agent', functions: ['aibitat-releases']},
|
||||
},
|
||||
})
|
||||
.use(terminal())
|
||||
.function({
|
||||
name: 'aibitat-releases',
|
||||
description: 'List of releases of AIbitat and the notes for each release.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
},
|
||||
handler: async () => {
|
||||
const response = await fetch(
|
||||
'https://github.com/wladiston/aibitat/releases',
|
||||
)
|
||||
const html = await response.text()
|
||||
const text = cheerio.load(html).text()
|
||||
return text
|
||||
},
|
||||
})
|
||||
|
||||
await aibitat.start({
|
||||
from: '🧑',
|
||||
to: '🤖',
|
||||
content: `Talk about the latest news about AIbitat`,
|
||||
})
|
||||
+18
-2
@@ -291,13 +291,27 @@ describe('as a group', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('should call a function', async () => {
|
||||
test.only('should call a function', async () => {
|
||||
// FIX: I can't mock the API yet
|
||||
// ai.create.mockImplementation(() =>
|
||||
// Promise.resolve({
|
||||
// function_call: {
|
||||
// name: 'internet',
|
||||
// arguments: '{"query": "I\'m feeling lucky"}',
|
||||
// },
|
||||
// }),
|
||||
// )
|
||||
|
||||
const internet = mock((props: {query: string}) =>
|
||||
Promise.resolve("I'm feeling lucky"),
|
||||
)
|
||||
|
||||
const aibitat = new AIbitat({
|
||||
...defaultaibitat,
|
||||
config: {
|
||||
...defaultaibitat.config,
|
||||
'🤖': {type: 'agent', functions: ['internet']},
|
||||
},
|
||||
})
|
||||
|
||||
aibitat.function({
|
||||
@@ -387,7 +401,9 @@ describe('when errors happen', () => {
|
||||
throw error
|
||||
}
|
||||
|
||||
return Promise.resolve('TERMINATE')
|
||||
return Promise.resolve({
|
||||
content: 'TERMINATE',
|
||||
})
|
||||
})
|
||||
|
||||
const aibitat = new AIbitat(defaultaibitat)
|
||||
|
||||
+4
-10
@@ -207,7 +207,7 @@ export type FunctionDefinition = {
|
||||
* hallucinate parameters not defined by your function schema. Validate the
|
||||
* arguments in your code before calling your function.
|
||||
*/
|
||||
handler: (props: unknown, aibitat: AIbitat) => Promise<string> | string
|
||||
handler: (...params: any[]) => Promise<string> | string
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -716,15 +716,9 @@ ${this.getHistory({to})
|
||||
|
||||
// get the functions that the node can call
|
||||
const functions =
|
||||
fromConfig.functions?.map(name => {
|
||||
const definition = this.functions.get(name)
|
||||
if (!definition) {
|
||||
throw new Error(`Function "${name}" is not defined`)
|
||||
}
|
||||
|
||||
const {handler, ...rest} = definition
|
||||
return rest
|
||||
}) || []
|
||||
(fromConfig.functions
|
||||
?.map(name => this.functions.get(name))
|
||||
.filter(a => !!a) as FunctionDefinition[]) || []
|
||||
|
||||
// get the chat completion
|
||||
const content = await provider.create(messages, functions)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {Function, Message} from '../types.ts'
|
||||
import {FunctionDefinition} from '../aibitat.ts'
|
||||
import {Message} from '../types.ts'
|
||||
|
||||
/**
|
||||
* A service that provides an AI client to create a completion.
|
||||
@@ -24,5 +25,8 @@ export abstract class AIProvider<T> {
|
||||
* @throws It should thrown known treated errors from `src/error.ts`.
|
||||
* @param messages A list of messages to send.
|
||||
*/
|
||||
abstract create(messages: Message[], functions?: Function[]): Promise<string>
|
||||
abstract create(
|
||||
messages: Message[],
|
||||
functions?: FunctionDefinition[],
|
||||
): Promise<string>
|
||||
}
|
||||
|
||||
+63
-4
@@ -15,6 +15,7 @@ import OpenAI, {
|
||||
UnprocessableEntityError as OpenAIUnprocessableEntityError,
|
||||
} from 'openai'
|
||||
|
||||
import {FunctionDefinition} from '../aibitat.ts'
|
||||
import {
|
||||
APIError,
|
||||
AuthorizationError,
|
||||
@@ -96,7 +97,10 @@ export class OpenAIProvider extends AIProvider<OpenAI> {
|
||||
* @param messages A list of messages to send to the OpenAI API.
|
||||
* @returns The completion.
|
||||
*/
|
||||
async create(messages: Message[], functions?: Function[]) {
|
||||
async create(
|
||||
messages: Message[],
|
||||
functions?: FunctionDefinition[],
|
||||
): Promise<string> {
|
||||
log(`calling 'openai.chat.completions.create' with model '${this.model}'`)
|
||||
|
||||
try {
|
||||
@@ -109,10 +113,35 @@ export class OpenAIProvider extends AIProvider<OpenAI> {
|
||||
|
||||
log('cost: ', this.getCost(response.usage))
|
||||
|
||||
// FIX: parei aqui.. agora preciso retornar a function call e chamar a função no reply
|
||||
// caso function_call
|
||||
return response.choices[0].message.content!
|
||||
if (functions && response.choices[0].message.function_call) {
|
||||
// send the info on the function call and function response to GPT
|
||||
// and return the response
|
||||
|
||||
const functionResponse = await this.callFunction(
|
||||
functions,
|
||||
response.choices[0].message.function_call,
|
||||
)
|
||||
|
||||
return await this.create(
|
||||
[
|
||||
...messages,
|
||||
response.choices[0].message, // extend conversation with assistant's reply
|
||||
// extend conversation with function response
|
||||
{
|
||||
role: 'function',
|
||||
name: response.choices[0].message.function_call.name,
|
||||
content: functionResponse,
|
||||
},
|
||||
],
|
||||
functions,
|
||||
)
|
||||
}
|
||||
|
||||
if (response.choices[0].message.content) {
|
||||
return response.choices[0].message.content
|
||||
}
|
||||
|
||||
throw new Error('No content found or function_call in the response')
|
||||
// const stream = OpenAIStream(response)
|
||||
// const result = new StreamingTextResponse(stream)
|
||||
// return await result.text()
|
||||
@@ -189,4 +218,34 @@ export class OpenAIProvider extends AIProvider<OpenAI> {
|
||||
currency: 'USD',
|
||||
}).format(total)
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the function from the completion.
|
||||
*
|
||||
* @param functions The list of functions to call.
|
||||
* @param completion The completion to get the function from.
|
||||
* @returns The completion.
|
||||
*/
|
||||
async callFunction(
|
||||
functions: FunctionDefinition[],
|
||||
call: OpenAI.Chat.ChatCompletionMessage.FunctionCall,
|
||||
) {
|
||||
const funcToCall = functions.find(f => f.name === call.name)
|
||||
if (!funcToCall) {
|
||||
throw new Error(`Function '${call.name}' not found`)
|
||||
}
|
||||
|
||||
let json: any
|
||||
|
||||
try {
|
||||
json = JSON.parse(call.arguments)
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Model created an invalid JSON: '${call.arguments}' for function '${call.name}'`,
|
||||
)
|
||||
}
|
||||
|
||||
log('calling function: ', funcToCall.name, 'with arguments: ', json)
|
||||
return await funcToCall.handler(json)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ export type Message = {
|
||||
* The role of the messages author. One of `system`, `user`, `assistant`, or
|
||||
* `function`.
|
||||
*/
|
||||
role: 'system' | 'user' | 'assistant' | 'function'
|
||||
role: Role
|
||||
|
||||
/**
|
||||
* The name and arguments of a function that should be called, as generated by the
|
||||
|
||||
Reference in New Issue
Block a user