开源地址: https://github.com/wqaetly/nkg-ai-flow

前言

最近 Agent、Workflow、Harness、AI Native 这些词越来越热,我自己实践下来最大的感受是:LLM 固然很强,但不够稳定

它很像一个极其聪明的推理核心。给它清楚的上下文,它能总结、规划、写代码、调用工具;边界没说清楚,它也能非常自信地把车开沟里。问题不是模型笨,而是我们不能把工程系统的稳定性押在模型每一次临场发挥上。

最开始做 Agent,写 Skill 是最快的。把步骤、约束、工具说明、验收标准写进文档里,让 LLM 照着干,验证一个想法非常舒服。

但 Skill 本质上还是指令,不是执行计划。流程短的时候还好,一旦步骤多起来,模型就可能漏掉关键动作,或者把“必须执行”理解成“可以参考”。比如该先检查文件状态再修改,它可能直接开改;该跑验证再总结,它可能写一句“验证通过”就收工。

这事不一定每次翻车,但只要关键步骤存在偶尔被略过的可能,工程链路就很难放心交给它。

所以我开始重新思考 Agent Harness。一个真正好用的 Agent 系统,不能只会“接模型”。它还要能表达流程、调试节点、回放错误,让 AI 在明确边界里参与修改流程,而不是直接冲进 Runtime 核心里开挖。

这就是 NKG AI Flow 的起点。

为什么自己做

LangFlow、LangGraph、Dify、Coze、n8n 这些工具我都试过,其中 LangFlow 和 LangGraph 用得更深一些。它们证明了可视化 Agent 工作流确实有需求,但深入到自定义节点、自定义 Flow、版本迭代和调试之后,总会有一种割裂感。

表面上是 AI 工作流,底层很多地方还是传统框架的扩展思路。想接一个新节点,要先理解内部目录、注册方式、配置约定、前端 Schema、后端执行,再祈祷改完以后别牵出一串连锁问题。

这对 AI Coding 很不友好。

很多系统还会把“画布状态”和“运行时契约”绑在一起。一个 Flow 如果只是前端状态的巨大 JSON dump,那它更像存档文件,不太像工程资产。调试也类似:Agent 出错通常不是一句报错能解释清楚的,没有节点级事件、Trace 和 replay,最后只能靠人肉翻日志。

说实话,翻多了真的会怀疑人生。

所以我想做的是一个从底层就适合 AI 参与开发的 Runtime:轻一点,边界清楚一点,调试证据多一点,热更新不要靠玄学。

AI Native Flow 是什么

AI Native Flow 是一个基于 TypeScript 的 Flow Runtime / Agent Harness。它的目标不是先做一个很炫酷的画布,而是先把运行时契约做好。

画布当然需要,但画布只是入口之一。同一个 Flow 应该能被 HTTP、CLI、MCP、SDK 和 Studio 调用,而且这些入口应该进入同一个 Runtime,消费同一条 Event Bus。这样 Studio 里看到的调试信息,和线上 HTTP 调用产生的事件,才是同一个世界里的东西。

flowchart LR
  A["AI / Developer"] --> B["Flow Builder / Graph Operations"]
  B --> C["Runtime"]
  C --> D["Event Bus"]
  D --> E["HTTP"]
  D --> F["CLI"]
  D --> G["MCP"]
  D --> H["SDK"]
  D --> I["Studio"]

目前内置节点已经有 startendtransformconditionhttptooltext_inputllmagent。这意味着它不是一个普通 DAG 运行器,而是已经能承载 LLM 节点、工具节点和一个小型 Agent 工具循环。

核心设计

Flow as Data

Flow 不能是一坨手写 JSON,它应该是稳定、可版本化、可校验的数据结构。

节点要有稳定 id,边要连到具体端口,端口要能表达控制流、数据流、事件流、错误流这些语义。这样 AI 修改 Flow 时,不是在一片没头绪的数据里乱改,而是在稳定的节点契约和具体代码里动手。

所以我更倾向让 AI 写 TypeScript FlowBuilder 代码,或者生成小粒度 Graph Operation,最后由 dump() 导出 JSON,再交给 Validator 检查。

模型上下文应该留给业务逻辑,不该拿去数括号。结构问题交给 Builder 和 Validator,比相信模型“感觉差不多”靠谱得多。

