Skip to content

Commit

Permalink
feat: Add claude-v2 API (#516)
Browse files Browse the repository at this point in the history
  • Loading branch information
ajkost authored Sep 27, 2023
1 parent adf95b3 commit 1573996
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/background/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import {
} from '../services/apis/openai-api'
import { generateAnswersWithCustomApi } from '../services/apis/custom-api.mjs'
import { generateAnswersWithAzureOpenaiApi } from '../services/apis/azure-openai-api.mjs'
import { generateAnswersWithClaudeApi } from '../services/apis/claude-api.mjs'
import { generateAnswersWithWaylaidwandererApi } from '../services/apis/waylaidwanderer-api.mjs'
import { generateAnswersWithPoeWebApi } from '../services/apis/poe-web.mjs'
import {
azureOpenAiApiModelKeys,
claudeApiModelKeys,
bardWebModelKeys,
bingWebModelKeys,
chatgptApiModelKeys,
Expand Down Expand Up @@ -111,6 +113,8 @@ async function executeApi(session, port, config) {
await generateAnswersWithCustomApi(port, session.question, session, '', config.customModelName)
} else if (azureOpenAiApiModelKeys.includes(session.modelName)) {
await generateAnswersWithAzureOpenaiApi(port, session.question, session)
} else if (claudeApiModelKeys.includes(session.modelName)) {
await generateAnswersWithClaudeApi(port, session.question, session)
} else if (githubThirdPartyApiModelKeys.includes(session.modelName)) {
await generateAnswersWithWaylaidwandererApi(port, session.question, session)
} else if (poeWebModelKeys.includes(session.modelName)) {
Expand Down
7 changes: 7 additions & 0 deletions src/config/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const chatgptApiModelKeys = [
]
export const customApiModelKeys = ['customModel']
export const azureOpenAiApiModelKeys = ['azureOpenAi']
export const claudeApiModelKeys = ['claude2Api']
export const githubThirdPartyApiModelKeys = ['waylaidwandererApi']
export const poeWebModelKeys = [
'poeAiWebSage', //poe.com/Assistant
Expand Down Expand Up @@ -83,6 +84,7 @@ export const Models = {
desc: 'ChatGPT (GPT-3.5-turbo-16k 0613)',
},
claude2WebFree: { value: 'claude-2', desc: 'Claude.ai (Web, Claude 2)' },
claude2Api: { value: '', desc: 'Claude.ai (API, Claude 2)' },
bingFree4: { value: '', desc: 'Bing (Web, GPT-4)' },
bingFreeSydney: { value: '', desc: 'Bing (Web, GPT-4, Sydney)' },
bardWebFree: { value: '', desc: 'Bard (Web)' },
Expand Down Expand Up @@ -149,6 +151,7 @@ export const defaultConfig = {

poeCustomBotName: '',

claudeApiKey: '',
/** @type {keyof ModelMode}*/
modelMode: 'balanced',

Expand All @@ -175,6 +178,7 @@ export const defaultConfig = {

alwaysCreateNewConversationWindow: false,
activeApiModes: [
// 'claude2Api',
'chatgptFree35',
'chatgptFree35Mobile',
// 'chatgptPlus4',
Expand Down Expand Up @@ -288,6 +292,9 @@ export function isUsingAzureOpenAi(configOrSession) {
return azureOpenAiApiModelKeys.includes(configOrSession.modelName)
}

export function isUsingClaude2Api(configOrSession) {
return claudeApiModelKeys.includes(configOrSession.modelName)
}
export function isUsingGithubThirdPartyApi(configOrSession) {
return githubThirdPartyApiModelKeys.includes(configOrSession.modelName)
}
Expand Down
14 changes: 14 additions & 0 deletions src/popup/sections/GeneralPart.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { openUrl } from '../../utils/index.mjs'
import {
isUsingApiKey,
isUsingAzureOpenAi,
isUsingClaude2Api,
isUsingCustomModel,
isUsingCustomNameOnlyModel,
isUsingGithubThirdPartyApi,
Expand Down Expand Up @@ -142,6 +143,7 @@ export function GeneralPart({ config, updateConfig }) {
isUsingMultiModeModel(config) ||
isUsingCustomModel(config) ||
isUsingAzureOpenAi(config) ||
isUsingClaude2Api(config) ||
isUsingCustomNameOnlyModel(config)
? 'width: 50%;'
: undefined
Expand Down Expand Up @@ -259,6 +261,18 @@ export function GeneralPart({ config, updateConfig }) {
}}
/>
)}
{isUsingClaude2Api(config) && (
<input
type="password"
style="width: 50%;"
value={config.claudeApiKey}
placeholder={t('Claude API Key')}
onChange={(e) => {
const apiKey = e.target.value
updateConfig({ claudeApiKey: apiKey })
}}
/>
)}
</span>
{isUsingCustomModel(config) && (
<input
Expand Down
87 changes: 87 additions & 0 deletions src/services/apis/claude-api.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { getUserConfig } from '../../config/index.mjs'
import { getChatSystemPromptBase, pushRecord, setAbortController } from './shared.mjs'
import { getConversationPairs } from '../../utils/get-conversation-pairs.mjs'
import { fetchSSE } from '../../utils/fetch-sse.mjs'
import { isEmpty } from 'lodash-es'

/**
* @param {Runtime.Port} port
* @param {string} question
* @param {Session} session
*/
export async function generateAnswersWithClaudeApi(port, question, session) {
const { controller, messageListener, disconnectListener } = setAbortController(port)
const config = await getUserConfig()

const prompt = getConversationPairs(
session.conversationRecords.slice(-config.maxConversationContextLength),
false,
)
prompt.unshift({ role: 'Assistant', content: await getChatSystemPromptBase() })
prompt.push({ role: 'Human', content: question })

let answer = ''
await fetchSSE(
`https://api.anthropic.com/v1/complete`,
{
method: 'POST',
signal: controller.signal,
headers: {
'Content-Type': 'application/json',
'accept': 'application/json',
'anthropic-version': '2023-06-01',
'x-api-key': config.claudeApiKey,
},
body: JSON.stringify({
model: "claude-2",
prompt: "\n\nHuman: " + question + "\n\nAssistant:",
stream: true,
max_tokens_to_sample: config.maxResponseTokenLength,
temperature: config.temperature,
}),
onMessage(message) {
console.debug('sse message', message);

let data;
try {
data = JSON.parse(message);
} catch (error) {
console.debug('json error', error);
return;
}

// The Claude v2 API may send metadata fields, handle them here
if (data.conversationId) session.conversationId = data.conversationId;
if (data.parentMessageId) session.parentMessageId = data.parentMessageId;

// In Claude's case, the "completion" key holds the text
if (data.completion) {
answer += data.completion;
port.postMessage({ answer: answer, done: false, session: null });
}

// Check if the message indicates that Claude is done
if (data.stop_reason === 'stop_sequence') {
pushRecord(session, question, answer);
console.debug('conversation history', { content: session.conversationRecords });
port.postMessage({ answer: null, done: true, session: session });
}
},
async onStart() {},
async onEnd() {
port.postMessage({ done: true })
port.onMessage.removeListener(messageListener)
port.onDisconnect.removeListener(disconnectListener)
},
async onError(resp) {
port.onMessage.removeListener(messageListener)
port.onDisconnect.removeListener(disconnectListener)
if (resp instanceof Error) throw resp
const error = await resp.json().catch(() => ({}))
throw new Error(
!isEmpty(error) ? JSON.stringify(error) : `${resp.status} ${resp.statusText}`,
)
},
},
)
}

0 comments on commit 1573996

Please sign in to comment.