项目开源地址:https://github.com/GuoBug/ai-prompt-orchestrator
往期回顾:
📖 《从 0 到 1 打造 AI 提示流编排器:双引擎架构与纯前端 DAG 实践(开源系列 01)》
📖 《从 0 到 1 打造 AI 提示流编排器:Kahn 拓扑排序、安全变量引擎与异步运行时实战(开源系列 02)》
💡 前言与承接
在 [系列 01] 与 [系列 02] 中,我们先后攻克了提示流编排器的双引擎架构设计、分层 Kahn 拓扑排序以及防原型链投毒的安全变量引擎。底层的调度器(Scheduler)与异步运行时(Runtime)已经能够毫秒级调度复杂的 DAG 工作流。
然而,当系统真正从“算法原型”迈向“开箱即用的实用工具”时,现实中各种更严苛的工程挑战接踵而至:
- 厂商生态割裂与模板报错:各家模型请求协议与命名各异,导入包含
gpt-4o的预设模板时,如果用户切到 Google 或 DeepSeek,接口直接抛出404 model_not_found导致工作流崩溃; - 推理模型思维链(Reasoning Process)的渲染难题:面对 DeepSeek-R1、Gemini Thinking 这类带深度思考过程的模型,如何做双流 SSE 渲染,既展示思考过程又不污染下游节点的结构化数据提取?
- 纯前端环境下的全链路日志记录与隐私脱敏:如何在浏览器内提供类似现代 IDE 的三级调试控制台,同时 100% 杜绝
sk-***等 API 密钥在日志中泄露? - 画布交互的防御性体验:用户在画布上连出循环依赖(死锁环路)时,如何在交互拖拽期毫秒级拦截?
本文将全面复盘 PatchCat 在多模型接入、流式思维链、安全日志体系以及画布交互工程上的完整实践。
模块一:多模型生态适配与启发式模型回落(Heuristic Fallback Mapping)
1. 是什么与解决什么痛点
现代 AI 应用不能绑定在单一厂商上。PatchCat 原生支持 Google Gemini、DeepSeek、OpenAI、SiliconFlow(硅基流动)与本地 Ollama。
但在多模型编排系统中,存在一个极其常见的产品痛点:预设模板与跨厂商的“水土不服”。
- 典型场景:社区预设了一个《客服意图分类》工作流,节点硬编码了
gpt-4o-mini; - 崩溃发生:用户手里只有 Google Gemini 的 API Key,切换服务商为 Google 后点击运行,Google API 会立即抛出
400/404: Model 'gpt-4o-mini' does not exist,整个工作流直接中断。
为了彻底解决这一问题,我们设计了启发式模型回落机制(Heuristic Fallback Mapping)。

2. 为什么叫“启发式(Heuristic)”?
“启发式”指的是:不依赖死板的全局全量硬编码字典,也不弹窗打断用户操作,而是基于模型命名的生态模式与前缀特征进行快速智能推断:
- 如果服务商是 Google,但模型名称完全不包含
gemini关键词,系统立即启发式判定这是“跨厂商遗留配置”,自动降级为当前厂商的默认模型(如gemini-2.5-flash); - 如果服务商是 OpenAI,却发现模型名带有
gemini-、claude-或deepseek-前缀,自动替换为gpt-4o-mini; - 如果配置的模型明确存在于厂商的动态模型列表(
availableModels)中,则直接放行。
3. 核心代码实现
/**
* 启发式模型智能解析与跨服务商安全回落
*/
export function resolveTargetModel(
configuredModel: string | undefined,
providerId: string,
availableModels: string[] = [],
defaultModel: string,
): string {
if (!configuredModel || configuredModel.trim() === '') {
return defaultModel;
}
const clean = configuredModel.trim();
// 1. 如果模型明确存在于当前服务商可用列表中,直接放行
if (availableModels.includes(clean)) {
return clean;
}
// 2. 启发式特征嗅探与跨厂商错配检测
// Google Gemini 生态嗅探
if (providerId === 'google') {
if (!clean.toLowerCase().includes('gemini')) {
return defaultModel; // 回落到 gemini-2.5-flash
}
}
// DeepSeek 生态嗅探
if (providerId === 'deepseek') {
if (!clean.toLowerCase().includes('deepseek')) {
return defaultModel; // 回落到 deepseek-chat
}
}
// OpenAI 生态嗅探
if (providerId === 'openai') {
if (clean.startsWith('gemini-') || clean.startsWith('claude-') || clean.startsWith('deepseek-')) {
return defaultModel; // 回落到 gpt-4o-mini
}
}
return clean;
}
此外,针对 Google 免费层高并发容易遭遇的瞬态 503 Service Overloaded,执行器内置了指数退避重试(Exponential Backoff)与贴心的中文诊断提示,极大提升了免费 Key 运行的韧性。
模块二:双流 SSE 吐字与思维链独立渲染(Dual-Stream SSE)
1. 是什么与设计权衡
以 DeepSeek-R1、Gemini 2.5 为代表的新一代推理大模型,会在输出最终答案前输出长达数千字的思考过程(reasoning_content 或 <think>...</think> 块)。
如果将思考过程与最终答案混在一个字段中,会引发灾难性后果:
- 下游提取瘫痪:下游的 JSON 抽取器或 JavaScript 代码沙箱试图解析输出时,会被冗长的思考文字干扰导致语法报错;
- 画布界面爆炸:长达几千字的思维链直接撑爆画布节点卡片。

