perf: mcp save raw schema (#5030)

* perf: mcp save raw schema

* fix: test

* code

* perf: json schema test

* perf: mcp
This commit is contained in:
Archer
2025-06-13 18:46:55 +08:00
committed by GitHub
parent 0914eacb5e
commit 9d6a48a62f
35 changed files with 424 additions and 415 deletions

View File

@@ -10,7 +10,11 @@ import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/c
import { createChatCompletion } from '../../../ai/config';
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/workflow/template/system/contextExtract/type';
import type { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { NodeOutputKeyEnum, toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
import {
NodeOutputKeyEnum,
toolValueTypeList,
valueTypeJsonSchemaMap
} from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { sliceJsonStr } from '@fastgpt/global/common/string/tools';
@@ -164,9 +168,10 @@ const getJsonSchema = ({ params: { extractKeys } }: ActionProps) => {
}
> = {};
extractKeys.forEach((item) => {
const jsonSchema = (
toolValueTypeList.find((type) => type.value === item.valueType) || toolValueTypeList[0]
)?.jsonSchema;
const jsonSchema = item.valueType
? valueTypeJsonSchemaMap[item.valueType] || toolValueTypeList[0].jsonSchema
: toolValueTypeList[0].jsonSchema;
properties[item.key] = {
...jsonSchema,
description: item.desc,

View File

@@ -37,7 +37,7 @@ import {
removeDatasetCiteText,
parseLLMStreamResponse
} from '../../../../ai/utils';
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
import { toolValueTypeList, valueTypeJsonSchemaMap } from '@fastgpt/global/core/workflow/constants';
import { type WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
@@ -162,6 +162,14 @@ export const runToolWithFunctionCall = async (
const assistantResponses = response?.assistantResponses || [];
const functions: ChatCompletionCreateParams.Function[] = toolNodes.map((item) => {
if (item.jsonSchema) {
return {
name: item.nodeId,
description: item.intro,
parameters: item.jsonSchema
};
}
const properties: Record<
string,
{
@@ -172,9 +180,9 @@ export const runToolWithFunctionCall = async (
}
> = {};
item.toolParams.forEach((item) => {
const jsonSchema = (
toolValueTypeList.find((type) => type.value === item.valueType) || toolValueTypeList[0]
).jsonSchema;
const jsonSchema = item.valueType
? valueTypeJsonSchemaMap[item.valueType] || toolValueTypeList[0].jsonSchema
: toolValueTypeList[0].jsonSchema;
properties[item.key] = {
...jsonSchema,

View File

@@ -1,4 +1,4 @@
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import type {
ChatDispatchProps,
@@ -22,7 +22,7 @@ import { formatModelChars2Points } from '../../../../../support/wallet/usage/uti
import { getHistoryPreview } from '@fastgpt/global/core/chat/utils';
import { runToolWithFunctionCall } from './functionCall';
import { runToolWithPromptCall } from './promptCall';
import { getNanoid, replaceVariable } from '@fastgpt/global/common/string/tools';
import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { getMultiplePrompt, Prompt_Tool_Call } from './constants';
import { filterToolResponseToPreview } from './utils';
import { type InteractiveNodeResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
@@ -32,6 +32,9 @@ import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { ModelTypeEnum } from '@fastgpt/global/core/ai/model';
import { getDocumentQuotePrompt } from '@fastgpt/global/core/ai/prompt/AIChat';
import { postTextCensor } from '../../../../chat/postTextCensor';
import type { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
import type { McpToolDataType } from '@fastgpt/global/core/app/mcpTools/type';
import type { JSONSchemaInputType } from '@fastgpt/global/core/app/jsonschema';
type Response = DispatchNodeResultType<{
[NodeOutputKeyEnum.answerText]: string;
@@ -78,10 +81,24 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
})
.filter(Boolean)
.map<ToolNodeItemType>((tool) => {
const toolParams = tool?.inputs.filter((input) => !!input.toolDescription) || [];
const toolParams: FlowNodeInputItemType[] = [];
// Raw json schema(MCP tool)
let jsonSchema: JSONSchemaInputType | undefined = undefined;
tool?.inputs.forEach((input) => {
if (input.toolDescription) {
toolParams.push(input);
}
if (input.key === NodeInputKeyEnum.toolData || input.key === 'toolData') {
const value = input.value as McpToolDataType;
jsonSchema = value.inputSchema;
}
});
return {
...(tool as RuntimeNodeItemType),
toolParams
toolParams,
jsonSchema
};
});
@@ -172,28 +189,26 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
reserveId: false
// reserveTool: !!toolModel.toolChoice
});
const requestParams = {
runtimeNodes,
runtimeEdges,
toolNodes,
toolModel,
messages: adaptMessages,
interactiveEntryToolParams: lastInteractive?.toolParams
};
if (toolModel.toolChoice) {
return runToolWithToolChoice({
...props,
runtimeNodes,
runtimeEdges,
toolNodes,
toolModel,
maxRunToolTimes: 30,
messages: adaptMessages,
interactiveEntryToolParams: lastInteractive?.toolParams
...requestParams,
maxRunToolTimes: 30
});
}
if (toolModel.functionCall) {
return runToolWithFunctionCall({
...props,
runtimeNodes,
runtimeEdges,
toolNodes,
toolModel,
messages: adaptMessages,
interactiveEntryToolParams: lastInteractive?.toolParams
...requestParams
});
}
@@ -218,12 +233,7 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
return runToolWithPromptCall({
...props,
runtimeNodes,
runtimeEdges,
toolNodes,
toolModel,
messages: adaptMessages,
interactiveEntryToolParams: lastInteractive?.toolParams
...requestParams
});
})();

View File

@@ -38,7 +38,7 @@ import {
parseLLMStreamResponse
} from '../../../../ai/utils';
import { type WorkflowResponseType } from '../../type';
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
import { toolValueTypeList, valueTypeJsonSchemaMap } from '@fastgpt/global/core/workflow/constants';
import { type WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
@@ -166,6 +166,14 @@ export const runToolWithPromptCall = async (
const toolsPrompt = JSON.stringify(
toolNodes.map((item) => {
if (item.jsonSchema) {
return {
toolId: item.nodeId,
description: item.intro,
parameters: item.jsonSchema
};
}
const properties: Record<
string,
{
@@ -176,9 +184,9 @@ export const runToolWithPromptCall = async (
}
> = {};
item.toolParams.forEach((item) => {
const jsonSchema = (
toolValueTypeList.find((type) => type.value === item.valueType) || toolValueTypeList[0]
).jsonSchema;
const jsonSchema = item.valueType
? valueTypeJsonSchemaMap[item.valueType] || toolValueTypeList[0].jsonSchema
: toolValueTypeList[0].jsonSchema;
properties[item.key] = {
...jsonSchema,

View File

@@ -33,7 +33,7 @@ import {
parseLLMStreamResponse
} from '../../../../ai/utils';
import { getNanoid, sliceStrStartEnd } from '@fastgpt/global/common/string/tools';
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
import { toolValueTypeList, valueTypeJsonSchemaMap } from '@fastgpt/global/core/workflow/constants';
import { type WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
import { getErrText } from '@fastgpt/global/common/error/utils';
@@ -211,6 +211,17 @@ export const runToolWithToolChoice = async (
const assistantResponses = response?.assistantResponses || [];
const tools: ChatCompletionTool[] = toolNodes.map((item) => {
if (item.jsonSchema) {
return {
type: 'function',
function: {
name: item.nodeId,
description: item.intro || item.name,
parameters: item.jsonSchema
}
};
}
const properties: Record<
string,
{
@@ -224,9 +235,10 @@ export const runToolWithToolChoice = async (
}
> = {};
item.toolParams.forEach((item) => {
const jsonSchema = (
toolValueTypeList.find((type) => type.value === item.valueType) || toolValueTypeList[0]
)?.jsonSchema;
const jsonSchema = item.valueType
? valueTypeJsonSchemaMap[item.valueType] || toolValueTypeList[0].jsonSchema
: toolValueTypeList[0].jsonSchema;
properties[item.key] = {
...jsonSchema,
description: item.toolDescription || '',

View File

@@ -16,6 +16,7 @@ import { ChatItemValueItemType } from '@fastgpt/global/core/chat/type';
import type { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import type { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model';
import type { JSONSchemaInputType } from '@fastgpt/global/core/app/jsonschema';
export type DispatchToolModuleProps = ModuleDispatchProps<{
[NodeInputKeyEnum.history]?: ChatItemType[];
@@ -51,4 +52,5 @@ export type RunToolResponse = {
};
export type ToolNodeItemType = RuntimeNodeItemType & {
toolParams: RuntimeNodeItemType['inputs'];
jsonSchema?: JSONSchemaInputType;
};

View File

@@ -3,18 +3,16 @@ import {
type ModuleDispatchProps
} from '@fastgpt/global/core/workflow/runtime/type';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import type { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { MCPClient } from '../../../app/mcp';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { type StoreSecretValueType } from '@fastgpt/global/common/secret/type';
import { getSecretValue } from '../../../../common/secret/utils';
import type { McpToolDataType } from '@fastgpt/global/core/app/mcpTools/type';
type RunToolProps = ModuleDispatchProps<{
toolData: {
name: string;
url: string;
headerSecret: StoreSecretValueType;
};
toolData?: McpToolDataType;
[NodeInputKeyEnum.toolData]: McpToolDataType;
}>;
type RunToolResponse = DispatchNodeResultType<{
@@ -27,13 +25,13 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
node: { avatar }
} = props;
const { toolData, ...restParams } = params;
const { name: toolName, url } = toolData;
const { toolData, system_toolData, ...restParams } = params;
const { name: toolName, url, headerSecret } = toolData || system_toolData;
const mcpClient = new MCPClient({
url,
headers: getSecretValue({
storeSecret: toolData.headerSecret
storeSecret: headerSecret
})
});

View File

@@ -14,6 +14,7 @@ import { getNanoid } from '@fastgpt/global/common/string/tools';
import { type SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
import { getMCPToolRuntimeNode } from '@fastgpt/global/core/app/mcpTools/utils';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import type { McpToolSetDataType } from '@fastgpt/global/core/app/mcpTools/type';
export const getWorkflowResponseWrite = ({
res,
@@ -161,11 +162,13 @@ export const rewriteRuntimeWorkFlow = (
for (const toolSetNode of toolSetNodes) {
nodeIdsToRemove.add(toolSetNode.nodeId);
const toolList =
toolSetNode.inputs.find((input) => input.key === 'toolSetData')?.value?.toolList || [];
const url = toolSetNode.inputs.find((input) => input.key === 'toolSetData')?.value?.url;
const headerSecret = toolSetNode.inputs.find((input) => input.key === 'toolSetData')?.value
?.headerSecret;
const toolSetValue = toolSetNode.inputs[0]?.value as McpToolSetDataType | undefined;
if (!toolSetValue) continue;
const toolList = toolSetValue.toolList;
const url = toolSetValue.url;
const headerSecret = toolSetValue.headerSecret;
const incomingEdges = edges.filter((edge) => edge.target === toolSetNode.nodeId);