mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-24 22:03:54 +00:00
feat: lafClaude
This commit is contained in:
@@ -26,7 +26,6 @@
|
|||||||
"axios": "^1.3.3",
|
"axios": "^1.3.3",
|
||||||
"crypto": "^1.0.1",
|
"crypto": "^1.0.1",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"delay": "^5.0.0",
|
|
||||||
"eventsource-parser": "^0.1.0",
|
"eventsource-parser": "^0.1.0",
|
||||||
"formidable": "^2.1.1",
|
"formidable": "^2.1.1",
|
||||||
"framer-motion": "^9.0.6",
|
"framer-motion": "^9.0.6",
|
||||||
|
7
pnpm-lock.yaml
generated
7
pnpm-lock.yaml
generated
@@ -28,7 +28,6 @@ specifiers:
|
|||||||
axios: ^1.3.3
|
axios: ^1.3.3
|
||||||
crypto: ^1.0.1
|
crypto: ^1.0.1
|
||||||
dayjs: ^1.11.7
|
dayjs: ^1.11.7
|
||||||
delay: ^5.0.0
|
|
||||||
eslint: 8.34.0
|
eslint: 8.34.0
|
||||||
eslint-config-next: 13.1.6
|
eslint-config-next: 13.1.6
|
||||||
eventsource-parser: ^0.1.0
|
eventsource-parser: ^0.1.0
|
||||||
@@ -83,7 +82,6 @@ dependencies:
|
|||||||
axios: registry.npmmirror.com/axios/1.3.3
|
axios: registry.npmmirror.com/axios/1.3.3
|
||||||
crypto: registry.npmmirror.com/crypto/1.0.1
|
crypto: registry.npmmirror.com/crypto/1.0.1
|
||||||
dayjs: registry.npmmirror.com/dayjs/1.11.7
|
dayjs: registry.npmmirror.com/dayjs/1.11.7
|
||||||
delay: 5.0.0
|
|
||||||
eventsource-parser: registry.npmmirror.com/eventsource-parser/0.1.0
|
eventsource-parser: registry.npmmirror.com/eventsource-parser/0.1.0
|
||||||
formidable: registry.npmmirror.com/formidable/2.1.1
|
formidable: registry.npmmirror.com/formidable/2.1.1
|
||||||
framer-motion: registry.npmmirror.com/framer-motion/9.0.6_biqbaboplfbrettd7655fr4n2y
|
framer-motion: registry.npmmirror.com/framer-motion/9.0.6_biqbaboplfbrettd7655fr4n2y
|
||||||
@@ -288,11 +286,6 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/delay/5.0.0:
|
|
||||||
resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/fsevents/2.3.2:
|
/fsevents/2.3.2:
|
||||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
|
@@ -3,12 +3,13 @@
|
|||||||
[Git 仓库](https://github.com/c121914yu/FastGPT)
|
[Git 仓库](https://github.com/c121914yu/FastGPT)
|
||||||
|
|
||||||
### 交流群/问题反馈
|
### 交流群/问题反馈
|
||||||
|
|
||||||
扫码满了,加个小号,定时拉
|
扫码满了,加个小号,定时拉
|
||||||
wx号: fastgpt123
|
wx 号: fastgpt123
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
### 快速开始
|
### 快速开始
|
||||||
|
|
||||||
1. 使用手机号注册账号。
|
1. 使用手机号注册账号。
|
||||||
2. 进入账号页面,添加关联账号,目前只有 openai 的账号可以添加,直接去 openai 官网,把 API Key 粘贴过来。
|
2. 进入账号页面,添加关联账号,目前只有 openai 的账号可以添加,直接去 openai 官网,把 API Key 粘贴过来。
|
||||||
3. 如果填写了自己的 openai 账号,使用时会直接用你的账号。如果没有填写,需要付费使用平台的账号。
|
3. 如果填写了自己的 openai 账号,使用时会直接用你的账号。如果没有填写,需要付费使用平台的账号。
|
||||||
@@ -16,15 +17,16 @@ wx号: fastgpt123
|
|||||||
5. 在模型列表点击【对话】,即可使用 API 进行聊天。
|
5. 在模型列表点击【对话】,即可使用 API 进行聊天。
|
||||||
|
|
||||||
### 价格表
|
### 价格表
|
||||||
|
|
||||||
如果使用了自己的 Api Key,不会计费。可以在账号页,看到详细账单。单纯使用 chatGPT 模型进行对话,只有一个计费项目。使用知识库时,包含**对话**和**索引**生成两个计费项。
|
如果使用了自己的 Api Key,不会计费。可以在账号页,看到详细账单。单纯使用 chatGPT 模型进行对话,只有一个计费项目。使用知识库时,包含**对话**和**索引**生成两个计费项。
|
||||||
| 计费项 | 价格: 元/ 1K tokens(包含上下文)|
|
| 计费项 | 价格: 元/ 1K tokens(包含上下文)|
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
| claude - 对话 | 免费 |
|
||||||
| chatgpt - 对话 | 0.03 |
|
| chatgpt - 对话 | 0.03 |
|
||||||
| 知识库 - 对话 | 0.03 |
|
| 知识库 - 对话 | 0.03 |
|
||||||
| 知识库 - 索引 | 0.004 |
|
| 知识库 - 索引 | 0.004 |
|
||||||
| 文件拆分 | 0.03 |
|
| 文件拆分 | 0.03 |
|
||||||
|
|
||||||
|
|
||||||
### 定制 prompt
|
### 定制 prompt
|
||||||
|
|
||||||
1. 进入模型编辑页
|
1. 进入模型编辑页
|
||||||
|
@@ -24,6 +24,7 @@ export const delChatHistoryById = (id: string) => GET(`/chat/removeHistory?id=${
|
|||||||
*/
|
*/
|
||||||
export const postSaveChat = (data: {
|
export const postSaveChat = (data: {
|
||||||
modelId: string;
|
modelId: string;
|
||||||
|
newChatId: '' | string;
|
||||||
chatId: '' | string;
|
chatId: '' | string;
|
||||||
prompts: ChatItemType[];
|
prompts: ChatItemType[];
|
||||||
}) => POST<string>('/chat/saveChat', data);
|
}) => POST<string>('/chat/saveChat', data);
|
||||||
|
102
src/api/fetch.ts
102
src/api/fetch.ts
@@ -1,5 +1,5 @@
|
|||||||
import { getToken } from '../utils/user';
|
import { getToken } from '../utils/user';
|
||||||
import { SYSTEM_PROMPT_PREFIX } from '@/constants/chat';
|
import { SYSTEM_PROMPT_HEADER, NEW_CHATID_HEADER } from '@/constants/chat';
|
||||||
|
|
||||||
interface StreamFetchProps {
|
interface StreamFetchProps {
|
||||||
url: string;
|
url: string;
|
||||||
@@ -8,60 +8,56 @@ interface StreamFetchProps {
|
|||||||
abortSignal: AbortController;
|
abortSignal: AbortController;
|
||||||
}
|
}
|
||||||
export const streamFetch = ({ url, data, onMessage, abortSignal }: StreamFetchProps) =>
|
export const streamFetch = ({ url, data, onMessage, abortSignal }: StreamFetchProps) =>
|
||||||
new Promise<{ responseText: string; systemPrompt: string }>(async (resolve, reject) => {
|
new Promise<{ responseText: string; systemPrompt: string; newChatId: string }>(
|
||||||
try {
|
async (resolve, reject) => {
|
||||||
const res = await fetch(url, {
|
try {
|
||||||
method: 'POST',
|
const res = await fetch(url, {
|
||||||
headers: {
|
method: 'POST',
|
||||||
'Content-Type': 'application/json',
|
headers: {
|
||||||
Authorization: getToken() || ''
|
'Content-Type': 'application/json',
|
||||||
},
|
Authorization: getToken() || ''
|
||||||
body: JSON.stringify(data),
|
},
|
||||||
signal: abortSignal.signal
|
body: JSON.stringify(data),
|
||||||
});
|
signal: abortSignal.signal
|
||||||
const reader = res.body?.getReader();
|
});
|
||||||
if (!reader) return;
|
const reader = res.body?.getReader();
|
||||||
|
if (!reader) return;
|
||||||
|
|
||||||
const decoder = new TextDecoder();
|
const decoder = new TextDecoder();
|
||||||
let responseText = '';
|
|
||||||
let systemPrompt = '';
|
|
||||||
|
|
||||||
const read = async () => {
|
const systemPrompt = decodeURIComponent(res.headers.get(SYSTEM_PROMPT_HEADER) || '');
|
||||||
try {
|
const newChatId = decodeURIComponent(res.headers.get(NEW_CHATID_HEADER) || '');
|
||||||
const { done, value } = await reader?.read();
|
|
||||||
if (done) {
|
let responseText = '';
|
||||||
if (res.status === 200) {
|
|
||||||
resolve({ responseText, systemPrompt });
|
const read = async () => {
|
||||||
} else {
|
try {
|
||||||
const parseError = JSON.parse(responseText);
|
const { done, value } = await reader?.read();
|
||||||
reject(parseError?.message || '请求异常');
|
if (done) {
|
||||||
|
if (res.status === 200) {
|
||||||
|
resolve({ responseText, systemPrompt, newChatId });
|
||||||
|
} else {
|
||||||
|
const parseError = JSON.parse(responseText);
|
||||||
|
reject(parseError?.message || '请求异常');
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
const text = decoder.decode(value).replace(/<br\/>/g, '\n');
|
||||||
return;
|
responseText += text;
|
||||||
|
onMessage(text);
|
||||||
|
read();
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err?.message === 'The user aborted a request.') {
|
||||||
|
return resolve({ responseText, systemPrompt, newChatId });
|
||||||
|
}
|
||||||
|
reject(typeof err === 'string' ? err : err?.message || '请求异常');
|
||||||
}
|
}
|
||||||
let text = decoder.decode(value).replace(/<br\/>/g, '\n');
|
};
|
||||||
// check system prompt
|
read();
|
||||||
if (text.includes(SYSTEM_PROMPT_PREFIX)) {
|
} catch (err: any) {
|
||||||
const arr = text.split(SYSTEM_PROMPT_PREFIX);
|
console.log(err, '====');
|
||||||
systemPrompt = arr.pop() || '';
|
reject(typeof err === 'string' ? err : err?.message || '请求异常');
|
||||||
|
}
|
||||||
text = arr.join('');
|
|
||||||
}
|
|
||||||
responseText += text;
|
|
||||||
onMessage(text);
|
|
||||||
|
|
||||||
read();
|
|
||||||
} catch (err: any) {
|
|
||||||
if (err?.message === 'The user aborted a request.') {
|
|
||||||
return resolve({ responseText, systemPrompt });
|
|
||||||
}
|
|
||||||
reject(typeof err === 'string' ? err : err?.message || '请求异常');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
read();
|
|
||||||
} catch (err: any) {
|
|
||||||
console.log(err, '====');
|
|
||||||
reject(typeof err === 'string' ? err : err?.message || '请求异常');
|
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
export const SYSTEM_PROMPT_PREFIX = 'SYSTEM_PROMPT:';
|
export const SYSTEM_PROMPT_HEADER = 'System-Prompt-Header';
|
||||||
|
export const NEW_CHATID_HEADER = 'Chat-Id-Header';
|
||||||
|
|
||||||
export enum ChatRoleEnum {
|
export enum ChatRoleEnum {
|
||||||
System = 'System',
|
System = 'System',
|
||||||
|
@@ -8,13 +8,17 @@ export enum OpenAiChatEnum {
|
|||||||
'GPT4' = 'gpt-4',
|
'GPT4' = 'gpt-4',
|
||||||
'GPT432k' = 'gpt-4-32k'
|
'GPT432k' = 'gpt-4-32k'
|
||||||
}
|
}
|
||||||
|
export enum ClaudeEnum {
|
||||||
|
'Claude' = 'Claude'
|
||||||
|
}
|
||||||
|
|
||||||
export type ChatModelType = `${OpenAiChatEnum}`;
|
export type ChatModelType = `${OpenAiChatEnum}` | `${ClaudeEnum}`;
|
||||||
|
|
||||||
export type ChatModelItemType = {
|
export type ChatModelItemType = {
|
||||||
chatModel: ChatModelType;
|
chatModel: ChatModelType;
|
||||||
name: string;
|
name: string;
|
||||||
contextMaxToken: number;
|
contextMaxToken: number;
|
||||||
|
systemMaxToken: number;
|
||||||
maxTemperature: number;
|
maxTemperature: number;
|
||||||
price: number;
|
price: number;
|
||||||
};
|
};
|
||||||
@@ -24,6 +28,7 @@ export const ChatModelMap = {
|
|||||||
chatModel: OpenAiChatEnum.GPT35,
|
chatModel: OpenAiChatEnum.GPT35,
|
||||||
name: 'ChatGpt',
|
name: 'ChatGpt',
|
||||||
contextMaxToken: 4096,
|
contextMaxToken: 4096,
|
||||||
|
systemMaxToken: 3000,
|
||||||
maxTemperature: 1.5,
|
maxTemperature: 1.5,
|
||||||
price: 3
|
price: 3
|
||||||
},
|
},
|
||||||
@@ -31,6 +36,7 @@ export const ChatModelMap = {
|
|||||||
chatModel: OpenAiChatEnum.GPT4,
|
chatModel: OpenAiChatEnum.GPT4,
|
||||||
name: 'Gpt4',
|
name: 'Gpt4',
|
||||||
contextMaxToken: 8000,
|
contextMaxToken: 8000,
|
||||||
|
systemMaxToken: 4000,
|
||||||
maxTemperature: 1.5,
|
maxTemperature: 1.5,
|
||||||
price: 30
|
price: 30
|
||||||
},
|
},
|
||||||
@@ -38,12 +44,24 @@ export const ChatModelMap = {
|
|||||||
chatModel: OpenAiChatEnum.GPT432k,
|
chatModel: OpenAiChatEnum.GPT432k,
|
||||||
name: 'Gpt4-32k',
|
name: 'Gpt4-32k',
|
||||||
contextMaxToken: 32000,
|
contextMaxToken: 32000,
|
||||||
|
systemMaxToken: 4000,
|
||||||
maxTemperature: 1.5,
|
maxTemperature: 1.5,
|
||||||
price: 30
|
price: 30
|
||||||
|
},
|
||||||
|
[ClaudeEnum.Claude]: {
|
||||||
|
chatModel: ClaudeEnum.Claude,
|
||||||
|
name: 'Claude(免费体验)',
|
||||||
|
contextMaxToken: 9000,
|
||||||
|
systemMaxToken: 2500,
|
||||||
|
maxTemperature: 1,
|
||||||
|
price: 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const chatModelList: ChatModelItemType[] = [ChatModelMap[OpenAiChatEnum.GPT35]];
|
export const chatModelList: ChatModelItemType[] = [
|
||||||
|
ChatModelMap[OpenAiChatEnum.GPT35],
|
||||||
|
ChatModelMap[ClaudeEnum.Claude]
|
||||||
|
];
|
||||||
|
|
||||||
export enum ModelStatusEnum {
|
export enum ModelStatusEnum {
|
||||||
running = 'running',
|
running = 'running',
|
||||||
|
@@ -42,11 +42,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
await connectToDatabase();
|
await connectToDatabase();
|
||||||
let startTime = Date.now();
|
let startTime = Date.now();
|
||||||
|
|
||||||
const { model, showModelDetail, content, userApiKey, systemApiKey, userId } = await authChat({
|
const { model, showModelDetail, content, userOpenAiKey, systemAuthKey, userId } =
|
||||||
modelId,
|
await authChat({
|
||||||
chatId,
|
modelId,
|
||||||
authorization
|
chatId,
|
||||||
});
|
authorization
|
||||||
|
});
|
||||||
|
|
||||||
const modelConstantsData = ChatModelMap[model.chat.chatModel];
|
const modelConstantsData = ChatModelMap[model.chat.chatModel];
|
||||||
|
|
||||||
@@ -56,8 +57,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
// 使用了知识库搜索
|
// 使用了知识库搜索
|
||||||
if (model.chat.useKb) {
|
if (model.chat.useKb) {
|
||||||
const { code, searchPrompt } = await searchKb({
|
const { code, searchPrompt } = await searchKb({
|
||||||
userApiKey,
|
userOpenAiKey,
|
||||||
systemApiKey,
|
|
||||||
prompts,
|
prompts,
|
||||||
similarity: ModelVectorSearchModeMap[model.chat.searchMode]?.similarity,
|
similarity: ModelVectorSearchModeMap[model.chat.searchMode]?.similarity,
|
||||||
model,
|
model,
|
||||||
@@ -86,10 +86,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
|
|
||||||
// 发出请求
|
// 发出请求
|
||||||
const { streamResponse } = await modelServiceToolMap[model.chat.chatModel].chatCompletion({
|
const { streamResponse } = await modelServiceToolMap[model.chat.chatModel].chatCompletion({
|
||||||
apiKey: userApiKey || systemApiKey,
|
apiKey: userOpenAiKey || systemAuthKey,
|
||||||
temperature: +temperature,
|
temperature: +temperature,
|
||||||
messages: prompts,
|
messages: prompts,
|
||||||
stream: true
|
stream: true,
|
||||||
|
res,
|
||||||
|
chatId
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('api response time:', `${(Date.now() - startTime) / 1000}s`);
|
console.log('api response time:', `${(Date.now() - startTime) / 1000}s`);
|
||||||
@@ -108,7 +110,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
|
|
||||||
// 只有使用平台的 key 才计费
|
// 只有使用平台的 key 才计费
|
||||||
pushChatBill({
|
pushChatBill({
|
||||||
isPay: !userApiKey,
|
isPay: !userOpenAiKey,
|
||||||
chatModel: model.chat.chatModel,
|
chatModel: model.chat.chatModel,
|
||||||
userId,
|
userId,
|
||||||
chatId,
|
chatId,
|
||||||
|
@@ -9,7 +9,8 @@ import mongoose from 'mongoose';
|
|||||||
/* 聊天内容存存储 */
|
/* 聊天内容存存储 */
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
try {
|
try {
|
||||||
const { chatId, modelId, prompts } = req.body as {
|
const { chatId, modelId, prompts, newChatId } = req.body as {
|
||||||
|
newChatId: '' | string;
|
||||||
chatId: '' | string;
|
chatId: '' | string;
|
||||||
modelId: string;
|
modelId: string;
|
||||||
prompts: ChatItemType[];
|
prompts: ChatItemType[];
|
||||||
@@ -35,6 +36,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
// 没有 chatId, 创建一个对话
|
// 没有 chatId, 创建一个对话
|
||||||
if (!chatId) {
|
if (!chatId) {
|
||||||
const { _id } = await Chat.create({
|
const { _id } = await Chat.create({
|
||||||
|
_id: newChatId ? new mongoose.Types.ObjectId(newChatId) : undefined,
|
||||||
userId,
|
userId,
|
||||||
modelId,
|
modelId,
|
||||||
content,
|
content,
|
||||||
|
@@ -65,7 +65,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
const similarity = ModelVectorSearchModeMap[model.chat.searchMode]?.similarity || 0.22;
|
const similarity = ModelVectorSearchModeMap[model.chat.searchMode]?.similarity || 0.22;
|
||||||
|
|
||||||
const { code, searchPrompt } = await searchKb({
|
const { code, searchPrompt } = await searchKb({
|
||||||
systemApiKey: apiKey,
|
|
||||||
prompts,
|
prompts,
|
||||||
similarity,
|
similarity,
|
||||||
model,
|
model,
|
||||||
|
@@ -116,7 +116,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
|
|
||||||
// 获取向量匹配到的提示词
|
// 获取向量匹配到的提示词
|
||||||
const { searchPrompt } = await searchKb({
|
const { searchPrompt } = await searchKb({
|
||||||
systemApiKey: apiKey,
|
|
||||||
similarity: ModelVectorSearchModeMap[model.chat.searchMode]?.similarity,
|
similarity: ModelVectorSearchModeMap[model.chat.searchMode]?.similarity,
|
||||||
prompts,
|
prompts,
|
||||||
model,
|
model,
|
||||||
|
@@ -206,7 +206,7 @@ const Chat = ({ modelId, chatId }: { modelId: string; chatId: string }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 流请求,获取数据
|
// 流请求,获取数据
|
||||||
const { responseText, systemPrompt } = await streamFetch({
|
let { responseText, systemPrompt, newChatId } = await streamFetch({
|
||||||
url: '/api/chat/chat',
|
url: '/api/chat/chat',
|
||||||
data: {
|
data: {
|
||||||
prompt,
|
prompt,
|
||||||
@@ -234,10 +234,10 @@ const Chat = ({ modelId, chatId }: { modelId: string; chatId: string }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let newChatId = '';
|
|
||||||
// save chat record
|
// save chat record
|
||||||
try {
|
try {
|
||||||
newChatId = await postSaveChat({
|
newChatId = await postSaveChat({
|
||||||
|
newChatId, // 如果有newChatId,会自动以这个Id创建对话框
|
||||||
modelId,
|
modelId,
|
||||||
chatId,
|
chatId,
|
||||||
prompts: [
|
prompts: [
|
||||||
|
@@ -45,12 +45,12 @@ export async function generateQA(next = false): Promise<any> {
|
|||||||
const textList: string[] = dataItem.textList.slice(-5);
|
const textList: string[] = dataItem.textList.slice(-5);
|
||||||
|
|
||||||
// 获取 openapi Key
|
// 获取 openapi Key
|
||||||
let userApiKey = '',
|
let userOpenAiKey = '',
|
||||||
systemApiKey = '';
|
systemAuthKey = '';
|
||||||
try {
|
try {
|
||||||
const key = await getApiKey({ model: OpenAiChatEnum.GPT35, userId: dataItem.userId });
|
const key = await getApiKey({ model: OpenAiChatEnum.GPT35, userId: dataItem.userId });
|
||||||
userApiKey = key.userApiKey;
|
userOpenAiKey = key.userOpenAiKey;
|
||||||
systemApiKey = key.systemApiKey;
|
systemAuthKey = key.systemAuthKey;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error?.code === 501) {
|
if (error?.code === 501) {
|
||||||
// 余额不够了, 清空该记录
|
// 余额不够了, 清空该记录
|
||||||
@@ -73,18 +73,18 @@ export async function generateQA(next = false): Promise<any> {
|
|||||||
textList.map((text) =>
|
textList.map((text) =>
|
||||||
modelServiceToolMap[OpenAiChatEnum.GPT35]
|
modelServiceToolMap[OpenAiChatEnum.GPT35]
|
||||||
.chatCompletion({
|
.chatCompletion({
|
||||||
apiKey: userApiKey || systemApiKey,
|
apiKey: userOpenAiKey || systemAuthKey,
|
||||||
temperature: 0.8,
|
temperature: 0.8,
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
obj: ChatRoleEnum.System,
|
obj: ChatRoleEnum.System,
|
||||||
value: `你是出题人
|
value: `你是出题人
|
||||||
${dataItem.prompt || '下面是"一段长文本"'}
|
${dataItem.prompt || '下面是"一段长文本"'}
|
||||||
从中选出5至20个题目和答案.答案详细.按格式返回: Q1:
|
从中选出5至20个题目和答案.答案详细.按格式返回: Q1:
|
||||||
A1:
|
A1:
|
||||||
Q2:
|
Q2:
|
||||||
A2:
|
A2:
|
||||||
...`
|
...`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
obj: 'Human',
|
obj: 'Human',
|
||||||
@@ -98,7 +98,7 @@ export async function generateQA(next = false): Promise<any> {
|
|||||||
console.log(`split result length: `, result.length);
|
console.log(`split result length: `, result.length);
|
||||||
// 计费
|
// 计费
|
||||||
pushSplitDataBill({
|
pushSplitDataBill({
|
||||||
isPay: !userApiKey && result.length > 0,
|
isPay: !userOpenAiKey && result.length > 0,
|
||||||
userId: dataItem.userId,
|
userId: dataItem.userId,
|
||||||
type: 'QA',
|
type: 'QA',
|
||||||
textLen: responseMessages.map((item) => item.value).join('').length,
|
textLen: responseMessages.map((item) => item.value).join('').length,
|
||||||
|
@@ -2,7 +2,6 @@ import { openaiCreateEmbedding } from '../utils/chat/openai';
|
|||||||
import { getApiKey } from '../utils/auth';
|
import { getApiKey } from '../utils/auth';
|
||||||
import { openaiError2 } from '../errorCode';
|
import { openaiError2 } from '../errorCode';
|
||||||
import { PgClient } from '@/service/pg';
|
import { PgClient } from '@/service/pg';
|
||||||
import { embeddingModel } from '@/constants/model';
|
|
||||||
|
|
||||||
export async function generateVector(next = false): Promise<any> {
|
export async function generateVector(next = false): Promise<any> {
|
||||||
if (process.env.queueTask !== '1') {
|
if (process.env.queueTask !== '1') {
|
||||||
@@ -42,11 +41,10 @@ export async function generateVector(next = false): Promise<any> {
|
|||||||
dataId = dataItem.id;
|
dataId = dataItem.id;
|
||||||
|
|
||||||
// 获取 openapi Key
|
// 获取 openapi Key
|
||||||
let userApiKey, systemApiKey;
|
let userOpenAiKey;
|
||||||
try {
|
try {
|
||||||
const res = await getApiKey({ model: embeddingModel, userId: dataItem.userId });
|
const res = await getApiKey({ model: 'gpt-3.5-turbo', userId: dataItem.userId });
|
||||||
userApiKey = res.userApiKey;
|
userOpenAiKey = res.userOpenAiKey;
|
||||||
systemApiKey = res.systemApiKey;
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error?.code === 501) {
|
if (error?.code === 501) {
|
||||||
await PgClient.delete('modelData', {
|
await PgClient.delete('modelData', {
|
||||||
@@ -63,8 +61,7 @@ export async function generateVector(next = false): Promise<any> {
|
|||||||
const { vectors } = await openaiCreateEmbedding({
|
const { vectors } = await openaiCreateEmbedding({
|
||||||
textArr: [dataItem.q],
|
textArr: [dataItem.q],
|
||||||
userId: dataItem.userId,
|
userId: dataItem.userId,
|
||||||
userApiKey,
|
userOpenAiKey
|
||||||
systemApiKey
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 更新 pg 向量和状态数据
|
// 更新 pg 向量和状态数据
|
||||||
|
@@ -3,22 +3,20 @@ import { ModelDataStatusEnum, ModelVectorSearchModeEnum, ChatModelMap } from '@/
|
|||||||
import { ModelSchema } from '@/types/mongoSchema';
|
import { ModelSchema } from '@/types/mongoSchema';
|
||||||
import { openaiCreateEmbedding } from '../utils/chat/openai';
|
import { openaiCreateEmbedding } from '../utils/chat/openai';
|
||||||
import { ChatRoleEnum } from '@/constants/chat';
|
import { ChatRoleEnum } from '@/constants/chat';
|
||||||
import { sliceTextByToken } from '@/utils/chat';
|
import { modelToolMap } from '@/utils/chat';
|
||||||
import { ChatItemSimpleType } from '@/types/chat';
|
import { ChatItemSimpleType } from '@/types/chat';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* use openai embedding search kb
|
* use openai embedding search kb
|
||||||
*/
|
*/
|
||||||
export const searchKb = async ({
|
export const searchKb = async ({
|
||||||
userApiKey,
|
userOpenAiKey,
|
||||||
systemApiKey,
|
|
||||||
prompts,
|
prompts,
|
||||||
similarity = 0.2,
|
similarity = 0.2,
|
||||||
model,
|
model,
|
||||||
userId
|
userId
|
||||||
}: {
|
}: {
|
||||||
userApiKey?: string;
|
userOpenAiKey?: string;
|
||||||
systemApiKey: string;
|
|
||||||
prompts: ChatItemSimpleType[];
|
prompts: ChatItemSimpleType[];
|
||||||
model: ModelSchema;
|
model: ModelSchema;
|
||||||
userId: string;
|
userId: string;
|
||||||
@@ -33,8 +31,7 @@ export const searchKb = async ({
|
|||||||
async function search(textArr: string[] = []) {
|
async function search(textArr: string[] = []) {
|
||||||
// 获取提示词的向量
|
// 获取提示词的向量
|
||||||
const { vectors: promptVectors } = await openaiCreateEmbedding({
|
const { vectors: promptVectors } = await openaiCreateEmbedding({
|
||||||
userApiKey,
|
userOpenAiKey,
|
||||||
systemApiKey,
|
|
||||||
userId,
|
userId,
|
||||||
textArr
|
textArr
|
||||||
});
|
});
|
||||||
@@ -81,11 +78,24 @@ export const searchKb = async ({
|
|||||||
].filter((item) => item);
|
].filter((item) => item);
|
||||||
const systemPrompts = await search(searchArr);
|
const systemPrompts = await search(searchArr);
|
||||||
|
|
||||||
// filter system prompt
|
// filter system prompts.
|
||||||
if (
|
const filterRateMap: Record<number, number[]> = {
|
||||||
systemPrompts.length === 0 &&
|
1: [1],
|
||||||
model.chat.searchMode === ModelVectorSearchModeEnum.hightSimilarity
|
2: [0.7, 0.3]
|
||||||
) {
|
};
|
||||||
|
const filterRate = filterRateMap[systemPrompts.length] || filterRateMap[0];
|
||||||
|
|
||||||
|
const filterSystemPrompt = filterRate
|
||||||
|
.map((rate, i) =>
|
||||||
|
modelToolMap[model.chat.chatModel].sliceText({
|
||||||
|
text: systemPrompts[i],
|
||||||
|
length: Math.floor(modelConstantsData.systemMaxToken * rate)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
/* 高相似度+不回复 */
|
||||||
|
if (!filterSystemPrompt && model.chat.searchMode === ModelVectorSearchModeEnum.hightSimilarity) {
|
||||||
return {
|
return {
|
||||||
code: 201,
|
code: 201,
|
||||||
searchPrompt: {
|
searchPrompt: {
|
||||||
@@ -95,7 +105,7 @@ export const searchKb = async ({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
/* 高相似度+无上下文,不添加额外知识,仅用系统提示词 */
|
/* 高相似度+无上下文,不添加额外知识,仅用系统提示词 */
|
||||||
if (systemPrompts.length === 0 && model.chat.searchMode === ModelVectorSearchModeEnum.noContext) {
|
if (!filterSystemPrompt && model.chat.searchMode === ModelVectorSearchModeEnum.noContext) {
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
searchPrompt: model.chat.systemPrompt
|
searchPrompt: model.chat.systemPrompt
|
||||||
@@ -107,25 +117,7 @@ export const searchKb = async ({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 有匹配情况下,system 添加知识库内容。 */
|
/* 有匹配 */
|
||||||
|
|
||||||
// filter system prompts. max 70% tokens
|
|
||||||
const filterRateMap: Record<number, number[]> = {
|
|
||||||
1: [0.7],
|
|
||||||
2: [0.5, 0.2]
|
|
||||||
};
|
|
||||||
const filterRate = filterRateMap[systemPrompts.length] || filterRateMap[0];
|
|
||||||
|
|
||||||
const filterSystemPrompt = filterRate
|
|
||||||
.map((rate, i) =>
|
|
||||||
sliceTextByToken({
|
|
||||||
model: model.chat.chatModel,
|
|
||||||
text: systemPrompts[i],
|
|
||||||
length: Math.floor(modelConstantsData.contextMaxToken * rate)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.join('\n');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
searchPrompt: {
|
searchPrompt: {
|
||||||
@@ -133,9 +125,9 @@ export const searchKb = async ({
|
|||||||
value: `
|
value: `
|
||||||
${model.chat.systemPrompt}
|
${model.chat.systemPrompt}
|
||||||
${
|
${
|
||||||
model.chat.searchMode === ModelVectorSearchModeEnum.hightSimilarity ? `不回答知识库外的内容.` : ''
|
model.chat.searchMode === ModelVectorSearchModeEnum.hightSimilarity ? '不回答知识库外的内容.' : ''
|
||||||
}
|
}
|
||||||
知识库内容为: ${filterSystemPrompt}'
|
知识库内容为: '${filterSystemPrompt}'
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -4,15 +4,10 @@ import { Chat, Model, OpenApi, User } from '../mongo';
|
|||||||
import type { ModelSchema } from '@/types/mongoSchema';
|
import type { ModelSchema } from '@/types/mongoSchema';
|
||||||
import type { ChatItemSimpleType } from '@/types/chat';
|
import type { ChatItemSimpleType } from '@/types/chat';
|
||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
import { defaultModel } from '@/constants/model';
|
import { ClaudeEnum, defaultModel } from '@/constants/model';
|
||||||
import { formatPrice } from '@/utils/user';
|
import { formatPrice } from '@/utils/user';
|
||||||
import { ERROR_ENUM } from '../errorCode';
|
import { ERROR_ENUM } from '../errorCode';
|
||||||
import {
|
import { ChatModelType, OpenAiChatEnum } from '@/constants/model';
|
||||||
ChatModelType,
|
|
||||||
OpenAiChatEnum,
|
|
||||||
embeddingModel,
|
|
||||||
EmbeddingModelType
|
|
||||||
} from '@/constants/model';
|
|
||||||
|
|
||||||
/* 校验 token */
|
/* 校验 token */
|
||||||
export const authToken = (token?: string): Promise<string> => {
|
export const authToken = (token?: string): Promise<string> => {
|
||||||
@@ -34,13 +29,7 @@ export const authToken = (token?: string): Promise<string> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* 获取 api 请求的 key */
|
/* 获取 api 请求的 key */
|
||||||
export const getApiKey = async ({
|
export const getApiKey = async ({ model, userId }: { model: ChatModelType; userId: string }) => {
|
||||||
model,
|
|
||||||
userId
|
|
||||||
}: {
|
|
||||||
model: ChatModelType | EmbeddingModelType;
|
|
||||||
userId: string;
|
|
||||||
}) => {
|
|
||||||
const user = await User.findById(userId);
|
const user = await User.findById(userId);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return Promise.reject({
|
return Promise.reject({
|
||||||
@@ -51,29 +40,29 @@ export const getApiKey = async ({
|
|||||||
|
|
||||||
const keyMap = {
|
const keyMap = {
|
||||||
[OpenAiChatEnum.GPT35]: {
|
[OpenAiChatEnum.GPT35]: {
|
||||||
userApiKey: user.openaiKey || '',
|
userOpenAiKey: user.openaiKey || '',
|
||||||
systemApiKey: process.env.OPENAIKEY as string
|
systemAuthKey: process.env.OPENAIKEY as string
|
||||||
},
|
},
|
||||||
[OpenAiChatEnum.GPT4]: {
|
[OpenAiChatEnum.GPT4]: {
|
||||||
userApiKey: user.openaiKey || '',
|
userOpenAiKey: user.openaiKey || '',
|
||||||
systemApiKey: process.env.OPENAIKEY as string
|
systemAuthKey: process.env.OPENAIKEY as string
|
||||||
},
|
},
|
||||||
[OpenAiChatEnum.GPT432k]: {
|
[OpenAiChatEnum.GPT432k]: {
|
||||||
userApiKey: user.openaiKey || '',
|
userOpenAiKey: user.openaiKey || '',
|
||||||
systemApiKey: process.env.OPENAIKEY as string
|
systemAuthKey: process.env.OPENAIKEY as string
|
||||||
},
|
},
|
||||||
[embeddingModel]: {
|
[ClaudeEnum.Claude]: {
|
||||||
userApiKey: user.openaiKey || '',
|
userOpenAiKey: '',
|
||||||
systemApiKey: process.env.OPENAIKEY as string
|
systemAuthKey: process.env.LAFKEY as string
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 有自己的key
|
// 有自己的key
|
||||||
if (keyMap[model].userApiKey) {
|
if (keyMap[model].userOpenAiKey) {
|
||||||
return {
|
return {
|
||||||
user,
|
user,
|
||||||
userApiKey: keyMap[model].userApiKey,
|
userOpenAiKey: keyMap[model].userOpenAiKey,
|
||||||
systemApiKey: ''
|
systemAuthKey: ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,8 +76,8 @@ export const getApiKey = async ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
user,
|
user,
|
||||||
userApiKey: '',
|
userOpenAiKey: '',
|
||||||
systemApiKey: keyMap[model].systemApiKey
|
systemAuthKey: keyMap[model].systemAuthKey
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -176,11 +165,11 @@ export const authChat = async ({
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
// 获取 user 的 apiKey
|
// 获取 user 的 apiKey
|
||||||
const { userApiKey, systemApiKey } = await getApiKey({ model: model.chat.chatModel, userId });
|
const { userOpenAiKey, systemAuthKey } = await getApiKey({ model: model.chat.chatModel, userId });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userApiKey,
|
userOpenAiKey,
|
||||||
systemApiKey,
|
systemAuthKey,
|
||||||
content,
|
content,
|
||||||
userId,
|
userId,
|
||||||
model,
|
model,
|
||||||
|
103
src/service/utils/chat/claude.ts
Normal file
103
src/service/utils/chat/claude.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import { modelToolMap } from '@/utils/chat';
|
||||||
|
import { ChatCompletionType, StreamResponseType } from './index';
|
||||||
|
import { ChatRoleEnum } from '@/constants/chat';
|
||||||
|
import axios from 'axios';
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
import { NEW_CHATID_HEADER } from '@/constants/chat';
|
||||||
|
import { ClaudeEnum } from '@/constants/model';
|
||||||
|
|
||||||
|
/* 模型对话 */
|
||||||
|
export const lafClaudChat = async ({
|
||||||
|
apiKey,
|
||||||
|
messages,
|
||||||
|
stream,
|
||||||
|
chatId,
|
||||||
|
res
|
||||||
|
}: ChatCompletionType) => {
|
||||||
|
const conversationId = chatId || String(new mongoose.Types.ObjectId());
|
||||||
|
// create a new chat
|
||||||
|
!chatId &&
|
||||||
|
messages.filter((item) => item.obj === 'Human').length === 1 &&
|
||||||
|
res?.setHeader(NEW_CHATID_HEADER, conversationId);
|
||||||
|
|
||||||
|
// get system prompt
|
||||||
|
const systemPrompt = messages
|
||||||
|
.filter((item) => item.obj === 'System')
|
||||||
|
.map((item) => item.value)
|
||||||
|
.join('\n');
|
||||||
|
const systemPromptText = systemPrompt ? `这是我的知识:'${systemPrompt}'\n` : '';
|
||||||
|
|
||||||
|
const prompt = systemPromptText + messages[messages.length - 1].value;
|
||||||
|
|
||||||
|
const lafResponse = await axios.post(
|
||||||
|
'https://hnvacz.laf.run/claude-gpt',
|
||||||
|
{
|
||||||
|
prompt,
|
||||||
|
stream,
|
||||||
|
conversationId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: apiKey
|
||||||
|
},
|
||||||
|
timeout: stream ? 40000 : 240000,
|
||||||
|
responseType: stream ? 'stream' : 'json'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let responseText = '';
|
||||||
|
let totalTokens = 0;
|
||||||
|
|
||||||
|
if (!stream) {
|
||||||
|
responseText = lafResponse.data?.text || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
streamResponse: lafResponse,
|
||||||
|
responseMessages: messages.concat({ obj: ChatRoleEnum.AI, value: responseText }),
|
||||||
|
responseText,
|
||||||
|
totalTokens
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/* openai stream response */
|
||||||
|
export const lafClaudStreamResponse = async ({
|
||||||
|
stream,
|
||||||
|
chatResponse,
|
||||||
|
prompts
|
||||||
|
}: StreamResponseType) => {
|
||||||
|
try {
|
||||||
|
let responseContent = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
for await (const chunk of chatResponse.data as any) {
|
||||||
|
if (stream.destroyed) {
|
||||||
|
// 流被中断了,直接忽略后面的内容
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const content = decoder.decode(chunk);
|
||||||
|
responseContent += content;
|
||||||
|
content && stream.push(content.replace(/\n/g, '<br/>'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('pipe error', error);
|
||||||
|
}
|
||||||
|
// count tokens
|
||||||
|
const finishMessages = prompts.concat({
|
||||||
|
obj: ChatRoleEnum.AI,
|
||||||
|
value: responseContent
|
||||||
|
});
|
||||||
|
const totalTokens = modelToolMap[ClaudeEnum.Claude].countTokens({
|
||||||
|
messages: finishMessages
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
responseContent,
|
||||||
|
totalTokens,
|
||||||
|
finishMessages
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
};
|
@@ -1,19 +1,19 @@
|
|||||||
import { ChatItemSimpleType } from '@/types/chat';
|
import { ChatItemSimpleType } from '@/types/chat';
|
||||||
import { modelToolMap } from '@/utils/chat';
|
import { modelToolMap } from '@/utils/chat';
|
||||||
import type { ChatModelType } from '@/constants/model';
|
import type { ChatModelType } from '@/constants/model';
|
||||||
import { ChatRoleEnum, SYSTEM_PROMPT_PREFIX } from '@/constants/chat';
|
import { ChatRoleEnum, SYSTEM_PROMPT_HEADER } from '@/constants/chat';
|
||||||
import { OpenAiChatEnum } from '@/constants/model';
|
import { OpenAiChatEnum, ClaudeEnum } from '@/constants/model';
|
||||||
import { chatResponse, openAiStreamResponse } from './openai';
|
import { chatResponse, openAiStreamResponse } from './openai';
|
||||||
|
import { lafClaudChat, lafClaudStreamResponse } from './claude';
|
||||||
import type { NextApiResponse } from 'next';
|
import type { NextApiResponse } from 'next';
|
||||||
import type { PassThrough } from 'stream';
|
import type { PassThrough } from 'stream';
|
||||||
import delay from 'delay';
|
|
||||||
|
|
||||||
export type ChatCompletionType = {
|
export type ChatCompletionType = {
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
temperature: number;
|
temperature: number;
|
||||||
messages: ChatItemSimpleType[];
|
messages: ChatItemSimpleType[];
|
||||||
stream: boolean;
|
stream: boolean;
|
||||||
params?: any;
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
export type ChatCompletionResponseType = {
|
export type ChatCompletionResponseType = {
|
||||||
streamResponse: any;
|
streamResponse: any;
|
||||||
@@ -25,6 +25,9 @@ export type StreamResponseType = {
|
|||||||
stream: PassThrough;
|
stream: PassThrough;
|
||||||
chatResponse: any;
|
chatResponse: any;
|
||||||
prompts: ChatItemSimpleType[];
|
prompts: ChatItemSimpleType[];
|
||||||
|
res: NextApiResponse;
|
||||||
|
systemPrompt?: string;
|
||||||
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
export type StreamResponseReturnType = {
|
export type StreamResponseReturnType = {
|
||||||
responseContent: string;
|
responseContent: string;
|
||||||
@@ -65,6 +68,10 @@ export const modelServiceToolMap: Record<
|
|||||||
model: OpenAiChatEnum.GPT432k,
|
model: OpenAiChatEnum.GPT432k,
|
||||||
...data
|
...data
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
[ClaudeEnum.Claude]: {
|
||||||
|
chatCompletion: lafClaudChat,
|
||||||
|
streamResponse: lafClaudStreamResponse
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -143,14 +150,13 @@ export const resStreamResponse = async ({
|
|||||||
prompts
|
prompts
|
||||||
}: StreamResponseType & {
|
}: StreamResponseType & {
|
||||||
model: ChatModelType;
|
model: ChatModelType;
|
||||||
res: NextApiResponse;
|
|
||||||
systemPrompt?: string;
|
|
||||||
}) => {
|
}) => {
|
||||||
// 创建响应流
|
// 创建响应流
|
||||||
res.setHeader('Content-Type', 'text/event-stream;charset-utf-8');
|
res.setHeader('Content-Type', 'text/event-stream;charset-utf-8');
|
||||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||||
res.setHeader('X-Accel-Buffering', 'no');
|
res.setHeader('X-Accel-Buffering', 'no');
|
||||||
res.setHeader('Cache-Control', 'no-cache, no-transform');
|
res.setHeader('Cache-Control', 'no-cache, no-transform');
|
||||||
|
systemPrompt && res.setHeader(SYSTEM_PROMPT_HEADER, encodeURIComponent(systemPrompt));
|
||||||
stream.pipe(res);
|
stream.pipe(res);
|
||||||
|
|
||||||
const { responseContent, totalTokens, finishMessages } = await modelServiceToolMap[
|
const { responseContent, totalTokens, finishMessages } = await modelServiceToolMap[
|
||||||
@@ -158,16 +164,11 @@ export const resStreamResponse = async ({
|
|||||||
].streamResponse({
|
].streamResponse({
|
||||||
chatResponse,
|
chatResponse,
|
||||||
stream,
|
stream,
|
||||||
prompts
|
prompts,
|
||||||
|
res,
|
||||||
|
systemPrompt
|
||||||
});
|
});
|
||||||
|
|
||||||
await delay(100);
|
|
||||||
|
|
||||||
// push system prompt
|
|
||||||
!stream.destroyed &&
|
|
||||||
systemPrompt &&
|
|
||||||
stream.push(`${SYSTEM_PROMPT_PREFIX}${systemPrompt.replace(/\n/g, '<br/>')}`);
|
|
||||||
|
|
||||||
// close stream
|
// close stream
|
||||||
!stream.destroyed && stream.push(null);
|
!stream.destroyed && stream.push(null);
|
||||||
stream.destroy();
|
stream.destroy();
|
||||||
|
@@ -19,18 +19,18 @@ export const getOpenAIApi = (apiKey: string) => {
|
|||||||
|
|
||||||
/* 获取向量 */
|
/* 获取向量 */
|
||||||
export const openaiCreateEmbedding = async ({
|
export const openaiCreateEmbedding = async ({
|
||||||
userApiKey,
|
userOpenAiKey,
|
||||||
systemApiKey,
|
|
||||||
userId,
|
userId,
|
||||||
textArr
|
textArr
|
||||||
}: {
|
}: {
|
||||||
userApiKey?: string;
|
userOpenAiKey?: string;
|
||||||
systemApiKey: string;
|
|
||||||
userId: string;
|
userId: string;
|
||||||
textArr: string[];
|
textArr: string[];
|
||||||
}) => {
|
}) => {
|
||||||
|
const systemAuthKey = process.env.OPENAIKEY as string;
|
||||||
|
|
||||||
// 获取 chatAPI
|
// 获取 chatAPI
|
||||||
const chatAPI = getOpenAIApi(userApiKey || systemApiKey);
|
const chatAPI = getOpenAIApi(userOpenAiKey || systemAuthKey);
|
||||||
|
|
||||||
// 把输入的内容转成向量
|
// 把输入的内容转成向量
|
||||||
const res = await chatAPI
|
const res = await chatAPI
|
||||||
@@ -50,7 +50,7 @@ export const openaiCreateEmbedding = async ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
pushGenerateVectorBill({
|
pushGenerateVectorBill({
|
||||||
isPay: !userApiKey,
|
isPay: !userOpenAiKey,
|
||||||
userId,
|
userId,
|
||||||
text: textArr.join(''),
|
text: textArr.join(''),
|
||||||
tokenLen: res.tokenLen
|
tokenLen: res.tokenLen
|
||||||
|
3
src/utils/chat/claude.ts
Normal file
3
src/utils/chat/claude.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export const ClaudeSliceTextByToken = ({ text, length }: { text: string; length: number }) => {
|
||||||
|
return text.slice(0, length);
|
||||||
|
};
|
@@ -1,46 +1,30 @@
|
|||||||
import { OpenAiChatEnum } from '@/constants/model';
|
import { ClaudeEnum, OpenAiChatEnum } from '@/constants/model';
|
||||||
import type { ChatModelType } from '@/constants/model';
|
import type { ChatModelType } from '@/constants/model';
|
||||||
import type { ChatItemSimpleType } from '@/types/chat';
|
import type { ChatItemSimpleType } from '@/types/chat';
|
||||||
import { countOpenAIToken, getOpenAiEncMap, adaptChatItem_openAI } from './openai';
|
import { countOpenAIToken, openAiSliceTextByToken } from './openai';
|
||||||
import { ChatCompletionRequestMessage } from 'openai';
|
import { ClaudeSliceTextByToken } from './claude';
|
||||||
|
|
||||||
export type CountTokenType = { messages: ChatItemSimpleType[] };
|
|
||||||
|
|
||||||
export const modelToolMap: Record<
|
export const modelToolMap: Record<
|
||||||
ChatModelType,
|
ChatModelType,
|
||||||
{
|
{
|
||||||
countTokens: (data: CountTokenType) => number;
|
countTokens: (data: { messages: ChatItemSimpleType[] }) => number;
|
||||||
adaptChatMessages: (data: CountTokenType) => ChatCompletionRequestMessage[];
|
sliceText: (data: { text: string; length: number }) => string;
|
||||||
}
|
}
|
||||||
> = {
|
> = {
|
||||||
[OpenAiChatEnum.GPT35]: {
|
[OpenAiChatEnum.GPT35]: {
|
||||||
countTokens: ({ messages }: CountTokenType) =>
|
countTokens: ({ messages }) => countOpenAIToken({ model: OpenAiChatEnum.GPT35, messages }),
|
||||||
countOpenAIToken({ model: OpenAiChatEnum.GPT35, messages }),
|
sliceText: (data) => openAiSliceTextByToken({ model: OpenAiChatEnum.GPT35, ...data })
|
||||||
adaptChatMessages: adaptChatItem_openAI
|
|
||||||
},
|
},
|
||||||
[OpenAiChatEnum.GPT4]: {
|
[OpenAiChatEnum.GPT4]: {
|
||||||
countTokens: ({ messages }: CountTokenType) =>
|
countTokens: ({ messages }) => countOpenAIToken({ model: OpenAiChatEnum.GPT4, messages }),
|
||||||
countOpenAIToken({ model: OpenAiChatEnum.GPT4, messages }),
|
sliceText: (data) => openAiSliceTextByToken({ model: OpenAiChatEnum.GPT35, ...data })
|
||||||
adaptChatMessages: adaptChatItem_openAI
|
|
||||||
},
|
},
|
||||||
[OpenAiChatEnum.GPT432k]: {
|
[OpenAiChatEnum.GPT432k]: {
|
||||||
countTokens: ({ messages }: CountTokenType) =>
|
countTokens: ({ messages }) => countOpenAIToken({ model: OpenAiChatEnum.GPT432k, messages }),
|
||||||
countOpenAIToken({ model: OpenAiChatEnum.GPT432k, messages }),
|
sliceText: (data) => openAiSliceTextByToken({ model: OpenAiChatEnum.GPT35, ...data })
|
||||||
adaptChatMessages: adaptChatItem_openAI
|
},
|
||||||
|
[ClaudeEnum.Claude]: {
|
||||||
|
countTokens: () => 0,
|
||||||
|
sliceText: ClaudeSliceTextByToken
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sliceTextByToken = ({
|
|
||||||
model = 'gpt-3.5-turbo',
|
|
||||||
text,
|
|
||||||
length
|
|
||||||
}: {
|
|
||||||
model: ChatModelType;
|
|
||||||
text: string;
|
|
||||||
length: number;
|
|
||||||
}) => {
|
|
||||||
const enc = getOpenAiEncMap()[model];
|
|
||||||
const encodeText = enc.encode(text);
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
return decoder.decode(enc.decode(encodeText.slice(0, length)));
|
|
||||||
};
|
|
||||||
|
@@ -2,7 +2,7 @@ import { encoding_for_model, type Tiktoken } from '@dqbd/tiktoken';
|
|||||||
import type { ChatItemSimpleType } from '@/types/chat';
|
import type { ChatItemSimpleType } from '@/types/chat';
|
||||||
import { ChatRoleEnum } from '@/constants/chat';
|
import { ChatRoleEnum } from '@/constants/chat';
|
||||||
import { ChatCompletionRequestMessage, ChatCompletionRequestMessageRoleEnum } from 'openai';
|
import { ChatCompletionRequestMessage, ChatCompletionRequestMessageRoleEnum } from 'openai';
|
||||||
|
import { OpenAiChatEnum } from '@/constants/model';
|
||||||
import Graphemer from 'graphemer';
|
import Graphemer from 'graphemer';
|
||||||
|
|
||||||
const textDecoder = new TextDecoder();
|
const textDecoder = new TextDecoder();
|
||||||
@@ -52,7 +52,7 @@ export function countOpenAIToken({
|
|||||||
model
|
model
|
||||||
}: {
|
}: {
|
||||||
messages: ChatItemSimpleType[];
|
messages: ChatItemSimpleType[];
|
||||||
model: 'gpt-3.5-turbo' | 'gpt-4' | 'gpt-4-32k';
|
model: `${OpenAiChatEnum}`;
|
||||||
}) {
|
}) {
|
||||||
function getChatGPTEncodingText(
|
function getChatGPTEncodingText(
|
||||||
messages: { role: 'system' | 'user' | 'assistant'; content: string; name?: string }[],
|
messages: { role: 'system' | 'user' | 'assistant'; content: string; name?: string }[],
|
||||||
@@ -104,3 +104,18 @@ export function countOpenAIToken({
|
|||||||
|
|
||||||
return text2TokensLen(getOpenAiEncMap()[model], getChatGPTEncodingText(adaptMessages, model));
|
return text2TokensLen(getOpenAiEncMap()[model], getChatGPTEncodingText(adaptMessages, model));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const openAiSliceTextByToken = ({
|
||||||
|
model = 'gpt-3.5-turbo',
|
||||||
|
text,
|
||||||
|
length
|
||||||
|
}: {
|
||||||
|
model: `${OpenAiChatEnum}`;
|
||||||
|
text: string;
|
||||||
|
length: number;
|
||||||
|
}) => {
|
||||||
|
const enc = getOpenAiEncMap()[model];
|
||||||
|
const encodeText = enc.encode(text);
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
return decoder.decode(enc.decode(encodeText.slice(0, length)));
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user