mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-13 22:56:28 +00:00
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:
23
.claude/design/service/common/cache/version.md
vendored
Normal file
23
.claude/design/service/common/cache/version.md
vendored
Normal 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,获取初始数据
|
@@ -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'
|
||||
};
|
||||
|
@@ -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,更利于模型理解。
|
||||
|
@@ -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",
|
||||
|
@@ -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],
|
||||
|
@@ -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}`,
|
||||
|
1
packages/service/core/ai/type.d.ts
vendored
1
packages/service/core/ai/type.d.ts
vendored
@@ -41,5 +41,6 @@ declare global {
|
||||
var reRankModelMap: Map<string, RerankModelItemType>;
|
||||
|
||||
var systemActiveModelList: SystemModelItemType[];
|
||||
var systemActiveDesensitizedModels: SystemModelItemType[];
|
||||
var systemDefaultModel: SystemDefaultModelType;
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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) {
|
||||
|
@@ -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",
|
||||
|
@@ -21,7 +21,7 @@
|
||||
"Optimizer_Placeholder_loading": "正在生成...请稍候",
|
||||
"Optimizer_Reoptimize": "重新优化",
|
||||
"Optimizer_Replace": "替换",
|
||||
"Optimizer_Tooltip": "AI 优化",
|
||||
"Optimizer_Tooltip": "AI 优化提示词",
|
||||
"Role_setting": "权限设置",
|
||||
"Run": "运行",
|
||||
"Search_dataset": "搜索知识库",
|
||||
|
@@ -21,7 +21,7 @@
|
||||
"Optimizer_Placeholder_loading": "正在生成...請稍候",
|
||||
"Optimizer_Reoptimize": "重新優化",
|
||||
"Optimizer_Replace": "替換",
|
||||
"Optimizer_Tooltip": "AI 優化",
|
||||
"Optimizer_Tooltip": "AI 優化提示詞",
|
||||
"Role_setting": "權限設定",
|
||||
"Run": "執行",
|
||||
"Search_dataset": "搜尋知識庫",
|
||||
|
@@ -95,3 +95,4 @@ CONFIG_JSON_PATH=
|
||||
# Signoz
|
||||
SIGNOZ_BASE_URL=
|
||||
SIGNOZ_SERVICE_NAME=
|
||||
SIGNOZ_STORE_LEVEL=warn
|
||||
|
@@ -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) => {
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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(
|
||||
|
@@ -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
|
||||
|
@@ -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,
|
||||
|
Reference in New Issue
Block a user