Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5be57da407 | ||
|
|
057c3411b9 | ||
|
|
83d755ad0e | ||
|
|
ec9852fc63 | ||
|
|
4e6f8aefe8 | ||
|
|
11352b754a | ||
|
|
965ad34283 | ||
|
|
986206b691 |
@@ -44,9 +44,10 @@ Fast GPT 允许你使用自己的 openai API KEY 来快速的调用 openai 接
|
|||||||
|
|
||||||
## Powered by
|
## Powered by
|
||||||
|
|
||||||
- [TuShan 5 分钟搭建后台管理系统](https://github.com/msgbyte/tushan)
|
- [TuShan: 5 分钟搭建后台管理系统](https://github.com/msgbyte/tushan)
|
||||||
- [Laf 3 分钟快速接入三方应用](https://github.com/labring/laf)
|
- [Laf: 3 分钟快速接入三方应用](https://github.com/labring/laf)
|
||||||
- [Sealos 快速部署集群应用](https://github.com/labring/sealos)
|
- [Sealos: 快速部署集群应用](https://github.com/labring/sealos)
|
||||||
|
- [One API: 令牌管理 & 二次分发,支持 Azure](https://github.com/songquanpeng/one-api)
|
||||||
|
|
||||||
## 🌟 Star History
|
## 🌟 Star History
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,12 @@ aliTemplateCode=xxxx
|
|||||||
TOKEN_KEY=dfdasfdas
|
TOKEN_KEY=dfdasfdas
|
||||||
# root key, 最高权限
|
# root key, 最高权限
|
||||||
ROOT_KEY=fdafasd
|
ROOT_KEY=fdafasd
|
||||||
# openai
|
# 使用 oneapi
|
||||||
# OPENAI_BASE_URL=http://ai.openai.com/v1
|
# ONEAPI_URL=https://xxxx.cloud.sealos.io/v1
|
||||||
# OPENAI_BASE_URL_AUTH=可选安全凭证,会放到 header.auth 里
|
# ONEAPI_KEY=sk-xxxx
|
||||||
OPENAIKEY=sk-xxx
|
# openai 的基本地址(国外的可以忽略,默认走 api.openai.com)。不用 oneapi 的话需要下面 2 个参数,用户的 key 也会走下面的参数
|
||||||
|
OPENAI_BASE_URL=https://xxxx.cloud.sealos.io/openai/v1
|
||||||
|
OPENAIKEY=sk-xxxx
|
||||||
# db
|
# db
|
||||||
MONGODB_URI=mongodb://username:password@0.0.0.0:27017/?authSource=admin
|
MONGODB_URI=mongodb://username:password@0.0.0.0:27017/?authSource=admin
|
||||||
MONGODB_NAME=fastgpt
|
MONGODB_NAME=fastgpt
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
"crypto": "^1.0.1",
|
"crypto": "^1.0.1",
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"eventsource-parser": "^0.1.0",
|
|
||||||
"formidable": "^2.1.1",
|
"formidable": "^2.1.1",
|
||||||
"framer-motion": "^9.0.6",
|
"framer-motion": "^9.0.6",
|
||||||
"hyperdown": "^2.4.29",
|
"hyperdown": "^2.4.29",
|
||||||
|
|||||||
10
client/pnpm-lock.yaml
generated
10
client/pnpm-lock.yaml
generated
@@ -56,9 +56,6 @@ dependencies:
|
|||||||
dayjs:
|
dayjs:
|
||||||
specifier: ^1.11.7
|
specifier: ^1.11.7
|
||||||
version: registry.npmmirror.com/dayjs@1.11.7
|
version: registry.npmmirror.com/dayjs@1.11.7
|
||||||
eventsource-parser:
|
|
||||||
specifier: ^0.1.0
|
|
||||||
version: registry.npmmirror.com/eventsource-parser@0.1.0
|
|
||||||
formidable:
|
formidable:
|
||||||
specifier: ^2.1.1
|
specifier: ^2.1.1
|
||||||
version: registry.npmmirror.com/formidable@2.1.1
|
version: registry.npmmirror.com/formidable@2.1.1
|
||||||
@@ -7510,13 +7507,6 @@ packages:
|
|||||||
version: 2.0.3
|
version: 2.0.3
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
registry.npmmirror.com/eventsource-parser@0.1.0:
|
|
||||||
resolution: {integrity: sha512-M9QjFtEIkwytUarnx113HGmgtk52LSn3jNAtnWKi3V+b9rqSfQeVdLsaD5AG/O4IrGQwmAAHBIsqbmURPTd2rA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/eventsource-parser/-/eventsource-parser-0.1.0.tgz}
|
|
||||||
name: eventsource-parser
|
|
||||||
version: 0.1.0
|
|
||||||
engines: {node: '>=14.18'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
registry.npmmirror.com/execa@5.1.1:
|
registry.npmmirror.com/execa@5.1.1:
|
||||||
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz}
|
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz}
|
||||||
name: execa
|
name: execa
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Props, ChatResponseType } from '@/pages/api/openapi/v1/chat/completions';
|
import { Props, ChatResponseType } from '@/pages/api/openapi/v1/chat/completions';
|
||||||
import { sseResponseEventEnum } from '@/constants/chat';
|
import { sseResponseEventEnum } from '@/constants/chat';
|
||||||
import { getErrText } from '@/utils/tools';
|
import { getErrText } from '@/utils/tools';
|
||||||
|
import { parseStreamChunk } from '@/utils/adapt';
|
||||||
|
|
||||||
interface StreamFetchProps {
|
interface StreamFetchProps {
|
||||||
data: Props;
|
data: Props;
|
||||||
@@ -8,103 +9,95 @@ interface StreamFetchProps {
|
|||||||
abortSignal: AbortController;
|
abortSignal: AbortController;
|
||||||
}
|
}
|
||||||
export const streamFetch = ({ data, onMessage, abortSignal }: StreamFetchProps) =>
|
export const streamFetch = ({ data, onMessage, abortSignal }: StreamFetchProps) =>
|
||||||
new Promise<ChatResponseType & { responseText: string }>(async (resolve, reject) => {
|
new Promise<ChatResponseType & { responseText: string; errMsg: string }>(
|
||||||
try {
|
async (resolve, reject) => {
|
||||||
const response = await window.fetch('/api/openapi/v1/chat/completions', {
|
try {
|
||||||
method: 'POST',
|
const response = await window.fetch('/api/openapi/v1/chat/completions', {
|
||||||
headers: {
|
method: 'POST',
|
||||||
'Content-Type': 'application/json'
|
headers: {
|
||||||
},
|
'Content-Type': 'application/json'
|
||||||
signal: abortSignal.signal,
|
},
|
||||||
body: JSON.stringify({
|
signal: abortSignal.signal,
|
||||||
...data,
|
body: JSON.stringify({
|
||||||
stream: true
|
...data,
|
||||||
})
|
stream: true
|
||||||
});
|
})
|
||||||
|
});
|
||||||
|
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
const err = await response.json();
|
const err = await response.json();
|
||||||
return reject(err);
|
return reject(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response?.body) {
|
if (!response?.body) {
|
||||||
throw new Error('Request Error');
|
throw new Error('Request Error');
|
||||||
}
|
}
|
||||||
|
|
||||||
const reader = response.body?.getReader();
|
const reader = response.body?.getReader();
|
||||||
const decoder = new TextDecoder('utf-8');
|
|
||||||
|
|
||||||
// response data
|
// response data
|
||||||
let responseText = '';
|
let responseText = '';
|
||||||
let newChatId = '';
|
let newChatId = '';
|
||||||
let quoteLen = 0;
|
let quoteLen = 0;
|
||||||
|
let errMsg = '';
|
||||||
|
|
||||||
const read = async () => {
|
const read = async () => {
|
||||||
try {
|
try {
|
||||||
const { done, value } = await reader.read();
|
const { done, value } = await reader.read();
|
||||||
if (done) {
|
if (done) {
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
|
return resolve({
|
||||||
|
responseText,
|
||||||
|
newChatId,
|
||||||
|
quoteLen,
|
||||||
|
errMsg
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return reject('响应过程出现异常~');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const chunkResponse = parseStreamChunk(value);
|
||||||
|
|
||||||
|
chunkResponse.forEach((item) => {
|
||||||
|
// parse json data
|
||||||
|
const data = (() => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(item.data);
|
||||||
|
} catch (error) {
|
||||||
|
return item.data;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (item.event === sseResponseEventEnum.answer && data !== '[DONE]') {
|
||||||
|
const answer: string = data?.choices?.[0].delta.content || '';
|
||||||
|
onMessage(answer);
|
||||||
|
responseText += answer;
|
||||||
|
} else if (item.event === sseResponseEventEnum.chatResponse) {
|
||||||
|
const chatResponse = data as ChatResponseType;
|
||||||
|
newChatId = chatResponse.newChatId;
|
||||||
|
quoteLen = chatResponse.quoteLen || 0;
|
||||||
|
} else if (item.event === sseResponseEventEnum.error) {
|
||||||
|
errMsg = getErrText(data, '流响应错误');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
read();
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err?.message === 'The user aborted a request.') {
|
||||||
return resolve({
|
return resolve({
|
||||||
responseText,
|
responseText,
|
||||||
newChatId,
|
newChatId,
|
||||||
quoteLen
|
quoteLen,
|
||||||
|
errMsg
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
return reject('响应过程出现异常~');
|
|
||||||
}
|
}
|
||||||
|
reject(getErrText(err, '请求异常'));
|
||||||
}
|
}
|
||||||
const chunk = decoder.decode(value);
|
};
|
||||||
const chunkLines = chunk.split('\n\n').filter((item) => item);
|
read();
|
||||||
const chunkResponse = chunkLines.map((item) => {
|
} catch (err: any) {
|
||||||
const splitEvent = item.split('\n');
|
console.log(err);
|
||||||
if (splitEvent.length === 2) {
|
|
||||||
return {
|
|
||||||
event: splitEvent[0].replace('event: ', ''),
|
|
||||||
data: splitEvent[1].replace('data: ', '')
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
event: '',
|
|
||||||
data: splitEvent[0].replace('data: ', '')
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
chunkResponse.forEach((item) => {
|
reject(getErrText(err, '请求异常'));
|
||||||
// parse json data
|
}
|
||||||
const data = (() => {
|
|
||||||
try {
|
|
||||||
return JSON.parse(item.data);
|
|
||||||
} catch (error) {
|
|
||||||
return item.data;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
if (item.event === sseResponseEventEnum.answer && data !== '[DONE]') {
|
|
||||||
const answer: string = data?.choices?.[0].delta.content || '';
|
|
||||||
onMessage(answer);
|
|
||||||
responseText += answer;
|
|
||||||
} else if (item.event === sseResponseEventEnum.chatResponse) {
|
|
||||||
const chatResponse = data as ChatResponseType;
|
|
||||||
newChatId = chatResponse.newChatId;
|
|
||||||
quoteLen = chatResponse.quoteLen || 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
read();
|
|
||||||
} catch (err: any) {
|
|
||||||
if (err?.message === 'The user aborted a request.') {
|
|
||||||
return resolve({
|
|
||||||
responseText,
|
|
||||||
newChatId,
|
|
||||||
quoteLen
|
|
||||||
});
|
|
||||||
}
|
|
||||||
reject(getErrText(err, '请求异常'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
read();
|
|
||||||
} catch (err: any) {
|
|
||||||
console.log(err);
|
|
||||||
|
|
||||||
reject(getErrText(err, '请求异常'));
|
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|||||||
1
client/src/api/response/chat.d.ts
vendored
1
client/src/api/response/chat.d.ts
vendored
@@ -5,6 +5,7 @@ export interface InitChatResponse {
|
|||||||
chatId: string;
|
chatId: string;
|
||||||
modelId: string;
|
modelId: string;
|
||||||
systemPrompt?: string;
|
systemPrompt?: string;
|
||||||
|
limitPrompt?: string;
|
||||||
model: {
|
model: {
|
||||||
name: string;
|
name: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const MySelect = ({ placeholder, value, width = 'auto', list, onchange, ...props
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu autoSelect={false} onOpen={onOpen} onClose={onClose}>
|
<Menu autoSelect={false} onOpen={onOpen} onClose={onClose}>
|
||||||
<MenuButton as={'span'}>
|
<MenuButton style={{ width: '100%' }} as={'span'}>
|
||||||
<Button
|
<Button
|
||||||
width={width}
|
width={width}
|
||||||
px={3}
|
px={3}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export enum sseResponseEventEnum {
|
export enum sseResponseEventEnum {
|
||||||
|
error = 'error',
|
||||||
answer = 'answer',
|
answer = 'answer',
|
||||||
chatResponse = 'chatResponse'
|
chatResponse = 'chatResponse'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export const defaultModel: ModelSchema = {
|
|||||||
searchLimit: 5,
|
searchLimit: 5,
|
||||||
searchEmptyText: '',
|
searchEmptyText: '',
|
||||||
systemPrompt: '',
|
systemPrompt: '',
|
||||||
|
limitPrompt: '',
|
||||||
temperature: 0,
|
temperature: 0,
|
||||||
maxToken: 4000,
|
maxToken: 4000,
|
||||||
chatModel: OpenAiChatEnum.GPT35
|
chatModel: OpenAiChatEnum.GPT35
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
},
|
},
|
||||||
chatModel: model.chat.chatModel,
|
chatModel: model.chat.chatModel,
|
||||||
systemPrompt: isOwner ? model.chat.systemPrompt : '',
|
systemPrompt: isOwner ? model.chat.systemPrompt : '',
|
||||||
|
limitPrompt: isOwner ? model.chat.limitPrompt : '',
|
||||||
history
|
history
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -65,10 +65,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
const modelConstantsData = ChatModelMap[model.chat.chatModel];
|
const modelConstantsData = ChatModelMap[model.chat.chatModel];
|
||||||
const prompt = prompts[prompts.length - 1];
|
const prompt = prompts[prompts.length - 1];
|
||||||
|
|
||||||
const { userSystemPrompt = [], quotePrompt = [] } = await (async () => {
|
const {
|
||||||
|
userSystemPrompt = [],
|
||||||
|
userLimitPrompt = [],
|
||||||
|
quotePrompt = []
|
||||||
|
} = await (async () => {
|
||||||
// 使用了知识库搜索
|
// 使用了知识库搜索
|
||||||
if (model.chat.relatedKbs?.length > 0) {
|
if (model.chat.relatedKbs?.length > 0) {
|
||||||
const { userSystemPrompt, quotePrompt } = await appKbSearch({
|
const { quotePrompt, userSystemPrompt, userLimitPrompt } = await appKbSearch({
|
||||||
model,
|
model,
|
||||||
userId,
|
userId,
|
||||||
fixedQuote: [],
|
fixedQuote: [],
|
||||||
@@ -78,21 +82,29 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userSystemPrompt: userSystemPrompt ? [userSystemPrompt] : [],
|
userSystemPrompt,
|
||||||
|
userLimitPrompt,
|
||||||
quotePrompt: [quotePrompt]
|
quotePrompt: [quotePrompt]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (model.chat.systemPrompt) {
|
return {
|
||||||
return {
|
userSystemPrompt: model.chat.systemPrompt
|
||||||
userSystemPrompt: [
|
? [
|
||||||
{
|
{
|
||||||
obj: ChatRoleEnum.System,
|
obj: ChatRoleEnum.System,
|
||||||
value: model.chat.systemPrompt
|
value: model.chat.systemPrompt
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
: [],
|
||||||
}
|
userLimitPrompt: model.chat.limitPrompt
|
||||||
return {};
|
? [
|
||||||
|
{
|
||||||
|
obj: ChatRoleEnum.Human,
|
||||||
|
value: model.chat.limitPrompt
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// search result is empty
|
// search result is empty
|
||||||
@@ -102,7 +114,13 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 读取对话内容
|
// 读取对话内容
|
||||||
const completePrompts = [...quotePrompt, ...prompts.slice(0, -1), ...userSystemPrompt, prompt];
|
const completePrompts = [
|
||||||
|
...quotePrompt,
|
||||||
|
...userSystemPrompt,
|
||||||
|
...prompts.slice(0, -1),
|
||||||
|
...userLimitPrompt,
|
||||||
|
prompt
|
||||||
|
];
|
||||||
|
|
||||||
// 计算温度
|
// 计算温度
|
||||||
const temperature = (modelConstantsData.maxTemperature * (model.chat.temperature / 10)).toFixed(
|
const temperature = (modelConstantsData.maxTemperature * (model.chat.temperature / 10)).toFixed(
|
||||||
|
|||||||
@@ -28,7 +28,11 @@ type Response = {
|
|||||||
userSystemPrompt: {
|
userSystemPrompt: {
|
||||||
obj: ChatRoleEnum;
|
obj: ChatRoleEnum;
|
||||||
value: string;
|
value: string;
|
||||||
};
|
}[];
|
||||||
|
userLimitPrompt: {
|
||||||
|
obj: ChatRoleEnum;
|
||||||
|
value: string;
|
||||||
|
}[];
|
||||||
quotePrompt: {
|
quotePrompt: {
|
||||||
obj: ChatRoleEnum;
|
obj: ChatRoleEnum;
|
||||||
value: string;
|
value: string;
|
||||||
@@ -130,17 +134,24 @@ export async function appKbSearch({
|
|||||||
|
|
||||||
// 计算固定提示词的 token 数量
|
// 计算固定提示词的 token 数量
|
||||||
const userSystemPrompt = model.chat.systemPrompt // user system prompt
|
const userSystemPrompt = model.chat.systemPrompt // user system prompt
|
||||||
? {
|
? [
|
||||||
obj: ChatRoleEnum.Human,
|
{
|
||||||
value: model.chat.systemPrompt
|
obj: ChatRoleEnum.System,
|
||||||
}
|
value: model.chat.systemPrompt
|
||||||
: {
|
}
|
||||||
obj: ChatRoleEnum.Human,
|
]
|
||||||
value: `知识库是关于 ${model.name} 的内容,参考知识库回答问题。与 "${model.name}" 无关内容,直接回复: "我不知道"。`
|
: [];
|
||||||
};
|
const userLimitPrompt = [
|
||||||
|
{
|
||||||
|
obj: ChatRoleEnum.Human,
|
||||||
|
value: model.chat.limitPrompt
|
||||||
|
? model.chat.limitPrompt
|
||||||
|
: `知识库是关于 ${model.name} 的内容,参考知识库回答问题。与 "${model.name}" 无关内容,直接回复: "我不知道"。`
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const fixedSystemTokens = modelToolMap[model.chat.chatModel].countTokens({
|
const fixedSystemTokens = modelToolMap[model.chat.chatModel].countTokens({
|
||||||
messages: [userSystemPrompt]
|
messages: [...userSystemPrompt, ...userLimitPrompt]
|
||||||
});
|
});
|
||||||
|
|
||||||
// filter part quote by maxToken
|
// filter part quote by maxToken
|
||||||
@@ -164,6 +175,7 @@ export async function appKbSearch({
|
|||||||
return {
|
return {
|
||||||
rawSearch,
|
rawSearch,
|
||||||
userSystemPrompt,
|
userSystemPrompt,
|
||||||
|
userLimitPrompt,
|
||||||
quotePrompt: {
|
quotePrompt: {
|
||||||
obj: ChatRoleEnum.System,
|
obj: ChatRoleEnum.System,
|
||||||
value: quoteText
|
value: quoteText
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
import { jsonRes } from '@/service/response';
|
import { jsonRes } from '@/service/response';
|
||||||
import { authUser, getApiKey } from '@/service/utils/auth';
|
import { authUser, getApiKey, getSystemOpenAiKey } from '@/service/utils/auth';
|
||||||
import { withNextCors } from '@/service/utils/tools';
|
import { withNextCors } from '@/service/utils/tools';
|
||||||
import { getOpenAIApi } from '@/service/utils/chat/openai';
|
import { getOpenAIApi } from '@/service/utils/chat/openai';
|
||||||
import { embeddingModel } from '@/constants/model';
|
import { embeddingModel } from '@/constants/model';
|
||||||
@@ -39,14 +39,10 @@ export async function openaiEmbedding({
|
|||||||
input,
|
input,
|
||||||
mustPay = false
|
mustPay = false
|
||||||
}: { userId: string; mustPay?: boolean } & Props) {
|
}: { userId: string; mustPay?: boolean } & Props) {
|
||||||
const { userOpenAiKey, systemAuthKey } = await getApiKey({
|
const apiKey = getSystemOpenAiKey();
|
||||||
model: OpenAiChatEnum.GPT35,
|
|
||||||
userId,
|
|
||||||
mustPay
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取 chatAPI
|
// 获取 chatAPI
|
||||||
const chatAPI = getOpenAIApi();
|
const chatAPI = getOpenAIApi(apiKey);
|
||||||
|
|
||||||
// 把输入的内容转成向量
|
// 把输入的内容转成向量
|
||||||
const result = await chatAPI
|
const result = await chatAPI
|
||||||
@@ -57,16 +53,22 @@ export async function openaiEmbedding({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
...axiosConfig(userOpenAiKey || systemAuthKey)
|
...axiosConfig(apiKey)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then((res) => ({
|
.then((res) => {
|
||||||
tokenLen: res.data?.usage?.total_tokens || 0,
|
if (!res.data?.usage?.total_tokens) {
|
||||||
vectors: res.data.data.map((item) => item.embedding)
|
// @ts-ignore
|
||||||
}));
|
return Promise.reject(res.data?.error?.message || 'Embedding Error');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
tokenLen: res.data.usage.total_tokens || 0,
|
||||||
|
vectors: res.data.data.map((item) => item.embedding)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
pushGenerateVectorBill({
|
pushGenerateVectorBill({
|
||||||
isPay: !userOpenAiKey,
|
isPay: mustPay,
|
||||||
userId,
|
userId,
|
||||||
text: input.join(''),
|
text: input.join(''),
|
||||||
tokenLen: result.tokenLen
|
tokenLen: result.tokenLen
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ import { gptMessage2ChatType, textAdaptGptResponse } from '@/utils/adapt';
|
|||||||
import { getChatHistory } from './getHistory';
|
import { getChatHistory } from './getHistory';
|
||||||
import { saveChat } from '@/pages/api/chat/saveChat';
|
import { saveChat } from '@/pages/api/chat/saveChat';
|
||||||
import { sseResponse } from '@/service/utils/tools';
|
import { sseResponse } from '@/service/utils/tools';
|
||||||
import { getErrText } from '@/utils/tools';
|
|
||||||
import { type ChatCompletionRequestMessage } from 'openai';
|
import { type ChatCompletionRequestMessage } from 'openai';
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
|
import { sensitiveCheck } from '../../text/sensitiveCheck';
|
||||||
|
|
||||||
export type MessageItemType = ChatCompletionRequestMessage & { _id?: string };
|
export type MessageItemType = ChatCompletionRequestMessage & { _id?: string };
|
||||||
type FastGptWebChatProps = {
|
type FastGptWebChatProps = {
|
||||||
@@ -108,11 +108,12 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
const {
|
const {
|
||||||
rawSearch = [],
|
rawSearch = [],
|
||||||
userSystemPrompt = [],
|
userSystemPrompt = [],
|
||||||
|
userLimitPrompt = [],
|
||||||
quotePrompt = []
|
quotePrompt = []
|
||||||
} = await (async () => {
|
} = await (async () => {
|
||||||
// 使用了知识库搜索
|
// 使用了知识库搜索
|
||||||
if (model.chat.relatedKbs?.length > 0) {
|
if (model.chat.relatedKbs?.length > 0) {
|
||||||
const { rawSearch, userSystemPrompt, quotePrompt } = await appKbSearch({
|
const { rawSearch, quotePrompt, userSystemPrompt, userLimitPrompt } = await appKbSearch({
|
||||||
model,
|
model,
|
||||||
userId,
|
userId,
|
||||||
fixedQuote: history[history.length - 1]?.quote,
|
fixedQuote: history[history.length - 1]?.quote,
|
||||||
@@ -123,21 +124,29 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
rawSearch,
|
rawSearch,
|
||||||
userSystemPrompt: userSystemPrompt ? [userSystemPrompt] : [],
|
userSystemPrompt,
|
||||||
|
userLimitPrompt,
|
||||||
quotePrompt: [quotePrompt]
|
quotePrompt: [quotePrompt]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (model.chat.systemPrompt) {
|
return {
|
||||||
return {
|
userSystemPrompt: model.chat.systemPrompt
|
||||||
userSystemPrompt: [
|
? [
|
||||||
{
|
{
|
||||||
obj: ChatRoleEnum.System,
|
obj: ChatRoleEnum.System,
|
||||||
value: model.chat.systemPrompt
|
value: model.chat.systemPrompt
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
: [],
|
||||||
}
|
userLimitPrompt: model.chat.limitPrompt
|
||||||
return {};
|
? [
|
||||||
|
{
|
||||||
|
obj: ChatRoleEnum.Human,
|
||||||
|
value: model.chat.limitPrompt
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// search result is empty
|
// search result is empty
|
||||||
@@ -167,7 +176,13 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
}
|
}
|
||||||
|
|
||||||
// api messages. [quote,context,systemPrompt,question]
|
// api messages. [quote,context,systemPrompt,question]
|
||||||
const completePrompts = [...quotePrompt, ...prompts.slice(0, -1), ...userSystemPrompt, prompt];
|
const completePrompts = [
|
||||||
|
...quotePrompt,
|
||||||
|
...userSystemPrompt,
|
||||||
|
...prompts.slice(0, -1),
|
||||||
|
...userLimitPrompt,
|
||||||
|
prompt
|
||||||
|
];
|
||||||
// chat temperature
|
// chat temperature
|
||||||
const modelConstantsData = ChatModelMap[model.chat.chatModel];
|
const modelConstantsData = ChatModelMap[model.chat.chatModel];
|
||||||
// FastGpt temperature range: 1~10
|
// FastGpt temperature range: 1~10
|
||||||
@@ -175,6 +190,10 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
2
|
2
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await sensitiveCheck({
|
||||||
|
input: `${userSystemPrompt[0]?.value}\n${userLimitPrompt[0]?.value}\n${prompt.value}`
|
||||||
|
});
|
||||||
|
|
||||||
// start model api. responseText and totalTokens: valid only if stream = false
|
// start model api. responseText and totalTokens: valid only if stream = false
|
||||||
const { streamResponse, responseMessages, responseText, totalTokens } =
|
const { streamResponse, responseMessages, responseText, totalTokens } =
|
||||||
await modelServiceToolMap[model.chat.chatModel].chatCompletion({
|
await modelServiceToolMap[model.chat.chatModel].chatCompletion({
|
||||||
@@ -231,8 +250,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
tokens: totalTokens
|
tokens: totalTokens
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('stream response error', error);
|
return Promise.reject(error);
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
@@ -256,7 +274,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
...(showAppDetail
|
...(showAppDetail
|
||||||
? {
|
? {
|
||||||
quote: rawSearch,
|
quote: rawSearch,
|
||||||
systemPrompt: userSystemPrompt?.[0]?.value
|
systemPrompt: `${userSystemPrompt[0]?.value}\n\n${userLimitPrompt[0]?.value}`
|
||||||
}
|
}
|
||||||
: {})
|
: {})
|
||||||
}
|
}
|
||||||
@@ -301,7 +319,12 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(500);
|
res.status(500);
|
||||||
if (step === 1) {
|
if (step === 1) {
|
||||||
res.end(getErrText(err, 'Stream response error'));
|
sseResponse({
|
||||||
|
res,
|
||||||
|
event: sseResponseEventEnum.error,
|
||||||
|
data: JSON.stringify(err)
|
||||||
|
});
|
||||||
|
res.end();
|
||||||
} else {
|
} else {
|
||||||
jsonRes(res, {
|
jsonRes(res, {
|
||||||
code: 500,
|
code: 500,
|
||||||
|
|||||||
@@ -7,11 +7,9 @@ import { ChatModelMap, OpenAiChatEnum } from '@/constants/model';
|
|||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const chatModelList: ChatModelItemType[] = [];
|
const chatModelList: ChatModelItemType[] = [];
|
||||||
|
|
||||||
if (process.env.OPENAIKEY) {
|
chatModelList.push(ChatModelMap[OpenAiChatEnum.GPT3516k]);
|
||||||
chatModelList.push(ChatModelMap[OpenAiChatEnum.GPT3516k]);
|
chatModelList.push(ChatModelMap[OpenAiChatEnum.GPT35]);
|
||||||
chatModelList.push(ChatModelMap[OpenAiChatEnum.GPT35]);
|
chatModelList.push(ChatModelMap[OpenAiChatEnum.GPT4]);
|
||||||
chatModelList.push(ChatModelMap[OpenAiChatEnum.GPT4]);
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonRes(res, {
|
jsonRes(res, {
|
||||||
data: chatModelList
|
data: chatModelList
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ const Chat = ({ modelId, chatId }: { modelId: string; chatId: string }) => {
|
|||||||
const messages = adaptChatItem_openAI({ messages: prompts, reserveId: true });
|
const messages = adaptChatItem_openAI({ messages: prompts, reserveId: true });
|
||||||
|
|
||||||
// 流请求,获取数据
|
// 流请求,获取数据
|
||||||
const { newChatId, quoteLen } = await streamFetch({
|
const { newChatId, quoteLen, errMsg } = await streamFetch({
|
||||||
data: {
|
data: {
|
||||||
messages,
|
messages,
|
||||||
chatId,
|
chatId,
|
||||||
@@ -219,7 +219,9 @@ const Chat = ({ modelId, chatId }: { modelId: string; chatId: string }) => {
|
|||||||
...item,
|
...item,
|
||||||
status: 'finish',
|
status: 'finish',
|
||||||
quoteLen,
|
quoteLen,
|
||||||
systemPrompt: chatData.systemPrompt
|
systemPrompt: `${chatData.systemPrompt}${`${
|
||||||
|
chatData.limitPrompt ? `\n\n${chatData.limitPrompt}` : ''
|
||||||
|
}`}`
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
@@ -230,17 +232,26 @@ const Chat = ({ modelId, chatId }: { modelId: string; chatId: string }) => {
|
|||||||
loadHistory({ pageNum: 1, init: true });
|
loadHistory({ pageNum: 1, init: true });
|
||||||
loadMyModels(true);
|
loadMyModels(true);
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
|
if (errMsg) {
|
||||||
|
toast({
|
||||||
|
status: 'warning',
|
||||||
|
title: errMsg
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
chatId,
|
chatId,
|
||||||
modelId,
|
modelId,
|
||||||
chatData.systemPrompt,
|
|
||||||
setChatData,
|
setChatData,
|
||||||
loadHistory,
|
|
||||||
loadMyModels,
|
|
||||||
generatingMessage,
|
generatingMessage,
|
||||||
setForbidLoadChatData,
|
setForbidLoadChatData,
|
||||||
router
|
router,
|
||||||
|
chatData.systemPrompt,
|
||||||
|
chatData.limitPrompt,
|
||||||
|
loadHistory,
|
||||||
|
loadMyModels,
|
||||||
|
toast
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -749,7 +760,7 @@ const Chat = ({ modelId, chatId }: { modelId: string; chatId: string }) => {
|
|||||||
px={[2, 4]}
|
px={[2, 4]}
|
||||||
onClick={() => setShowSystemPrompt(item.systemPrompt || '')}
|
onClick={() => setShowSystemPrompt(item.systemPrompt || '')}
|
||||||
>
|
>
|
||||||
提示词
|
提示词 & 限定词
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{!!item.quoteLen && (
|
{!!item.quoteLen && (
|
||||||
|
|||||||
@@ -42,6 +42,12 @@ const Test = () => {
|
|||||||
pushKbTestItem(testItem);
|
pushKbTestItem(testItem);
|
||||||
setInputText('');
|
setInputText('');
|
||||||
setKbTestItem(testItem);
|
setKbTestItem(testItem);
|
||||||
|
},
|
||||||
|
onError(err) {
|
||||||
|
toast({
|
||||||
|
title: getErrText(err),
|
||||||
|
status: 'error'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
import React, { useCallback, useState, useMemo } from 'react';
|
import React, { useCallback, useState, useMemo } from 'react';
|
||||||
import { Box, Flex, Button, FormControl, Input, Textarea, Divider } from '@chakra-ui/react';
|
import {
|
||||||
|
Box,
|
||||||
|
Flex,
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
Input,
|
||||||
|
Textarea,
|
||||||
|
Divider,
|
||||||
|
Tooltip
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
@@ -20,6 +30,11 @@ import Avatar from '@/components/Avatar';
|
|||||||
import MySelect from '@/components/Select';
|
import MySelect from '@/components/Select';
|
||||||
import MySlider from '@/components/Slider';
|
import MySlider from '@/components/Slider';
|
||||||
|
|
||||||
|
const systemPromptTip =
|
||||||
|
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。';
|
||||||
|
const limitPromptTip =
|
||||||
|
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"';
|
||||||
|
|
||||||
const Settings = ({ modelId }: { modelId: string }) => {
|
const Settings = ({ modelId }: { modelId: string }) => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -60,7 +75,7 @@ const Settings = ({ modelId }: { modelId: string }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return max;
|
return max;
|
||||||
}, [getValues, setValue, refresh]);
|
}, [getValues, setValue]);
|
||||||
|
|
||||||
// 提交保存模型修改
|
// 提交保存模型修改
|
||||||
const saveSubmitSuccess = useCallback(
|
const saveSubmitSuccess = useCallback(
|
||||||
@@ -211,7 +226,7 @@ const Settings = ({ modelId }: { modelId: string }) => {
|
|||||||
介绍
|
介绍
|
||||||
</Box>
|
</Box>
|
||||||
<Textarea
|
<Textarea
|
||||||
rows={5}
|
rows={4}
|
||||||
maxLength={500}
|
maxLength={500}
|
||||||
placeholder={'给你的 AI 应用一个介绍'}
|
placeholder={'给你的 AI 应用一个介绍'}
|
||||||
{...register('intro')}
|
{...register('intro')}
|
||||||
@@ -225,11 +240,14 @@ const Settings = ({ modelId }: { modelId: string }) => {
|
|||||||
对话模型
|
对话模型
|
||||||
</Box>
|
</Box>
|
||||||
<MySelect
|
<MySelect
|
||||||
width={['200px', '240px']}
|
width={['100%', '280px']}
|
||||||
value={getValues('chat.chatModel')}
|
value={getValues('chat.chatModel')}
|
||||||
list={chatModelList.map((item) => ({
|
list={chatModelList.map((item) => ({
|
||||||
id: item.chatModel,
|
id: item.chatModel,
|
||||||
label: item.name
|
label: `${item.name} (${formatPrice(
|
||||||
|
ChatModelMap[getValues('chat.chatModel')]?.price,
|
||||||
|
1000
|
||||||
|
)} 元/1k tokens)`
|
||||||
}))}
|
}))}
|
||||||
onchange={(val: any) => {
|
onchange={(val: any) => {
|
||||||
setValue('chat.chatModel', val);
|
setValue('chat.chatModel', val);
|
||||||
@@ -237,15 +255,6 @@ const Settings = ({ modelId }: { modelId: string }) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex alignItems={'center'} mt={5}>
|
|
||||||
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
|
||||||
价格
|
|
||||||
</Box>
|
|
||||||
<Box fontSize={['sm', 'md']}>
|
|
||||||
{formatPrice(ChatModelMap[getValues('chat.chatModel')]?.price, 1000)}
|
|
||||||
元/1K tokens(包括上下文和回答)
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
<Flex alignItems={'center'} my={10}>
|
<Flex alignItems={'center'} my={10}>
|
||||||
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
||||||
温度
|
温度
|
||||||
@@ -269,7 +278,7 @@ const Settings = ({ modelId }: { modelId: string }) => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
<Flex alignItems={'center'} mt={12} mb={10}>
|
<Flex alignItems={'center'} mt={12} mb={10}>
|
||||||
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
||||||
最大长度
|
回复上限
|
||||||
</Box>
|
</Box>
|
||||||
<Box flex={1} ml={'10px'}>
|
<Box flex={1} ml={'10px'}>
|
||||||
<MySlider
|
<MySlider
|
||||||
@@ -292,15 +301,29 @@ const Settings = ({ modelId }: { modelId: string }) => {
|
|||||||
<Flex mt={10} alignItems={'flex-start'}>
|
<Flex mt={10} alignItems={'flex-start'}>
|
||||||
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
||||||
提示词
|
提示词
|
||||||
|
<Tooltip label={systemPromptTip}>
|
||||||
|
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||||
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
<Textarea
|
<Textarea
|
||||||
rows={8}
|
rows={8}
|
||||||
placeholder={
|
placeholder={systemPromptTip}
|
||||||
'模型默认的 prompt 词,通过调整该内容,可以引导模型聊天方向。\n\n如果使用了知识库搜索,没有填写该内容时,系统会自动补充提示词;如果填写了内容,则以填写的内容为准。'
|
|
||||||
}
|
|
||||||
{...register('chat.systemPrompt')}
|
{...register('chat.systemPrompt')}
|
||||||
></Textarea>
|
></Textarea>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<Flex mt={5} alignItems={'flex-start'}>
|
||||||
|
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
||||||
|
限定词
|
||||||
|
<Tooltip label={limitPromptTip}>
|
||||||
|
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
<Textarea
|
||||||
|
rows={5}
|
||||||
|
placeholder={limitPromptTip}
|
||||||
|
{...register('chat.limitPrompt')}
|
||||||
|
></Textarea>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
<Flex mt={5} alignItems={'center'}>
|
<Flex mt={5} alignItems={'center'}>
|
||||||
<Box w={['60px', '100px', '140px']} flexShrink={0}></Box>
|
<Box w={['60px', '100px', '140px']} flexShrink={0}></Box>
|
||||||
|
|||||||
@@ -43,7 +43,10 @@ const ModelSchema = new Schema({
|
|||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
systemPrompt: {
|
systemPrompt: {
|
||||||
// 系统提示词
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
limitPrompt: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ export const authUser = async ({
|
|||||||
|
|
||||||
/* random get openai api key */
|
/* random get openai api key */
|
||||||
export const getSystemOpenAiKey = () => {
|
export const getSystemOpenAiKey = () => {
|
||||||
return process.env.OPENAIKEY || '';
|
return process.env.ONEAPI_KEY || process.env.OPENAIKEY || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
/* 获取 api 请求的 key */
|
/* 获取 api 请求的 key */
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { sseResponse } from '../tools';
|
|||||||
import { OpenAiChatEnum } from '@/constants/model';
|
import { OpenAiChatEnum } from '@/constants/model';
|
||||||
import { chatResponse, openAiStreamResponse } from './openai';
|
import { chatResponse, openAiStreamResponse } from './openai';
|
||||||
import type { NextApiResponse } from 'next';
|
import type { NextApiResponse } from 'next';
|
||||||
import { createParser, ParsedEvent, ReconnectInterval } from 'eventsource-parser';
|
|
||||||
import { textAdaptGptResponse } from '@/utils/adapt';
|
import { textAdaptGptResponse } from '@/utils/adapt';
|
||||||
|
import { parseStreamChunk } from '@/utils/adapt';
|
||||||
|
|
||||||
export type ChatCompletionType = {
|
export type ChatCompletionType = {
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
@@ -185,65 +185,63 @@ export const V2_StreamResponse = async ({
|
|||||||
model: ChatModelType;
|
model: ChatModelType;
|
||||||
}) => {
|
}) => {
|
||||||
let responseContent = '';
|
let responseContent = '';
|
||||||
|
let error: any = null;
|
||||||
|
|
||||||
|
const clientRes = async (data: string) => {
|
||||||
|
const { content = '' } = (() => {
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(data);
|
||||||
|
const content: string = json?.choices?.[0].delta.content || '';
|
||||||
|
error = json.error;
|
||||||
|
responseContent += content;
|
||||||
|
return { content };
|
||||||
|
} catch (error) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (res.closed || error) return;
|
||||||
|
|
||||||
|
if (data === '[DONE]') {
|
||||||
|
sseResponse({
|
||||||
|
res,
|
||||||
|
event: sseResponseEventEnum.answer,
|
||||||
|
data: textAdaptGptResponse({
|
||||||
|
text: null,
|
||||||
|
finish_reason: 'stop'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
sseResponse({
|
||||||
|
res,
|
||||||
|
event: sseResponseEventEnum.answer,
|
||||||
|
data: '[DONE]'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
sseResponse({
|
||||||
|
res,
|
||||||
|
event: sseResponseEventEnum.answer,
|
||||||
|
data: textAdaptGptResponse({
|
||||||
|
text: content
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const onParse = async (e: ParsedEvent | ReconnectInterval) => {
|
for await (const chunk of chatResponse.data as any) {
|
||||||
if (e.type !== 'event') return;
|
if (res.closed) break;
|
||||||
|
const parse = parseStreamChunk(chunk);
|
||||||
const data = e.data;
|
parse.forEach((item) => clientRes(item.data));
|
||||||
|
|
||||||
const { content = '' } = (() => {
|
|
||||||
try {
|
|
||||||
const json = JSON.parse(data);
|
|
||||||
const content: string = json?.choices?.[0].delta.content || '';
|
|
||||||
responseContent += content;
|
|
||||||
return { content };
|
|
||||||
} catch (error) {}
|
|
||||||
return {};
|
|
||||||
})();
|
|
||||||
|
|
||||||
if (res.closed) return;
|
|
||||||
|
|
||||||
if (data === '[DONE]') {
|
|
||||||
sseResponse({
|
|
||||||
res,
|
|
||||||
event: sseResponseEventEnum.answer,
|
|
||||||
data: textAdaptGptResponse({
|
|
||||||
text: null,
|
|
||||||
finish_reason: 'stop'
|
|
||||||
})
|
|
||||||
});
|
|
||||||
sseResponse({
|
|
||||||
res,
|
|
||||||
event: sseResponseEventEnum.answer,
|
|
||||||
data: '[DONE]'
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
sseResponse({
|
|
||||||
res,
|
|
||||||
event: sseResponseEventEnum.answer,
|
|
||||||
data: textAdaptGptResponse({
|
|
||||||
text: content
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const parser = createParser(onParse);
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
for await (const chunk of chatResponse.data as any) {
|
|
||||||
if (res.closed) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
parser.feed(decoder.decode(chunk, { stream: true }));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log('pipe error', error);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('stream error', error);
|
console.log('pipe error', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.log(error);
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
// count tokens
|
// count tokens
|
||||||
const finishMessages = prompts.concat({
|
const finishMessages = prompts.concat({
|
||||||
obj: ChatRoleEnum.AI,
|
obj: ChatRoleEnum.AI,
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
import { Configuration, OpenAIApi } from 'openai';
|
import { Configuration, OpenAIApi } from 'openai';
|
||||||
import { createParser, ParsedEvent, ReconnectInterval } from 'eventsource-parser';
|
|
||||||
import { axiosConfig } from '../tools';
|
import { axiosConfig } from '../tools';
|
||||||
import { ChatModelMap, OpenAiChatEnum } from '@/constants/model';
|
import { ChatModelMap, OpenAiChatEnum } from '@/constants/model';
|
||||||
import { adaptChatItem_openAI } from '@/utils/plugin/openai';
|
import { adaptChatItem_openAI } from '@/utils/plugin/openai';
|
||||||
import { modelToolMap } from '@/utils/plugin';
|
import { modelToolMap } from '@/utils/plugin';
|
||||||
import { ChatCompletionType, ChatContextFilter, StreamResponseType } from './index';
|
import { ChatCompletionType, ChatContextFilter, StreamResponseType } from './index';
|
||||||
import { ChatRoleEnum } from '@/constants/chat';
|
import { ChatRoleEnum } from '@/constants/chat';
|
||||||
|
import { parseStreamChunk } from '@/utils/adapt';
|
||||||
|
|
||||||
export const getOpenAIApi = () =>
|
export const getOpenAIApi = (apiKey: string) => {
|
||||||
new OpenAIApi(
|
const openaiBaseUrl = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1';
|
||||||
|
return new OpenAIApi(
|
||||||
new Configuration({
|
new Configuration({
|
||||||
basePath: process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1'
|
basePath: apiKey === process.env.ONEAPI_KEY ? process.env.ONEAPI_URL : openaiBaseUrl
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/* 模型对话 */
|
/* 模型对话 */
|
||||||
export const chatResponse = async ({
|
export const chatResponse = async ({
|
||||||
@@ -31,7 +33,7 @@ export const chatResponse = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const adaptMessages = adaptChatItem_openAI({ messages: filterMessages, reserveId: false });
|
const adaptMessages = adaptChatItem_openAI({ messages: filterMessages, reserveId: false });
|
||||||
const chatAPI = getOpenAIApi();
|
const chatAPI = getOpenAIApi(apiKey);
|
||||||
|
|
||||||
const promptsToken = modelToolMap[model].countTokens({
|
const promptsToken = modelToolMap[model].countTokens({
|
||||||
messages: filterMessages
|
messages: filterMessages
|
||||||
@@ -47,8 +49,8 @@ export const chatResponse = async ({
|
|||||||
messages: adaptMessages,
|
messages: adaptMessages,
|
||||||
frequency_penalty: 0.5, // 越大,重复内容越少
|
frequency_penalty: 0.5, // 越大,重复内容越少
|
||||||
presence_penalty: -0.5, // 越大,越容易出现新内容
|
presence_penalty: -0.5, // 越大,越容易出现新内容
|
||||||
stream,
|
stream
|
||||||
stop: ['.!?。']
|
// stop: ['.!?。']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
timeout: stream ? 60000 : 480000,
|
timeout: stream ? 60000 : 480000,
|
||||||
@@ -80,29 +82,29 @@ export const openAiStreamResponse = async ({
|
|||||||
try {
|
try {
|
||||||
let responseContent = '';
|
let responseContent = '';
|
||||||
|
|
||||||
const onParse = async (event: ParsedEvent | ReconnectInterval) => {
|
const clientRes = async (data: string) => {
|
||||||
if (event.type !== 'event') return;
|
const { content = '' } = (() => {
|
||||||
const data = event.data;
|
try {
|
||||||
if (data === '[DONE]') return;
|
const json = JSON.parse(data);
|
||||||
try {
|
const content: string = json?.choices?.[0].delta.content || '';
|
||||||
const json = JSON.parse(data);
|
responseContent += content;
|
||||||
const content: string = json?.choices?.[0].delta.content || '';
|
return { content };
|
||||||
responseContent += content;
|
} catch (error) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
!res.closed && content && res.write(content);
|
if (data === '[DONE]') return;
|
||||||
} catch (error) {
|
|
||||||
error;
|
!res.closed && content && res.write(content);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const decoder = new TextDecoder();
|
|
||||||
const parser = createParser(onParse);
|
|
||||||
for await (const chunk of chatResponse.data as any) {
|
for await (const chunk of chatResponse.data as any) {
|
||||||
if (res.closed) {
|
if (res.closed) break;
|
||||||
break;
|
|
||||||
}
|
const parse = parseStreamChunk(chunk);
|
||||||
parser.feed(decoder.decode(chunk, { stream: true }));
|
parse.forEach((item) => clientRes(item.data));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('pipe error', error);
|
console.log('pipe error', error);
|
||||||
|
|||||||
@@ -34,14 +34,18 @@ export const clearCookie = (res: NextApiResponse) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* openai axios config */
|
/* openai axios config */
|
||||||
export const axiosConfig = (apikey: string) => ({
|
export const axiosConfig = (apikey: string) => {
|
||||||
baseURL: process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1',
|
const openaiBaseUrl = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1';
|
||||||
httpsAgent: global.httpsAgent,
|
|
||||||
headers: {
|
return {
|
||||||
Authorization: `Bearer ${apikey}`,
|
baseURL: apikey === process.env.ONEAPI_KEY ? process.env.ONEAPI_URL : openaiBaseUrl, // 此处仅对非 npm 模块有效
|
||||||
auth: process.env.OPENAI_BASE_URL_AUTH || ''
|
httpsAgent: global.httpsAgent,
|
||||||
}
|
headers: {
|
||||||
});
|
Authorization: `Bearer ${apikey}`,
|
||||||
|
auth: process.env.OPENAI_BASE_URL_AUTH || ''
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export function withNextCors(handler: NextApiHandler): NextApiHandler {
|
export function withNextCors(handler: NextApiHandler): NextApiHandler {
|
||||||
return async function nextApiHandlerWrappedWithNextCors(
|
return async function nextApiHandlerWrappedWithNextCors(
|
||||||
|
|||||||
1
client/src/types/mongoSchema.d.ts
vendored
1
client/src/types/mongoSchema.d.ts
vendored
@@ -43,6 +43,7 @@ export interface ModelSchema {
|
|||||||
searchLimit: number;
|
searchLimit: number;
|
||||||
searchEmptyText: string;
|
searchEmptyText: string;
|
||||||
systemPrompt: string;
|
systemPrompt: string;
|
||||||
|
limitPrompt: string;
|
||||||
temperature: number;
|
temperature: number;
|
||||||
maxToken: number;
|
maxToken: number;
|
||||||
chatModel: ChatModelType; // 聊天时用的模型,训练后就是训练的模型
|
chatModel: ChatModelType; // 聊天时用的模型,训练后就是训练的模型
|
||||||
|
|||||||
@@ -54,3 +54,24 @@ export const textAdaptGptResponse = ({
|
|||||||
choices: [{ delta: text === null ? {} : { content: text }, index: 0, finish_reason }]
|
choices: [{ delta: text === null ? {} : { content: text }, index: 0, finish_reason }]
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
export const parseStreamChunk = (value: BufferSource) => {
|
||||||
|
const chunk = decoder.decode(value);
|
||||||
|
const chunkLines = chunk.split('\n\n').filter((item) => item);
|
||||||
|
const chunkResponse = chunkLines.map((item) => {
|
||||||
|
const splitEvent = item.split('\n');
|
||||||
|
if (splitEvent.length === 2) {
|
||||||
|
return {
|
||||||
|
event: splitEvent[0].replace('event: ', ''),
|
||||||
|
data: splitEvent[1].replace('data: ', '')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
event: '',
|
||||||
|
data: splitEvent[0].replace('data: ', '')
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return chunkResponse;
|
||||||
|
};
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export const voiceBroadcast = ({ text }: { text: string }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getErrText = (err: any, def = '') => {
|
export const getErrText = (err: any, def = '') => {
|
||||||
const msg = typeof err === 'string' ? err : err?.message || def || '';
|
const msg: string = typeof err === 'string' ? err : err?.message || def || '';
|
||||||
msg && console.log('error =>', msg);
|
msg && console.log('error =>', msg);
|
||||||
return msg;
|
return msg;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -67,10 +67,13 @@ services:
|
|||||||
- PG_USER=fastgpt
|
- PG_USER=fastgpt
|
||||||
- PG_PASSWORD=1234
|
- PG_PASSWORD=1234
|
||||||
- PG_DB_NAME=fastgpt
|
- PG_DB_NAME=fastgpt
|
||||||
# openai, 推荐使用 one-api 管理key
|
# oneapi 配置 推荐使用 one-api 管理key
|
||||||
|
- ONEAPI_URL=https://kfcwurtbijvh.cloud.sealos.io/v1
|
||||||
|
- ONEAPI_KEY=sk-itJ9v8qthRiFDzfs62Ea21Aa9b004c8791937dCf4cC568Ff
|
||||||
|
# openai 相关配置:使用了 oneapi 后,下面只需要填下 OPENAI_BASE_URL (国外可全忽略)
|
||||||
- OPENAIKEY=sk-xxxxx
|
- OPENAIKEY=sk-xxxxx
|
||||||
- OPENAI_BASE_URL=https://api.openai.com/v1
|
- OPENAI_BASE_URL=https://api.openai.com/v1
|
||||||
- OPENAI_BASE_URL_AUTH=可选的安全凭证
|
- OPENAI_BASE_URL_AUTH=可选的安全凭证,会放到 header.auth 里
|
||||||
fastgpt-admin:
|
fastgpt-admin:
|
||||||
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-admin:latest
|
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-admin:latest
|
||||||
container_name: fastgpt-admin
|
container_name: fastgpt-admin
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ https://cloud.sealos.io/
|
|||||||
|
|
||||||
## 4. 填写对应参数
|
## 4. 填写对应参数
|
||||||
|
|
||||||
|
镜像:ghcr.io/songquanpeng/one-api:latest
|
||||||
|
|
||||||

|

|
||||||
打开外网访问开关后,Sealos 会自动分配一个可访问的地址,不需要自己配置。
|
打开外网访问开关后,Sealos 会自动分配一个可访问的地址,不需要自己配置。
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user