Commit 025b3dac by Archer Committed by GitHub

fix: http tool schema (#6768)

* fix: http tool schema

* perf: del dataset

* perf: review

* add test
parent 7506a147
...@@ -9,12 +9,13 @@ description: 'FastGPT V4.14.10 更新说明' ...@@ -9,12 +9,13 @@ description: 'FastGPT V4.14.10 更新说明'
以下针对的是 `docker compose` 部署方案的配置调整,使用`sealos`的商业版用户,可私信支持人员,提供在线的沙盒服务方案。 以下针对的是 `docker compose` 部署方案的配置调整,使用`sealos`的商业版用户,可私信支持人员,提供在线的沙盒服务方案。
打开[最新 yml 部署文件](https://github.com/labring/FastGPT/blob/main/deploy/docker/cn/docker-compose.pg.yml),调整以下内容: 参考[最新 yml 部署文件](https://github.com/labring/FastGPT/blob/main/deploy/docker/cn/docker-compose.pg.yml),调整本地 yml 文件,加入以下内容:
1. 在文件顶部增加 `x-volume-manager-auth-token: &x-volume-manager-auth-token 'vmtoken'` 变量配置。 1. 在文件顶部增加 `x-volume-manager-auth-token: &x-volume-manager-auth-token 'vmtoken'` 变量配置。
2. 增加 3 组 services: `opensandbox-server`,`volume-manager`,`agent-sandbox-image` 2. 增加 5 组 services: `opensandbox-server`,`opensandbox-agent-sandbox-image`,`opensandbox-execd-image`,`opensandbox-egress-image`,`fastgpt-volume-manager`
3. 增加 `configs`, 文件底部可找到该内容,直接复制添加。 3. 调整 `networks`,可参考最新的 yml 完全修改。
4. 修改 `fastgpt-app`/`fastgpt-pro` 环境变量, 增加以下变量: 4. 增加 `configs`配置, 文件底部可找到该内容,直接复制添加。
5. 修改 `fastgpt-app`/`fastgpt-pro` 环境变量, 增加以下变量:
```bash ```bash
# ==================== Agent sandbox 配置 ==================== # ==================== Agent sandbox 配置 ====================
...@@ -25,6 +26,7 @@ AGENT_SANDBOX_OPENSANDBOX_API_KEY: ...@@ -25,6 +26,7 @@ AGENT_SANDBOX_OPENSANDBOX_API_KEY:
AGENT_SANDBOX_OPENSANDBOX_RUNTIME: docker AGENT_SANDBOX_OPENSANDBOX_RUNTIME: docker
AGENT_SANDBOX_OPENSANDBOX_IMAGE_REPO: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-agent-sandbox AGENT_SANDBOX_OPENSANDBOX_IMAGE_REPO: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-agent-sandbox
AGENT_SANDBOX_OPENSANDBOX_IMAGE_TAG: v0.1 AGENT_SANDBOX_OPENSANDBOX_IMAGE_TAG: v0.1
AGENT_SANDBOX_OPENSANDBOX_USE_SERVER_PROXY: true
# Volume 持久化配置(opensandbox provider 下可选) # Volume 持久化配置(opensandbox provider 下可选)
AGENT_SANDBOX_ENABLE_VOLUME: true AGENT_SANDBOX_ENABLE_VOLUME: true
AGENT_SANDBOX_VOLUME_MANAGER_URL: http://volume-manager:3000 AGENT_SANDBOX_VOLUME_MANAGER_URL: http://volume-manager:3000
...@@ -39,7 +41,7 @@ AGENT_SANDBOX_VOLUME_MANAGER_TOKEN: *x-volume-manager-auth-token ...@@ -39,7 +41,7 @@ AGENT_SANDBOX_VOLUME_MANAGER_TOKEN: *x-volume-manager-auth-token
### 3. 更新镜像 tag ### 3. 更新镜像 tag
- 更新 fastgpt-app(fastgpt 主服务) 镜像 tag: v4.14.10.2 - 更新 fastgpt-app(fastgpt 主服务) 镜像 tag: v4.14.10.4
- 更新 fastpgt-pro(商业版) 镜像 tag: v4.14.10 - 更新 fastpgt-pro(商业版) 镜像 tag: v4.14.10
- 更新 code-sandbox 镜像 tag: v4.14.10 - 更新 code-sandbox 镜像 tag: v4.14.10
- 更新 fastgpt-plugin 镜像 tag: v0.5.6 - 更新 fastgpt-plugin 镜像 tag: v0.5.6
......
...@@ -39,4 +39,4 @@ description: 'FastGPT V4.14.11 更新说明' ...@@ -39,4 +39,4 @@ description: 'FastGPT V4.14.11 更新说明'
8. 工作流代码运行节点,AI 生成代码后,会讲输出值的 id 全部替换,优化成相同 key 的 id 不替换。 8. 工作流代码运行节点,AI 生成代码后,会讲输出值的 id 全部替换,优化成相同 key 的 id 不替换。
9. 工作流中,父级节点受到辅助线自动对齐时候,其子节点位置会偏移。 9. 工作流中,父级节点受到辅助线自动对齐时候,其子节点位置会偏移。
10. 评估列表权限过滤未覆盖继承权限。 10. 评估列表权限过滤未覆盖继承权限。
11. MCP 工具 raw schema 未成功保存,导致工具调用时候,schema 不准确。 11. MCP 工具和 Http 工具 raw schema 未成功保存,导致工具调用时候,schema 不准确。
\ No newline at end of file \ No newline at end of file
...@@ -73,8 +73,8 @@ ...@@ -73,8 +73,8 @@
"document/content/docs/introduction/guide/dashboard/workflow/laf.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/dashboard/workflow/laf.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/guide/dashboard/workflow/loop.en.mdx": "2026-02-26T22:14:30+08:00", "document/content/docs/introduction/guide/dashboard/workflow/loop.en.mdx": "2026-02-26T22:14:30+08:00",
"document/content/docs/introduction/guide/dashboard/workflow/loop.mdx": "2025-09-17T22:29:56+08:00", "document/content/docs/introduction/guide/dashboard/workflow/loop.mdx": "2025-09-17T22:29:56+08:00",
"document/content/docs/introduction/guide/dashboard/workflow/parallel_run.en.mdx": "2026-04-17T15:12:11+08:00", "document/content/docs/introduction/guide/dashboard/workflow/parallel_run.en.mdx": "2026-04-17T23:28:43+08:00",
"document/content/docs/introduction/guide/dashboard/workflow/parallel_run.mdx": "2026-04-17T15:12:11+08:00", "document/content/docs/introduction/guide/dashboard/workflow/parallel_run.mdx": "2026-04-17T23:28:43+08:00",
"document/content/docs/introduction/guide/dashboard/workflow/question_classify.en.mdx": "2026-02-26T22:14:30+08:00", "document/content/docs/introduction/guide/dashboard/workflow/question_classify.en.mdx": "2026-02-26T22:14:30+08:00",
"document/content/docs/introduction/guide/dashboard/workflow/question_classify.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/dashboard/workflow/question_classify.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/guide/dashboard/workflow/reply.en.mdx": "2026-02-26T22:14:30+08:00", "document/content/docs/introduction/guide/dashboard/workflow/reply.en.mdx": "2026-02-26T22:14:30+08:00",
...@@ -155,8 +155,8 @@ ...@@ -155,8 +155,8 @@
"document/content/docs/self-host/config/model/minimax.mdx": "2026-03-19T09:32:57-05:00", "document/content/docs/self-host/config/model/minimax.mdx": "2026-03-19T09:32:57-05:00",
"document/content/docs/self-host/config/model/siliconCloud.en.mdx": "2026-03-19T14:09:03+08:00", "document/content/docs/self-host/config/model/siliconCloud.en.mdx": "2026-03-19T14:09:03+08:00",
"document/content/docs/self-host/config/model/siliconCloud.mdx": "2026-03-19T14:09:03+08:00", "document/content/docs/self-host/config/model/siliconCloud.mdx": "2026-03-19T14:09:03+08:00",
"document/content/docs/self-host/config/object-storage.en.mdx": "2026-04-13T17:52:30+08:00", "document/content/docs/self-host/config/object-storage.en.mdx": "2026-04-17T23:28:43+08:00",
"document/content/docs/self-host/config/object-storage.mdx": "2026-04-13T17:52:30+08:00", "document/content/docs/self-host/config/object-storage.mdx": "2026-04-17T23:28:43+08:00",
"document/content/docs/self-host/config/signoz.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/config/signoz.en.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/config/signoz.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/config/signoz.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/custom-models/bge-rerank.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/custom-models/bge-rerank.en.mdx": "2026-03-03T17:39:47+08:00",
...@@ -175,8 +175,8 @@ ...@@ -175,8 +175,8 @@
"document/content/docs/self-host/custom-models/ollama.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/custom-models/ollama.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/custom-models/xinference.en.mdx": "2026-03-30T10:05:42+08:00", "document/content/docs/self-host/custom-models/xinference.en.mdx": "2026-03-30T10:05:42+08:00",
"document/content/docs/self-host/custom-models/xinference.mdx": "2026-03-30T10:05:42+08:00", "document/content/docs/self-host/custom-models/xinference.mdx": "2026-03-30T10:05:42+08:00",
"document/content/docs/self-host/deploy/docker.en.mdx": "2026-04-16T15:22:51+08:00", "document/content/docs/self-host/deploy/docker.en.mdx": "2026-04-17T23:28:43+08:00",
"document/content/docs/self-host/deploy/docker.mdx": "2026-04-16T15:22:51+08:00", "document/content/docs/self-host/deploy/docker.mdx": "2026-04-17T23:28:43+08:00",
"document/content/docs/self-host/deploy/sealos.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/deploy/sealos.en.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/deploy/sealos.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/deploy/sealos.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/design/dataset.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/design/dataset.en.mdx": "2026-03-03T17:39:47+08:00",
...@@ -223,8 +223,8 @@ ...@@ -223,8 +223,8 @@
"document/content/docs/self-host/upgrading/4-14/4141.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/4-14/4141.en.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/4-14/4141.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/4-14/4141.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/4-14/41410.en.mdx": "2026-03-31T23:15:29+08:00", "document/content/docs/self-host/upgrading/4-14/41410.en.mdx": "2026-03-31T23:15:29+08:00",
"document/content/docs/self-host/upgrading/4-14/41410.mdx": "2026-04-08T16:15:25+08:00", "document/content/docs/self-host/upgrading/4-14/41410.mdx": "2026-04-18T19:42:07+08:00",
"document/content/docs/self-host/upgrading/4-14/41411.mdx": "2026-04-17T17:46:20+08:00", "document/content/docs/self-host/upgrading/4-14/41411.mdx": "2026-04-18T19:42:07+08:00",
"document/content/docs/self-host/upgrading/4-14/4142.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/4-14/4142.en.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/4-14/4142.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/4-14/4142.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/4-14/4143.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/4-14/4143.en.mdx": "2026-03-03T17:39:47+08:00",
...@@ -385,8 +385,8 @@ ...@@ -385,8 +385,8 @@
"document/content/docs/self-host/upgrading/outdated/499.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/499.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/upgrade-intruction.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/upgrade-intruction.en.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/upgrade-intruction.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/upgrade-intruction.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/toc.en.mdx": "2026-04-17T15:12:11+08:00", "document/content/docs/toc.en.mdx": "2026-04-17T23:28:43+08:00",
"document/content/docs/toc.mdx": "2026-04-17T15:12:11+08:00", "document/content/docs/toc.mdx": "2026-04-17T23:28:43+08:00",
"document/content/docs/use-cases/app-cases/dalle3.en.mdx": "2026-02-26T22:14:30+08:00", "document/content/docs/use-cases/app-cases/dalle3.en.mdx": "2026-02-26T22:14:30+08:00",
"document/content/docs/use-cases/app-cases/dalle3.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/use-cases/app-cases/dalle3.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/use-cases/app-cases/english_essay_correction_bot.en.mdx": "2026-02-26T22:14:30+08:00", "document/content/docs/use-cases/app-cases/english_essay_correction_bot.en.mdx": "2026-02-26T22:14:30+08:00",
......
...@@ -8,6 +8,7 @@ import { type StoreSecretValueType } from '../../../../common/secret/type'; ...@@ -8,6 +8,7 @@ import { type StoreSecretValueType } from '../../../../common/secret/type';
import { type JsonSchemaPropertiesItemType } from '../../jsonschema'; import { type JsonSchemaPropertiesItemType } from '../../jsonschema';
import { NodeOutputKeyEnum, WorkflowIOValueTypeEnum } from '../../../workflow/constants'; import { NodeOutputKeyEnum, WorkflowIOValueTypeEnum } from '../../../workflow/constants';
import { i18nT } from '../../../../../web/i18n/utils'; import { i18nT } from '../../../../../web/i18n/utils';
import type { NodeToolConfigType } from '../../../workflow/type/node';
export const getHTTPToolSetRuntimeNode = ({ export const getHTTPToolSetRuntimeNode = ({
name, name,
...@@ -89,6 +90,25 @@ export const getHTTPToolRuntimeNode = ({ ...@@ -89,6 +90,25 @@ export const getHTTPToolRuntimeNode = ({
}; };
}; };
export const parseHttpToolConfig = (
config: NonNullable<NodeToolConfigType['httpTool']>
):
| {
toolsetId: string;
toolName: string;
}
| undefined => {
const prefix = `${AppToolSourceEnum.http}-`;
if (!config.toolId.startsWith(prefix)) return undefined;
const [toolsetId, ...rest] = config.toolId.slice(prefix.length).split('/');
const toolName = rest.join('/');
if (!toolsetId || !toolName) return undefined;
return {
toolsetId,
toolName
};
};
export const pathData2ToolList = async ( export const pathData2ToolList = async (
pathData: PathDataType[] pathData: PathDataType[]
): Promise<HttpToolConfigType[]> => { ): Promise<HttpToolConfigType[]> => {
......
...@@ -92,7 +92,8 @@ export const parsetMcpToolConfig = ( ...@@ -92,7 +92,8 @@ export const parsetMcpToolConfig = (
| undefined => { | undefined => {
const prefix = `${AppToolSourceEnum.mcp}-`; const prefix = `${AppToolSourceEnum.mcp}-`;
if (!config.toolId.startsWith(prefix)) return undefined; if (!config.toolId.startsWith(prefix)) return undefined;
const [toolsetId, toolName] = config.toolId.slice(prefix.length).split('/'); const [toolsetId, ...rest] = config.toolId.slice(prefix.length).split('/');
const toolName = rest.join('/');
if (!toolsetId || !toolName) return undefined; if (!toolsetId || !toolName) return undefined;
return { return {
toolsetId, toolsetId,
......
import type { AppSchemaType } from '@fastgpt/global/core/app/type';
import { MongoApp } from '../../schema';
export const getHttpToolsets = ({
teamId,
ids,
field
}: {
teamId: string;
ids: string[];
field?: Record<string, boolean>;
}): Promise<AppSchemaType[]> => {
return MongoApp.find({ teamId, _id: { $in: ids } }, field).lean();
};
import type { AppSchemaType } from '@fastgpt/global/core/app/type';
import { MongoApp } from '../../schema'; import { MongoApp } from '../../schema';
export const getMcpToolsets = ({ export const getMcpToolsets = ({
...@@ -8,6 +9,6 @@ export const getMcpToolsets = ({ ...@@ -8,6 +9,6 @@ export const getMcpToolsets = ({
teamId: string; teamId: string;
ids: string[]; ids: string[];
field?: Record<string, boolean>; field?: Record<string, boolean>;
}) => { }): Promise<AppSchemaType[]> => {
return MongoApp.find({ teamId, _id: { $in: ids } }, field).lean(); return MongoApp.find({ teamId, _id: { $in: ids } }, field).lean();
}; };
...@@ -43,6 +43,8 @@ export const deleteTeamAllDatasets = async (teamId: string) => { ...@@ -43,6 +43,8 @@ export const deleteTeamAllDatasets = async (teamId: string) => {
teamId, teamId,
datasetIds: datasets.map((d) => d._id) datasetIds: datasets.map((d) => d._id)
}); });
await mongoSessionRun(async (session) => {
await MongoDataset.updateMany( await MongoDataset.updateMany(
{ {
teamId teamId
...@@ -51,10 +53,14 @@ export const deleteTeamAllDatasets = async (teamId: string) => { ...@@ -51,10 +53,14 @@ export const deleteTeamAllDatasets = async (teamId: string) => {
$set: { $set: {
deleteTime: new Date() deleteTime: new Date()
} }
},
{
session
} }
); );
await Promise.all( await Promise.all(
datasets.map((dataset) => { datasets.map((dataset) => {
// 有 parentId 的忽略,只需要删 root 下的即可。
if (dataset.parentId) return; if (dataset.parentId) return;
return addDatasetDeleteJob({ return addDatasetDeleteJob({
teamId, teamId,
...@@ -62,6 +68,7 @@ export const deleteTeamAllDatasets = async (teamId: string) => { ...@@ -62,6 +68,7 @@ export const deleteTeamAllDatasets = async (teamId: string) => {
}); });
}) })
); );
});
}; };
// 批量删除函数 // 批量删除函数
......
...@@ -25,7 +25,10 @@ import { ...@@ -25,7 +25,10 @@ import {
import { getNanoid } from '@fastgpt/global/common/string/tools'; import { getNanoid } from '@fastgpt/global/common/string/tools';
import { type SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type'; import { type SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
import { getMCPToolRuntimeNode } from '@fastgpt/global/core/app/tool/mcpTool/utils'; import { getMCPToolRuntimeNode } from '@fastgpt/global/core/app/tool/mcpTool/utils';
import { getHTTPToolRuntimeNode } from '@fastgpt/global/core/app/tool/httpTool/utils'; import {
getHTTPToolRuntimeNode,
parseHttpToolConfig
} from '@fastgpt/global/core/app/tool/httpTool/utils';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { MongoApp } from '../../../core/app/schema'; import { MongoApp } from '../../../core/app/schema';
import { getMCPChildren } from '../../../core/app/mcp'; import { getMCPChildren } from '../../../core/app/mcp';
...@@ -40,6 +43,7 @@ import { presignVariablesFileUrls } from '../../chat/utils'; ...@@ -40,6 +43,7 @@ import { presignVariablesFileUrls } from '../../chat/utils';
import { getSystemTime } from '@fastgpt/global/common/time/timezone'; import { getSystemTime } from '@fastgpt/global/common/time/timezone';
import { parsetMcpToolConfig } from '@fastgpt/global/core/app/tool/mcpTool/utils'; import { parsetMcpToolConfig } from '@fastgpt/global/core/app/tool/mcpTool/utils';
import { getMcpToolsets } from '../../app/tool/mcpTool/entity'; import { getMcpToolsets } from '../../app/tool/mcpTool/entity';
import { getHttpToolsets } from '../../app/tool/httpTool/entity';
import { getHTTPToolList } from '../../app/http'; import { getHTTPToolList } from '../../app/http';
/* get system variable */ /* get system variable */
...@@ -396,7 +400,7 @@ export const rewriteRuntimeWorkFlow = async ({ ...@@ -396,7 +400,7 @@ export const rewriteRuntimeWorkFlow = async ({
}) => { }) => {
/* Toolset 展开 */ /* Toolset 展开 */
// TODO: 待性能优化 // TODO: 待性能优化
{ const parseToolset = async () => {
const toolSetNodes = nodes.filter((node) => node.flowNodeType === FlowNodeTypeEnum.toolSet); const toolSetNodes = nodes.filter((node) => node.flowNodeType === FlowNodeTypeEnum.toolSet);
if (toolSetNodes.length > 0) { if (toolSetNodes.length > 0) {
const nodeIdsToRemove = new Set<string>(); const nodeIdsToRemove = new Set<string>();
...@@ -481,10 +485,10 @@ export const rewriteRuntimeWorkFlow = async ({ ...@@ -481,10 +485,10 @@ export const rewriteRuntimeWorkFlow = async ({
} }
} }
} }
} };
/* MCP tool 获取原始 schema 加入到 jsonschema 字段里 */ /* MCP tool 获取原始 schema 加入到 jsonschema 字段里 */
{ const parseMcpTool = async () => {
const mcpToolNodes = nodes.filter( const mcpToolNodes = nodes.filter(
(node) => node.flowNodeType === FlowNodeTypeEnum.tool && node.toolConfig?.mcpTool (node) => node.flowNodeType === FlowNodeTypeEnum.tool && node.toolConfig?.mcpTool
); );
...@@ -517,7 +521,45 @@ export const rewriteRuntimeWorkFlow = async ({ ...@@ -517,7 +521,45 @@ export const rewriteRuntimeWorkFlow = async ({
node.jsonSchema = toolRaw.inputSchema; node.jsonSchema = toolRaw.inputSchema;
node.intro = toolRaw.description; node.intro = toolRaw.description;
}); });
};
/* Http tool 获取原始 schema 加入到 jsonschema 字段里 */
const parseHttpTool = async () => {
const httpToolNodes = nodes.filter(
(node) => node.flowNodeType === FlowNodeTypeEnum.tool && node.toolConfig?.httpTool
);
const parseHttpToolConfigs = httpToolNodes
.map((node) => parseHttpToolConfig(node.toolConfig?.httpTool!))
.filter(Boolean) as { toolsetId: string; toolName: string }[];
// 批量获取 toolset
const toolsets = await getHttpToolsets({
teamId,
ids: parseHttpToolConfigs.map((config) => config.toolsetId),
field: {
_id: true,
modules: true
} }
});
const toolsetMap = new Map<string, (typeof toolsets)[number]>();
toolsets.forEach((toolset) => {
toolsetMap.set(String(toolset._id), toolset);
});
httpToolNodes.forEach((node) => {
const httpTool = node.toolConfig?.httpTool;
if (!httpTool) return;
const parseResult = parseHttpToolConfig(httpTool);
if (!parseResult) return;
const toolset = toolsetMap.get(parseResult.toolsetId);
const toolList = toolset?.modules?.[0].toolConfig?.httpToolSet?.toolList;
if (!toolList) return;
const toolRaw = toolList.find((tool) => tool.name === parseResult.toolName);
if (!toolRaw) return;
node.jsonSchema = toolRaw.requestSchema;
node.intro = toolRaw.description;
});
};
await Promise.all([parseToolset(), parseMcpTool(), parseHttpTool()]);
}; };
export const getNodeErrResponse = ({ export const getNodeErrResponse = ({
......
...@@ -30,6 +30,10 @@ async function handler(req: ApiRequestProps) { ...@@ -30,6 +30,10 @@ async function handler(req: ApiRequestProps) {
fields: '_id' fields: '_id'
}); });
const datasetIds = deleteDatasets.map((d) => d._id); const datasetIds = deleteDatasets.map((d) => d._id);
await deleteDatasetsImmediate({
teamId,
datasetIds
});
await mongoSessionRun(async (session) => { await mongoSessionRun(async (session) => {
// 1. Mark as deleted // 1. Mark as deleted
...@@ -46,11 +50,6 @@ async function handler(req: ApiRequestProps) { ...@@ -46,11 +50,6 @@ async function handler(req: ApiRequestProps) {
} }
); );
await deleteDatasetsImmediate({
teamId,
datasetIds
});
// 2. Add to delete queue // 2. Add to delete queue
await addDatasetDeleteJob({ await addDatasetDeleteJob({
teamId, teamId,
......
...@@ -2,6 +2,7 @@ import { describe, expect, it, vi } from 'vitest'; ...@@ -2,6 +2,7 @@ import { describe, expect, it, vi } from 'vitest';
import { import {
getHTTPToolSetRuntimeNode, getHTTPToolSetRuntimeNode,
getHTTPToolRuntimeNode, getHTTPToolRuntimeNode,
parseHttpToolConfig,
pathData2ToolList pathData2ToolList
} from '@fastgpt/global/core/app/tool/httpTool/utils'; } from '@fastgpt/global/core/app/tool/httpTool/utils';
import { import {
...@@ -159,6 +160,72 @@ describe('httpTool utils', () => { ...@@ -159,6 +160,72 @@ describe('httpTool utils', () => {
}); });
}); });
describe('parseHttpToolConfig', () => {
it('should parse toolsetId and toolName from a valid toolId', () => {
const result = parseHttpToolConfig({
toolId: 'http-toolset-456/someTool'
});
expect(result).toEqual({ toolsetId: 'toolset-456', toolName: 'someTool' });
});
it('should return undefined when toolId does not match http- prefix pattern', () => {
const result = parseHttpToolConfig({
toolId: 'mcp-foo/bar'
});
expect(result).toBeUndefined();
});
it('should return undefined when toolId has no slash separator', () => {
const result = parseHttpToolConfig({
toolId: 'http-toolset-no-tool'
});
expect(result).toBeUndefined();
});
it('should return undefined when toolsetId segment is empty in toolId', () => {
const result = parseHttpToolConfig({
toolId: 'http-/toolName'
});
expect(result).toBeUndefined();
});
it('should return undefined when toolName segment is empty in toolId', () => {
const result = parseHttpToolConfig({
toolId: 'http-toolset-abc/'
});
expect(result).toBeUndefined();
});
it('should return undefined when toolId is empty string', () => {
const result = parseHttpToolConfig({
toolId: ''
});
expect(result).toBeUndefined();
});
it('should preserve slashes inside tool name', () => {
const result = parseHttpToolConfig({
toolId: 'http-toolset-abc/namespace/nestedTool'
});
expect(result).toEqual({ toolsetId: 'toolset-abc', toolName: 'namespace/nestedTool' });
});
it('should preserve multiple slashes inside tool name', () => {
const result = parseHttpToolConfig({
toolId: 'http-toolset-xyz/a/b/c/d'
});
expect(result).toEqual({ toolsetId: 'toolset-xyz', toolName: 'a/b/c/d' });
});
});
describe('pathData2ToolList', () => { describe('pathData2ToolList', () => {
it('should convert simple path data to tool list', async () => { it('should convert simple path data to tool list', async () => {
const pathData: PathDataType[] = [ const pathData: PathDataType[] = [
......
...@@ -191,12 +191,28 @@ describe('mcpTool utils', () => { ...@@ -191,12 +191,28 @@ describe('mcpTool utils', () => {
expect(result).toBeUndefined(); expect(result).toBeUndefined();
}); });
it('should parse toolsetId correctly when tool name contains additional slashes', () => { it('should preserve slashes inside tool name', () => {
const result = parsetMcpToolConfig({ const result = parsetMcpToolConfig({
toolId: 'mcp-toolset-abc/namespace/nestedTool' toolId: 'mcp-toolset-abc/namespace/nestedTool'
}); });
expect(result).toEqual({ toolsetId: 'toolset-abc', toolName: 'namespace' }); expect(result).toEqual({ toolsetId: 'toolset-abc', toolName: 'namespace/nestedTool' });
});
it('should preserve multiple slashes inside tool name', () => {
const result = parsetMcpToolConfig({
toolId: 'mcp-toolset-xyz/a/b/c/d'
});
expect(result).toEqual({ toolsetId: 'toolset-xyz', toolName: 'a/b/c/d' });
});
it('should return undefined when toolName segment is empty in toolId', () => {
const result = parsetMcpToolConfig({
toolId: 'mcp-toolset-abc/'
});
expect(result).toBeUndefined();
}); });
}); });
}); });
import { describe, it, expect, vi, beforeEach } from 'vitest';
const { mockMongoAppFind } = vi.hoisted(() => ({
mockMongoAppFind: vi.fn()
}));
vi.mock('@fastgpt/service/core/app/schema', () => ({
MongoApp: {
find: mockMongoAppFind
}
}));
import { getHttpToolsets } from '@fastgpt/service/core/app/tool/httpTool/entity';
const setupFindReturn = (result: any) => {
const leanFn = vi.fn().mockResolvedValue(result);
mockMongoAppFind.mockReturnValue({ lean: leanFn });
return { leanFn };
};
beforeEach(() => {
vi.clearAllMocks();
});
describe('getHttpToolsets', () => {
it('should query MongoApp with teamId, ids, and field, and return lean result', async () => {
const docs = [
{ _id: 'id1', modules: [{ toolConfig: { httpToolSet: { toolList: [] } } }] },
{ _id: 'id2', modules: [] }
];
const { leanFn } = setupFindReturn(docs);
const field = { _id: true, modules: true };
const res = await getHttpToolsets({
teamId: 'team1',
ids: ['id1', 'id2'],
field
});
expect(mockMongoAppFind).toHaveBeenCalledTimes(1);
expect(mockMongoAppFind).toHaveBeenCalledWith(
{ teamId: 'team1', _id: { $in: ['id1', 'id2'] } },
field
);
expect(leanFn).toHaveBeenCalledTimes(1);
expect(res).toBe(docs);
});
it('should pass undefined field when not provided', async () => {
setupFindReturn([]);
await getHttpToolsets({ teamId: 'team1', ids: ['id1'] });
expect(mockMongoAppFind).toHaveBeenCalledWith(
{ teamId: 'team1', _id: { $in: ['id1'] } },
undefined
);
});
it('should handle empty ids array', async () => {
const { leanFn } = setupFindReturn([]);
const res = await getHttpToolsets({ teamId: 'team1', ids: [] });
expect(mockMongoAppFind).toHaveBeenCalledWith({ teamId: 'team1', _id: { $in: [] } }, undefined);
expect(leanFn).toHaveBeenCalledTimes(1);
expect(res).toEqual([]);
});
it('should propagate rejection from lean()', async () => {
const leanFn = vi.fn().mockRejectedValue(new Error('db error'));
mockMongoAppFind.mockReturnValue({ lean: leanFn });
await expect(
getHttpToolsets({ teamId: 'team1', ids: ['id1'], field: { _id: true } })
).rejects.toThrow('db error');
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment