fix: ai response test (#5544)

* fix: ai response test

* fix: skip edge check

* fix: app list

* fix: toolset conflict interactive node

* fix: username show
This commit is contained in:
Archer
2025-08-27 00:08:22 +08:00
committed by GitHub
parent d2d4c76bd5
commit 324aaae769
15 changed files with 75 additions and 34 deletions

View File

@@ -7,7 +7,7 @@ description: 'FastGPT V4.12.2 更新说明'
### 1. 更新镜像:
- 更新 FastGPT 镜像tag: v4.12.2
- 更新 FastGPT 镜像tag: v4.12.2-fix
- 更新 FastGPT 商业版镜像tag: v4.12.2
- 更新 fastgpt-plugin 镜像 tag: v0.1.11
- mcp_server 无需更新
@@ -42,6 +42,7 @@ description: 'FastGPT V4.12.2 更新说明'
8. 工作流,添加团队应用,搜索无效。
9. 应用版本ref 字段错误,导致无法正常使用。
10. Oceanbase 批量插入时,未正确返回插入的 id。
11. 交互节点与工具集存在冲突,导致交互节点后工具集无法正常使用。
## 🔨 工具更新

View File

@@ -104,7 +104,7 @@
"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-15T22:53:06+08:00",
"document/content/docs/upgrading/4-12/4122.mdx": "2025-08-26T17:29:42+08:00",
"document/content/docs/upgrading/4-12/4122.mdx": "2025-08-26T23:08:11+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

@@ -18,6 +18,11 @@ export const getErrText = (err: any, def = ''): any => {
return ERROR_RESPONSE[msg].message;
}
// Axios special
if (err?.errors && Array.isArray(err.errors) && err.errors.length > 0) {
return err.errors[0].message;
}
// msg && console.log('error =>', msg);
return replaceSensitiveText(msg);
};

View File

