feat: add VSCode launch configuration and enhance debug API handler
This commit is contained in:
39
.vscode/launch.json
vendored
Normal file
39
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Next.js: debug server-side",
|
||||||
|
"type": "node-terminal",
|
||||||
|
"request": "launch",
|
||||||
|
"command": "pnpm run dev",
|
||||||
|
"cwd": "${workspaceFolder}/projects/app"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Next.js: debug client-side",
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"url": "http://localhost:3000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Next.js: debug client-side (Edge)",
|
||||||
|
"type": "msedge",
|
||||||
|
"request": "launch",
|
||||||
|
"url": "http://localhost:3000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Next.js: debug full stack",
|
||||||
|
"type": "node-terminal",
|
||||||
|
"request": "launch",
|
||||||
|
"command": "pnpm run dev",
|
||||||
|
"cwd": "${workspaceFolder}/projects/app",
|
||||||
|
"skipFiles": ["<node_internals>/**"],
|
||||||
|
"serverReadyAction": {
|
||||||
|
"action": "debugWithEdge",
|
||||||
|
"killOnServerStop": true,
|
||||||
|
"pattern": "- Local:.+(https?://.+)",
|
||||||
|
"uriFormat": "%s",
|
||||||
|
"webRoot": "${workspaceFolder}/projects/app"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -10,13 +10,50 @@ import { NextAPI } from '@/service/middleware/entry';
|
|||||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||||
import { defaultApp } from '@/web/core/app/constants';
|
import { defaultApp } from '@/web/core/app/constants';
|
||||||
import { WORKFLOW_MAX_RUN_TIMES } from '@fastgpt/service/core/workflow/constants';
|
import { WORKFLOW_MAX_RUN_TIMES } from '@fastgpt/service/core/workflow/constants';
|
||||||
|
import { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type';
|
||||||
|
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
|
||||||
|
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
|
||||||
|
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||||
|
import {
|
||||||
|
getMaxHistoryLimitFromNodes,
|
||||||
|
getWorkflowEntryNodeIds,
|
||||||
|
initWorkflowEdgeStatus,
|
||||||
|
rewriteNodeOutputByHistories,
|
||||||
|
storeNodes2RuntimeNodes,
|
||||||
|
textAdaptGptResponse
|
||||||
|
} from '@fastgpt/global/core/workflow/runtime/utils';
|
||||||
|
import { responseWrite } from '@fastgpt/service/common/response';
|
||||||
|
import { getWorkflowResponseWrite } from '@fastgpt/service/core/workflow/dispatch/utils';
|
||||||
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
|
import { getPluginInputsFromStoreNodes } from '@fastgpt/global/core/app/plugin/utils';
|
||||||
|
import { UserChatItemType, UserChatItemValueItemType } from '@fastgpt/global/core/chat/type';
|
||||||
|
import {
|
||||||
|
getPluginRunUserQuery,
|
||||||
|
updatePluginInputByVariables
|
||||||
|
} from '@fastgpt/global/core/workflow/utils';
|
||||||
|
import { concatHistories, removeEmptyUserInput } from '@fastgpt/global/core/chat/utils';
|
||||||
|
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
|
||||||
|
import { getChatItems } from '@fastgpt/service/core/chat/controller';
|
||||||
|
|
||||||
async function handler(
|
async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse
|
res: NextApiResponse
|
||||||
): Promise<PostWorkflowDebugResponse> {
|
): Promise<PostWorkflowDebugResponse> {
|
||||||
const { nodes = [], edges = [], variables = {}, appId } = req.body as PostWorkflowDebugProps;
|
const {
|
||||||
|
nodes = [],
|
||||||
|
edges = [],
|
||||||
|
appId,
|
||||||
|
messages = [],
|
||||||
|
chatId = '',
|
||||||
|
chatConfig = defaultApp.chatConfig,
|
||||||
|
responseChatItemId = '' // 在内联类型中添加
|
||||||
|
} = req.body as PostWorkflowDebugProps & {
|
||||||
|
messages?: ChatCompletionMessageParam[];
|
||||||
|
chatId?: string;
|
||||||
|
responseChatItemId?: string; // 在内联类型中添加
|
||||||
|
chatConfig?: AppChatConfigType; // 在内联类型中添加
|
||||||
|
};
|
||||||
|
let variables = req.body.variables || {};
|
||||||
if (!nodes) {
|
if (!nodes) {
|
||||||
throw new Error('Prams Error');
|
throw new Error('Prams Error');
|
||||||
}
|
}
|
||||||
@@ -27,6 +64,25 @@ async function handler(
|
|||||||
throw new Error('Edges is not array');
|
throw new Error('Edges is not array');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const entryNodeIds = nodes
|
||||||
|
.filter((node) => node.isEntry)
|
||||||
|
.map((node) => node.nodeId)
|
||||||
|
.filter((nodeId) => !!nodeId);
|
||||||
|
if (entryNodeIds.length === 0) {
|
||||||
|
throw new Error('No entry node found');
|
||||||
|
}
|
||||||
|
if (entryNodeIds.length > 1) {
|
||||||
|
throw new Error('More than one entry node found');
|
||||||
|
}
|
||||||
|
const isInteractiveNode = nodes.some(
|
||||||
|
(node) =>
|
||||||
|
node.nodeId === entryNodeIds[0] &&
|
||||||
|
(node.flowNodeType === 'userSelect' || node.flowNodeType === 'formInput')
|
||||||
|
);
|
||||||
|
console.log('isInteractiveNode', isInteractiveNode);
|
||||||
|
|
||||||
|
const chatMessages = GPTMessages2Chats(messages);
|
||||||
|
//
|
||||||
/* user auth */
|
/* user auth */
|
||||||
const [{ teamId, tmbId }, { app }] = await Promise.all([
|
const [{ teamId, tmbId }, { app }] = await Promise.all([
|
||||||
authCert({
|
authCert({
|
||||||
@@ -35,15 +91,79 @@ async function handler(
|
|||||||
}),
|
}),
|
||||||
authApp({ req, authToken: true, appId, per: ReadPermissionVal })
|
authApp({ req, authToken: true, appId, per: ReadPermissionVal })
|
||||||
]);
|
]);
|
||||||
|
const appName = `${app.name}-Debug`;
|
||||||
|
|
||||||
// auth balance
|
// auth balance
|
||||||
const { timezone, externalProvider } = await getUserChatInfoAndAuthTeamPoints(tmbId);
|
const isPlugin = app.type === AppTypeEnum.plugin;
|
||||||
|
|
||||||
|
let userQuestion: UserChatItemType | undefined;
|
||||||
|
if (isInteractiveNode) {
|
||||||
|
userQuestion = (() => {
|
||||||
|
if (isPlugin) {
|
||||||
|
return getPluginRunUserQuery({
|
||||||
|
pluginInputs: getPluginInputsFromStoreNodes(app.modules),
|
||||||
|
variables,
|
||||||
|
files: variables.files
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const latestHumanChat = chatMessages.pop() as UserChatItemType | undefined;
|
||||||
|
if (!latestHumanChat) {
|
||||||
|
throw new Error('User question is empty');
|
||||||
|
}
|
||||||
|
return latestHumanChat;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
const limit = getMaxHistoryLimitFromNodes(nodes);
|
||||||
|
const [{ histories }, chatDetail, { timezone, externalProvider }] = await Promise.all([
|
||||||
|
getChatItems({
|
||||||
|
appId,
|
||||||
|
chatId,
|
||||||
|
offset: 0,
|
||||||
|
limit,
|
||||||
|
field: `dataId obj value nodeOutputs`
|
||||||
|
}),
|
||||||
|
MongoChat.findOne({ appId: app._id, chatId }, 'source variableList variables'),
|
||||||
|
// auth balance
|
||||||
|
getUserChatInfoAndAuthTeamPoints(tmbId)
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (chatDetail?.variables) {
|
||||||
|
variables = {
|
||||||
|
...chatDetail.variables,
|
||||||
|
...variables
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const newHistories = concatHistories(histories, chatMessages);
|
||||||
|
|
||||||
|
// Get runtimeNodes
|
||||||
|
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, newHistories));
|
||||||
|
if (isPlugin) {
|
||||||
|
runtimeNodes = updatePluginInputByVariables(runtimeNodes, variables);
|
||||||
|
variables = {};
|
||||||
|
}
|
||||||
|
runtimeNodes = rewriteNodeOutputByHistories(newHistories, runtimeNodes);
|
||||||
|
let runtimeEdges = edges;
|
||||||
|
let query: UserChatItemValueItemType[] = [];
|
||||||
|
if (isInteractiveNode && userQuestion) {
|
||||||
|
runtimeEdges = initWorkflowEdgeStatus(edges, newHistories);
|
||||||
|
query = removeEmptyUserInput(userQuestion.value);
|
||||||
|
} else if (!isInteractiveNode) {
|
||||||
|
runtimeEdges = edges;
|
||||||
|
runtimeNodes = nodes;
|
||||||
|
query = [];
|
||||||
|
}
|
||||||
/* start process */
|
/* start process */
|
||||||
const { flowUsages, flowResponses, debugResponse, newVariables } = await dispatchWorkFlow({
|
const { flowUsages, flowResponses, debugResponse, newVariables } = await dispatchWorkFlow({
|
||||||
res,
|
res,
|
||||||
requestOrigin: req.headers.origin,
|
requestOrigin: req.headers.origin,
|
||||||
mode: 'debug',
|
mode: 'debug',
|
||||||
|
timezone,
|
||||||
|
externalProvider,
|
||||||
|
uid: tmbId,
|
||||||
|
|
||||||
runningAppInfo: {
|
runningAppInfo: {
|
||||||
id: app._id,
|
id: app._id,
|
||||||
teamId: app.teamId,
|
teamId: app.teamId,
|
||||||
@@ -53,21 +173,21 @@ async function handler(
|
|||||||
teamId,
|
teamId,
|
||||||
tmbId
|
tmbId
|
||||||
},
|
},
|
||||||
uid: tmbId,
|
|
||||||
timezone,
|
chatId,
|
||||||
externalProvider,
|
responseChatItemId,
|
||||||
runtimeNodes: nodes,
|
runtimeNodes,
|
||||||
runtimeEdges: edges,
|
runtimeEdges,
|
||||||
variables,
|
variables,
|
||||||
query: [],
|
query,
|
||||||
chatConfig: defaultApp.chatConfig,
|
chatConfig: defaultApp.chatConfig,
|
||||||
histories: [],
|
histories: newHistories,
|
||||||
stream: false,
|
stream: false,
|
||||||
maxRunTimes: WORKFLOW_MAX_RUN_TIMES
|
maxRunTimes: WORKFLOW_MAX_RUN_TIMES
|
||||||
});
|
});
|
||||||
|
|
||||||
createChatUsage({
|
createChatUsage({
|
||||||
appName: `${app.name}-Debug`,
|
appName,
|
||||||
appId,
|
appId,
|
||||||
teamId,
|
teamId,
|
||||||
tmbId,
|
tmbId,
|
||||||
|
|||||||
Reference in New Issue
Block a user