perF: getInitData api cache;perf: tool description field;signoz store level (#5465)

* perf: auto focus

* perF: getInitData api cache

* perf: tool description field

* signoz store level

* perF: chat logs index
This commit is contained in:
Archer
2025-08-15 15:01:20 +08:00
committed by GitHub
parent d78a0e9e4b
commit 76dc23c2e4
20 changed files with 91 additions and 71 deletions

View File

@@ -0,0 +1,23 @@
# 服务端资源版本 ID 缓存方案
## 背景
FastGPT 会采用多节点部署方式,有部分数据缓存会存储在内存里。当需要使用这部分数据时(不管是通过 API 获取,还是后端服务自己获取),都是直接拉取内存数据,这可能会导致数据不一致问题,尤其是用户通过 API 更新数据后再获取,就容易获取未修改数据的节点。
## 解决方案
1. 给每一个缓存数据加上一个版本 ID。
2. 获取该数据时候,不直接引用该数据,而是通过一个 function 获取,该 function 可选的传入一个 versionId。
3. 获取数据时,先检查该 versionId 与 redis 中资源版本id 与传入的 versionId 是否一致。
4. 如果数据一致,则直接返回数据。
5. 如果数据不一致,则重新获取数据,并返回最新的 versionId。调用方则需要更新其缓存的 versionId。
## 代码方案
* 获取和更新缓存的代码,直接复用 FastGPT/packages/service/common/redis/cache.ts
* 每个资源,自己维护一个 cacheKey
* 每次更新资源/触发拉取最新资源时,都需要更新 cacheKey 的值。
## 涉及的业务
* [ ] FastGPT/projects/app/src/pages/api/common/system/getInitData.ts获取初始数据

View File

@@ -10,7 +10,7 @@ const exactMap: Record<string, string> = {
'/docs/guide/admin/sso_dingtalk':
'/docs/introduction/guide/admin/sso#/docs/introduction/guide/admin/sso#钉钉',
'/docs/guide/knowledge_base/rag': '/docs/introduction/guide/knowledge_base/RAG',
'/docs/commercial/intro/': '/docs/introduction',
'/docs/commercial/intro/': '/docs/introduction/commercial',
'/docs/upgrading/intro/': '/docs/upgrading',
'/docs/introduction/shopping_cart/intro/': '/docs/introduction/commercial'
};

View File

@@ -7,6 +7,7 @@ description: 'FastGPT V4.12.1 更新说明'
## 🚀 新增内容
1. Prompt 自动生成和优化。
2. 增加`SIGNOZ_STORE_LEVEL`参数,可以控制 Signoz 日志存储级别。
## ⚙️ 优化
@@ -21,3 +22,5 @@ description: 'FastGPT V4.12.1 更新说明'
3. 对话日志看板数据表索引不正确。
## 🔨 工具更新
1. 支持对系统工具单独配置 Tool description更利于模型理解。

View File

@@ -103,7 +103,7 @@
"document/content/docs/upgrading/4-11/4110.mdx": "2025-08-05T23:20:39+08:00",
"document/content/docs/upgrading/4-11/4111.mdx": "2025-08-07T22:49:09+08:00",
"document/content/docs/upgrading/4-12/4120.mdx": "2025-08-12T22:45:19+08:00",
"document/content/docs/upgrading/4-12/4121.mdx": "2025-08-14T22:01:36+08:00",
"document/content/docs/upgrading/4-12/4121.mdx": "2025-08-15T14:27:32+08:00",
"document/content/docs/upgrading/4-8/40.mdx": "2025-08-02T19:38:37+08:00",
"document/content/docs/upgrading/4-8/41.mdx": "2025-08-02T19:38:37+08:00",
"document/content/docs/upgrading/4-8/42.mdx": "2025-08-02T19:38:37+08:00",

View File

@@ -34,13 +34,15 @@ const envLogLevelMap: Record<string, number> = {
error: LogLevelEnum.error
};
const { LOG_LEVEL, STORE_LOG_LEVEL } = (() => {
const { LOG_LEVEL, STORE_LOG_LEVEL, SIGNOZ_STORE_LEVEL } = (() => {
const LOG_LEVEL = (process.env.LOG_LEVEL || 'info').toLocaleLowerCase();
const STORE_LOG_LEVEL = (process.env.STORE_LOG_LEVEL || '').toLocaleLowerCase();
const SIGNOZ_STORE_LEVEL = (process.env.SIGNOZ_STORE_LEVEL || 'warn').toLocaleLowerCase();
return {
LOG_LEVEL: envLogLevelMap[LOG_LEVEL] ?? LogLevelEnum.info,
STORE_LOG_LEVEL: envLogLevelMap[STORE_LOG_LEVEL] ?? 99
STORE_LOG_LEVEL: envLogLevelMap[STORE_LOG_LEVEL] ?? 99,
SIGNOZ_STORE_LEVEL: envLogLevelMap[SIGNOZ_STORE_LEVEL] ?? LogLevelEnum.warn
};
})();
@@ -60,7 +62,7 @@ export const addLog = {
level === LogLevelEnum.error && console.error(obj);
if (logger) {
if (logger && level >= SIGNOZ_STORE_LEVEL) {
logger.emit({
severityNumber: level.valueOf(),
severityText: ['debug', 'info', 'warn', 'error'][level],

View File

@@ -173,6 +173,17 @@ export const loadSystemModels = async (init = false) => {
const providerB = getModelProvider(b.provider);
return providerA.order - providerB.order;
});
global.systemActiveDesensitizedModels = global.systemActiveModelList.map((model) => ({
...model,
defaultSystemChatPrompt: undefined,
fieldMap: undefined,
defaultConfig: undefined,
weight: undefined,
dbConfig: undefined,
queryConfig: undefined,
requestUrl: undefined,
requestAuth: undefined
})) as SystemModelItemType[];
console.log(
`Load models success, total: ${global.systemModelList.length}, active: ${global.systemActiveModelList.length}`,

View File

@@ -41,5 +41,6 @@ declare global {
var reRankModelMap: Map<string, RerankModelItemType>;
var systemActiveModelList: SystemModelItemType[];
var systemActiveDesensitizedModels: SystemModelItemType[];
var systemDefaultModel: SystemDefaultModelType;
}

View File

@@ -314,11 +314,7 @@ export async function getChildAppPreviewNode({
}))
}
}
: {
systemTool: {
toolId: app.id
}
})
: { systemTool: { toolId: app.id } })
},
showSourceHandle: app.isFolder ? false : true,
showTargetHandle: app.isFolder ? false : true
@@ -541,23 +537,6 @@ export const getSystemTools = async (): Promise<SystemPluginTemplateItemType[]>
const systemToolsArray = await MongoSystemPlugin.find({}).lean();
const systemTools = new Map(systemToolsArray.map((plugin) => [plugin.pluginId, plugin]));
// tools.forEach((tool) => {
// // 如果有插件的配置信息,则需要进行替换
// const dbPluginConfig = systemTools.get(tool.id);
// if (dbPluginConfig) {
// const children = tools.filter((item) => item.parentId === tool.id);
// const list = [tool, ...children];
// list.forEach((item) => {
// item.isActive = dbPluginConfig.isActive ?? item.isActive ?? true;
// item.originCost = dbPluginConfig.originCost ?? 0;
// item.currentCost = dbPluginConfig.currentCost ?? 0;
// item.hasTokenFee = dbPluginConfig.hasTokenFee ?? false;
// item.pluginOrder = dbPluginConfig.pluginOrder ?? 0;
// });
// }
// });
const formatTools = tools.map<SystemPluginTemplateItemType>((item) => {
const dbPluginConfig = systemTools.get(item.id);
const isFolder = tools.some((tool) => tool.parentId === item.id);

View File

@@ -88,6 +88,7 @@ export async function rewriteAppWorkflowToDetail({
node.isFolder = preview.isFolder;
node.toolConfig = preview.toolConfig;
node.toolDescription = preview.toolDescription;
// Latest version
if (!node.version) {

View File

@@ -21,7 +21,7 @@
"Optimizer_Placeholder_loading": "Generating...please wait",
"Optimizer_Reoptimize": "Re-optimize",
"Optimizer_Replace": "replace",
"Optimizer_Tooltip": "AI Optimization",
"Optimizer_Tooltip": "AI optimization prompt words",
"Role_setting": "Permission",
"Run": "Execute",
"Search_dataset": "Search dataset",

View File

@@ -21,7 +21,7 @@
"Optimizer_Placeholder_loading": "正在生成...请稍候",
"Optimizer_Reoptimize": "重新优化",
"Optimizer_Replace": "替换",
"Optimizer_Tooltip": "AI 优化",
"Optimizer_Tooltip": "AI 优化提示词",
"Role_setting": "权限设置",
"Run": "运行",
"Search_dataset": "搜索知识库",

View File

@@ -21,7 +21,7 @@
"Optimizer_Placeholder_loading": "正在生成...請稍候",
"Optimizer_Reoptimize": "重新優化",
"Optimizer_Replace": "替換",
"Optimizer_Tooltip": "AI 優化",
"Optimizer_Tooltip": "AI 優化提示詞",
"Role_setting": "權限設定",
"Run": "執行",
"Search_dataset": "搜尋知識庫",

View File

@@ -95,3 +95,4 @@ CONFIG_JSON_PATH=
# Signoz
SIGNOZ_BASE_URL=
SIGNOZ_SERVICE_NAME=
SIGNOZ_STORE_LEVEL=warn

View File

@@ -37,6 +37,8 @@ const OptimizerPopover = ({
const { t } = useTranslation();
const { llmModelList, defaultModels } = useSystemStore();
const InputRef = useRef<HTMLTextAreaElement>(null);
const [optimizerInput, setOptimizerInput] = useState('');
const [optimizedResult, setOptimizedResult] = useState('');
const [selectedModel = '', setSelectedModel] = useLocalStorageState<string>(
@@ -122,7 +124,7 @@ const OptimizerPopover = ({
Trigger={
<Flex {...iconButtonStyle}>
<MyTooltip label={t('app:Optimizer_Tooltip')}>
<MyIcon name={'optimizer'} w={'18px'} />
<MyIcon name={'optimizer'} w={'1.2rem'} />
</MyTooltip>
</Flex>
}
@@ -136,6 +138,11 @@ const OptimizerPopover = ({
closePopoverRef.current?.();
}
}}
onOpenFunc={() => {
setTimeout(() => {
InputRef.current?.focus();
}, 50);
}}
>
{({ onClose }) => {
closePopoverRef.current = onClose;
@@ -234,11 +241,13 @@ const OptimizerPopover = ({
>
<MyIcon name={'optimizer'} alignSelf={'flex-start'} mt={0.5} w={5} />
<Textarea
ref={InputRef}
placeholder={
!loading
? t('app:Optimizer_Placeholder')
: t('app:Optimizer_Placeholder_loading')
}
autoFocus
resize={'none'}
rows={1}
minHeight={'24px'}
@@ -246,14 +255,11 @@ const OptimizerPopover = ({
maxHeight={'96px'}
overflowY={'hidden'}
border={'none'}
_focus={{
boxShadow: 'none'
}}
boxShadow={'none !important'}
fontSize={'sm'}
p={0}
borderRadius={'none'}
value={optimizerInput}
autoFocus
onKeyDown={handleKeyDown}
isDisabled={loading}
onChange={(e) => {

View File

@@ -38,7 +38,7 @@ const ChatTest = ({ isOpen, nodes = [], edges = [], onClose, chatId }: Props) =>
const isPlugin = appDetail.type === AppTypeEnum.plugin;
const { copyData } = useCopyData();
const { restartChat, ChatContainer, loading } = useChatTest({
const { restartChat, ChatContainer } = useChatTest({
nodes,
edges,
chatConfig: appDetail.chatConfig,

View File

@@ -25,6 +25,7 @@ export const uiWorkflow2StoreWorkflow = ({
parentNodeId: item.data.parentNodeId,
name: item.data.name,
intro: item.data.intro,
toolDescription: item.data.toolDescription,
avatar: item.data.avatar,
flowNodeType: item.data.flowNodeType,
showStatus: item.data.showStatus,

View File

@@ -2,7 +2,6 @@ import type { NextApiResponse } from 'next';
import { type ApiRequestProps } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
import { type InitDateResponse } from '@/global/common/api/systemRes';
import { type SystemModelItemType } from '@fastgpt/service/core/ai/type';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
async function handler(
@@ -11,18 +10,6 @@ async function handler(
): Promise<InitDateResponse> {
const { bufferId } = req.query;
const activeModelList = global.systemActiveModelList.map((model) => ({
...model,
defaultSystemChatPrompt: undefined,
fieldMap: undefined,
defaultConfig: undefined,
weight: undefined,
dbConfig: undefined,
queryConfig: undefined,
requestUrl: undefined,
requestAuth: undefined
})) as SystemModelItemType[];
try {
await authCert({ req, authToken: true });
// If bufferId is the same as the current bufferId, return directly
@@ -38,7 +25,7 @@ async function handler(
feConfigs: global.feConfigs,
subPlans: global.subPlans,
systemVersion: global.systemVersion,
activeModelList,
activeModelList: global.systemActiveDesensitizedModels,
defaultModels: global.systemDefaultModel
};
} catch (error) {
@@ -47,7 +34,7 @@ async function handler(
return {
feConfigs: global.feConfigs,
subPlans: global.subPlans,
activeModelList
activeModelList: global.systemActiveDesensitizedModels
};
}

View File

@@ -18,6 +18,8 @@ import { type ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import { AppLogKeysEnum } from '@fastgpt/global/core/app/logs/constants';
import { sanitizeCsvField } from '@fastgpt/service/common/file/csv';
import { AppReadChatLogPerVal } from '@fastgpt/global/support/permission/app/constant';
import { addAuditLog, getI18nAppType } from '@fastgpt/service/support/user/audit/util';
import { AuditEventEnum } from '@fastgpt/global/support/user/audit/constants';
const formatJsonString = (data: any) => {
if (data == null) return '';
@@ -47,7 +49,12 @@ async function handler(req: ApiRequestProps<ExportChatLogsBody, {}>, res: NextAp
throw new Error('缺少参数');
}
const { teamId } = await authApp({ req, authToken: true, appId, per: AppReadChatLogPerVal });
const { teamId, tmbId, app } = await authApp({
req,
authToken: true,
appId,
per: AppReadChatLogPerVal
});
const teamMemberWithContact = await MongoTeamMember.aggregate([
{ $match: { teamId: new Types.ObjectId(teamId) } },
@@ -394,6 +401,18 @@ async function handler(req: ApiRequestProps<ExportChatLogsBody, {}>, res: NextAp
res.status(500);
res.end();
});
(async () => {
addAuditLog({
tmbId,
teamId,
event: AuditEventEnum.EXPORT_APP_CHAT_LOG,
params: {
appName: app.name,
appType: getI18nAppType(app.type)
}
});
})();
}
export default NextAPI(

View File

@@ -11,9 +11,6 @@ import { readFromSecondary } from '@fastgpt/service/common/mongo/utils';
import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination';
import { type PaginationResponse } from '@fastgpt/web/common/fetch/type';
import { addSourceMember } from '@fastgpt/service/support/user/utils';
import { addAuditLog } from '@fastgpt/service/support/user/audit/util';
import { AuditEventEnum } from '@fastgpt/global/support/user/audit/constants';
import { getI18nAppType } from '@fastgpt/service/support/user/audit/util';
import { replaceRegChars } from '@fastgpt/global/common/string/tools';
import { AppReadChatLogPerVal } from '@fastgpt/global/support/permission/app/constant';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
@@ -38,7 +35,7 @@ async function handler(
}
// 凭证校验
const { teamId, tmbId, app } = await authApp({
const { teamId } = await authApp({
req,
authToken: true,
appId,
@@ -48,12 +45,12 @@ async function handler(
const where = {
teamId: new Types.ObjectId(teamId),
appId: new Types.ObjectId(appId),
source: sources ? { $in: sources } : { $exists: true },
tmbId: tmbIds ? { $in: tmbIds.map((item) => new Types.ObjectId(item)) } : { $exists: true },
updateTime: {
$gte: new Date(dateStart),
$lte: new Date(dateEnd)
},
source: sources ? { $in: sources } : { $exists: true },
tmbId: tmbIds ? { $in: tmbIds.map((item) => new Types.ObjectId(item)) } : { $exists: true },
...(chatSearch && {
$or: [
{ chatId: { $regex: new RegExp(`${replaceRegChars(chatSearch)}`, 'i') } },
@@ -77,7 +74,7 @@ async function handler(
{
$lookup: {
from: ChatItemCollectionName,
let: { chatId: '$chatId', appId: new Types.ObjectId(appId) },
let: { appId: new Types.ObjectId(appId), chatId: '$chatId' },
pipeline: [
{
$match: {
@@ -244,18 +241,6 @@ async function handler(
const listWithoutTmbId = list.filter((item) => !item.tmbId);
(async () => {
addAuditLog({
tmbId,
teamId,
event: AuditEventEnum.EXPORT_APP_CHAT_LOG,
params: {
appName: app.name,
appType: getI18nAppType(app.type)
}
});
})();
return {
list: listWithSourceMember.concat(listWithoutTmbId),
total

View File

@@ -393,6 +393,7 @@ export function form2AppWorkflow(
pluginId: tool.pluginId,
name: tool.name,
intro: tool.intro,
toolDescription: tool.toolDescription,
avatar: tool.avatar,
flowNodeType: tool.flowNodeType,
showStatus: tool.showStatus,