@@ -61,7 +61,7 @@ export const getMCPToolRuntimeNode = ({
parentId: string;
}): RuntimeNodeItemType => {
return {
nodeId: getNanoid(16),
nodeId: getNanoid(),
flowNodeType: FlowNodeTypeEnum.tool,
avatar,
intro: tool.description,

View File

@@ -67,8 +67,6 @@ export const createLLMResponse = async <T extends CompletionsBodyType>(
const { body, custonHeaders, userKey } = args;
const { messages, useVision, requestOrigin, tools, toolCallMode } = body;
const modelData = getLLMModel(body.model);
// Messages process
const requestMessages = await loadRequestMessages({
messages,
@@ -475,13 +473,11 @@ type LLMRequestBodyType<T> = Omit<T, 'model' | 'stop' | 'response_format' | 'mes
// Custom field
retainDatasetCite?: boolean;
reasoning?: boolean; // Whether to response reasoning content
toolCallMode?: 'toolChoice' | 'prompt';
useVision?: boolean;
requestOrigin?: string;
};
const llmCompletionsBodyFormat = async <T extends CompletionsBodyType>({
reasoning,
retainDatasetCite,
useVision,
requestOrigin,

View File

@@ -287,7 +287,6 @@ export const runToolCall = async (
body: {
model: toolModel.model,
stream,
reasoning: aiChatReasoning,
messages: filterMessages,
tool_choice: 'auto',
toolCallMode: toolModel.toolChoice ? 'toolChoice' : 'prompt',
@@ -308,6 +307,7 @@ export const runToolCall = async (
isAborted: () => res?.closed,
userKey: externalProvider.openaiAccount,
onReasoning({ text }) {
if (!aiChatReasoning) return;
workflowStreamResponse?.({
write,
event: SseResponseEventEnum.answer,

View File

@@ -184,7 +184,6 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
body: {
model: modelConstantsData.model,
stream,
reasoning: aiChatReasoning,
messages: filterMessages,
temperature,
max_tokens,
@@ -201,6 +200,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
userKey: externalProvider.openaiAccount,
isAborted: () => res?.closed,
onReasoning({ text }) {
if (!aiChatReasoning) return;
workflowStreamResponse?.({
write,
event: SseResponseEventEnum.answer,
@@ -210,6 +210,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
});
},
onStreaming({ text }) {
if (!isResponseAnswerText) return;
workflowStreamResponse?.({
write,
event: SseResponseEventEnum.answer,

View File

@@ -152,11 +152,13 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
方案:
- 采用回调的方式,避免深度递归。
- 使用 activeRunQueue 记录待运行检查的节点(可能可以运行),并控制并发数量。
- 每次添加新节点,以及节点运行结束后,均会执行一次 processNextNode 方法。 processNextNode 方法,如果没触发跳出条件,则必定会取一个 activeRunQueue 继续检查处理。
- 每次添加新节点,以及节点运行结束后,均会执行一次 processActiveNode 方法。 processActiveNode 方法,如果没触发跳出条件,则必定会取一个 activeRunQueue 继续检查处理。
- checkNodeCanRun 会检查该节点状态
- 没满足运行条件:跳出函数
- 运行:执行节点逻辑,并返回结果,将 target node 加入到 activeRunQueue 中,等待队列处理。
- 跳过:执行跳过逻辑,并将其后续的 target node 也进行一次检查。
特殊情况:
- 触发交互节点后,需要跳过所有 skip 节点,避免后续执行了 skipNode。
*/
class WorkflowQueue {
// Workflow variables
@@ -176,6 +178,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
// Queue variables
private activeRunQueue = new Set<string>();
private skipNodeQueue: { node: RuntimeNodeItemType; skippedNodeIdList: Set<string> }[] = [];
private runningNodeCount = 0;
private maxConcurrency: number;
private resolve: (e: WorkflowQueue) => void;
@@ -198,13 +201,17 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
}
this.activeRunQueue.add(nodeId);
this.processNextNode();
this.processActiveNode();
}
// Process next active node
private processNextNode() {
private processActiveNode() {
// Finish
if (this.activeRunQueue.size === 0 && this.runningNodeCount === 0) {
this.resolve(this);
if (this.skipNodeQueue.length > 0 && !this.nodeInteractiveResponse) {
this.processSkipNodes();
} else {
this.resolve(this);
}
return;
}
@@ -224,12 +231,26 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
this.checkNodeCanRun(node).finally(() => {
this.runningNodeCount--;
this.processNextNode();
this.processActiveNode();
});
}
// 兜底,除非极端情况,否则不可能触发
else {
this.processNextNode();
this.processActiveNode();
}
}
private addSkipNode(node: RuntimeNodeItemType, skippedNodeIdList: Set<string>) {
this.skipNodeQueue.push({ node, skippedNodeIdList });
}
private processSkipNodes() {
const skipItem = this.skipNodeQueue.shift();
if (skipItem) {
this.checkNodeCanRun(skipItem.node, skipItem.skippedNodeIdList).finally(() => {
this.processActiveNode();
});
} else {
this.processActiveNode();
}
}
@@ -695,9 +716,9 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
nodeRunResult.result
);
await Promise.all(
nextStepSkipNodes.map((node) => this.checkNodeCanRun(node, skippedNodeIdList))
);
nextStepSkipNodes.forEach((node) => {
this.addSkipNode(node, skippedNodeIdList);
});
// Run next nodes
nextStepActiveNodes.forEach((node) => {

View File

@@ -5,7 +5,6 @@ import axios from 'axios';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { SandboxCodeTypeEnum } from '@fastgpt/global/core/workflow/template/system/sandbox/constants';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { getNodeErrResponse } from '../utils';
type RunCodeType = ModuleDispatchProps<{
[NodeInputKeyEnum.codeType]: string;

View File

@@ -210,17 +210,22 @@ export const rewriteRuntimeWorkFlow = async ({
if (!app) continue;
const toolList = await getMCPChildren(app);
for (const tool of toolList) {
const parentId = mcpToolsetVal.toolId ?? toolSetNode.pluginId;
toolList.forEach((tool, index) => {
const newToolNode = getMCPToolRuntimeNode({
avatar: toolSetNode.avatar,
tool,
// New ?? Old
parentId: mcpToolsetVal.toolId ?? toolSetNode.pluginId
parentId
});
newToolNode.nodeId = `${parentId}${index}`; // ID 不能随机,否则下次生成时候就和之前的记录对不上
nodes.push({ ...newToolNode, name: `${toolSetNode.name}/${tool.name}` });
nodes.push({
...newToolNode,
name: `${toolSetNode.name}/${tool.name}`
});
pushEdges(newToolNode.nodeId);
}
});
}
}

View File

@@ -48,7 +48,7 @@ export async function getSystemToolRunTimeNodeFromSystemToolset({
(item) => item.parentId === systemToolId && item.isActive !== false
);
const nodes = await Promise.all(
children.map(async (child) => {
children.map(async (child, index) => {
const toolListItem = toolSetNode.toolConfig?.systemToolSet?.toolList.find(
(item) => item.toolId === child.id
);
@@ -70,7 +70,7 @@ export async function getSystemToolRunTimeNodeFromSystemToolset({
name: toolListItem?.name || parseI18nString(tool.name, lang),
intro: toolListItem?.description || parseI18nString(tool.intro, lang),
flowNodeType: FlowNodeTypeEnum.tool,
nodeId: getNanoid(),
nodeId: `${toolSetNode.nodeId}${index}`,
toolConfig: {
systemTool: {
toolId: child.id

View File

@@ -362,7 +362,6 @@ const BottomSection = () => {
const { userInfo } = useUserStore();
const isLoggedIn = !!userInfo;
const avatar = userInfo?.avatar;
const username = userInfo?.username;
const isAdmin = !!userInfo?.team.permission.hasManagePer;
const isShare = pathname === '/chat/share';
@@ -448,7 +447,7 @@ const BottomSection = () => {
fontWeight={500}
minW={0}
>
{username}
{userInfo?.team?.memberName}
</AnimatedText>
</Flex>
</UserAvatarPopover>

View File

@@ -106,7 +106,9 @@ async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemTy
// Filter apps by permission, if not owner, only get apps that I have permission to access
const idList = { _id: { $in: myPerList.map((item) => item.resourceId) } };
const appPerQuery = teamPer.isOwner
? {}
? {
parentId: parentId ? parseParentIdInMongo(parentId) : null
}
: parentId
? {
$or: [idList, parseParentIdInMongo(parentId)]
@@ -138,6 +140,7 @@ async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemTy
...searchMatch,
type: _type
};
// @ts-ignore
delete data.parentId;
return data;

View File

@@ -5,9 +5,11 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import type { McpToolConfigType } from '@fastgpt/global/core/app/type';
import { UserError } from '@fastgpt/global/common/error/utils';
import { getMCPChildren } from '@fastgpt/service/core/app/mcp';
import { replaceRegChars } from '@fastgpt/global/common/string/tools';
export type McpGetChildrenmQuery = {
id: string;
searchKey?: string;
};
export type McpGetChildrenmBody = {};
export type McpGetChildrenmResponse = (McpToolConfigType & {
@@ -19,7 +21,7 @@ async function handler(
req: ApiRequestProps<McpGetChildrenmBody, McpGetChildrenmQuery>,
_res: ApiResponseType<any>
): Promise<McpGetChildrenmResponse> {
const { id } = req.query;
const { id, searchKey } = req.query;
const app = await MongoApp.findOne({ _id: id }).lean();
@@ -28,6 +30,12 @@ async function handler(
if (app.type !== AppTypeEnum.toolSet)
return Promise.reject(new UserError('the parent is not a mcp toolset'));
return getMCPChildren(app);
return (await getMCPChildren(app)).filter((item) => {
if (searchKey && searchKey.trim() !== '') {
const regx = new RegExp(replaceRegChars(searchKey.trim()), 'i');
return regx.test(item.name);
}
return true;
});
}
export default NextAPI(handler);

View File

@@ -29,7 +29,10 @@ import type {
getToolVersionListProps,
getToolVersionResponse
} from '@/pages/api/core/app/plugin/getVersionList';
import type { McpGetChildrenmResponse } from '@/pages/api/core/app/mcpTools/getChildren';
import type {
McpGetChildrenmQuery,
McpGetChildrenmResponse
} from '@/pages/api/core/app/mcpTools/getChildren';
/* ============ team plugin ============== */
export const getTeamPlugTemplates = async (data?: {
@@ -40,7 +43,7 @@ export const getTeamPlugTemplates = async (data?: {
// handle get mcptools
const app = await getAppDetailById(data.parentId);
if (app.type === AppTypeEnum.toolSet) {
const children = await getMcpChildren(data.parentId);
const children = await getMcpChildren({ id: data.parentId, searchKey: data.searchKey });
return children.map((item) => ({
...item,
flowNodeType: FlowNodeTypeEnum.tool,
@@ -109,8 +112,8 @@ export const getMCPTools = (data: getMCPToolsBody) =>
export const postRunMCPTool = (data: RunMCPToolBody) =>
POST('/support/mcp/client/runTool', data, { timeout: 300000 });
export const getMcpChildren = (id: string) =>
GET<McpGetChildrenmResponse>('/core/app/mcpTools/getChildren', { id });
export const getMcpChildren = (data: McpGetChildrenmQuery) =>
GET<McpGetChildrenmResponse>('/core/app/mcpTools/getChildren', data);
/* ============ http plugin ============== */
export const postCreateHttpPlugin = (data: createHttpPluginBody) =>