feat: 知识库匹配模式选择
This commit is contained in:
parent
1fe5cd751a
commit
c605964fa8
@ -8,3 +8,4 @@ README.md
|
|||||||
|
|
||||||
.yalc/
|
.yalc/
|
||||||
yalc.lock
|
yalc.lock
|
||||||
|
testApi/
|
||||||
@ -4,14 +4,12 @@ import type { RedisModelDataItemType } from '@/types/redis';
|
|||||||
export enum ChatModelNameEnum {
|
export enum ChatModelNameEnum {
|
||||||
GPT35 = 'gpt-3.5-turbo',
|
GPT35 = 'gpt-3.5-turbo',
|
||||||
VECTOR_GPT = 'VECTOR_GPT',
|
VECTOR_GPT = 'VECTOR_GPT',
|
||||||
GPT3 = 'text-davinci-003',
|
|
||||||
VECTOR = 'text-embedding-ada-002'
|
VECTOR = 'text-embedding-ada-002'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChatModelNameMap = {
|
export const ChatModelNameMap = {
|
||||||
[ChatModelNameEnum.GPT35]: 'gpt-3.5-turbo',
|
[ChatModelNameEnum.GPT35]: 'gpt-3.5-turbo',
|
||||||
[ChatModelNameEnum.VECTOR_GPT]: 'gpt-3.5-turbo',
|
[ChatModelNameEnum.VECTOR_GPT]: 'gpt-3.5-turbo',
|
||||||
[ChatModelNameEnum.GPT3]: 'text-davinci-003',
|
|
||||||
[ChatModelNameEnum.VECTOR]: 'text-embedding-ada-002'
|
[ChatModelNameEnum.VECTOR]: 'text-embedding-ada-002'
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -34,7 +32,7 @@ export const modelList: ModelConstantsData[] = [
|
|||||||
trainName: '',
|
trainName: '',
|
||||||
maxToken: 4000,
|
maxToken: 4000,
|
||||||
contextMaxToken: 7500,
|
contextMaxToken: 7500,
|
||||||
maxTemperature: 2,
|
maxTemperature: 1.5,
|
||||||
price: 3
|
price: 3
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -47,16 +45,6 @@ export const modelList: ModelConstantsData[] = [
|
|||||||
maxTemperature: 1,
|
maxTemperature: 1,
|
||||||
price: 3
|
price: 3
|
||||||
}
|
}
|
||||||
// {
|
|
||||||
// serviceCompany: 'openai',
|
|
||||||
// name: 'GPT3',
|
|
||||||
// model: ChatModelNameEnum.GPT3,
|
|
||||||
// trainName: 'davinci',
|
|
||||||
// maxToken: 4000,
|
|
||||||
// contextMaxToken: 7500,
|
|
||||||
// maxTemperature: 2,
|
|
||||||
// price: 30
|
|
||||||
// }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export enum TrainingStatusEnum {
|
export enum TrainingStatusEnum {
|
||||||
@ -97,6 +85,34 @@ export const ModelDataStatusMap: Record<RedisModelDataItemType['status'], string
|
|||||||
waiting: '训练中'
|
waiting: '训练中'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* 知识库搜索时的配置 */
|
||||||
|
// 搜索方式
|
||||||
|
export enum ModelVectorSearchModeEnum {
|
||||||
|
hightSimilarity = 'hightSimilarity', // 高相似度+禁止回复
|
||||||
|
lowSimilarity = 'lowSimilarity', // 低相似度
|
||||||
|
noContext = 'noContex' // 高相似度+无上下文回复
|
||||||
|
}
|
||||||
|
export const ModelVectorSearchModeMap: Record<
|
||||||
|
`${ModelVectorSearchModeEnum}`,
|
||||||
|
{
|
||||||
|
text: string;
|
||||||
|
similarity: number;
|
||||||
|
}
|
||||||
|
> = {
|
||||||
|
[ModelVectorSearchModeEnum.hightSimilarity]: {
|
||||||
|
text: '高相似度, 无匹配时拒绝回复',
|
||||||
|
similarity: 0.2
|
||||||
|
},
|
||||||
|
[ModelVectorSearchModeEnum.noContext]: {
|
||||||
|
text: '高相似度,无匹配时直接回复',
|
||||||
|
similarity: 0.2
|
||||||
|
},
|
||||||
|
[ModelVectorSearchModeEnum.lowSimilarity]: {
|
||||||
|
text: '低相似度匹配',
|
||||||
|
similarity: 0.8
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const defaultModel: ModelSchema = {
|
export const defaultModel: ModelSchema = {
|
||||||
_id: '',
|
_id: '',
|
||||||
userId: '',
|
userId: '',
|
||||||
@ -108,6 +124,9 @@ export const defaultModel: ModelSchema = {
|
|||||||
systemPrompt: '',
|
systemPrompt: '',
|
||||||
intro: '',
|
intro: '',
|
||||||
temperature: 5,
|
temperature: 5,
|
||||||
|
search: {
|
||||||
|
mode: ModelVectorSearchModeEnum.hightSimilarity
|
||||||
|
},
|
||||||
service: {
|
service: {
|
||||||
company: 'openai',
|
company: 'openai',
|
||||||
trainId: '',
|
trainId: '',
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { ChatItemType } from '@/types/chat';
|
|||||||
import { jsonRes } from '@/service/response';
|
import { jsonRes } from '@/service/response';
|
||||||
import type { ModelSchema } from '@/types/mongoSchema';
|
import type { ModelSchema } from '@/types/mongoSchema';
|
||||||
import { PassThrough } from 'stream';
|
import { PassThrough } from 'stream';
|
||||||
import { modelList } from '@/constants/model';
|
import { modelList, ModelVectorSearchModeMap, ModelVectorSearchModeEnum } from '@/constants/model';
|
||||||
import { pushChatBill } from '@/service/events/pushBill';
|
import { pushChatBill } from '@/service/events/pushBill';
|
||||||
import { connectRedis } from '@/service/redis';
|
import { connectRedis } from '@/service/redis';
|
||||||
import { VecModelDataPrefix } from '@/constants/redis';
|
import { VecModelDataPrefix } from '@/constants/redis';
|
||||||
@ -65,13 +65,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
text: prompt.value
|
text: prompt.value
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const similarity = ModelVectorSearchModeMap[model.search.mode]?.similarity || 0.22;
|
||||||
// 搜索系统提示词, 按相似度从 redis 中搜出相关的 q 和 text
|
// 搜索系统提示词, 按相似度从 redis 中搜出相关的 q 和 text
|
||||||
const redisData: any[] = await redis.sendCommand([
|
const redisData: any[] = await redis.sendCommand([
|
||||||
'FT.SEARCH',
|
'FT.SEARCH',
|
||||||
`idx:${VecModelDataPrefix}:hash`,
|
`idx:${VecModelDataPrefix}:hash`,
|
||||||
`@modelId:{${String(
|
`@modelId:{${String(
|
||||||
chat.modelId._id
|
chat.modelId._id
|
||||||
)}} @vector:[VECTOR_RANGE 0.22 $blob]=>{$YIELD_DISTANCE_AS: score}`,
|
)}} @vector:[VECTOR_RANGE ${similarity} $blob]=>{$YIELD_DISTANCE_AS: score}`,
|
||||||
'RETURN',
|
'RETURN',
|
||||||
'1',
|
'1',
|
||||||
'text',
|
'text',
|
||||||
@ -97,7 +98,24 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (formatRedisPrompt.length > 0) {
|
/* 高相似度+退出,无法匹配时直接退出 */
|
||||||
|
if (
|
||||||
|
formatRedisPrompt.length === 0 &&
|
||||||
|
model.search.mode === ModelVectorSearchModeEnum.hightSimilarity
|
||||||
|
) {
|
||||||
|
return res.send('对不起,你的问题不在知识库中。');
|
||||||
|
}
|
||||||
|
/* 高相似度+无上下文,不添加额外知识 */
|
||||||
|
if (
|
||||||
|
formatRedisPrompt.length === 0 &&
|
||||||
|
model.search.mode === ModelVectorSearchModeEnum.noContext
|
||||||
|
) {
|
||||||
|
prompts.unshift({
|
||||||
|
obj: 'SYSTEM',
|
||||||
|
value: model.systemPrompt
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 有匹配情况下,添加知识库内容。
|
||||||
// 系统提示词过滤,最多 2800 tokens
|
// 系统提示词过滤,最多 2800 tokens
|
||||||
const systemPrompt = systemPromptFilter(formatRedisPrompt, 2800);
|
const systemPrompt = systemPromptFilter(formatRedisPrompt, 2800);
|
||||||
|
|
||||||
@ -107,8 +125,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
'YYYY/MM/DD HH:mm:ss'
|
'YYYY/MM/DD HH:mm:ss'
|
||||||
)} ${systemPrompt}"`
|
)} ${systemPrompt}"`
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
return res.send('对不起,你的问题不在知识库中。');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 控制在 tokens 数量,防止超出
|
// 控制在 tokens 数量,防止超出
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import type { ModelUpdateParams } from '@/types/model';
|
|||||||
/* 获取我的模型 */
|
/* 获取我的模型 */
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||||
try {
|
try {
|
||||||
const { name, service, security, systemPrompt, intro, temperature } =
|
const { name, search, service, security, systemPrompt, intro, temperature } =
|
||||||
req.body as ModelUpdateParams;
|
req.body as ModelUpdateParams;
|
||||||
const { modelId } = req.query as { modelId: string };
|
const { modelId } = req.query as { modelId: string };
|
||||||
const { authorization } = req.headers;
|
const { authorization } = req.headers;
|
||||||
@ -37,6 +37,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
systemPrompt,
|
systemPrompt,
|
||||||
intro,
|
intro,
|
||||||
temperature,
|
temperature,
|
||||||
|
search,
|
||||||
// service,
|
// service,
|
||||||
security
|
security
|
||||||
}
|
}
|
||||||
|
|||||||
@ -83,22 +83,22 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
下面是一些例子:
|
下面是一些例子:
|
||||||
实现一个手机号发生注册验证码方法.
|
实现一个手机号发生注册验证码方法.
|
||||||
1. 从 query 中获取 phone.
|
1. 从 query 中获取 phone.
|
||||||
2. 校验手机号格式是否正确,不正确返回{error: "手机号格式错误"}.
|
2. 校验手机号格式是否正确,不正确则返回错误响应,消息为:手机号格式错误.
|
||||||
3. 给 phone 发送一个短信验证码,验证码长度为6位字符串,内容为:你正在注册laf,验证码为:code.
|
3. 给 phone 发送一个短信验证码,验证码长度为6位字符串,内容为:你正在注册laf,验证码为:code.
|
||||||
4. 数据库添加数据,表为"codes",内容为 {phone, code}.
|
4. 数据库添加数据,表为"codes",内容为 {phone, code}.
|
||||||
|
|
||||||
实现根据手机号注册账号,需要验证手机验证码.
|
实现根据手机号注册账号,需要验证手机验证码.
|
||||||
1. 从 body 中获取 phone 和 code.
|
1. 从 body 中获取 phone 和 code.
|
||||||
2. 校验手机号格式是否正确,不正确返回{error: "手机号格式错误"}.
|
2. 校验手机号格式是否正确,不正确返回错误响应,消息为:手机号格式错误.
|
||||||
2. 获取数据库数据,表为"codes",查找是否有符合 phone, code 等于body参数的记录,没有的话返回 {error:"验证码不正确"}.
|
2. 获取数据库数据,表为"codes",查找是否有符合 phone, code 等于body参数的记录,没有的话错误响应,消息为:验证码不正确.
|
||||||
4. 添加数据库数据,表为"users" ,内容为{phone, code, createTime}.
|
4. 添加数据库数据,表为"users" ,内容为{phone, code, createTime}.
|
||||||
5. 删除数据库数据,删除 code 记录.
|
5. 删除数据库数据,删除 code 记录.
|
||||||
|
|
||||||
更新博客记录。传入blogId,blogText,tags,还需要记录更新的时间.
|
更新博客记录。传入blogId,blogText,tags,还需要记录更新的时间.
|
||||||
1. 从 body 中获取 blogId,blogText 和 tags.
|
1. 从 body 中获取 blogId,blogText 和 tags.
|
||||||
2. 校验 blogId 是否为空,为空则返回 {error: "博客ID不能为空"}.
|
2. 校验 blogId 是否为空,为空则错误响应,消息为:博客ID不能为空.
|
||||||
3. 校验 blogText 是否为空,为空则返回 {error: "博客内容不能为空"}.
|
3. 校验 blogText 是否为空,为空则错误响应,消息为:博客内容不能为空.
|
||||||
4. 校验 tags 是否为数组,不是则返回 {error: "标签必须为数组"}.
|
4. 校验 tags 是否为数组,不是则错误响应,消息为:标签必须为数组.
|
||||||
5. 获取当前时间,记录为 updateTime.
|
5. 获取当前时间,记录为 updateTime.
|
||||||
6. 更新数据库数据,表为"blogs",更新符合 blogId 的记录的内容为{blogText, tags, updateTime}.
|
6. 更新数据库数据,表为"blogs",更新符合 blogId 的记录的内容为{blogText, tags, updateTime}.
|
||||||
7. 返回结果 {message: "更新博客记录成功"}.`
|
7. 返回结果 {message: "更新博客记录成功"}.`
|
||||||
|
|||||||
@ -114,8 +114,7 @@ const Chat = ({ chatId }: { chatId: string }) => {
|
|||||||
async (prompts: ChatSiteItemType) => {
|
async (prompts: ChatSiteItemType) => {
|
||||||
const urlMap: Record<string, string> = {
|
const urlMap: Record<string, string> = {
|
||||||
[ChatModelNameEnum.GPT35]: '/api/chat/chatGpt',
|
[ChatModelNameEnum.GPT35]: '/api/chat/chatGpt',
|
||||||
[ChatModelNameEnum.VECTOR_GPT]: '/api/chat/vectorGpt',
|
[ChatModelNameEnum.VECTOR_GPT]: '/api/chat/vectorGpt'
|
||||||
[ChatModelNameEnum.GPT3]: '/api/chat/gpt3'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!urlMap[chatData.modelName]) return Promise.reject('找不到模型');
|
if (!urlMap[chatData.modelName]) return Promise.reject('找不到模型');
|
||||||
|
|||||||
@ -12,12 +12,13 @@ import {
|
|||||||
SliderThumb,
|
SliderThumb,
|
||||||
SliderMark,
|
SliderMark,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Button
|
Button,
|
||||||
|
Select
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||||
import type { ModelSchema } from '@/types/mongoSchema';
|
import type { ModelSchema } from '@/types/mongoSchema';
|
||||||
import { UseFormReturn } from 'react-hook-form';
|
import { UseFormReturn } from 'react-hook-form';
|
||||||
import { modelList } from '@/constants/model';
|
import { modelList, ModelVectorSearchModeMap } from '@/constants/model';
|
||||||
import { formatPrice } from '@/utils/user';
|
import { formatPrice } from '@/utils/user';
|
||||||
import { useConfirm } from '@/hooks/useConfirm';
|
import { useConfirm } from '@/hooks/useConfirm';
|
||||||
|
|
||||||
@ -89,15 +90,6 @@ const ModelEditForm = ({
|
|||||||
删除模型
|
删除模型
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
{/* <FormControl mt={4}>
|
|
||||||
<Box mb={1}>介绍:</Box>
|
|
||||||
<Textarea
|
|
||||||
rows={5}
|
|
||||||
maxLength={500}
|
|
||||||
{...register('intro')}
|
|
||||||
placeholder={'模型的介绍,仅做展示,不影响模型的效果'}
|
|
||||||
/>
|
|
||||||
</FormControl> */}
|
|
||||||
</Card>
|
</Card>
|
||||||
<Card p={4}>
|
<Card p={4}>
|
||||||
<Box fontWeight={'bold'}>模型效果</Box>
|
<Box fontWeight={'bold'}>模型效果</Box>
|
||||||
@ -143,6 +135,20 @@ const ModelEditForm = ({
|
|||||||
</Slider>
|
</Slider>
|
||||||
</Flex>
|
</Flex>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
{canTrain && (
|
||||||
|
<FormControl mt={4}>
|
||||||
|
<Flex alignItems={'center'}>
|
||||||
|
<Box flex={'0 0 70px'}>搜索模式</Box>
|
||||||
|
<Select {...register('search.mode', { required: '搜索模式不能为空' })}>
|
||||||
|
{Object.entries(ModelVectorSearchModeMap).map(([key, { text }]) => (
|
||||||
|
<option key={key} value={key}>
|
||||||
|
{text}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Flex>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
<Box mt={4}>
|
<Box mt={4}>
|
||||||
<Box mb={1}>系统提示词</Box>
|
<Box mb={1}>系统提示词</Box>
|
||||||
<Textarea
|
<Textarea
|
||||||
|
|||||||
@ -143,6 +143,7 @@ const ModelDetail = ({ modelId }: { modelId: string }) => {
|
|||||||
systemPrompt: data.systemPrompt,
|
systemPrompt: data.systemPrompt,
|
||||||
intro: data.intro,
|
intro: data.intro,
|
||||||
temperature: data.temperature,
|
temperature: data.temperature,
|
||||||
|
search: data.search,
|
||||||
service: data.service,
|
service: data.service,
|
||||||
security: data.security
|
security: data.security
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { Schema, model, models, Model as MongoModel } from 'mongoose';
|
import { Schema, model, models, Model as MongoModel } from 'mongoose';
|
||||||
import { ModelSchema as ModelType } from '@/types/mongoSchema';
|
import { ModelSchema as ModelType } from '@/types/mongoSchema';
|
||||||
|
import { ModelVectorSearchModeMap, ModelVectorSearchModeEnum } from '@/constants/model';
|
||||||
|
|
||||||
const ModelSchema = new Schema({
|
const ModelSchema = new Schema({
|
||||||
userId: {
|
userId: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
@ -43,6 +45,13 @@ const ModelSchema = new Schema({
|
|||||||
max: 10,
|
max: 10,
|
||||||
default: 4
|
default: 4
|
||||||
},
|
},
|
||||||
|
search: {
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
enum: Object.keys(ModelVectorSearchModeMap),
|
||||||
|
default: ModelVectorSearchModeEnum.hightSimilarity
|
||||||
|
}
|
||||||
|
},
|
||||||
service: {
|
service: {
|
||||||
company: {
|
company: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|||||||
5
src/types/model.d.ts
vendored
5
src/types/model.d.ts
vendored
@ -5,8 +5,9 @@ export interface ModelUpdateParams {
|
|||||||
systemPrompt: string;
|
systemPrompt: string;
|
||||||
intro: string;
|
intro: string;
|
||||||
temperature: number;
|
temperature: number;
|
||||||
service: ModelSchema.service;
|
search: ModelSchema['search'];
|
||||||
security: ModelSchema.security;
|
service: ModelSchema['service'];
|
||||||
|
security: ModelSchema['security'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModelDataItemType {
|
export interface ModelDataItemType {
|
||||||
|
|||||||
10
src/types/mongoSchema.d.ts
vendored
10
src/types/mongoSchema.d.ts
vendored
@ -1,5 +1,10 @@
|
|||||||
import type { ChatItemType } from './chat';
|
import type { ChatItemType } from './chat';
|
||||||
import { ModelStatusEnum, TrainingStatusEnum, ChatModelNameEnum } from '@/constants/model';
|
import {
|
||||||
|
ModelStatusEnum,
|
||||||
|
TrainingStatusEnum,
|
||||||
|
ChatModelNameEnum,
|
||||||
|
ModelVectorSearchModeEnum
|
||||||
|
} from '@/constants/model';
|
||||||
import type { DataType } from './data';
|
import type { DataType } from './data';
|
||||||
|
|
||||||
export type ServiceName = 'openai';
|
export type ServiceName = 'openai';
|
||||||
@ -32,6 +37,9 @@ export interface ModelSchema {
|
|||||||
updateTime: number;
|
updateTime: number;
|
||||||
trainingTimes: number;
|
trainingTimes: number;
|
||||||
temperature: number;
|
temperature: number;
|
||||||
|
search: {
|
||||||
|
mode: `${ModelVectorSearchModeEnum}`;
|
||||||
|
};
|
||||||
service: {
|
service: {
|
||||||
company: ServiceName;
|
company: ServiceName;
|
||||||
trainId: string; // 训练的模型,训练后就是训练的模型id
|
trainId: string; // 训练的模型,训练后就是训练的模型id
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user