版本化和黑板变量

开发阶段热重载很好用,但生产阶段的热更新不能靠删缓存、换模块。

在 AI Native Flow 里,Flow Artifact 和 Node Logic Artifact 都要版本化。Registry 做原子 promote,Run 创建时 pin 住版本。旧 Run 继续用旧版本,新 Run 才切到新版本。这样热更新才是工程能力,而不是玄学仪式。

节点参数也一样,不能到处手填。LLM Base URL、默认模型、超时时间、HTTP 端口、业务开关,一旦散落在不同节点里,改一次配置就像在仓库里找散落的螺丝。

所以我把这些配置放进项目内置的环境变量模块,也就是黑板。节点配置里保存引用关系,运行时通过 ctx.variables 取值。AI 生成节点时,不需要猜用户机器上的 .env 长什么样,只要按约定引用 LLM_BASE_URLLLM_DEFAULT_MODEL 这类变量名。

为了保密,每个 Flow 再配一份同目录的伴生 env JSON,本机真实配置 gitignore,不提交。这样本地可用,云端也不会把敏感配置带上去。

事件流、日志和 AI 自查

这次实践里,我发现一个很重要的点:节点形式的流程,配合完善的日志系统和追踪系统,会让 AI 非常容易看懂一次 Run 到底发生了什么。

每个节点什么时候开始、什么时候结束、拿到了什么输入、调用了什么工具、输出了什么结果、哪里报错,都可以进入统一的 Event Bus。stdoutstderr 可以做诊断日志,但 LLM token、工具调用、节点状态、错误、取消、回放这些语义事件,应该统一成标准 NodeEvent

这对人调试当然有用,但更重要的是,对 AI 也有用。

以前让 AI 修一个复杂 Agent,它很多时候只能靠猜:猜流程怎么跑,猜哪个节点错了,猜日志里哪一行有用。现在不一样了。节点 + trace + log 把一次运行变成了可读的流程证据,AI 可以先跑 Flow,再看追踪信息,根据调用顺序、节点输入输出和详细日志定位问题,然后修改 Prompt、节点逻辑、工具调用或规则校验,再重新运行。

这就形成了一个很自然的自我检查闭环。

尤其是 vibe coding 这种场景,AI 可以不断运行 Flow,根据 trace 里的证据自己查问题,再不断更正和优化 Flow。它不是在黑盒里瞎改,而是能看到自己刚才到底干了什么、哪里开始歪、改完有没有变好。

讲道理,这一点非常关键。因为它意味着 Harness 不只是方便人类调试,也是在给 AI 自我迭代提供眼睛。

实践出来的 Harness 规则

这些东西最后沉淀下来,其实就变成了一套适用于 AI 的 Harness 规则。它不是拍脑袋想出来的,而是我在开发卡厄斯梦境、HKS,以及把 Skill 转成 Flow 的过程中一点点踩出来的。

我现在大致把它归成五类:

  • Instructions:Flow 自己要说清楚目的、调用者、输入输出、必用工具、禁止行为和验收标准。
  • State:状态流动要显式,context 不是垃圾桶,数据依赖走 data edge,不能靠“前一个节点大概干了什么”这种玄学。
  • Verification:关键路径必须能验。LLM、联网搜索、HTTP、RAG、向量库要走真实调用,失败就硬失败,不准 mock、不准 stub、不准假成功。
  • Boundaries:节点边界要清楚。确定性候选生成、过滤、打分、Prompt 拼装和 LLM 调用最好分开。LLM 是判官和写手,不是推荐引擎。
  • Handoff:交付要能续接。最终输出消费结构化 context,trace、event、log 要足够让另一个 Coding Agent 在新会话里接着干。

这些规则听起来有点严,但不是为了折磨人。恰恰相反,这是给 AI 用的护栏。只有把 Instructions、State、Verification、Boundaries、Handoff 这些东西钉住,Coding Agent 才能真正参与 Flow 开发,而不是写完一坨看起来能跑、实际上没人敢维护的东西。

所以项目里也内置了一份 nkg-ai-flow-skill。它不是这个 AI 系统对外提供的能力,也不是给业务对话 Agent 用的,而是给 vibe coding 过程里的 Coding Agent 看的一份开发指引。

