tested the assitent and userproxy

This commit is contained in:
Wlad Paiva
2023-10-11 22:17:08 -03:00
parent dfb07e56ee
commit 1d5df6c223
11 changed files with 330 additions and 94 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}
BIN
View File
Binary file not shown.
+1
View File
@@ -20,6 +20,7 @@
},
"dependencies": {
"ai": "^2.2.14",
"debug": "^4.3.4",
"openai": "^4.11.1"
},
"nano-staged": {
+15 -8
View File
@@ -4,7 +4,7 @@ import {
type ConversableAgentConfig,
} from './conversable-agent'
type AssistantAgentConfig<T extends AIProvider<unknown>> = Omit<
export type AssistantAgentConfig<T extends AIProvider<unknown>> = Omit<
ConversableAgentConfig<T>,
'humanInputMode' | 'codeExecutionConfig'
>
@@ -15,8 +15,8 @@ type AssistantAgentConfig<T extends AIProvider<unknown>> = Omit<
* AssistantAgent is a subclass of ConversableAgent configured with a default system message.
* The default system message is designed to solve a task with LLM,
* including suggesting python code blocks and debugging.
* `human_input_mode` is default to "NEVER"
* and `code_execution_config` is default to False.
* `humanInputMode` is default to "NEVER"
* and `codeExecutionConfig` is default to False.
* This agent doesn't execute code by default, and expects the user to execute the code.
*/
export class AssistantAgent<
@@ -25,11 +25,17 @@ export class AssistantAgent<
/**
* Default system message for the AssistantAgent.
*/
static DEFAULT_SYSTEM_MESSAGE = `
You are a helpful AI assistant...
(rest of the message)
...Reply "TERMINATE" in the end when everything is done.
`
static DEFAULT_SYSTEM_MESSAGE = `You are a helpful AI assistant.
Solve tasks using your coding and language skills.
In the following cases, suggest python code (in a python coding block) or shell script (in a sh coding block) for the user to execute.
1. When you need to collect info, use the code to output the info you need, for example, browse or search the web, download/read a file, print the content of a webpage or a file, get the current date/time, check the operating system. After sufficient info is printed and the task is ready to be solved based on your language skill, you can solve the task by yourself.
2. When you need to perform some task with code, use the code to perform the task and output the result. Finish the task smartly.
Solve the task step by step if you need to. If a plan is not provided, explain your plan first. Be clear which step uses code, and which step uses your language skill.
When using code, you must indicate the script type in the code block. The user cannot provide any other feedback or perform any other action beyond executing the code you suggest. The user can't modify your code. So do not suggest incomplete code which requires users to modify. Don't use a code block if it's not intended to be executed by the user.
If you want the user to save the code in a file before executing it, put # filename: <filename> inside the code block as the first line. Don't include multiple code blocks in one response. Do not ask users to copy and paste the result. Instead, use 'print' function for the output when relevant. Check the execution result returned by the user.
If the result indicates there is an error, fix the error and output the code again. Suggest the full code instead of partial code or code changes. If the error can't be fixed or if the task is not solved even after the code is executed successfully, analyze the problem, revisit your assumption, collect additional info you need, and think of a different approach to try.
When you find an answer, verify the answer carefully. Include verifiable evidence in your response if possible.
Reply "TERMINATE" in the end when everything is done.`
constructor(config: AssistantAgentConfig<T>) {
const {systemMessage = AssistantAgent.DEFAULT_SYSTEM_MESSAGE, ...rest} =
@@ -37,6 +43,7 @@ export class AssistantAgent<
super({
systemMessage,
humanInputMode: 'NEVER',
...rest,
})
}
+189 -71
View File
@@ -1,71 +1,19 @@
import {OpenAIStream, StreamingTextResponse} from 'ai'
import OpenAI, {ClientOptions} from 'openai'
import debug from 'debug'
import {AIProvider} from '../providers/ai-provider'
import type {Callable, LlmConfig, Message, ReplyFunc, Role} from '../types'
import type {
Callable,
HumanInputMode,
LlmConfig,
Message,
ReplyFunc,
Role,
} from '../types'
import {Agent} from './agent'
/**
* The model to use for the OpenAI API.
*/
type Model = OpenAI.Chat.Completions.ChatCompletionCreateParams['model']
/**
* The configuration for the OpenAI provider.
*/
export type OpenAIProviderConfig = {
/**
* The options for the OpenAI client.
* @default {apiKey: process.env.OPENAI_API_KEY}
*/
options?: ClientOptions
/**
* The model to use for the OpenAI API.
* @default 'gpt-3.5-turbo'
*/
model?: Model
}
/**
* The provider for the OpenAI API.
*/
export class OpenAIProvider extends AIProvider<OpenAI> {
private model: Model
constructor(config: OpenAIProviderConfig = {}) {
const {
options = {
apiKey: process.env.OPENAI_API_KEY,
},
model = 'gpt-3.5-turbo',
} = config
const client = new OpenAI(options)
super(client)
this.model = model
}
/**
* Create a completion based on the received messages.
*
* @param messages A list of messages to send to the OpenAI API.
* @returns The completion.
*/
async create(messages: Message[]) {
const response = await this.client.chat.completions.create({
model: this.model,
stream: true,
messages: messages!,
})
const stream = OpenAIStream(response)
const result = new StreamingTextResponse(stream)
return await result.text()
}
}
const log = debug('autogen:agent')
type TerminatingMessageFunc = (message: Message) => boolean
export type ConversableAgentConfig<T extends AIProvider<unknown>> = {
/**
* The name of the agent.
@@ -91,7 +39,7 @@ export type ConversableAgentConfig<T extends AIProvider<unknown>> = {
/**
* A termination message function.
*/
isTerminationMsg?: Callable
isTerminationMsg?: TerminatingMessageFunc
/**
* Max consecutive auto replies.
@@ -103,7 +51,7 @@ export type ConversableAgentConfig<T extends AIProvider<unknown>> = {
* The human input mode.
* @default "TERMINATE"
*/
// humanInputMode?: HumanInputMode
humanInputMode?: HumanInputMode
/**
* A map of functions.
@@ -144,14 +92,22 @@ export class ConversableAgent<T extends AIProvider<unknown>> extends Agent {
private defaultAutoReply: string
private provider: T
private _systemMessage: Message[]
private humanInputMode: HumanInputMode
private isTerminationMsg: TerminatingMessageFunc
private replyAtReceive: Map<Agent, boolean> = new Map()
private consecutiveAutoReplyCounter: Map<Agent, number> = new Map()
private maxConsecutiveAutoReply: number
constructor(config: ConversableAgentConfig<T>) {
const {
name,
provider,
onMessageReceived,
systemMessage = '',
systemMessage = 'You are a helpful AI Assistant.',
defaultAutoReply = '',
humanInputMode = 'TERMINATE',
isTerminationMsg = message => !!message.content?.endsWith('TERMINATE'),
maxConsecutiveAutoReply = 100,
} = config
super(name)
@@ -160,6 +116,10 @@ export class ConversableAgent<T extends AIProvider<unknown>> extends Agent {
this._messages = new Map<Agent, Message[]>()
this.onMessageReceived = onMessageReceived
this.defaultAutoReply = defaultAutoReply
this.humanInputMode = humanInputMode
this.isTerminationMsg = isTerminationMsg
this.maxConsecutiveAutoReply = maxConsecutiveAutoReply
this._systemMessage = [
{
content: systemMessage,
@@ -181,10 +141,10 @@ export class ConversableAgent<T extends AIProvider<unknown>> extends Agent {
// trigger: this,
// replyFunc: this.generate_function_call_reply,
// });
// this.registerReply({
// trigger: this,
// replyFunc: this.check_termination_and_human_reply,
// });
this.registerReply({
trigger: this,
replyFunc: this.checkTerminationAndHumanReply,
})
}
/**
@@ -291,7 +251,10 @@ export class ConversableAgent<T extends AIProvider<unknown>> extends Agent {
requestReply?: boolean,
) {
this.processReceivedMessage(message, sender)
if (!requestReply) {
if (
requestReply === false ||
(requestReply === undefined && !this.replyAtReceive.get(sender))
) {
return
}
@@ -443,6 +406,161 @@ export class ConversableAgent<T extends AIProvider<unknown>> extends Agent {
} as const
}
/**
* Check if the conversation should be terminated, and if human reply is provided.
* @param messages
* @param sender
* @param config
* @returns
*/
public async checkTerminationAndHumanReply(
messages?: Message[],
sender?: Agent,
) {
if (!sender) {
throw new Error('Sender must be provided.')
}
if (!messages) {
messages = this.chatMessages.get(sender!) || []
}
const message = messages[messages.length - 1]
let reply = ''
if (this.humanInputMode === 'ALWAYS') {
// `Provide feedback to ${sender.name}. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: `,
// TODO: should let human know that can use auto-reply
reply = await this.getHumanInput(true)
if (this.isTerminationMsg(message)) {
reply = 'exit'
}
} else {
const x = this.consecutiveAutoReplyCounter.get(sender) || 0
if (x >= this.maxConsecutiveAutoReply) {
if (this.humanInputMode === 'NEVER') {
reply = 'exit'
} else {
const terminate = this.isTerminationMsg(message)
// TODO: should let human know that can use auto-reply if terminate is true
reply = await this.getHumanInput(true)
reply = reply || !terminate ? reply : 'exit'
}
} else if (this.isTerminationMsg(message)) {
if (this.humanInputMode === 'NEVER') {
reply = 'exit'
} else {
reply = await this.getHumanInput()
reply = reply || 'exit'
}
}
}
// stop the conversation
if (reply === 'exit') {
this.consecutiveAutoReplyCounter.set(sender, 0)
return {
success: true,
reply: null,
} as const
}
// send the human reply
if (reply || this.maxConsecutiveAutoReply === 0) {
this.consecutiveAutoReplyCounter.set(sender, 0)
return {
success: true,
reply,
} as const
}
// increment the consecutive_auto_reply_counter
const x = this.consecutiveAutoReplyCounter.get(sender) || 0
this.consecutiveAutoReplyCounter.set(sender, x + 1)
if (this.humanInputMode !== 'NEVER') {
log('🤖 USING AUTO REPLY...')
}
return {
success: false,
reply: null,
} as const
}
/**
* Get human input.
* @returns The human input.
*/
public async getHumanInput(autoReply = false) {
log('🤖 GETTING HUMAN INPUT...', {autoReply})
return ''
}
/**
* Initiate a chat with the recipient agent.
* Reset the consecutive auto reply counter.
* If `clearHistory` is True, the chat history with the recipient agent will be cleared.
* `generateInitMessage` is called to generate the initial message for the agent.
* @param recipient the recipient agent.
* @param clearHistory whether to clear the chat history with the agent.
* @param context Any context information. "message" needs to be provided if the `generate_init_message` method is not overridden.
*/
public initiateChat<T extends AIProvider<unknown>>(
recipient: ConversableAgent<T>,
message: string,
clearHistory = true,
) {
this.prepareChat(recipient, clearHistory)
this.send(message, recipient)
}
private prepareChat<T extends AIProvider<unknown>>(
recipient: ConversableAgent<T>,
clearHistory: boolean,
) {
this.resetConsecutiveAutoReplyCounter(recipient)
recipient.resetConsecutiveAutoReplyCounter(this)
this.replyAtReceive.set(recipient, true)
recipient.replyAtReceive.set(this, true)
if (clearHistory) {
this.clearHistory(recipient)
recipient.clearHistory(this)
}
}
public resetConsecutiveAutoReplyCounter(sender?: Agent) {
if (!sender) {
this.consecutiveAutoReplyCounter = new Map()
return
}
this.consecutiveAutoReplyCounter.set(sender, 0)
}
/**
* Clear the chat history of the agent.
* @param sender the agent with whom the chat history to clear. If None, clear the chat history with all agents.
* @returns
*/
public clearHistory(sender?: Agent) {
if (!sender) {
this._messages.clear()
return
}
this._messages.set(sender, [])
}
// def generate_init_message(self, **context) -> Union[str, Dict]:
// """Generate the initial message for the agent.
// Override this function to customize the initial message based on user's request.
// If not overriden, "message" needs to be provided in the context.
// """
// return context["message"]
reset(): void {
throw new Error('Method not implemented.')
}
+4
View File
@@ -0,0 +1,4 @@
export * from './agent'
export * from './assistent-agent'
export * from './conversable-agent'
export * from './user-proxy-agent'
+7 -5
View File
@@ -4,6 +4,9 @@ import {
type ConversableAgentConfig,
} from './conversable-agent'
export type UserProxyAgentConfig<T extends AIProvider<unknown>> =
ConversableAgentConfig<T>
/**
* A proxy agent for the user, that can execute code and provide feedback to the other agents.
*
@@ -18,13 +21,12 @@ import {
export class UserProxyAgent<
T extends AIProvider<unknown>,
> extends ConversableAgent<T> {
constructor(config: ConversableAgentConfig<T>) {
const {
// humanInputMode = 'ALWAYS',
...rest
} = config
constructor(config: UserProxyAgentConfig<T>) {
const {humanInputMode = 'ALWAYS', ...rest} = config
super({
humanInputMode,
systemMessage: '',
...rest,
})
}
+32
View File
@@ -1,3 +1,35 @@
import {AssistantAgent, UserProxyAgent} from './agents'
import {OpenAIProvider} from './providers'
console.log('🚀 starting')
console.time('🚀 finishing')
const provider = new OpenAIProvider({
model: 'gpt-3.5-turbo',
})
const assistant = new AssistantAgent({
name: '🤖',
provider,
onMessageReceived(message, sender) {
console.log(`${sender.name}: ${message.content}`)
},
})
const user = new UserProxyAgent({
name: '🧑',
provider,
systemMessage:
'You are a human assistant. Reply "TERMINATE" in the end when there is a correct answer.',
onMessageReceived(message, sender) {
console.log(`${sender.name}: ${message.content}`)
},
})
await user.initiateChat(assistant, 'how much is 2 + 2?')
console.timeEnd('🚀 finishing')
// const response = await openai.chat.completions.create({
// model: "gpt-3.5-turbo",
// stream: true,
+2
View File
@@ -0,0 +1,2 @@
export * from './ai-provider'
export * from './openai-provider'
+72
View File
@@ -0,0 +1,72 @@
import {OpenAIStream, StreamingTextResponse} from 'ai'
import debug from 'debug'
import OpenAI, {ClientOptions} from 'openai'
import {Message} from '../types'
import {AIProvider} from './ai-provider'
const log = debug('autogen:provider')
/**
* The model to use for the OpenAI API.
*/
type Model = OpenAI.Chat.Completions.ChatCompletionCreateParams['model']
/**
* The configuration for the OpenAI provider.
*/
export type OpenAIProviderConfig = {
/**
* The options for the OpenAI client.
* @default {apiKey: process.env.OPENAI_API_KEY}
*/
options?: ClientOptions
/**
* The model to use for the OpenAI API.
* @default 'gpt-3.5-turbo'
*/
model?: Model
}
/**
* The provider for the OpenAI API.
* By default, the model is set to 'gpt-3.5-turbo'.
*/
export class OpenAIProvider extends AIProvider<OpenAI> {
private model: Model
constructor(config: OpenAIProviderConfig = {}) {
const {
options = {
apiKey: process.env.OPENAI_API_KEY,
},
model = 'gpt-3.5-turbo',
} = config
const client = new OpenAI(options)
super(client)
this.model = model
}
/**
* Create a completion based on the received messages.
*
* @param messages A list of messages to send to the OpenAI API.
* @returns The completion.
*/
async create(messages: Message[]) {
log('calling `openai.chat.completions.create`')
const response = await this.client.chat.completions.create({
model: this.model,
stream: true,
messages: messages!,
})
const stream = OpenAIStream(response)
const result = new StreamingTextResponse(stream)
return await result.text()
}
}
+5 -10
View File
@@ -11,19 +11,14 @@ export type Callable = (
messages?: Message[],
sender?: Agent,
config?: LlmConfig,
) => Promise<
| {
success: false
reply: null
}
| {
success: true
reply: string
}
>
) => Promise<{
success: boolean
reply: string | null
}>
export type LlmConfig = {
// TODO: Add types for this
}
export type HumanInputMode = 'TERMINATE' | 'ALWAYS' | 'NEVER'
export type ReplyFunc = {
/**