Commit 0d6897e1 by archer

feat: lafClaude

parent 3c8f3879
...@@ -26,7 +26,6 @@ ...@@ -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",
......
...@@ -28,7 +28,6 @@ specifiers: ...@@ -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: ...@@ -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: ...@@ -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,39 +3,41 @@ ...@@ -3,39 +3,41 @@
[Git 仓库](https://github.com/c121914yu/FastGPT) [Git 仓库](https://github.com/c121914yu/FastGPT)
### 交流群/问题反馈 ### 交流群/问题反馈
扫码满了,加个小号,定时拉 扫码满了,加个小号,定时拉
wx号: fastgpt123 wx 号: fastgpt123
![](/imgs/wx300.jpg) ![](/imgs/wx300.jpg)
### 快速开始 ### 快速开始
1. 使用手机号注册账号。
2. 进入账号页面,添加关联账号,目前只有 openai 的账号可以添加,直接去 openai 官网,把 API Key 粘贴过来。 1. 使用手机号注册账号。
2. 进入账号页面,添加关联账号,目前只有 openai 的账号可以添加,直接去 openai 官网,把 API Key 粘贴过来。
3. 如果填写了自己的 openai 账号,使用时会直接用你的账号。如果没有填写,需要付费使用平台的账号。 3. 如果填写了自己的 openai 账号,使用时会直接用你的账号。如果没有填写,需要付费使用平台的账号。
4. 进入模型页,创建一个模型,建议直接用 ChatGPT。 4. 进入模型页,创建一个模型,建议直接用 ChatGPT。
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. 进入模型编辑页
2. 调整温度和提示词 2. 调整温度和提示词
3. 使用该模型对话。每次对话时,提示词和温度都会自动注入,方便管理个人的模型。建议把自己日常经常需要使用的 5~10 个方向预设好。 3. 使用该模型对话。每次对话时,提示词和温度都会自动注入,方便管理个人的模型。建议把自己日常经常需要使用的 5~10 个方向预设好。
### 知识库 ### 知识库
1. 创建模型时选择【知识库】 1. 创建模型时选择【知识库】
2. 进入模型编辑页 2. 进入模型编辑页
3. 导入数据,可以选择手动导入,或者选择文件导入。文件导入会自动调用 chatGPT 理解文件内容,并生成知识库。 3. 导入数据,可以选择手动导入,或者选择文件导入。文件导入会自动调用 chatGPT 理解文件内容,并生成知识库。
4. 使用该模型对话。 4. 使用该模型对话。
注意:使用知识库模型对话时,tokens 消耗会加快。 注意:使用知识库模型对话时,tokens 消耗会加快。
...@@ -24,6 +24,7 @@ export const delChatHistoryById = (id: string) => GET(`/chat/removeHistory?id=${ ...@@ -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);
......
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 { ...@@ -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) {
if (res.status === 200) {
resolve({ responseText, systemPrompt });
} else {
const parseError = JSON.parse(responseText);
reject(parseError?.message || '请求异常');
}
return; let responseText = '';
}
let text = decoder.decode(value).replace(/<br\/>/g, '\n');
// check system prompt
if (text.includes(SYSTEM_PROMPT_PREFIX)) {
const arr = text.split(SYSTEM_PROMPT_PREFIX);
systemPrompt = arr.pop() || '';
text = arr.join(''); const read = async () => {
} try {
responseText += text; const { done, value } = await reader?.read();
onMessage(text); if (done) {
if (res.status === 200) {
resolve({ responseText, systemPrompt, newChatId });
} else {
const parseError = JSON.parse(responseText);
reject(parseError?.message || '请求异常');
}
read(); return;
} catch (err: any) { }
if (err?.message === 'The user aborted a request.') { const text = decoder.decode(value).replace(/<br\/>/g, '\n');
return resolve({ responseText, systemPrompt }); 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 || '请求异常');
} }
reject(typeof err === 'string' ? err : err?.message || '请求异常'); };
} read();
}; } catch (err: any) {
console.log(err, '====');
read(); reject(typeof err === 'string' ? err : err?.message || '请求异常');
} catch (err: any) { }
console.log(err, '====');
reject(typeof err === 'string' ? err : err?.message || '请求异常');
} }
}); );
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 { ...@@ -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 = { ...@@ -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 = { ...@@ -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 = { ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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'; ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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 }) => { ...@@ -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 }) => { ...@@ -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> { ...@@ -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> { ...@@ -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> { ...@@ -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'; ...@@ -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> { ...@@ -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> { ...@@ -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 '@/ ...@@ -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 ({ ...@@ -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 ({ ...@@ -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 ({ ...@@ -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 ({ ...@@ -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 ({ ...@@ -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'; ...@@ -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> => { ...@@ -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 ({ ...@@ -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 ({ ...@@ -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 ({ ...@@ -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,
......
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);
}
};
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 = { ...@@ -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< ...@@ -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 ({ ...@@ -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 ({ ...@@ -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) => { ...@@ -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 ({ ...@@ -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
......
export const ClaudeSliceTextByToken = ({ text, length }: { text: string; length: number }) => {
return text.slice(0, length);
};
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'; ...@@ -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({ ...@@ -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({ ...@@ -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)));
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment