Skip to content

Commit c5664c7

Browse files
authored
feat: vision model (#489)
* mongo init * perf: mongo connect * perf: tts perf: whisper and tts peref: tts whisper permission log reabase (#488) * perf: modal * i18n * perf: schema lean * feat: vision model format * perf: tts loading * perf: static data * perf: tts * feat: image * perf: image * perf: upload image and title * perf: image size * doc * perf: color * doc * speaking can not select file * doc
1 parent 70f3373 commit c5664c7

File tree

58 files changed

+648
-252
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+648
-252
lines changed

docSite/content/docs/development/configuration.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ weight: 520
3636
"quoteMaxToken": 2000, // 最大引用内容长度
3737
"maxTemperature": 1.2, // 最大温度值
3838
"censor": false, // 是否开启敏感词过滤(商业版)
39+
"vision": false, // 支持图片输入
3940
"defaultSystemChatPrompt": ""
4041
},
4142
{
@@ -47,6 +48,7 @@ weight: 520
4748
"quoteMaxToken": 8000,
4849
"maxTemperature": 1.2,
4950
"censor": false,
51+
"vision": false,
5052
"defaultSystemChatPrompt": ""
5153
},
5254
{
@@ -58,6 +60,19 @@ weight: 520
5860
"quoteMaxToken": 4000,
5961
"maxTemperature": 1.2,
6062
"censor": false,
63+
"vision": false,
64+
"defaultSystemChatPrompt": ""
65+
},
66+
{
67+
"model": "gpt-4-vision-preview",
68+
"name": "GPT4-Vision",
69+
"maxContext": 128000,
70+
"maxResponse": 4000,
71+
"price": 0,
72+
"quoteMaxToken": 100000,
73+
"maxTemperature": 1.2,
74+
"censor": false,
75+
"vision": true,
6176
"defaultSystemChatPrompt": ""
6277
}
6378
],
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
title: 'V4.6.1'
3+
description: 'FastGPT V4.6 .1'
4+
icon: 'upgrade'
5+
draft: false
6+
toc: true
7+
weight: 835
8+
---
9+
10+
11+
## V4.6.1 功能介绍
12+
13+
1. 新增 - GPT4-v 模型支持
14+
2. 新增 - whisper 语音输入
15+
3. 优化 - TTS 流传输
16+
4. 优化 - TTS 缓存

packages/global/common/string/tools.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const simpleText = (text: string) => {
2424
};
2525

2626
/*
27-
replace {{variable}} to value
27+
replace {{variable}} to value
2828
*/
2929
export function replaceVariable(text: string, obj: Record<string, string | number>) {
3030
for (const key in obj) {

packages/global/core/ai/model.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export type ChatModelItemType = LLMModelItemType & {
99
quoteMaxToken: number;
1010
maxTemperature: number;
1111
censor?: boolean;
12+
vision?: boolean;
1213
defaultSystemChatPrompt?: string;
1314
};
1415

packages/global/core/ai/model.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const defaultChatModels: ChatModelItemType[] = [
1717
quoteMaxToken: 2000,
1818
maxTemperature: 1.2,
1919
censor: false,
20+
vision: false,
2021
defaultSystemChatPrompt: ''
2122
},
2223
{
@@ -28,6 +29,7 @@ export const defaultChatModels: ChatModelItemType[] = [
2829
quoteMaxToken: 8000,
2930
maxTemperature: 1.2,
3031
censor: false,
32+
vision: false,
3133
defaultSystemChatPrompt: ''
3234
},
3335
{
@@ -39,6 +41,19 @@ export const defaultChatModels: ChatModelItemType[] = [
3941
quoteMaxToken: 4000,
4042
maxTemperature: 1.2,
4143
censor: false,
44+
vision: false,
45+
defaultSystemChatPrompt: ''
46+
},
47+
{
48+
model: 'gpt-4-vision-preview',
49+
name: 'GPT4-Vision',
50+
maxContext: 128000,
51+
maxResponse: 4000,
52+
price: 0,
53+
quoteMaxToken: 100000,
54+
maxTemperature: 1.2,
55+
censor: false,
56+
vision: true,
4257
defaultSystemChatPrompt: ''
4358
}
4459
];

packages/global/core/ai/type.d.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import type {
55
ChatCompletionMessageParam,
66
ChatCompletionContentPart
77
} from 'openai/resources';
8+
89
export type ChatCompletionContentPart = ChatCompletionContentPart;
910
export type ChatCompletionCreateParams = ChatCompletionCreateParams;
10-
export type ChatMessageItemType = Omit<ChatCompletionMessageParam> & {
11+
export type ChatMessageItemType = Omit<ChatCompletionMessageParam, 'name'> & {
12+
name?: any;
1113
dataId?: string;
1214
content: any;
13-
};
15+
} & any;
1416

1517
export type ChatCompletion = ChatCompletion;
1618
export type StreamChatType = Stream<ChatCompletionChunk>;

packages/global/core/chat/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,6 @@ export const ChatSourceMap = {
5454

5555
export const HUMAN_ICON = `/icon/human.svg`;
5656
export const LOGO_ICON = `/icon/logo.svg`;
57+
58+
export const IMG_BLOCK_KEY = 'img-block';
59+
export const FILE_BLOCK_KEY = 'file-block';

packages/global/core/chat/utils.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { IMG_BLOCK_KEY, FILE_BLOCK_KEY } from './constants';
2+
3+
export function chatContentReplaceBlock(content: string = '') {
4+
const regex = new RegExp(`\`\`\`(${IMG_BLOCK_KEY})\\n([\\s\\S]*?)\`\`\``, 'g');
5+
return content.replace(regex, '').trim();
6+
}

packages/service/common/buffer/tts/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,4 @@ try {
3333

3434
export const MongoTTSBuffer: Model<TTSBufferSchemaType> =
3535
models[collectionName] || model(collectionName, TTSBufferSchema);
36+
MongoTTSBuffer.syncIndexes();

packages/service/common/file/image/controller.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,26 @@ export function getMongoImgUrl(id: string) {
55
return `${imageBaseUrl}${id}`;
66
}
77

8-
export async function uploadMongoImg({ base64Img, userId }: { base64Img: string; userId: string }) {
8+
export const maxImgSize = 1024 * 1024 * 12;
9+
export async function uploadMongoImg({
10+
base64Img,
11+
teamId,
12+
expiredTime
13+
}: {
14+
base64Img: string;
15+
teamId: string;
16+
expiredTime?: Date;
17+
}) {
18+
if (base64Img.length > maxImgSize) {
19+
return Promise.reject('Image too large');
20+
}
21+
922
const base64Data = base64Img.split(',')[1];
1023

1124
const { _id } = await MongoImage.create({
12-
userId,
13-
binary: Buffer.from(base64Data, 'base64')
25+
teamId,
26+
binary: Buffer.from(base64Data, 'base64'),
27+
expiredTime
1428
});
1529

1630
return getMongoImgUrl(String(_id));
Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
1+
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
12
import { connectionMongo, type Model } from '../../mongo';
23
const { Schema, model, models } = connectionMongo;
34

45
const ImageSchema = new Schema({
5-
userId: {
6+
teamId: {
67
type: Schema.Types.ObjectId,
7-
ref: 'user',
8-
required: true
8+
ref: TeamCollectionName
99
},
1010
binary: {
1111
type: Buffer
12+
},
13+
expiredTime: {
14+
type: Date
1215
}
1316
});
1417

15-
export const MongoImage: Model<{ userId: string; binary: Buffer }> =
18+
try {
19+
ImageSchema.index({ expiredTime: 1 }, { expireAfterSeconds: 60 });
20+
} catch (error) {
21+
console.log(error);
22+
}
23+
24+
export const MongoImage: Model<{ teamId: string; binary: Buffer }> =
1625
models['image'] || model('image', ImageSchema);
26+
27+
MongoImage.syncIndexes();

packages/service/core/app/schema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,5 @@ try {
6767

6868
export const MongoApp: Model<AppType> =
6969
models[appCollectionName] || model(appCollectionName, AppSchema);
70+
71+
MongoApp.syncIndexes();

packages/service/core/chat/chatItemSchema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,5 @@ try {
8383

8484
export const MongoChatItem: Model<ChatItemType> =
8585
models['chatItem'] || model('chatItem', ChatItemSchema);
86+
87+
MongoChatItem.syncIndexes();

packages/service/core/chat/chatSchema.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ const ChatSchema = new Schema({
9292
});
9393

9494
try {
95-
ChatSchema.index({ userId: 1 });
95+
ChatSchema.index({ tmbId: 1 });
9696
ChatSchema.index({ updateTime: -1 });
9797
ChatSchema.index({ appId: 1 });
9898
} catch (error) {
@@ -101,3 +101,4 @@ try {
101101

102102
export const MongoChat: Model<ChatType> =
103103
models[chatCollectionName] || model(chatCollectionName, ChatSchema);
104+
MongoChat.syncIndexes();

packages/service/core/chat/utils.ts

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
2-
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
2+
import { ChatRoleEnum, IMG_BLOCK_KEY } from '@fastgpt/global/core/chat/constants';
33
import { countMessagesTokens, countPromptTokens } from '@fastgpt/global/common/string/tiktoken';
44
import { adaptRole_Chat2Message } from '@fastgpt/global/core/chat/adapt';
5+
import type { ChatCompletionContentPart } from '@fastgpt/global/core/ai/type.d';
56

67
/* slice chat context by tokens */
78
export function ChatContextFilter({
@@ -51,3 +52,101 @@ export function ChatContextFilter({
5152

5253
return [...systemPrompts, ...chats];
5354
}
55+
56+
/**
57+
string to vision model. Follow the markdown code block rule for interception:
58+
59+
@rule:
60+
```img-block
61+
{src:""}
62+
{src:""}
63+
```
64+
```file-block
65+
{name:"",src:""},
66+
{name:"",src:""}
67+
```
68+
@example:
69+
What’s in this image?
70+
```img-block
71+
{src:"https://1.png"}
72+
```
73+
@return
74+
[
75+
{ type: 'text', text: 'What’s in this image?' },
76+
{
77+
type: 'image_url',
78+
image_url: {
79+
url: 'https://1.png'
80+
}
81+
}
82+
]
83+
*/
84+
export function formatStr2ChatContent(str: string) {
85+
const content: ChatCompletionContentPart[] = [];
86+
let lastIndex = 0;
87+
const regex = new RegExp(`\`\`\`(${IMG_BLOCK_KEY})\\n([\\s\\S]*?)\`\`\``, 'g');
88+
89+
let match;
90+
91+
while ((match = regex.exec(str)) !== null) {
92+
// add previous text
93+
if (match.index > lastIndex) {
94+
const text = str.substring(lastIndex, match.index).trim();
95+
if (text) {
96+
content.push({ type: 'text', text });
97+
}
98+
}
99+
100+
const blockType = match[1].trim();
101+
102+
if (blockType === IMG_BLOCK_KEY) {
103+
const blockContentLines = match[2].trim().split('\n');
104+
const jsonLines = blockContentLines.map((item) => {
105+
try {
106+
return JSON.parse(item) as { src: string };
107+
} catch (error) {
108+
return { src: '' };
109+
}
110+
});
111+
112+
for (const item of jsonLines) {
113+
if (!item.src) throw new Error("image block's content error");
114+
}
115+
116+
content.push(
117+
...jsonLines.map((item) => ({
118+
type: 'image_url' as any,
119+
image_url: {
120+
url: item.src
121+
}
122+
}))
123+
);
124+
}
125+
126+
lastIndex = regex.lastIndex;
127+
}
128+
129+
// add remaining text
130+
if (lastIndex < str.length) {
131+
const remainingText = str.substring(lastIndex).trim();
132+
if (remainingText) {
133+
content.push({ type: 'text', text: remainingText });
134+
}
135+
}
136+
137+
// Continuous text type content, if type=text, merge them
138+
for (let i = 0; i < content.length - 1; i++) {
139+
const currentContent = content[i];
140+
const nextContent = content[i + 1];
141+
if (currentContent.type === 'text' && nextContent.type === 'text') {
142+
currentContent.text += nextContent.text;
143+
content.splice(i + 1, 1);
144+
i--;
145+
}
146+
}
147+
148+
if (content.length === 1 && content[0].type === 'text') {
149+
return content[0].text;
150+
}
151+
return content ? content : null;
152+
}

packages/service/core/dataset/controller.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ export async function findDatasetIdTreeByTopDatasetId(
2222
}
2323

2424
export async function getCollectionWithDataset(collectionId: string) {
25-
const data = (
26-
await MongoDatasetCollection.findById(collectionId).populate('datasetId')
27-
)?.toJSON() as CollectionWithDatasetType;
25+
const data = (await MongoDatasetCollection.findById(collectionId)
26+
.populate('datasetId')
27+
.lean()) as CollectionWithDatasetType;
2828
if (!data) {
2929
return Promise.reject('Collection is not exist');
3030
}

packages/service/core/dataset/data/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,4 @@ try {
7676

7777
export const MongoDatasetData: Model<DatasetDataSchemaType> =
7878
models[DatasetDataCollectionName] || model(DatasetDataCollectionName, DatasetDataSchema);
79+
MongoDatasetData.syncIndexes();

packages/service/core/dataset/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,4 @@ try {
8282

8383
export const MongoDataset: Model<DatasetSchemaType> =
8484
models[DatasetCollectionName] || model(DatasetCollectionName, DatasetSchema);
85+
MongoDataset.syncIndexes();

packages/service/core/dataset/training/schema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,5 @@ try {
104104

105105
export const MongoDatasetTraining: Model<DatasetTrainingSchemaType> =
106106
models[DatasetTrainingCollectionName] || model(DatasetTrainingCollectionName, TrainingDataSchema);
107+
108+
MongoDatasetTraining.syncIndexes();

packages/service/core/plugin/schema.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,11 @@ const PluginSchema = new Schema({
4646
});
4747

4848
try {
49-
PluginSchema.index({ userId: 1 });
49+
PluginSchema.index({ tmbId: 1 });
5050
} catch (error) {
5151
console.log(error);
5252
}
5353

5454
export const MongoPlugin: Model<PluginItemSchema> =
5555
models[ModuleCollectionName] || model(ModuleCollectionName, PluginSchema);
56+
MongoPlugin.syncIndexes();

packages/service/support/activity/promotion/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ const PromotionRecordSchema = new Schema({
3131

3232
export const MongoPromotionRecord: Model<PromotionRecordType> =
3333
models['promotionRecord'] || model('promotionRecord', PromotionRecordSchema);
34+
MongoPromotionRecord.syncIndexes();

packages/service/support/openapi/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,4 @@ const OpenApiSchema = new Schema(
7070

7171
export const MongoOpenApi: Model<OpenApiSchema> =
7272
models['openapi'] || model('openapi', OpenApiSchema);
73+
MongoOpenApi.syncIndexes();

packages/service/support/outLink/schema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,5 @@ const OutLinkSchema = new Schema({
7171

7272
export const MongoOutLink: Model<SchemaType> =
7373
models['outlinks'] || model('outlinks', OutLinkSchema);
74+
75+
MongoOutLink.syncIndexes();

0 commit comments

Comments
 (0)