const Anthropic = require("@anthropic-ai/sdk");
const { RetryError } = require("../error.js");
const Provider = require("./ai-provider.js");
/**
* The provider for the Anthropic API.
* By default, the model is set to 'claude-2'.
*/
class AnthropicProvider extends Provider {
model;
constructor(config = {}) {
const {
options = {
apiKey: process.env.ANTHROPIC_API_KEY,
maxRetries: 3,
},
model = "claude-2",
} = config;
const client = new Anthropic(options);
super(client);
this.model = model;
}
/**
* Create a completion based on the received messages.
*
* @param messages A list of messages to send to the Anthropic API.
* @param functions
* @returns The completion.
*/
async complete(messages, functions) {
// clone messages to avoid mutating the original array
const promptMessages = [...messages];
if (functions) {
const functionPrompt = this.getFunctionPrompt(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} ${content}`
: "";
case "function":
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: 3000,
stream: false,
prompt,
});
const result = response.completion.trim();
// TODO: get cost from response
const cost = 0;
// Handle function calls if the model returns a function call
if (result.includes("function_name") && functions) {
let functionCall;
try {
functionCall = JSON.parse(result);
} catch (error) {
// call the complete function again in case it gets a json error
return await this.complete(
[
...messages,
{
role: "function",
content: `You gave me this function call: ${result} but I couldn't parse it.
${error?.message}
Please try again.`,
},
],
functions
);
}
return {
result: null,
functionCall,
cost,
};
}
return {
result,
cost,
};
} catch (error) {
// If invalid Auth error we need to abort because no amount of waiting
// will make auth better.
if (error instanceof Anthropic.AuthenticationError) throw error;
if (
error instanceof Anthropic.RateLimitError ||
error instanceof Anthropic.InternalServerError ||
error instanceof Anthropic.APIError // Also will catch AuthenticationError!!!
) {
throw new RetryError(error.message);
}
throw error;
}
}
getFunctionPrompt(functions = []) {
const functionPrompt = `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, 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: {}
}
`;
return functionPrompt;
}
}
module.exports = AnthropicProvider;