2. 核心架构解法
- 流式分块解复用(Demuxing):在 SSE 接收层对
delta.reasoning_content与delta.content进行实时双通道分流; - 抽屉式双流呈现:画布节点只展示最终答案生成的轻量状态,右侧属性抽屉则以可折叠的终端风格实时打字机渲染 DeepSeek-R1 的完整思维链,做到“可看可查、互不干扰”。
模块三:三级日志记录体系与递归安全脱敏(Sanitize Engine)
1. 为什么纯前端也需要一套完整的日志体系?
在纯浏览器执行(Client-Only BYOK)模式下,如果仅靠浏览器的 console.log,用户在调试几十个节点的大型工作流时无异于大海捞针。
系统打造了类似现代 IDE 底部的可折叠、可调高度的三级日志控制台:
| 日志等级 | 记录范围 | 适用场景 |
|---|---|---|
Summary (概要) |
系统启动/结束、节点状态流转、HTTP 状态码、毫秒级耗时、Token 统计、异常栈 | 生产运行、日常监控 |
Detailed (详细) |
在概要基础上,记录节点拓扑波次、模型超参(Temperature、MaxTokens) | 流程性能调优、排查分支走势 |
Development (开发) |
在详细基础上,完整捕获变量插槽替换前后的完整 Prompt、模型原始返回全文 | 提示词精细调优、Badcase 深度复盘 |
2. 绝对安全红线:递归脱敏清洗器(sanitizeData)
无论用户开启多么详细的开发日志,API 密钥绝不允许以明文形式出现在日志面板、导出的 JSON/TXT 文件或投屏中。
在数据写入 Zustand 日志 Store 之前,必须经过强力脱敏管道:
/**
* 递归深度脱敏清洗器:100% 物理消除所有 Headers、Query 与 Payload 中的敏感密钥
*/
export function sanitizeData(data: unknown): unknown {
if (!data) return data;
if (typeof data === 'string') {
return data
// 掩码 OpenAI / DeepSeek / 兼容接口 Key
.replace(/sk-[a-zA-Z0-9_-]{10,}/g, 'sk-***[REDACTED]')
// 掩码 Google Gemini AI Studio API Key
.replace(/AIzaSy[a-zA-Z0-9_-]{20,}/g, 'AIzaSy***[REDACTED]')
// 掩码 Bearer Token
.replace(/Bearer\s+[a-zA-Z0-9._-]+/gi, 'Bearer ***[REDACTED]');
}
if (Array.isArray(data)) {
return data.map(item => sanitizeData(item));
}
if (typeof data === 'object') {
const sanitized: Record<string, unknown> = {};
for (const [key, value] of Object.entries(data as Record<string, unknown>)) {
const lowerKey = key.toLowerCase();
// 针对敏感字段名进行全量打码
if (['apikey', 'api_key', 'token', 'authorization', 'secret', 'password'].includes(lowerKey)) {
sanitized[key] = '***[REDACTED]';
} else {
sanitized[key] = sanitizeData(value);
}
}
return sanitized;
}
return data;
}
模块四:画布交互工程:实时环路死锁阻断与卡片轻量化
1. 实时环路死锁检测(Canvas Cycle Detection)
在 DAG 工作流中,一旦用户不小心把下游节点的输出连回上游输入(例如 A -> B -> C -> A),拓扑排序就无法收敛,如果不做防护,运行时将陷入死锁或内存溢出。
我们把安全防线提前到了“连线拖拽交互期”:
- 每次连线发生时,底层图引擎以毫秒级运行快速深度优先搜索(DFS)探测环路;
- 一旦发现成环,画布立即触发:
- 拦截当前连线并拒绝挂载;
- 环路相关节点边框显示红色呼吸高亮;
- 画布顶部弹出警示横幅(Banner),明确告知用户形成死锁的节点链路。

2. 节点卡片抽屉化解耦
在早期原型中,节点卡片内部放满了 Prompt 编辑框、输入框和执行结果。当画布上有 10 个以上节点时,界面拥挤不堪。
重构策略:
- 画布卡片极简:卡片只保留节点类型图标、状态指示灯、关键变量名以及执行耗时;
- 右侧抽屉承载重型交互:点击任意节点,右侧滑出属性抽屉,承载 Markdown 语法高亮、JSON 编辑器、代码沙箱脚本与输出结果预览;
- 连线悬停剪刀(Hover-to-Delete):鼠标悬停在连线上即出现剪刀按钮,一键切断连接,交互体验极其流畅。
模块五:工程化、测试与 PatchCat 品牌演进
随着功能体系的全面完善,项目正式确立品牌形象:PatchCat(赛博独眼猫,寓意精准修补、严谨编排与丝滑串联)。
- 50/50 单元测试全覆盖:用自动化测试锁定了分层 Kahn 算法、环路检测、变量递归提取与脱敏逻辑,确保重构过程零 Regression;
- 双主题支持:提供专为深夜编码打造的 Dark Slate(
#0B0F17极黑纯色背景)与 Modern Slate 明亮模式。
🎯 总结与后续演进
从 [系列 01] 的理念构想,到 [系列 02] 的底层调度算法,再到本篇 [系列 03] 的多模型生态、流式思维链、安全日志与防御性交互工程,PatchCat 已经完成了一次完整的从“玩具原型”到“生产就绪的实用工具”的蜕变。
后续我们将继续探索:
- 本地 Python 沙箱深度执行(结合 Local FastAPI 引擎);
- 复杂预设模板生态(研报反思工作流、多 Agent 辩论等);
- 工作流 JSON Schema 一键导出与社区分享。
欢迎体验项目在线 Demo,并在 GitHub 上与我们共同探讨!
💡 项目开源与交流:如果你对前端图计算、轻量级 AI 工作流编排或 Vibe Coding 实践感兴趣,欢迎访问项目仓库并点个 Star ⭐️ 支持!
🔗 GitHub 仓库地址:https://github.com/GuoBug/ai-prompt-orchestrator