这份 skill 会告诉 AI:新的 Flow 应该怎么构造,节点应该怎么拆,FlowBuilderdefineNode、context 传递、变量引用、验证命令这些东西应该怎么用。没有这类规范时,AI 很容易在开发过程中碰壁:一会儿手写一坨不稳定 JSON,一会儿把状态塞进错误位置,一会儿跳过验证。有了这套约定之后,它就能少走很多弯路,沿着项目本身认可的方式去开发新的 Flow 和相关节点。

Skills 转 Flow

早期我还是推荐用 Skill 来验证 Agent 流程。原因很朴素:快。

Skill 写起来快,改起来也快。需求还没稳定的时候,先用 Skill 探路很划算。等流程稳定之后,再把固定步骤代码化,把需要发散的地方继续留给 LLM。

固定流程进代码,发散部分留给模型,稳定性也就慢慢回到工程系统里。

@ai-native-flow/skill-to-flow 就是这个思路的实现。

当前的 skill_to_flow 是一个 10 节点、16 条边的 Flow,大致流程如下:

flowchart LR
  A["SKILL.md"] --> B["skill_parser"]
  B --> C["skill_planner"]
  C --> D["node_designer"]
  D --> E["code_synthesizer"]
  E --> F["flow_validator"]
  F --> G["package_materializer"]
  G --> H["end"]

  B -.规则解析.-> B1["rules"]
  C -.LLM 规划.-> C1["LLM"]
  D -.LLM 并发设计.-> D1["LLM"]
  E -.LLM 合成 + 规则组装.-> E1["LLM + rules"]
  F -.真校验.-> F1["validator"]
  G -.写文件 + 跑验证.-> G1["agent"]

Skill 解析适合用规则做,这一步不需要模型发挥。规划和节点设计交给 LLM,因为这里确实需要理解和抽象。Flow JSON 不让模型硬写,而是通过 flow-builder 组装。校验也不看字符串里有没有 defineNode,而是真调用 flow-validator

最后生成的包由内置 agent 节点落盘,它可以写文件、跑验证、做局部修复。

这一套下来,LLM 不是被赶出系统,而是被放到了更合适的位置。

Agent 节点

agent 节点很容易被误解成“又一个把所有事情丢给大模型的黑盒”。我的定位刚好相反:它应该是一个受限的工具循环。

输入任务和上下文,模型每轮只做一次决策,要么调用一个允许的工具,要么给出最终结果。文件读写、搜索、批量写入、命令执行这些动作,不在模型手里,而是在 Runtime 注入的 toolHost 手里。

flowchart LR
  A["task + context"] --> B["agent node"]
  B --> C["LLM decision"]
  C --> D{"tool call or final?"}
  D -->|tool call| E["toolHost"]
  E --> F["list/read/grep/edit/write/run_bash"]
  F --> B
  D -->|final| G["summary + context"]
  E --> H["tool_log"]
  H --> I["runtime facts"]
  I --> G

工具权限也不是模型说了算。节点配置里有 allowedToolsallowBashworkingDirtimeoutMsmaxOutputChars。模型可以提议“我要改这个文件”,但能不能改、在哪个目录里改、能不能跑命令,是 Runtime 说了算。

我还专门限制了:changed_fileswritten_filesverification_resultsvalidator_status 这些运行时事实,不让模型自己编。模型只写它负责的总结和补充上下文,文件变更、命令结果、验证结果来自真实工具日志。

少了这个限制,调试时会很麻烦。模型一句“验证已通过”,我们还得猜它是真的跑了,还是只是心情不错。

Studio

Studio界面

Studio 的定位很清楚:它是编辑和调试入口,不是另一套执行系统。

为什么还需要 Studio?因为 Flow 不是一段线性脚本。节点参数、端口、边、事件、Trace、流式输出混在一起之后,只看 JSON 或 CLI,讲道理还是很容易疲倦的。

Studio 要解决的就是这个问题:把 Flow 的结构、运行状态和调试证据放在同一个界面里。节点有没有连错,参数是不是引用了正确变量,哪一步开始输出异常 token,工具调用到底返回了什么,都应该能顺着画布一路追下去。

AI 参与改 Flow 之后,Studio 的价值更明显。AI 可以生成 Graph Operation 或 Patch Proposal,但我们需要有一个地方看差异、看校验错误、看运行结果,再决定要不要接受。它不是给 Runtime 套一层皮,而是给我们和 AI 一个共同的检查台。

