Commit d9b09684 by Jon Committed by GitHub

feat: Add new PI agent features and update dependencies (#6762)

parent a50a9b92
......@@ -45,6 +45,7 @@ import type { PlanAgentParamsType } from './sub/plan/constants';
import type { AppFormEditFormType } from '@fastgpt/global/core/app/formEdit/type';
import { getLogger, LogCategories } from '../../../../../common/logger';
import { env } from '../../../../../env';
import { dispatchPiAgent } from './piAgent';
export type DispatchAgentModuleProps = ModuleDispatchProps<{
[NodeInputKeyEnum.history]?: ChatItemMiniType[];
......@@ -77,6 +78,11 @@ type Response = DispatchNodeResultType<{
*/
export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise<Response> => {
// pi-agent-core engine: bypass Plan+Step orchestration
if (env.AGENT_ENGINE === 'pi') {
return dispatchPiAgent(props);
}
const MAX_PLAN_ITERATIONS = 10; // 最大规划轮次
let {
......
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import {
DispatchNodeResponseKeyEnum,
SseResponseEventEnum
} from '@fastgpt/global/core/workflow/runtime/constants';
import type {
AIChatItemValueItemType,
ChatHistoryItemResType
} from '@fastgpt/global/core/chat/type';
import type { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { getHistories, getNodeErrResponse } from '../../../utils';
import { parseUserSystemPrompt } from '../sub/plan/prompt';
import { formatFileInput } from '../sub/file/utils';
import { normalizeSkillIds } from '@fastgpt/global/core/app/formEdit/type';
import { systemSubInfo } from '@fastgpt/global/core/workflow/node/agent/constants';
import { parseI18nString } from '@fastgpt/global/common/i18n/utils';
import { getSubapps } from '../utils';
import { createCapabilityToolCallHandler, type AgentCapability } from '../capability/type';
import { createSandboxSkillsCapability } from '../capability/sandboxSkills';
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
import { buildPiModel, getModelApiKey } from './modelBridge';
import { buildAgentTools, type ToolDispatchContext } from './toolAdapter';
import { getLogger, LogCategories } from '../../../../../../common/logger';
import { env } from '../../../../../../env';
import type { DispatchAgentModuleProps } from '..';
type Response = DispatchNodeResultType<{
[NodeOutputKeyEnum.answerText]: string;
}>;
export const dispatchPiAgent = async (props: DispatchAgentModuleProps): Promise<Response> => {
const {
checkIsStopping,
node: { nodeId, inputs },
lang,
histories,
query: _query,
requestOrigin,
chatConfig,
runningAppInfo,
workflowStreamResponse,
usagePush,
mode,
chatId,
showSkillReferences,
params: {
model,
systemPrompt,
userChatInput,
history = 6,
fileUrlList: fileLinksInput,
agent_selectedTools: selectedTools = [],
skills: skillIds = [],
useEditDebugSandbox,
agent_datasetParams: datasetParams,
useAgentSandbox = false,
aiChatVision
}
} = props;
const chatHistories = getHistories(history, histories);
const normalizedSkillIds = normalizeSkillIds(skillIds);
const assistantResponses: AIChatItemValueItemType[] = [];
const nodeResponses: ChatHistoryItemResType[] = [];
const capabilities: AgentCapability[] = [];
try {
// Get files — check whether fileUrlList input has actual values
const fileUrlInput = inputs.find((item) => item.key === NodeInputKeyEnum.fileUrlList);
const fileLinks =
fileUrlInput && fileUrlInput.value && fileUrlInput.value.length > 0
? fileLinksInput
: undefined;
const {
filesMap,
allFilesMap,
prompt: fileInputPrompt
} = formatFileInput({
fileUrls: fileLinks,
requestOrigin,
maxFiles: chatConfig?.fileSelectConfig?.maxFiles || 20,
histories: chatHistories,
useSkill: skillIds.length > 0
});
const formatUserChatInput = fileInputPrompt
? `${fileInputPrompt}\n\n${userChatInput}`
: userChatInput;
// Initialize capabilities — sandbox skills (lazy-init, gated by SHOW_SKILL)
if (env.SHOW_SKILL) {
const sandboxSessionId = mode === 'chat' ? chatId : `debug-${runningAppInfo.id}-${nodeId}`;
const sandboxMode = useEditDebugSandbox ? 'editDebug' : 'sessionRuntime';
const sandboxCap = await createSandboxSkillsCapability({
skillIds: normalizedSkillIds,
teamId: runningAppInfo.teamId,
tmbId: runningAppInfo.tmbId,
sessionId: sandboxSessionId,
mode: sandboxMode,
workflowStreamResponse,
showSkillReferences: showSkillReferences === true,
allFilesMap
});
capabilities.push(sandboxCap);
}
// Aggregate capability contributions
const capabilitySystemPrompt = capabilities
.map((c) => c.systemPrompt)
.filter(Boolean)
.join('\n\n');
const capabilityTools = capabilities.flatMap((c) => c.completionTools ?? []);
const capabilityToolCallHandler =
capabilities.length > 0 ? createCapabilityToolCallHandler(capabilities) : undefined;
// Get sub apps — pi-agent-core manages reasoning, no plan tool needed
const { completionTools: agentCompletionTools, subAppsMap: agentSubAppsMap } = await getSubapps(
{
tools: selectedTools,
tmbId: runningAppInfo.tmbId,
lang,
getPlanTool: false,
hasDataset: datasetParams && datasetParams.datasets.length > 0,
hasFiles: !!chatConfig?.fileSelectConfig?.canSelectFile,
useAgentSandbox: useAgentSandbox && !!global.feConfigs?.show_agent_sandbox,
extraTools: capabilityTools
}
);
const getSubAppInfo = (id: string) => {
const formatId = id.startsWith('t') ? id.slice(1) : id;
const userToolNode = agentSubAppsMap.get(id) || agentSubAppsMap.get(formatId);
if (userToolNode) {
return {
name: userToolNode.name || '',
avatar: userToolNode.avatar || '',
toolDescription: userToolNode.toolDescription || userToolNode.name || ''
};
}
const systemToolNode = systemSubInfo[id] || systemSubInfo[formatId];
const systemDisplayName = parseI18nString(systemToolNode?.name, lang);
return {
name: systemDisplayName || '',
avatar: systemToolNode?.avatar || '',
toolDescription: systemToolNode?.toolDescription || systemDisplayName || ''
};
};
const getSubApp = (id: string) => {
const formatId = id.slice(1);
return agentSubAppsMap.get(id) || agentSubAppsMap.get(formatId);
};
const formatedSystemPrompt = parseUserSystemPrompt({
userSystemPrompt: capabilitySystemPrompt
? `${systemPrompt || ''}\n\n${capabilitySystemPrompt}`.trim()
: systemPrompt,
selectedDataset: datasetParams?.datasets
});
/* ===== Build pi-agent-core model & tools ===== */
const piModel = buildPiModel(model, aiChatVision);
const apiKey = getModelApiKey(model);
const toolCtx: ToolDispatchContext = {
checkIsStopping,
chatConfig,
runningUserInfo: props.runningUserInfo,
runningAppInfo,
chatId,
uid: props.uid,
variables: props.variables,
externalProvider: props.externalProvider,
workflowStreamResponse,
lang,
requestOrigin,
mode,
timezone: props.timezone,
retainDatasetCite: props.retainDatasetCite,
maxRunTimes: props.maxRunTimes,
workflowDispatchDeep: props.workflowDispatchDeep,
usagePush,
model,
datasetParams
};
const piTools = await buildAgentTools({
completionTools: agentCompletionTools,
ctx: toolCtx,
filesMap,
getSubApp,
getSubAppInfo,
capabilityToolCallHandler,
nodeResponses
});
/* ===== Restore session messages from last AI history ===== */
const piMessagesKey = `piMessages-${nodeId}`;
const lastHistory = chatHistories[chatHistories.length - 1];
const restoredMessages =
lastHistory?.obj === ChatRoleEnum.AI
? (lastHistory.memories?.[piMessagesKey] as any[] | undefined) ?? []
: [];
/* ===== Create & run Agent ===== */
const { Agent } = await import('@mariozechner/pi-agent-core');
type AgentEvent = import('@mariozechner/pi-agent-core').AgentEvent;
const agent = new Agent({
initialState: {
systemPrompt: formatedSystemPrompt,
model: piModel,
tools: piTools,
messages: restoredMessages
},
getApiKey: () => apiKey
});
// Collect text deltas to build answerText
let answerText = '';
agent.subscribe((event: AgentEvent) => {
if (event.type === 'message_update') {
const e = event.assistantMessageEvent;
if (e.type === 'text_delta') {
answerText += e.delta;
workflowStreamResponse?.({
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({ text: e.delta })
});
}
} else if (event.type === 'turn_end') {
const errMsg = (event.message as any).errorMessage as string | undefined;
if (errMsg) {
getLogger(LogCategories.MODULE.AI.AGENT).error(`[piAgent] Turn error: ${errMsg}`);
}
}
// SSE toolCall / toolResponse events are emitted inside each tool's execute()
// wrapper in toolAdapter.ts
});
// Poll for user-initiated stop
const stopPoller = setInterval(() => {
if (checkIsStopping()) {
agent.abort();
clearInterval(stopPoller);
}
}, 200);
getLogger(LogCategories.MODULE.AI.AGENT).debug(`[piAgent] Starting agent prompt`);
await agent.prompt(formatUserChatInput);
clearInterval(stopPoller);
getLogger(LogCategories.MODULE.AI.AGENT).debug(`[piAgent] Agent completed`);
// Surface API errors that pi-agent-core stores instead of throwing
if (agent.state.errorMessage) {
throw new Error(agent.state.errorMessage);
}
// Build assistant responses
if (answerText) {
assistantResponses.push({ text: { content: answerText } });
}
return {
data: {
[NodeOutputKeyEnum.answerText]: answerText
},
[DispatchNodeResponseKeyEnum.memories]: {
[piMessagesKey]: agent.state.messages
},
[DispatchNodeResponseKeyEnum.assistantResponses]: assistantResponses,
[DispatchNodeResponseKeyEnum.nodeResponses]: nodeResponses
};
} catch (error) {
getLogger(LogCategories.MODULE.AI.AGENT).error(`[piAgent] dispatchPiAgent error`, { error });
return getNodeErrResponse({ error });
} finally {
for (const cap of capabilities) {
await cap.dispose?.();
}
}
};
import { getLLMModel } from '../../../../../ai/model';
type Model = import('@mariozechner/pi-ai').Model<'openai-completions'>;
const aiProxyBaseUrl = process.env.AIPROXY_API_ENDPOINT
? `${process.env.AIPROXY_API_ENDPOINT}/v1`
: undefined;
const defaultBaseUrl = aiProxyBaseUrl || process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1';
const defaultApiKey = process.env.AIPROXY_API_TOKEN || process.env.CHAT_API_KEY || '';
export function buildPiModel(modelNameOrId?: string, useVision?: boolean): Model {
const cfg = getLLMModel(modelNameOrId);
// requestUrl is the full endpoint (e.g. https://api.deepseek.com/chat/completions).
// pi-ai's openai-completions provider appends /chat/completions automatically,
// so we strip it to get baseUrl.
const rawUrl = cfg?.requestUrl ?? '';
const baseUrl = rawUrl ? rawUrl.replace(/\/chat\/completions$/, '') : defaultBaseUrl;
const apiKey = cfg?.requestAuth || defaultApiKey;
return {
id: cfg?.model ?? 'gpt-4o',
name: cfg?.name ?? cfg?.model ?? 'gpt-4o',
api: 'openai-completions',
provider: 'openai',
baseUrl,
reasoning: cfg?.reasoning ?? false,
input: useVision ? ['text', 'image'] : ['text'],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: cfg?.maxContext ?? 128000,
maxTokens: Math.min(cfg?.maxResponse ?? 4096, (cfg?.maxContext ?? 128000) - 2048),
headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined,
// Most non-OpenAI endpoints don't support the "developer" role or "store" field.
// Use "max_tokens" instead of OpenAI-specific "max_completion_tokens" for wider
// compatibility with vLLM and other OpenAI-compatible servers.
compat: {
supportsDeveloperRole: false,
supportsStore: false,
maxTokensField: 'max_tokens'
}
};
}
export function getModelApiKey(modelNameOrId?: string): string {
const cfg = getLLMModel(modelNameOrId);
return cfg?.requestAuth || defaultApiKey;
}
import type { ChatCompletionTool } from '@fastgpt/global/core/ai/llm/type';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type';
import { SubAppIds } from '@fastgpt/global/core/workflow/node/agent/constants';
import {
SANDBOX_TOOL_NAME,
SANDBOX_GET_FILE_URL_TOOL_NAME,
SandboxShellToolSchema,
SandboxGetFileUrlToolSchema
} from '@fastgpt/global/core/ai/sandbox/constants';
import { ReadFileToolSchema } from '../sub/file/utils';
import { DatasetSearchToolSchema } from '../sub/dataset/utils';
import { dispatchFileRead } from '../sub/file';
import { dispatchAgentDatasetSearch } from '../sub/dataset';
import { dispatchSandboxShell, dispatchSandboxGetFileUrl } from '../sub/sandbox';
import { dispatchTool } from '../sub/tool';
import { dispatchApp, dispatchPlugin } from '../sub/app';
import { parseJsonArgs } from '../../../../../ai/utils';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import type { GetSubAppInfoFnType, SubAppRuntimeType } from '../type';
import type { CapabilityToolCallHandlerType } from '../capability/type';
import type { DispatchAgentModuleProps } from '..';
import type { AppFormEditFormType } from '@fastgpt/global/core/app/formEdit/type';
import type { OpenaiAccountType } from '@fastgpt/global/support/user/team/type';
type AgentTool = import('@mariozechner/pi-agent-core').AgentTool<any>;
// Flatten context for tool dispatch (avoids NodeInputKeyEnum computed-key Pick issues)
export type ToolDispatchContext = Pick<
DispatchAgentModuleProps,
| 'checkIsStopping'
| 'chatConfig'
| 'runningUserInfo'
| 'runningAppInfo'
| 'chatId'
| 'uid'
| 'variables'
| 'externalProvider'
| 'workflowStreamResponse'
| 'lang'
| 'requestOrigin'
| 'mode'
| 'timezone'
| 'retainDatasetCite'
| 'maxRunTimes'
| 'workflowDispatchDeep'
| 'usagePush'
> & {
model: string;
datasetParams?: AppFormEditFormType['dataset'];
};
export async function buildAgentTools({
completionTools,
ctx,
filesMap,
getSubApp,
getSubAppInfo,
capabilityToolCallHandler,
nodeResponses
}: {
completionTools: ChatCompletionTool[];
ctx: ToolDispatchContext;
filesMap: Record<string, string>;
getSubApp: (id: string) => SubAppRuntimeType | undefined;
getSubAppInfo: GetSubAppInfoFnType;
capabilityToolCallHandler?: CapabilityToolCallHandlerType;
nodeResponses: ChatHistoryItemResType[];
}): Promise<AgentTool[]> {
const { Type } = await import('@mariozechner/pi-ai');
const {
checkIsStopping,
chatConfig,
runningUserInfo,
runningAppInfo,
chatId,
uid,
variables,
externalProvider,
workflowStreamResponse,
lang,
requestOrigin,
mode,
timezone,
retainDatasetCite,
maxRunTimes,
workflowDispatchDeep,
usagePush,
model,
datasetParams
} = ctx;
const tools: AgentTool[] = [];
for (const tool of completionTools) {
const toolId = tool.function.name;
// pi-agent-core manages multi-turn reasoning; skip the plan tool
if (toolId === SubAppIds.plan) continue;
const execute = async (
callId: string,
args: Record<string, any>,
_signal?: AbortSignal
): Promise<{ content: { type: 'text'; text: string }[]; details: Record<string, unknown> }> => {
const argStr = JSON.stringify(args);
try {
const { response, usages = [] } = await (async (): Promise<{
response: string;
usages?: any[];
}> => {
if (toolId === SubAppIds.fileRead) {
const toolParams = ReadFileToolSchema.safeParse(args);
if (!toolParams.success) return { response: toolParams.error.message };
const files = toolParams.data.file_indexes.map((index) => ({
index,
url: filesMap[index]
}));
const result = await dispatchFileRead({
files,
teamId: runningUserInfo.teamId,
tmbId: runningUserInfo.tmbId,
customPdfParse: chatConfig?.fileSelectConfig?.customPdfParse,
model,
userKey: externalProvider.openaiAccount as OpenaiAccountType | undefined
});
if (result.nodeResponse) nodeResponses.push(result.nodeResponse);
return { response: result.response, usages: result.usages };
}
if (toolId === SubAppIds.datasetSearch) {
const toolParams = DatasetSearchToolSchema.safeParse(args);
if (!toolParams.success) return { response: toolParams.error.message };
if (!datasetParams || datasetParams.datasets.length === 0) {
return { response: 'No dataset selected' };
}
const result = await dispatchAgentDatasetSearch({
query: toolParams.data.query,
config: {
datasets: datasetParams.datasets,
similarity: datasetParams.similarity || 0.4,
maxTokens: datasetParams.limit || 5000,
searchMode: datasetParams.searchMode,
embeddingWeight: datasetParams.embeddingWeight,
usingReRank: datasetParams.usingReRank ?? false,
rerankModel: datasetParams.rerankModel,
rerankWeight: datasetParams.rerankWeight || 0.5,
usingExtensionQuery: datasetParams.datasetSearchUsingExtensionQuery ?? false,
extensionModel: datasetParams.datasetSearchExtensionModel,
extensionBg: datasetParams.datasetSearchExtensionBg
},
teamId: runningUserInfo.teamId,
tmbId: runningUserInfo.tmbId,
llmModel: model
});
if (result.nodeResponse) nodeResponses.push(result.nodeResponse);
return { response: result.response, usages: result.usages };
}
if (toolId === SANDBOX_TOOL_NAME) {
const toolParams = SandboxShellToolSchema.safeParse(args);
if (!toolParams.success) return { response: toolParams.error.message };
const result = await dispatchSandboxShell({
command: toolParams.data.command,
timeout: toolParams.data.timeout,
appId: runningAppInfo.id,
userId: uid,
chatId,
lang
});
nodeResponses.push(result.nodeResponse);
return { response: result.response, usages: result.usages };
}
if (toolId === SANDBOX_GET_FILE_URL_TOOL_NAME) {
const toolParams = SandboxGetFileUrlToolSchema.safeParse(args);
if (!toolParams.success) return { response: toolParams.error.message };
const result = await dispatchSandboxGetFileUrl({
paths: toolParams.data.paths,
appId: runningAppInfo.id,
userId: uid,
chatId,
lang
});
nodeResponses.push(result.nodeResponse);
return { response: result.response, usages: result.usages };
}
// Capability tools (e.g. sandbox skills)
const capResult = await capabilityToolCallHandler?.(toolId, argStr, callId);
if (capResult != null) {
const subInfo = getSubAppInfo(toolId);
nodeResponses.push({
nodeId: callId,
id: callId,
moduleType: FlowNodeTypeEnum.tool,
moduleName: subInfo.name,
moduleLogo: subInfo.avatar,
toolInput: parseJsonArgs(argStr),
toolRes: capResult.response
});
if (capResult.usages?.length) usagePush(capResult.usages);
return { response: capResult.response, usages: capResult.usages };
}
// User sub-apps
const subApp = getSubApp(toolId);
if (!subApp) return { response: `Can't find the tool ${toolId}` };
const requestParams = { ...subApp.params, ...args };
if (subApp.type === 'tool') {
const { response, usages, runningTime, toolParams, result } = await dispatchTool({
tool: {
name: subApp.name,
version: subApp.version,
toolConfig: subApp.toolConfig
},
params: requestParams,
runningUserInfo,
runningAppInfo,
chatId,
uid,
variables,
workflowStreamResponse
});
nodeResponses.push({
nodeId: callId,
id: callId,
runningTime,
moduleType: FlowNodeTypeEnum.tool,
moduleName: subApp.name,
moduleLogo: subApp.avatar,
toolInput: toolParams,
toolRes: result || response,
totalPoints: usages?.reduce((sum: number, item: any) => sum + item.totalPoints, 0)
});
return { response, usages };
}
if (subApp.type === 'workflow') {
const { userChatInput, ...params } = requestParams;
const { response, runningTime, usages } = await dispatchApp({
appId: subApp.id,
userChatInput: userChatInput ?? '',
customAppVariables: params,
checkIsStopping,
lang,
requestOrigin,
mode,
timezone,
externalProvider,
runningAppInfo,
runningUserInfo,
retainDatasetCite,
maxRunTimes,
workflowDispatchDeep,
variables
});
nodeResponses.push({
nodeId: callId,
id: callId,
runningTime,
moduleType: FlowNodeTypeEnum.appModule,
moduleName: subApp.name,
moduleLogo: subApp.avatar,
toolInput: requestParams,
toolRes: response,
totalPoints: usages?.reduce((sum: number, item: any) => sum + item.totalPoints, 0)
});
return { response, usages };
}
if (subApp.type === 'toolWorkflow') {
const { response, result, runningTime, usages } = await dispatchPlugin({
appId: subApp.id,
userChatInput: '',
customAppVariables: requestParams,
checkIsStopping,
lang,
requestOrigin,
mode,
timezone,
externalProvider,
runningAppInfo,
runningUserInfo,
retainDatasetCite,
maxRunTimes,
workflowDispatchDeep,
variables
});
nodeResponses.push({
nodeId: callId,
id: callId,
runningTime,
moduleType: FlowNodeTypeEnum.pluginModule,
moduleName: subApp.name,
moduleLogo: subApp.avatar,
toolInput: requestParams,
toolRes: result,
totalPoints: usages?.reduce((sum: number, item: any) => sum + item.totalPoints, 0)
});
return { response, usages };
}
return { response: 'Invalid tool type' };
})();
if (usages && usages.length > 0) usagePush(usages);
// SSE tool response
workflowStreamResponse?.({
id: callId,
event: SseResponseEventEnum.toolResponse,
data: { tool: { response } }
});
return { content: [{ type: 'text' as const, text: response }], details: {} };
} catch (error) {
const errText = `Tool error: ${getErrText(error)}`;
return { content: [{ type: 'text' as const, text: errText }], details: {} };
}
};
// Wrap execute to also emit SSE toolCall event before execution
const wrappedExecute = async (
callId: string,
args: Record<string, any>,
signal?: AbortSignal
) => {
const subAppInfo = getSubAppInfo(toolId);
workflowStreamResponse?.({
id: callId,
event: SseResponseEventEnum.toolCall,
data: {
tool: {
id: callId,
toolName: subAppInfo?.name || toolId,
toolAvatar: subAppInfo?.avatar || '',
functionName: toolId,
params: JSON.stringify(args)
}
}
});
return execute(callId, args, signal);
};
tools.push({
name: toolId,
label: tool.function.name,
description: tool.function.description || '',
// Convert JSON Schema to TypeBox using Type.Unsafe
parameters: Type.Unsafe<any>((tool.function.parameters as Record<string, unknown>) ?? {}),
execute: wrappedExecute
});
}
return tools;
}
......@@ -115,7 +115,10 @@ export const env = createEnv({
// Beta features
// Whether the Skill feature is enabled (frontend entries + backend runtime)
SHOW_SKILL: BoolSchema.default(false)
SHOW_SKILL: BoolSchema.default(false),
// Agent engine selection: 'default' uses the built-in Plan+Step engine, 'pi' uses pi-agent-core
AGENT_ENGINE: z.enum(['default', 'pi']).default('default')
},
emptyStringAsUndefined: true,
runtimeEnv: process.env,
......
......@@ -8,6 +8,8 @@
},
"dependencies": {
"@apidevtools/json-schema-ref-parser": "^11.7.2",
"@mariozechner/pi-agent-core": "^0.67.3",
"@mariozechner/pi-ai": "^0.67.3",
"@fastgpt-sdk/sandbox-adapter": "^0.0.36",
"@fastgpt-sdk/otel": "catalog:",
"@fastgpt-sdk/storage": "catalog:",
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -192,6 +192,8 @@ MAX_HTML_TRANSFORM_CHARS=
# ==================== Beta features ====================
# 是否展示 Skill 功能入口
SHOW_SKILL=false
# Agent 引擎选择:default(Plan+Step 编排)| pi(pi-agent-core 引擎)
AGENT_ENGINE=default
# ==================== 对话日志推送(可选) ====================
# 日志服务地址
......
......@@ -176,7 +176,9 @@ const nextConfig: NextConfig = {
'bullmq',
'@zilliz/milvus2-sdk-node',
'tiktoken',
'@opentelemetry/api-logs'
'@opentelemetry/api-logs',
'@mariozechner/pi-agent-core',
'@mariozechner/pi-ai'
],
// 优化大库的 barrel exports tree-shaking
experimental: {
......
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