FastGPT插件实现逻辑和代码梳理
1. 插件系统概述
FastGPT通过插件丰富智能体生态,集成多种的方式的各种功能的工具,无限扩展智能体功能。插件模块支持多种类型的插件,提供了灵活的扩展机制:
1.1 插件类型
typescript
export enum PluginTypeEnum {
folder = 'folder', // 文件夹
custom = 'custom', // 自定义插件
http = 'http' // HTTP插件
}
export enum PluginSourceEnum {
personal = 'personal', // 个人插件
community = 'community', // 社区插件
commercial = 'commercial' // 商业插件
}
1.2 插件ID规则
typescript
// 插件ID规则:
// personal: id (直接使用应用ID)
// community: community-id
// commercial: commercial-id
export async function splitCombinePluginId(id: string) {
const splitRes = id.split('-');
if (splitRes.length === 1) {
// 个人应用ID
return {
source: PluginSourceEnum.personal,
pluginId: id
};
}
const [source, pluginId] = id.split('-') as [PluginSourceEnum, string];
return { source, pluginId: id };
}
2. 插件注册机制
2.1 插件注册器
核心文件: packages/plugins/register.ts
typescript
// 主线程运行的静态插件
const staticPluginList = [
'getTime',
'fetchUrl',
'feishu',
'DingTalkWebhook',
'WeWorkWebhook',
'google',
'bing',
'delay'
];
// Worker线程运行的包插件(包含npm依赖)
const packagePluginList = [
'mathExprVal',
'duckduckgo',
'duckduckgo/search',
'duckduckgo/searchImg',
'duckduckgo/searchNews',
'duckduckgo/searchVideo',
'drawing',
'drawing/baseChart',
'wiki',
'databaseConnection',
'Doc2X',
'Doc2X/PDF2text',
'searchXNG'
];
export const getCommunityPlugins = () => {
return Promise.all(
list.map<Promise<SystemPluginTemplateItemType>>(async (name) => {
const config = (await import(`./src/${name}/template.json`))?.default;
const isFolder = list.find((item) => item.startsWith(`${name}/`));
const parentIdList = name.split('/').slice(0, -1);
const parentId = parentIdList.length > 0
? `${PluginSourceEnum.community}-${parentIdList.join('/')}`
: null;
return {
...config,
id: `${PluginSourceEnum.community}-${name}`,
isFolder,
parentId,
isActive: true,
isOfficial: true
};
})
);
};
export const getCommunityCb = async () => {
const result = await Promise.all(
list.map(async (name) => {
return {
name,
cb: staticPluginList.includes(name)
? await loadCommunityModule(name) // 主线程执行
: (e: any) => { // Worker线程执行
return runWorker(WorkerNameEnum.systemPluginRun, {
pluginName: name,
data: e
});
}
};
})
);
return result.reduce<Record<string, (e: any) => SystemPluginResponseType>>(
(acc, { name, cb }) => {
acc[name] = cb;
return acc;
}, {}
);
};
2.2 插件模板结构
每个插件都包含两个核心文件:
- template.json - 插件配置和工作流定义
- index.ts - 插件执行逻辑(可选,用于静态插件)
示例: packages/plugins/src/getTime/template.json
json
{
"author": "",
"version": "481",
"templateType": "tools",
"name": "获取当前时间",
"avatar": "core/workflow/template/getTime",
"intro": "获取用户当前时区的时间。",
"showStatus": false,
"isTool": true,
"weight": 10,
"workflow": {
"nodes": [
{
"nodeId": "lmpb9v2lo2lk",
"name": "插件开始",
"flowNodeType": "pluginInput",
"inputs": [],
"outputs": []
},
{
"nodeId": "i7uow4wj2wdp",
"name": "插件输出",
"flowNodeType": "pluginOutput",
"inputs": [
{
"key": "time",
"valueType": "string",
"label": "time",
"value": ["ebLCxU43hHuZ", "rH4tMV02robs"]
}
],
"outputs": []
},
{
"nodeId": "ebLCxU43hHuZ",
"name": "HTTP 请求",
"flowNodeType": "httpRequest468",
"inputs": [
{
"key": "system_httpReqUrl",
"value": "getTime"
},
{
"key": "system_httpJsonBody",
"value": "{\n \"time\": \"{{cTime}}\"\n}"
}
],
"outputs": [
{
"id": "rH4tMV02robs",
"key": "time",
"label": "time",
"valueType": "string"
}
]
}
],
"edges": [
{
"source": "lmpb9v2lo2lk",
"target": "ebLCxU43hHuZ"
},
{
"source": "ebLCxU43hHuZ",
"target": "i7uow4wj2wdp"
}
]
}
}
3. 插件执行机制
3.1 插件运行调度
核心文件: packages/service/core/workflow/dispatch/plugin/run.ts
typescript
export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPluginResponse> => {
const {
node: { pluginId, version },
runningAppInfo,
query,
params: { system_forbid_stream = false, ...data }
} = props;
// 1. 权限验证
const pluginData = await authPluginByTmbId({
appId: pluginId,
tmbId: runningAppInfo.tmbId,
per: ReadPermissionVal
});
// 2. 获取插件运行时数据
const plugin = await getChildAppRuntimeById(pluginId, version);
// 3. 构建运行时节点
const runtimeNodes = storeNodes2RuntimeNodes(
plugin.nodes,
getWorkflowEntryNodeIds(plugin.nodes)
).map((node) => {
// 更新插件输入节点的值
if (node.flowNodeType === FlowNodeTypeEnum.pluginInput) {
return {
...node,
showStatus: false,
inputs: node.inputs.map((input) => ({
...input,
value: data[input.key] ?? input.value
}))
};
}
return { ...node, showStatus: false };
});
// 4. 执行工作流
const { flowResponses, flowUsages, assistantResponses, runTimes } = await dispatchWorkFlow({
...props,
runningAppInfo: {
id: String(plugin.id),
teamId: plugin.teamId || runningAppInfo.teamId,
tmbId: pluginData?.tmbId || runningAppInfo.tmbId
},
variables: runtimeVariables,
query: getPluginRunUserQuery({
pluginInputs: getPluginInputsFromStoreNodes(plugin.nodes),
variables: runtimeVariables,
files
}).value,
runtimeNodes,
runtimeEdges: initWorkflowEdgeStatus(plugin.edges)
});
// 5. 处理输出结果
const output = flowResponses.find(item => item.moduleType === FlowNodeTypeEnum.pluginOutput);
const usagePoints = await computedPluginUsage({
plugin,
childrenUsage: flowUsages,
error: !!output?.pluginOutput?.error
});
return {
assistantResponses: system_forbid_stream ? [] : assistantResponses,
[DispatchNodeResponseKeyEnum.runTimes]: runTimes,
[DispatchNodeResponseKeyEnum.nodeResponse]: {
moduleLogo: plugin.avatar,
totalPoints: usagePoints,
pluginOutput: output?.pluginOutput
},
[DispatchNodeResponseKeyEnum.toolResponses]: output?.pluginOutput,
...(output ? output.pluginOutput : {})
};
};
3.2 插件输入处理
核心文件: packages/service/core/workflow/dispatch/plugin/runInput.ts
typescript
export const dispatchPluginInput = (props: PluginInputProps) => {
const { params, query } = props;
const { files } = chatValue2RuntimePrompt(query);
// 处理文件类型参数
for (const key in params) {
const val = params[key];
if (
Array.isArray(val) &&
val.every(item =>
item.type === ChatFileTypeEnum.file ||
item.type === ChatFileTypeEnum.image
)
) {
params[key] = val.map(item => item.url);
}
}
return {
...params,
[DispatchNodeResponseKeyEnum.nodeResponse]: {},
[NodeOutputKeyEnum.userFiles]: files
.map(item => item?.url ?? '')
.filter(Boolean)
};
};
3.3 Worker线程执行
核心文件: packages/plugins/runtime/worker.ts
typescript
const loadModule = async (name: string): Promise<(e: any) => SystemPluginResponseType> => {
const module = await import(`../src/${name}/index`);
return module.default;
};
parentPort?.on('message', async ({ pluginName, data }: { pluginName: string; data: any }) => {
try {
const cb = await loadModule(pluginName);
parentPort?.postMessage({
type: 'success',
data: await cb(data)
});
} catch (error) {
parentPort?.postMessage({
type: 'error',
data: error
});
}
process.exit();
});
4. HTTP插件系统
4.1 HTTP插件创建
核心文件: projects/app/src/pageComponents/app/list/HttpPluginEditModal.tsx
HTTP插件支持通过OpenAPI Schema自动生成:
typescript
const HttpPluginEditModal = ({ defaultPlugin, onClose }) => {
const [apiData, setApiData] = useState<OpenApiJsonSchema>({
pathData: [],
serverPath: ''
});
// 从URL加载API Schema
const { mutate: onClickUrlLoadApi } = useRequest({
mutationFn: async () => {
if (!schemaUrl || (!schemaUrl.startsWith('https://') && !schemaUrl.startsWith('http://'))) {
return toast({
title: t('common:plugin.Invalid URL'),
status: 'warning'
});
}
const schema = await getApiSchemaByUrl(schemaUrl);
setValue('pluginData.apiSchemaStr', JSON.stringify(schema, null, 2));
}
});
// 解析API Schema
useEffect(() => {
(async () => {
if (!apiSchemaStr) {
return setApiData({ pathData: [], serverPath: '' });
}
try {
setApiData(await str2OpenApiSchema(apiSchemaStr));
} catch (err) {
toast({
status: 'warning',
title: t('common:plugin.Invalid Schema')
});
setApiData({ pathData: [], serverPath: '' });
}
})();
}, [apiSchemaStr]);
return (
<MyModal>
{/* 基本信息设置 */}
<Input {...register('name')} />
<Textarea {...register('intro')} />
{/* OpenAPI Schema输入 */}
<Textarea {...register('pluginData.apiSchemaStr')} />
{/* 自定义请求头 */}
<Table>
{customHeaders.map((item, index) => (
<Tr key={index}>
<Td>
<HttpInput
value={item.key}
onBlur={(val) => updateHeaders(index, 'key', val)}
/>
</Td>
<Td>
<HttpInput
value={item.value}
onBlur={(val) => updateHeaders(index, 'value', val)}
/>
</Td>
</Tr>
))}
</Table>
{/* API列表预览 */}
<Table>
{apiData.pathData?.map((item, index) => (
<Tr key={index}>
<Td>{item.name}</Td>
<Td>{item.description}</Td>
<Td>{item.method}</Td>
<Td>{item.path}</Td>
</Tr>
))}
</Table>
</MyModal>
);
};
4.2 cURL解析功能
核心文件: projects/app/src/web/core/app/templates.ts
支持从cURL命令自动生成插件:
typescript
export const parsePluginFromCurlString = (curl: string) => {
const { url, method, headers, body, params, bodyArray } = parseCurl(curl);
// 解析参数生成插件输入
const allInputs = Array.from(
new Map([...params, ...bodyArray].map(item => [item.key, item])).values()
);
const formatPluginStartInputs = allInputs.map(item => {
const valueType = item.value === null ? 'string' : typeof item.value;
const valueTypeMap = {
string: {
renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference],
valueType: WorkflowIOValueTypeEnum.string,
isToolType: true
},
number: {
renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference],
valueType: WorkflowIOValueTypeEnum.number,
isToolType: true
},
boolean: {
renderTypeList: [FlowNodeInputTypeEnum.switch, FlowNodeInputTypeEnum.reference],
valueType: WorkflowIOValueTypeEnum.boolean,
isToolType: true
}
};
return {
renderTypeList: valueTypeMap[valueType].renderTypeList,
valueType: valueTypeMap[valueType].valueType,
key: item.key,
label: item.key,
required: false,
toolDescription: item.key
};
});
// 生成HTTP请求节点配置
const referenceBody = Object.entries(JSON.parse(body)).reduce((acc, [key, value]) => {
acc[key] = typeof value === 'string'
? `###{{$pluginInput.${key}$}}###`
: `{{$pluginInput.${key}$}}`;
return acc;
}, {} as Record<string, any>);
return {
nodes: [
// 插件输入节点
{
nodeId: 'pluginInput',
name: '插件开始',
flowNodeType: FlowNodeTypeEnum.pluginInput,
inputs: formatPluginStartInputs,
outputs: formatPluginStartOutputs
},
// HTTP请求节点
{
nodeId: 'httpRequest',
name: 'HTTP 请求',
flowNodeType: FlowNodeTypeEnum.httpRequest468,
inputs: [
{
key: 'system_httpMethod',
value: method
},
{
key: 'system_httpReqUrl',
value: url
},
{
key: 'system_httpHeader',
value: referenceHeaders
},
{
key: 'system_httpJsonBody',
value: referenceBodyStr
}
]
},
// 插件输出节点
{
nodeId: 'pluginOutput',
name: '插件输出',
flowNodeType: FlowNodeTypeEnum.pluginOutput
}
],
edges: [
{ source: 'pluginInput', target: 'httpRequest' },
{ source: 'httpRequest', target: 'pluginOutput' }
]
};
};
5. 插件管理系统
5.1 插件控制器
核心文件: packages/service/core/app/plugin/controller.ts
typescript
// 获取插件预览节点数据
export async function getChildAppPreviewNode({ id }: { id: string }): Promise<FlowNodeTemplateType> {
const app = await (async () => {
const { source, pluginId } = await splitCombinePluginId(id);
if (source === PluginSourceEnum.personal) {
// 个人插件
const item = await MongoApp.findById(id).lean();
const version = await getAppLatestVersion(id, item);
return {
id: String(item._id),
teamId: String(item.teamId),
name: item.name,
avatar: item.avatar,
workflow: {
nodes: version.nodes,
edges: version.edges,
chatConfig: version.chatConfig
},
templateType: FlowNodeTemplateTypeEnum.teamApp
};
} else {
// 系统插件
return getSystemPluginTemplateById(pluginId);
}
})();
const isPlugin = !!app.workflow.nodes.find(
node => node.flowNodeType === FlowNodeTypeEnum.pluginInput
);
return {
id: getNanoid(),
pluginId: app.id,
templateType: app.templateType,
flowNodeType: isPlugin ? FlowNodeTypeEnum.pluginModule : FlowNodeTypeEnum.appModule,
avatar: app.avatar,
name: app.name,
intro: app.intro,
showStatus: app.showStatus,
isTool: true,
version: app.version,
...(isPlugin
? pluginData2FlowNodeIO({ nodes: app.workflow.nodes })
: appData2FlowNodeIO({ chatConfig: app.workflow.chatConfig }))
};
}
// 获取插件运行时数据
export async function getChildAppRuntimeById(
id: string,
versionId?: string
): Promise<PluginRuntimeType> {
const app = await (async () => {
const { source, pluginId } = await splitCombinePluginId(id);
if (source === PluginSourceEnum.personal) {
const item = await MongoApp.findById(id).lean();
const version = await getAppVersionById({ appId: id, versionId, app: item });
return {
id: String(item._id),
teamId: String(item.teamId),
name: item.name,
avatar: item.avatar,
workflow: {
nodes: version.nodes,
edges: version.edges,
chatConfig: version.chatConfig
}
};
} else {
return getSystemPluginTemplateById(pluginId, versionId);
}
})();
return {
id: app.id,
teamId: app.teamId,
name: app.name,
avatar: app.avatar,
showStatus: app.showStatus,
currentCost: app.currentCost,
nodes: app.workflow.nodes,
edges: app.workflow.edges,
hasTokenFee: app.hasTokenFee
};
}
5.2 插件加载机制
核心文件: projects/app/src/service/core/app/plugin.ts
typescript
export const getSystemPlugins = async (refresh = false) => {
if (isProduction && global.systemPlugins && global.systemPlugins.length > 0 && !refresh)
return cloneDeep(global.systemPlugins);
try {
if (!global.systemPlugins) {
global.systemPlugins = [];
}
// 根据环境加载不同的插件
global.systemPlugins = FastGPTProUrl
? await getCommercialPlugins() // 商业版插件
: await getCommunityPlugins(); // 社区版插件
addLog.info(`Load system plugin successfully: ${global.systemPlugins.length}`);
return cloneDeep(global.systemPlugins);
} catch (error) {
global.systemPlugins = undefined;
return Promise.reject(error);
}
};
export const getSystemPluginCb = async (refresh = false) => {
if (
isProduction &&
global.systemPluginCb &&
Object.keys(global.systemPluginCb).length > 0 &&
!refresh
)
return global.systemPluginCb;
try {
global.systemPluginCb = {};
await getSystemPlugins(refresh);
// 根据环境获取不同的回调函数
global.systemPluginCb = FastGPTProUrl
? await getCommercialCb() // 商业版回调
: await getCommunityCb(); // 社区版回调
return global.systemPluginCb;
} catch (error) {
return Promise.reject(error);
}
};
6. 插件开发示例
6.1 简单插件示例
文件: packages/plugins/src/getTime/index.ts
typescript
type Props = {
time: string;
};
type Response = Promise<{
time: string;
}>;
const main = async ({ time }: Props): Response => {
return {
time
};
};
export default main;
6.2 复杂插件示例
文件: packages/plugins/src/mathExprVal/index.ts
typescript
import { evaluate } from 'mathjs';
type Props = {
expression: string;
};
type Response = Promise<{
result: number | string;
error?: string;
}>;
const main = async ({ expression }: Props): Response => {
try {
if (!expression) {
return {
result: '',
error: 'Expression is required'
};
}
const result = evaluate(expression);
return {
result: typeof result === 'number' ? result : String(result)
};
} catch (error) {
return {
result: '',
error: `Math evaluation error: ${error.message}`
};
}
};
export default main;
7. 插件系统架构总结
7.1 核心组件
- 插件注册器 - 负责插件的发现和注册
- 插件控制器 - 管理插件的生命周期
- 插件调度器 - 执行插件工作流
- Worker管理器 - 处理需要隔离执行的插件
- HTTP插件系统 - 支持OpenAPI规范的HTTP插件
7.2 执行流程
- 插件注册 → 系统启动时扫描并注册所有插件
- 插件发现 → 用户在工作流中选择插件
- 参数绑定 → 将用户输入绑定到插件参数
- 插件执行 → 根据插件类型选择执行方式
- 结果返回 → 处理插件输出并返回结果
7.3 关键文件路径
核心系统文件
packages/plugins/register.ts
- 插件注册器packages/plugins/runtime/worker.ts
- Worker执行器packages/service/core/app/plugin/controller.ts
- 插件控制器packages/service/core/workflow/dispatch/plugin/run.ts
- 插件调度器
插件源码目录
packages/plugins/src/
- 社区插件源码plugins/model/
- 模型插件plugins/webcrawler/
- 网络爬虫插件
前端界面文件
projects/app/src/pageComponents/app/list/HttpPluginEditModal.tsx
- HTTP插件编辑projects/app/src/service/core/app/plugin.ts
- 插件服务
这套插件系统提供了完整的插件开发、注册、管理和执行能力,支持从简单的工具函数到复杂的HTTP API集成。