flowchart LR
  A["Studio Canvas"] --> B["Graph Operation"]
  B --> C["Runtime Graph Schema"]
  C --> D["Flow Validator"]
  D --> E["Flow JSON"]

  A --> F["Run / Stream"]
  F --> G["HTTP Handler"]
  H["CLI / SDK / MCP"] --> G
  G --> I["Invocation Router"]
  I --> J["Runtime"]
  J --> K["Event Bus"]
  K --> L["Run Timeline / Trace / Stream Inspector"]

React Flow 只负责画布和交互适配,运行时图模型仍然是独立 Schema。编辑动作通过 Graph Operation 表达,运行时还是走同一套 Runtime。从 Studio 点击 Run,和从 HTTP 或 CLI 调用 Flow,本质上是同一条链路。

实例

卡厄斯梦境配队 AI Agent 与 LOL 海克斯大乱斗 AI 助手

口说无凭,我已经用 NKG AI Flow 做了一个卡厄斯梦境配队 AI Agent,用来处理游戏里的角色配队、机制解释和结果推荐。效果还不错,也收获了很多玩家的认同和喜欢。

QQ20260610-211648.png

这个场景很适合验证前面那些设计。配队不是简单问答,而是意图分析、检索、候选构建、规则校验、机制解释、最终输出一整套流程。如果把这些都塞进一个大 Prompt,很难调整。拆成节点之后,哪个环节不满意,就改哪个环节,不用把整套 Agent 推翻重来。

同样的思路,我也用在了另一个实例上:LOL 海克斯大乱斗 AI 助手

海克斯大乱斗的数据更杂,用户问法也更发散。有人问某个英雄怎么玩,有人问技能强化怎么选,有人问装备和强化之间的相性,还有人只是想知道这把到底该怎么爽。看起来都是“攻略问答”,拆开之后就是意图识别、联网搜索、RAG 检索、英雄/装备/强化数据整理、结果生成和规则校验。

这类 Agent 最怕的就是把所有东西塞进一个大 Prompt,然后祈祷模型别漏规则。某些装备互斥,某些强化只对特定技能有意义,某些推荐还会受到版本环境影响。这些东西如果只靠模型临场发挥,讲道理肯定不稳。

所以 HKS 这个实例里,我更看重的不是模型能不能说得漂亮,而是 Flow 能不能把它托住:先明确用户意图,再去拿最新资料,再把本地知识和搜索结果合并,最后经过规则约束和结果整理。AI 负责在合理空间内推理,Harness 负责把这个空间圈出来。

联网搜索和 RAG

游戏会更新,Flow 可以定型,但数据不是一成不变。角色调整、版本环境、社区理解都可能变化。旧 Prompt 写得再漂亮,也遭不住资料过期。

所以我更愿意把联网搜索节点放在推理前面。它先去拿最新资料,再让后面的节点判断。这样流程不用天天改,数据和输出还能自动更新。

RAG 负责另一块:项目自己的资料。角色机制、规则笔记、测试结论、历史方案,这些东西不适合每次都塞进 Prompt。需要的时候查一小段就够了。它的价值不是“多给模型一点上下文”,而是少给模型一堆没用的上下文。

网页检索节点也不能只当搬运工。整页网页直接丢给 LLM,模型看着累,我们排查也累。更好的方式是先整理一下:这条资料从哪来,什么时候的,主要说了什么,和当前问题有什么关系。

后面的 LLM 节点拿到的是干净材料,推理质量自然会稳很多。我用的是自建 SearXNG 方案,体验很不错,推荐。

总结

说了这么多,我个人感触最深的其实还是一句话:LLM 像一个极其强力的海克斯科技核心,但它本身不够可控。

我们需要为它搭一套高达机甲级别的控制系统,也就是 Harness。这个系统要管流程、管工具、管数据源、管规则,也要能留下足够完整的日志和 trace,让人能调试,让 AI 也能自查。

目前这个高达机甲的设计方案,我给出的答案就是:NKG AI Flow

这套东西肯定还会继续迭代。Flow Runtime、Studio、Skill 转 Flow、Agent 节点、联网搜索、RAG、Run Trace,这些模块后面都还有继续打磨的空间。现在先把阶段性的设计思路记下来,后续继续开干。