Skip to content

Commit

Permalink
add anthropic api response and function calls
Browse files Browse the repository at this point in the history
  • Loading branch information
wladpaiva committed Oct 27, 2023
1 parent 9a0ba57 commit b5c1b6b
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 16 deletions.
5 changes: 4 additions & 1 deletion examples/latest-news-aibitat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import * as cheerio from 'cheerio'
import {AIbitat} from '../src'
import {cli} from '../src/plugins'

export const aibitat = new AIbitat()
export const aibitat = new AIbitat({
provider: 'anthropic',
model: 'claude-2',
})
.use(cli())
.function({
name: 'aibitat-releases',
Expand Down
16 changes: 15 additions & 1 deletion src/aibitat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import debug from 'debug'
import {APIError} from './error.ts'
import {
AIProvider,
AnthropicProvider,
OpenAIProvider,
type AnthropicModel,
type OpenAIModel,
} from './providers/index.ts'

Expand All @@ -15,12 +17,22 @@ const log = debug('autogen:chat-aibitat')
* The provider config to use for the AI.
*/
export type ProviderConfig =
// FIX: should only show Openai models when there's no provider
| {
/** The OpenAI API provider */
provider?: 'openai'
/** The model to use with the OpenAI */
model?: OpenAIModel
}
| {
/** The custom AI provider */
provider: 'anthropic'
/**
* The model to use with the Anthropic API.
* @default 'claude-2'
*/
model?: AnthropicModel
}
| {
/** The custom AI provider */
provider: AIProvider<unknown>
Expand Down Expand Up @@ -286,7 +298,7 @@ export class AIbitat {
}
return {
maxRounds: 10,
role: 'Group chat manager.',
role: '',
...config,
}
}
Expand Down Expand Up @@ -824,6 +836,8 @@ ${this.getHistory({to: route.to})
switch (config.provider) {
case 'openai':
return new OpenAIProvider({model: config.model})
case 'anthropic':
return new AnthropicProvider({model: config.model})

default:
throw new Error(
Expand Down
39 changes: 39 additions & 0 deletions src/providers/anthropic.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {beforeEach, describe, expect, mock, test} from 'bun:test'

import {AuthorizationError} from '../error.ts'
import {Message} from './ai-provider.ts'
import {AnthropicProvider} from './anthropic.ts'

// NOTE: some tests are skipped because it requires a way to mock the http requests.

// // ANTHROPIC - https://docs.anthropic.com/claude/reference/errors-and-rate-limits
// 400 - Invalid request: there was an issue with the format or content of your request.
// 401 - Unauthorized: there's an issue with your API key.
// 403 - Forbidden: your API key does not have permission to use the specified resource.
// 404 - Not found: the requested resource was not found.
// 429 - Your account has hit a rate limit.
// 500 - An unexpected error has occurred internal to Anthropic's systems.
// 529 - Anthropic's API is temporarily overloaded.

const message: Message[] = [
{
content: 'Hello',
role: 'user',
},
]

test('should throw an error when there`s an authorization error', async () => {
const provider = new AnthropicProvider({
options: {
apiKey: 'invalid',
},
})

await expect(provider.create(message)).rejects.toBeInstanceOf(
AuthorizationError,
)
})

test.todo('should throw a generic error when something else happens', () => {})
test.todo('should throw a RateLimitError', () => {})
test.todo('should throw a ServerError', () => {})
158 changes: 144 additions & 14 deletions src/providers/anthropic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import {
ServerError,
UnknownError,
} from '../error.ts'
import {Message} from '../types.ts'
import {AIProvider} from './ai-provider.ts'
import {AIProvider, Message} from './ai-provider.ts'

const log = debug('autogen:provider:anthropic')

Expand Down Expand Up @@ -70,17 +69,148 @@ export class AnthropicProvider extends AIProvider<Anthropic> {
): Promise<string> {
log(`calling 'anthropic.completions.create' with model '${this.model}'`)

const response = await this.client.completions.create({
model: this.model,
max_tokens_to_sample: 300,
stream: false,
prompt: `${Anthropic.HUMAN_PROMPT}
TODO: build a prompt from the messages and functions
${Anthropic.AI_PROMPT}`,
})

return response.completion
// clone messages to avoid mutating the original array
const promptMessages = [...messages]

if (functions) {
const functionPrompt = `<functions>You have been trained to directly call a Javascript function passing a JSON Schema parameter as a response to this chat. This function will return a string that you can use to keep chatting.
Here is a list of functions available to you:
${JSON.stringify(
functions.map(({handler, ...rest}) => rest),
null,
2,
)}
When calling any of those function in order to complete your task, respond only this JSON format. Do not include any other information or any other stuff.
Function call format:
{
function_name: "givenfunctionname",
parameters: {}
}
</functions>`
// add function prompt after the first message
promptMessages.splice(1, 0, {
content: functionPrompt,
role: 'system',
})
}

const prompt = promptMessages
.map(message => {
const {content, role} = message

switch (role) {
case 'system':
return content
? `${Anthropic.HUMAN_PROMPT} <admin>${content}</admin>`
: ''

case 'user':
return `${Anthropic.HUMAN_PROMPT} ${content}`

case 'assistant':
return `${Anthropic.AI_PROMPT} ${content}`

default:
return content
}
})
.filter(Boolean)
.join('\n')
.concat(` ${Anthropic.AI_PROMPT}`)

try {
const response = await this.client.completions.create({
model: this.model,
max_tokens_to_sample: 300,
stream: false,
prompt,
})

const result = response.completion

// Handle function calls if the model returns a function call
if (result.includes('function_name') && functions) {
const functionResponse = await this.callFunction(result, functions)

return await this.create(
[
...messages,
// extend conversation with function response
{
role: 'user',
content: functionResponse,
},
],
functions,
)
}

return result
} catch (error) {
// if (error instanceof Anthropic.BadRequestError) {
// throw new Error(error.message)
// }

if (
error instanceof Anthropic.AuthenticationError ||
error instanceof Anthropic.PermissionDeniedError
) {
throw new AuthorizationError(error.message)
}

// if (error instanceof Anthropic.NotFoundError) {
// throw new Error(error.message)
// }

// if (error instanceof Anthropic.ConflictError) {
// throw new Error(error.message)
// }

// if (error instanceof Anthropic.UnprocessableEntityError) {
// throw new Error(error.message)
// }

if (error instanceof Anthropic.RateLimitError) {
throw new RateLimitError(error.message)
}

if (error instanceof Anthropic.InternalServerError) {
throw new ServerError(error.message)
}

if (error instanceof Anthropic.APIError) {
throw new UnknownError(error.message)
}

throw error
}
}

private callFunction(callJson: string, functions: FunctionDefinition[]) {
let call: object
try {
call = JSON.parse(callJson)
} catch (error) {
return `${callJson}
Invalid JSON: ${(error as Error).message}`
}

const {function_name, parameters} = call as {
function_name: string
parameters: object
}

const functionDefinition = functions.find(
({name}) => name === function_name,
)

if (!functionDefinition) {
return `${callJson} gave me a function not found.`
}

return functionDefinition.handler(parameters)
}
}
1 change: 1 addition & 0 deletions src/providers/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './ai-provider.ts'
export * from './anthropic.ts'
export * from './openai.ts'

0 comments on commit b5c1b6b

Please sign in to comment.