低代码画布 + Function Calling + MCP,AI 应用开发从未如此灵活
前言
2026 年,AI 应用开发正经历一场深刻的范式转移。从最初直接调用 API 的"裸调"模式,到如今 "可视化编排 + 标准化协议" 的工程化体系,开发者构建 AI 应用的方式发生了质的变化。
根据行业报告,智能体开发平台正加速向低代码化、多模态融合及智能体工作流(Agentic Workflow) 方向演进。与此同时,MCP(模型上下文协议)最近刚完成史上最大规模更新------2026-07-28 规范发布,转向无状态核心,月 SDK 下载量已突破 4 亿次。
在这样的背景下,我基于 Next.js + React Flow 从零搭建了一个可视化 AI 工作流编排平台,支持拖拽编排、条件分支、Function Calling 智能代理,并内置了 MCP 服务器。这篇文章将完整分享我的技术选型、架构设计和核心实现。
一、为什么要造这个轮子?
1.1 痛点:AI 应用开发的"三座大山"
在日常 AI 应用开发中,我们经常面临三个核心问题:
- 逻辑固化:每次新增一个工具或调整流程,都要改代码、重新部署,迭代周期长。
- 调用复杂 :Function Calling 的
tools数组需要手动维护,工具一多,上下文急剧膨胀。 - 缺乏可视化:复杂的 AI 流程只能靠代码"脑补",难以向非技术人员展示和协作。
1.2 目标
我希望构建一个低代码 AI 工作流平台,让:
- 非技术人员(产品、运营)通过拖拽节点即可完成 AI 流程编排;
- 开发者无需每次加工具都改核心代码,实现"即插即用";
- AI Agent能自主决策调用哪些工具(ReAct 模式),而不是预设死路径。
二、技术选型
2.1 全栈框架:Next.js 14(App Router)
选择 Next.js 的核心原因是类型安全的全栈一致性。
在 Express 架构中,前端和后端的类型定义需要维护两份,一旦 WorkflowNode 的字段发生变化,前后端极易不同步。而 Next.js 允许我在 @types/ 中统一管理类型,前端 page.tsx 和后端 route.ts 共享同一套定义。
此外,Next.js API Routes 对 Web Standard ReadableStream 的原生支持,让流式输出的实现异常简洁------无需配置额外的 SSE 库。
2.2 可视化画布:React Flow(@xyflow/react)
React Flow 是目前最成熟的 React 流程图库,Dify、Flowise 等主流 AI 工作流平台都在使用。
我利用它实现了:
- 自定义节点:输入节点、LLM 节点、条件分支节点、HTTP 请求节点、数据库查询节点、天气节点、通知节点、智能代理节点等;
- 动态连线 :支持
sourceHandle区分不同出口(如条件节点的 true/false); - 节点增删:工具栏按钮 + 键盘快捷键(Delete/Backspace)。
2.3 AI 模型接入:OpenAI / DeepSeek 兼容 API
通过环境变量配置 OPENAI_API_KEY / DEEPSEEK_API_KEY、LLM_BASE_URL 和 LLM_MODEL,支持任意兼容 OpenAI 格式的模型(GPT-4o、DeepSeek、Claude 等)。
2.4 数据库:MongoDB + Mongoose
用于存储产品数据(Product 模型),支持 productId 和 title 的模糊查询。
2.5 安全条件评估:expr-eval
用户可在条件分支节点中编写 JavaScript 表达式(如 input.includes('错误'))。为了防止 new Function 带来的代码注入风险,我使用 expr-eval 库在沙箱中安全执行。
三、整体架构
text
┌─────────────────────────────────────────────────────────────────┐
│ 前端 (React + React Flow) │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ 输入节点 │ │ LLM节点 │ │ 条件分支节点 │ │ 智能代理节点 │ │
│ └──────────┘ └──────────┘ └──────────────┘ └───────────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ 数据库节点│ │ 天气节点 │ │ 通知节点 │ │
│ └──────────┘ └──────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Next.js API Routes (后端执行引擎) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ POST /api/run-workflow (工作流执行 + 流式输出) │ │
│ │ - 解析 nodes/edges JSON │ │
│ │ - 按拓扑顺序执行节点 (input → condition → llm → ...) │ │
│ │ - 支持条件分支、工具调用、错误处理 │ │
│ │ - 最终结果以 ReadableStream 流式返回 (打字机效果) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ POST /api/mcp (MCP 服务器) │ │
│ │ - get_weather: 查询天气 │ │
│ │ - query_database: 查询 MongoDB 产品数据 │ │
│ │ - send_email: 发送邮件 │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 外部依赖 │
│ ┌──────────┐ ┌──────────────┐ ┌────────────────────────┐ │
│ │ MongoDB │ │ OpenWeather │ │ SMTP / 邮件服务 │ │
│ └──────────┘ └──────────────┘ └────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
四、核心实现
4.1 节点系统:从"死节点"到"活组件"
每个自定义节点都是一个独立的 React 组件,通过 nodeTypes 注册到 React Flow:
tsx
// app/page.tsx
const nodeTypes: NodeTypes = {
inputNode: InputNode,
llmNode: LLMNode,
conditionNode: ConditionNode,
toolAgentNode: ToolAgentNode,
// ...
};
以条件分支节点为例,它有两个输出端口(true / false),通过 Handle 组件的 id 属性区分:
tsx
<Handle type="source" position={Position.Right} id="true" />
<Handle type="source" position={Position.Right} id="false" />
用户拖拽连线时,React Flow 会自动记录 sourceHandle,后端据此判断走哪个分支。
4.2 执行引擎:工作流如何跑起来?
核心逻辑在 POST /api/run-workflow 中:
typescript
let currentId: string | null = startNode.id;
let previousResult = '';
while (currentId !== null) {
const currentNode = nodeMap.get(currentId);
if (!currentNode) break;
if (currentNode.type === 'inputNode') {
previousResult = currentNode.data.value || '';
} else if (currentNode.type === 'llmNode') {
previousResult = await callLLMNonStream(systemPrompt, previousResult);
} else if (currentNode.type === 'conditionNode') {
const result = evaluateCondition(previousResult, conditionExpr);
const handleId = result ? 'true' : 'false';
const nextEdge = edges.find(e => e.source === currentId && e.sourceHandle === handleId);
currentId = nextEdge ? nextEdge.target : null;
continue;
}
// ... 其他节点类型
// 普通节点:查找下一条边
const nextEdge = edges.find(e => e.source === currentId && !e.sourceHandle);
currentId = nextEdge ? nextEdge.target : null;
}
这里的核心设计是:节点只负责"执行"和"产出数据",路由逻辑由 edges 数组和 sourceHandle 共同决定 。这使得节点本身与流程解耦,新增节点类型只需在 while 循环中添加一个 else if 分支。
4.3 流式输出:打字机效果是怎么实现的?
最终结果通过 ReadableStream 逐字符返回:
typescript
const stream = new ReadableStream({
async start(controller) {
for (const char of text) {
controller.enqueue(new TextEncoder().encode(char));
await new Promise(resolve => setTimeout(resolve, 10));
}
controller.close();
},
});
return new Response(stream, {
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
});
前端使用 response.body.getReader() 逐块读取并更新 UI,实现类似 ChatGPT 的打字机效果。这种方式相比一次性返回,首字节时间(TTFB)降低约 80% ,且服务器内存占用极低。
4.4 Function Calling:让 AI 自己决定用哪个工具
在"智能代理节点(ToolAgentNode)"中,我实现了完整的 ReAct 模式:
- 构建工具 Schema :根据用户启用的工具列表,动态生成 OpenAI 格式的
tools数组; - LLM 决策 :将用户问题和工具列表一起发给 LLM,LLM 返回
tool_calls; - 执行工具 :根据
tool_calls中的name和arguments调用对应的工具函数; - 结果回传 :将工具执行结果以
role: 'tool'的消息格式再次发给 LLM,生成最终自然语言回答。
typescript
// 工具执行示例:天气查询
case 'get_weather': {
const res = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${args.city}&appid=${apiKey}&units=metric&lang=zh_cn`
);
const data = await res.json();
toolResult = `当前${args.city}天气:${data.weather?.[0]?.description},温度${data.main?.temp}°C`;
break;
}
4.5 MCP 服务器:把工具变成"标准插头"
MCP(模型上下文协议)最近刚完成 2026-07-28 规范更新,转向无状态核心,已成为连接 AI Agent 与应用程序的行业标准。
我在 app/api/mcp/route.ts 中内置了一个 MCP 服务器,使用 mcp-handler 库将三个工具(天气、数据库、邮件)注册为标准 MCP 工具:
typescript
import { createMcpHandler } from "mcp-handler";
import { z } from "zod";
const handler = createMcpHandler((server) => {
server.registerTool(
"get_weather",
{
title: "获取天气",
description: "获取指定城市的实时天气信息",
inputSchema: z.object({
city: z.string().describe("城市名称,如 '北京'"),
}),
},
async ({ city }) => {
const result = await getWeather(city);
return { content: [{ type: "text", text: result }] };
}
);
// ... query_database, send_email
});
配置 Claude Desktop 后,即可在对话中直接调用这些工具:
json
{
"mcpServers": {
"my-nextjs-tools": {
"url": "http://localhost:3000/api/mcp"
}
}
}
MCP 与 Function Calling 的关系:MCP 是标准化协议,Function Calling 是模型输出格式。两者协同工作------MCP 负责工具发现和调用,Function Calling 负责让 LLM 以结构化格式输出调用指令。
4.6 执行日志:让 Agent 的"思考"可见
为了提升可观测性,我实现了执行日志面板 。后端在执行每个节点和工具调用时,会生成结构化日志并通过 [LOG] 前缀流式发送:
typescript
logLines.push({
type: 'tool_call',
timestamp: Date.now(),
tool: 'get_weather',
args: { city: '北京' },
});
前端解析后展示在右下角日志面板中,支持实时滚动、清空和折叠:
text
[14:32:05] 🧠 智能代理开始处理:北京今天天气怎么样?
[14:32:06] 🔧 调用工具: get_weather({"city":"北京"})
[14:32:07] ✅ 工具返回: 当前北京天气:晴,温度25°C
[14:32:08] 💬 最终回答:北京今天天气晴朗,气温25°C...
这让整个 AI 决策过程变得透明、可调试------用户能清楚看到 Agent 每一步在做什么。
五、项目成果与数据
| 模块 | 实现内容 |
|---|---|
| 节点类型 | 8 种(输入、LLM、条件分支、HTTP 请求、数据库、天气、通知、智能代理) |
| 执行引擎 | 支持顺序执行、条件分支、错误处理、流式输出 |
| Function Calling | 支持 4 种工具(天气、数据库、邮件、HTTP),Agent 自主决策 |
| MCP 服务器 | 3 个标准 MCP 工具,可被 Claude Desktop 等客户端调用 |
| 日志系统 | 实时执行日志,支持可视化展示工具调用链路 |
| 代码量 | ~1500 行 TypeScript(前后端合计) |
六、踩坑与经验
6.1 React Flow 的 sourceHandle 陷阱
React Flow 在连线时会自动为每个 Handle 生成 sourceHandle。对于只有一个输出端口的节点,默认是 'output'。如果后端查找边时使用 !e.sourceHandle,会漏掉这些边。我的解决方案是:
typescript
const nextEdge = edges.find(e =>
e.source === currentId &&
(!e.sourceHandle || e.sourceHandle === 'output')
);
6.2 条件表达式安全执行
new Function 可以执行任意代码,存在安全风险。我改用 expr-eval 库,仅暴露 input 变量:
typescript
import { Parser } from 'expr-eval';
const parser = new Parser();
const result = parser.evaluate(conditionExpr, { input });
6.3 流式输出与条件分支的冲突
最初的版本试图让 LLM 流式输出,但条件分支需要拿到完整结果才能判断走向。最终采用 "同步执行 + 最终结果流式返回" 的混合架构------节点间同步执行,最终结果以流式输出。
6.4 MCP 服务器的部署路径
MCP 服务器通过 Next.js API 路由暴露(/api/mcp),与主应用共享同一端口和环境变量,无需额外启动 Python 进程,部署和维护成本大幅降低。
七、未来展望
- HTTP 请求节点增强:支持更灵活的认证方式(OAuth、API Key)和响应解析;
- 循环节点:支持对数组进行迭代处理;
- 子工作流:将一个完整的工作流封装成可复用的子节点;
- 版本管理:工作流定义的保存、加载和历史版本回滚;
- MCP 工具自动发现:让低代码平台自动连接 MCP 服务器,动态获取工具列表,彻底告别硬编码。
八、总结
这个项目的核心收获是:AI 应用开发正在从"写代码"走向"编流程" 。可视化画布降低了使用门槛,Function Calling 赋予了 AI 自主决策能力,MCP 标准化了工具生态。
当这三者结合时,我们得到的不再是一个"调用 API 的小工具",而是一个可扩展、可观测、可协作的 AI 应用开发平台------让 AI 从"聊天玩具"真正进化成能解决业务问题的"数字员工"。
最后
✅完整示例代码已开源 GitHub:github.com/wxw1314/my-... 如果对你有帮助,欢迎点个 Star,感谢支持!