mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-13 22:56:28 +00:00
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:
@@ -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. 交互节点与工具集存在冲突,导致交互节点后工具集无法正常使用。
|
||||
|
||||
## 🔨 工具更新
|
||||
|
||||
|
@@ -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",
|
||||
|
@@ -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);
|
||||
};
|
||||
|
@@ -61,7 +61,7 @@ export const getMCPToolRuntimeNode = ({
|
||||
parentId: string;
|
||||
}): RuntimeNodeItemType => {
|
||||
return {
|
||||
nodeId: getNanoid(16),
|
||||
nodeId: getNanoid(),
|
||||
flowNodeType: FlowNodeTypeEnum.tool,
|
||||
avatar,
|
||||
intro: tool.description,
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -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) => {
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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>
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
@@ -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) =>
|
||||
|
Reference in New Issue
Block a user