Skip to content

Commit 1573996

Browse files
authored
feat: Add claude-v2 API (#516)
1 parent adf95b3 commit 1573996

File tree

4 files changed

+112
-0
lines changed

4 files changed

+112
-0
lines changed

src/background/index.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import {
1111
} from '../services/apis/openai-api'
1212
import { generateAnswersWithCustomApi } from '../services/apis/custom-api.mjs'
1313
import { generateAnswersWithAzureOpenaiApi } from '../services/apis/azure-openai-api.mjs'
14+
import { generateAnswersWithClaudeApi } from '../services/apis/claude-api.mjs'
1415
import { generateAnswersWithWaylaidwandererApi } from '../services/apis/waylaidwanderer-api.mjs'
1516
import { generateAnswersWithPoeWebApi } from '../services/apis/poe-web.mjs'
1617
import {
1718
azureOpenAiApiModelKeys,
19+
claudeApiModelKeys,
1820
bardWebModelKeys,
1921
bingWebModelKeys,
2022
chatgptApiModelKeys,
@@ -111,6 +113,8 @@ async function executeApi(session, port, config) {
111113
await generateAnswersWithCustomApi(port, session.question, session, '', config.customModelName)
112114
} else if (azureOpenAiApiModelKeys.includes(session.modelName)) {
113115
await generateAnswersWithAzureOpenaiApi(port, session.question, session)
116+
} else if (claudeApiModelKeys.includes(session.modelName)) {
117+
await generateAnswersWithClaudeApi(port, session.question, session)
114118
} else if (githubThirdPartyApiModelKeys.includes(session.modelName)) {
115119
await generateAnswersWithWaylaidwandererApi(port, session.question, session)
116120
} else if (poeWebModelKeys.includes(session.modelName)) {

src/config/index.mjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export const chatgptApiModelKeys = [
4444
]
4545
export const customApiModelKeys = ['customModel']
4646
export const azureOpenAiApiModelKeys = ['azureOpenAi']
47+
export const claudeApiModelKeys = ['claude2Api']
4748
export const githubThirdPartyApiModelKeys = ['waylaidwandererApi']
4849
export const poeWebModelKeys = [
4950
'poeAiWebSage', //poe.com/Assistant
@@ -83,6 +84,7 @@ export const Models = {
8384
desc: 'ChatGPT (GPT-3.5-turbo-16k 0613)',
8485
},
8586
claude2WebFree: { value: 'claude-2', desc: 'Claude.ai (Web, Claude 2)' },
87+
claude2Api: { value: '', desc: 'Claude.ai (API, Claude 2)' },
8688
bingFree4: { value: '', desc: 'Bing (Web, GPT-4)' },
8789
bingFreeSydney: { value: '', desc: 'Bing (Web, GPT-4, Sydney)' },
8890
bardWebFree: { value: '', desc: 'Bard (Web)' },
@@ -149,6 +151,7 @@ export const defaultConfig = {
149151

150152
poeCustomBotName: '',
151153

154+
claudeApiKey: '',
152155
/** @type {keyof ModelMode}*/
153156
modelMode: 'balanced',
154157

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

176179
alwaysCreateNewConversationWindow: false,
177180
activeApiModes: [
181+
// 'claude2Api',
178182
'chatgptFree35',
179183
'chatgptFree35Mobile',
180184
// 'chatgptPlus4',
@@ -288,6 +292,9 @@ export function isUsingAzureOpenAi(configOrSession) {
288292
return azureOpenAiApiModelKeys.includes(configOrSession.modelName)
289293
}
290294

295+
export function isUsingClaude2Api(configOrSession) {
296+
return claudeApiModelKeys.includes(configOrSession.modelName)
297+
}
291298
export function isUsingGithubThirdPartyApi(configOrSession) {
292299
return githubThirdPartyApiModelKeys.includes(configOrSession.modelName)
293300
}

src/popup/sections/GeneralPart.jsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { openUrl } from '../../utils/index.mjs'
44
import {
55
isUsingApiKey,
66
isUsingAzureOpenAi,
7+
isUsingClaude2Api,
78
isUsingCustomModel,
89
isUsingCustomNameOnlyModel,
910
isUsingGithubThirdPartyApi,
@@ -142,6 +143,7 @@ export function GeneralPart({ config, updateConfig }) {
142143
isUsingMultiModeModel(config) ||
143144
isUsingCustomModel(config) ||
144145
isUsingAzureOpenAi(config) ||
146+
isUsingClaude2Api(config) ||
145147
isUsingCustomNameOnlyModel(config)
146148
? 'width: 50%;'
147149
: undefined
@@ -259,6 +261,18 @@ export function GeneralPart({ config, updateConfig }) {
259261
}}
260262
/>
261263
)}
264+
{isUsingClaude2Api(config) && (
265+
<input
266+
type="password"
267+
style="width: 50%;"
268+
value={config.claudeApiKey}
269+
placeholder={t('Claude API Key')}
270+
onChange={(e) => {
271+
const apiKey = e.target.value
272+
updateConfig({ claudeApiKey: apiKey })
273+
}}
274+
/>
275+
)}
262276
</span>
263277
{isUsingCustomModel(config) && (
264278
<input

src/services/apis/claude-api.mjs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { getUserConfig } from '../../config/index.mjs'
2+
import { getChatSystemPromptBase, pushRecord, setAbortController } from './shared.mjs'
3+
import { getConversationPairs } from '../../utils/get-conversation-pairs.mjs'
4+
import { fetchSSE } from '../../utils/fetch-sse.mjs'
5+
import { isEmpty } from 'lodash-es'
6+
7+
/**
8+
* @param {Runtime.Port} port
9+
* @param {string} question
10+
* @param {Session} session
11+
*/
12+
export async function generateAnswersWithClaudeApi(port, question, session) {
13+
const { controller, messageListener, disconnectListener } = setAbortController(port)
14+
const config = await getUserConfig()
15+
16+
const prompt = getConversationPairs(
17+
session.conversationRecords.slice(-config.maxConversationContextLength),
18+
false,
19+
)
20+
prompt.unshift({ role: 'Assistant', content: await getChatSystemPromptBase() })
21+
prompt.push({ role: 'Human', content: question })
22+
23+
let answer = ''
24+
await fetchSSE(
25+
`https://api.anthropic.com/v1/complete`,
26+
{
27+
method: 'POST',
28+
signal: controller.signal,
29+
headers: {
30+
'Content-Type': 'application/json',
31+
'accept': 'application/json',
32+
'anthropic-version': '2023-06-01',
33+
'x-api-key': config.claudeApiKey,
34+
},
35+
body: JSON.stringify({
36+
model: "claude-2",
37+
prompt: "\n\nHuman: " + question + "\n\nAssistant:",
38+
stream: true,
39+
max_tokens_to_sample: config.maxResponseTokenLength,
40+
temperature: config.temperature,
41+
}),
42+
onMessage(message) {
43+
console.debug('sse message', message);
44+
45+
let data;
46+
try {
47+
data = JSON.parse(message);
48+
} catch (error) {
49+
console.debug('json error', error);
50+
return;
51+
}
52+
53+
// The Claude v2 API may send metadata fields, handle them here
54+
if (data.conversationId) session.conversationId = data.conversationId;
55+
if (data.parentMessageId) session.parentMessageId = data.parentMessageId;
56+
57+
// In Claude's case, the "completion" key holds the text
58+
if (data.completion) {
59+
answer += data.completion;
60+
port.postMessage({ answer: answer, done: false, session: null });
61+
}
62+
63+
// Check if the message indicates that Claude is done
64+
if (data.stop_reason === 'stop_sequence') {
65+
pushRecord(session, question, answer);
66+
console.debug('conversation history', { content: session.conversationRecords });
67+
port.postMessage({ answer: null, done: true, session: session });
68+
}
69+
},
70+
async onStart() {},
71+
async onEnd() {
72+
port.postMessage({ done: true })
73+
port.onMessage.removeListener(messageListener)
74+
port.onDisconnect.removeListener(disconnectListener)
75+
},
76+
async onError(resp) {
77+
port.onMessage.removeListener(messageListener)
78+
port.onDisconnect.removeListener(disconnectListener)
79+
if (resp instanceof Error) throw resp
80+
const error = await resp.json().catch(() => ({}))
81+
throw new Error(
82+
!isEmpty(error) ? JSON.stringify(error) : `${resp.status} ${resp.statusText}`,
83+
)
84+
},
85+
},
86+
)
87+
}

0 commit comments

Comments
 (0)