
1. 从获客工具说起一个好用产品的天花板1.1 第一版AI获客工具的原始形态先说清楚我们最早做的是什么。名字很直白叫AI获客助手本质上是一个嵌在企业官网右下角的Web聊天窗口。访客打开页面AI先打招呼根据访客浏览的页面、停留时间、点击行为主动发消息问一句您对我们哪个产品感兴趣。然后通过对话了解需求、留资、约演示最后把线索同步到销售系统。当时这个工具确实解决了一个真问题企业网站的流量来了但大量访客看一眼就走连个联系方式都没留下。人工客服只能覆盖一小部分时段深夜来访的潜在客户基本全流失。AI对话机器人相当于7乘24小时守在大门口先把客人的意图摸清楚再决定要不要喊销售过来聊。技术栈并不复杂前端是React后端是Node.js对话层直接调商用大模型API知识库用最简单的向量检索。上线后效果出奇地好陆续签了几十家客户有做SaaS的、有做工业设备的、有做教育培训的连月子中心都找上门来。1.2 工具做得越深越逼近那个天花板一开始大家都很兴奋但大概跑了半年问题开始集中爆发。每个客户都在提定制需求而且是完全不同的方向。做SaaS的说你能不能帮我把对话记录同步到CRM系统做培训的说你的AI能不能根据学员的提问自动推荐课程做设备的说我们内部售后有几百份手册能不能让AI直接查手册回答问题每个需求单独看都不难团队技术能力也够但做着做着就发现不对劲。代码里开始充斥各种互相冲突的配置项今天的改动可能影响明天的客户有些场景明明可以用同一个对话引擎却被各自的需求拧成了麻花维护成本直线上升。有一件事让我印象特别深。我们内部想把这个对话引擎复用到两三个不同场景里一个放在官网当导购一个放在员工后台当知识库问答助手还有一个给销售团队当话术陪练。本来以为改改配置就行结果发现根本行不通。对话引擎和获客场景的耦合太深了整个系统里到处是线索字段、转化率统计、话术模板想抽出来得伤筋动骨。1.3 触发重写的三个临界信号第一个信号来自客户。有一个客户提了一个极其朴素的请求我不想把它放在网页右下角我能不能把它放进自己的管理后台里让我的员工用它查内部资料这个请求听起来合理但落到技术上意味着我们要把整个界面从访客端对话窗改造成内部工作台。这个改动本质上已经超出了获客工具的产品边界。第二个信号来自社区。我们在技术群里分享过一些实现方案有个开发者问了一句你们这个接的是闭源大模型API我这边数据不能出内网想接本地部署的开源模型能做到吗我们沉默了很久。当时架构里所有能力都绑定在云端调用上本地模型这条路径几乎要从头打通。第三个信号来自代码本身。团队内部想大规模重构一次结果评估下来发现重构的成本比重写还高。与其缝缝补补不如推倒重来。回头看问题的本质不是获客工具做得不好而是我们把它做成了一个封闭应用。应用的特点就是场景固定、边界清晰、不可扩展。但AI这个技术方向恰恰是应用场景最发散、变化最快的领域。死守一个应用形态等于给自己的未来画了一个小小的圈。2. 为什么是Web AI 交互层而不是更好的 AI 助手2.1 交互层的定义不只是一个聊天框Web AI交互层这个词听起来有点玄但如果换成一句人话就是我们不再做一个能跟用户聊天的网页而是做一个能让任何网页拥有AI对话能力的基础设施。传统意义上的Web AI助手是一个完整的应用。它有登录注册、有会员体系、有聊天界面、有历史记录、有设置面板。用户打开这个网站用它来完成和AI聊天这件事。而交互层是完全不同的形态。它不关心你做的是什么业务不关心用户是谁也不关心界面长什么样。它只提供一种能力把大模型的对话能力封装成一套标准的、可嵌入的、可以被任意Web应用调用的服务。你可以把它理解成水电管线而不是某一栋装修好的房子。房子可以装修成客厅、厨房、卧室但水电管线是一致的。这个转变意味着什么意味着你不再需要跟客户说请使用我们的产品而是跟客户说你的业务里任何需要对话的地方都可以接上我们的能力。官网需要就嵌官网后台需要就嵌后台小程序需要就走WebView甚至你只想在命令行里调我们也提供API。2.2 交互层与普通AI助手的本质差异先上一张对比表看得更清楚维度传统Web AI助手Web AI交互层产品形态一个完整的聊天网站一套API加可嵌入前端组件场景归属用户来你的网站使用能力去客户的页面落地定制方式提供有限的设置选项支持自研UI、自接模型、自定义知识库扩展边界受产品功能清单限制受接口能力和开发者想象力限制多模型支持通常绑定单一模型可切换/混用不同模型嵌入能力一般不提供原生支持iframe、Web Component、SDK传统AI助手解决问题的思路是用户来使用我们的产品交互层的思路是我们的能力去适应客户的场景。举一个真实的接入案例。我们重构之后同一套内核跑在三个完全不同的地方某电商官网的智能导购、某企业内部的知识库问答助手、某猎头团队的简历筛选助手。这三个场景的UI完全不同、交互模式完全不同、背后的知识和工具也完全不同但底层对话引擎是同一套。如果是传统Web AI助手这三个场景你得开发三个独立产品累死团队也覆盖不过来。2.3 为什么押注Web而不是客户端或浏览器插件这个选择我们内部吵过一轮。有人提议做桌面客户端理由是可以本地存储用户数据、可以做端侧推理、体验更原生有人提议做浏览器插件理由是Chrome商店分发方便、可以读取网页上下文。但最终我们还是选了Web。理由有三条。第一条Web零安装、免部署。客户拿到一个链接就能打开嵌入到自己的页面只需要一行代码。桌面端和插件都需要用户主动安装这在to B场景里是一个巨大的门槛。你让企业IT部门去部署一个桌面客户端和给他们的官网加一行script标签难度不是一个数量级。第二条Web的嵌入能力最强。企业想要的是把AI能力放进自己的业务系统里而不是让用户跳到另一个网站去对话。iframe、Web Component、SDK注入这些机制都是Web天然的生态优势。第三条Web的协议生态成熟。SSE、WebSocket、WebRTC、Service Worker各种实时通信和离线能力都有成熟实现。随着浏览器能力的增强很多原本必须借助桌面端的场景比如实时语音对话、屏幕共享、本地小模型加载Web也能逐步覆盖。选型的核心逻辑很简单在AI能力普及的早期阶段谁能用最低的成本触达最多的场景谁就能跑得最快。Web是目前唯一一个发个链接就能用、贴段代码就能嵌、改个样式就能换皮的平台。这不是说客户端没有价值而是说在这个时间节点Web是最短的那条路径。3. 开源的动机与商业逻辑3.1 为什么选择开源这条路决定开源之前我们内部开了三次会争论很激烈。反对的人说这是我们的核心资产开源了靠什么吃饭支持的人说如果不开源我们永远只是一个几十人规模的小工具团队做不成生态。最后说服所有人的是下面这个判断AI时代的开发者正在集体重复造同一个轮子。不管是做官网导购、做知识库问答、做客服系统还是做内部工具所有人的底层需求其实是一样的我需要一个能接大模型、能管理上下文、能调用知识库和工具、能前端对话展示的Web应用。这个应用本身没有行业属性但每个团队都在从零开始写。与其让一千个团队各自重复造轮子不如我们把轮子做成开源项目把对话内核和标准接口贡献出去。别人基于我们的内核做二次开发相当于替我们把轮子推向了更多场景他们的反馈和改进又反哺内核本身。开源不是放弃商业而是换一个打法建立生态。3.2 开源项目的定位内核、插件、协议三位一体我们给开源项目的定位非常明确一个Web AI交互层的参考实现同时也是一套可以落地的完整方案。按层次拆开是这样的内核层负责会话管理、消息流转、上下文管理、记忆管理和工具调用调度。这是整套系统的发动机不依赖具体模型不依赖具体业务。插件层负责扩展能力。有人需要知识库就装一个向量检索插件有人需要对接企业微信就装一个消息桥接插件有人需要定时总结对话就装一个任务调度插件。插件机制让内核保持核心纯粹又让系统可以无限延展。协议层是最容易被忽略但价值最高的一层。我们定义了AI能力接入Web应用的标准协议如何发起对话、如何处理流式输出、如何注册工具、如何同步会话状态。协议的意义在于任何支持这套协议的客户端和服务端都可以互相通信形成一个开放生态。开发者的使用路径通常是Fork项目改掉品牌信息接入自己的模型API或本地模型配置知识库然后部署上线。快的团队一天就能跑通。3.3 社区生态如何形成正循环很多开源项目死在只有代码没有社区我们在这个问题上花了大量精力。第一件事是文档。代码写得再好文档不行社区就起不来。我们把文档分成快速开始、架构指南、二次开发指南、插件开发指南四部分每个部分都配了可运行的示例代码。尤其是插件开发指南基本做到了照着写就能跑通的程度。第二件事是issue管理。我们设了几个典型的标签bug、enhancement、question、good first issue。其中good first issue专门标记那些适合新手上手的问题让首次贡献者能快速进入状态。这个细节对社区活跃度帮助巨大。第三件事是接受不完美的贡献。刚开始有些PR写得很粗糙只要思路对我们宁愿花时间帮贡献者调整代码结构也不要打击积极性。原因很简单每一个被合并的PR都会让贡献者从路人变成共建者。社区转起来之后商业价值自然就出来了。有企业用户基于开源版做了私有化部署有服务商基于内核帮客户做定制开发还有一些头部公司主动来问我们提供不提供托管服务。开源的底盘加上商业的服务两条腿走路反而比原来闭源工具时代看得更清楚。4. 重写中的核心架构与技术选型4.1 分层架构三层分开各自演进重写最大的变化是从一个单体应用拆成了三个松散耦合的层次。内核层是所有对话能力的核心包括会话管理、消息模型、上下文管理、工具注册、模型适配。它不依赖HTTP不依赖数据库不依赖任何具体的前端框架可以被嵌入到Node.js服务、Python服务甚至边缘函数里。服务层负责对外提供能力接口。包括认证鉴权、限流熔断、会话同步、文件上传、异步任务调度。服务层是内核层的适配器把内核能力翻译成Web可调用的API和实时通道。交互层负责最终的用户体验。包括一个开箱即用的Chat UI组件、一个面向开发的SDK以及一个支持自定义主题的嵌入方案。交互层通过标准协议与服务层通信理论上只要协议不变前端完全可以替换成Vue版、Svelte版甚至原生JS版。分层的最大好处是每个层次都可以独立演进。模型厂商发布了新模型内核层扩展一个adapter就能支持客户想要一个完全定制的前端交互层重新写一套即可内核和服务层动都不动。这在旧的单体架构里是无法想象的。4.2 关键决策一流式交互用SSE还是WebSocketAI对话最常见的交互形式是流式输出模型一边生成一边把token推给前端用户看到的是一个字一个字蹦出来的效果。选对流式传输方案基本就决定了整个系统的通信架构。我们对比了两个方案SSEServer-Sent Events是HTTP协议上的单向推送通道服务器可以持续把数据推给客户端。优点是与HTTP完全兼容不需要额外握手协议自带断线重连机制负载均衡和代理服务器都认识它。缺点是只能服务端向客户端单向推送客户端要发消息得另走一个普通HTTP请求。WebSocket是双向全双工通信客户端和服务端可以互相推数据。优点是双向实时通信缺点是要维护长连接状态需要处理心跳保活穿透代理时需要额外配置后端负载均衡也要考虑连接亲和性。对绝大多数提问-回答型的人机对话场景SSE完全够用用户发起请求用普通POST模型输出用SSE推送这比WebSocket省心得多。只有当系统需要服务端主动向客户端推送事件时比如通知有新的会话分配给你了才需要引入WebSocket。最终我们采用了一个务实的方案默认走SSE在业务需要的模块里单独引入WebSocket。避免一开始就把系统绑死在复杂的长连接模型上。实测效果非常稳定SSE配合合理的重试机制完全可以支撑企业级并发。4.3 关键决策二模型接入层怎么设计才能兼容本地模型模型接入是重写时争议最大的模块。林林总总的模型来源包括封闭的商业API、开源的云端API、企业内网的本地模型服务甚至未来可能出现的端侧模型。我们的解决方案是抽象一个ModelAdapter接口把所有模型来源统一封装成四个方法chat用于非流式对话stream用于流式对话embeddings用于向量化tools用于工具调用。// 模型适配器接口示意 interface ModelAdapter { chat(messages: Message[], options?: ChatOptions): PromiseMessage; stream(messages: Message[], options?: StreamOptions): AsyncIterableDelta; embeddings(inputs: string[]): Promisenumber[][]; tools?: ToolExecutor; }默认实现走的是OpenAI兼容协议因为目前大部分模型服务都提供了这个协议的兼容层把它当作普通话来用。但在协议之外我们重点做了一件事支持本地模型服务。企业数据不能出内网这是很多行业客户的高压线。他们会在内网部署Ollama或者vLLM跑开源模型我们提供一个LocalAdapter让系统直接对接内网地址。对上层应用来说无论是云端API还是本地模型调用方式完全一致切换不需要改任何业务代码。这个设计带来一个很实际的好处客户可以先用云端模型快速验证效果验证通过之后再切换到私有化部署模型。业务代码零改动只是改一个配置文件的事。4.4 关键决策三知识库与工具调用的抽象AI对话真正值钱的不是闲聊而是懂业务。懂业务靠两个能力一是检索企业内部知识二是调用业务系统里的工具。知识库方面我们定义了VectorStore接口接具体的向量数据库实现。这样既可以接开源的向量库方案也可以接商业的向量检索服务。每次收到用户问题系统先在知识库里做语义检索把命中的知识片段连同问题一起送进大模型。这个流程就是业内常说的RAG。工具调用方面我们参考了Function Calling的标准思路。对话引擎在每轮回复前会判断是否需要调用外部工具如果需要就以结构化参数的形式触发工具执行执行完结果再回传给大模型生成最终答复。举一个典型的场景用户问帮我查一下上个月的订单量对话引擎先识别意图然后调用订单查询工具工具返回数据引擎把数据组织成自然语言回复。整个过程对用户是无感的用户只看到AI帮我查了订单。这套抽象让交互层彻底摆脱了具体业务。开发一个银行客服系统接银行的账户查询工具开发一个HR助手接人事系统里的请假和考勤工具。内核什么都不需要改。4.5 前端交互层的设计思路前端是用户能直接感受到的部分交互层的价值在这里体现得最直接。我们设计前端组件时核心目标是让开发者可以快速接入同时又保留了完全自定义的能力。组件内部采用状态机来管理对话周期空闲、思考中、流式输出中、处理错误。每个状态都对外暴露生命周期钩子开发者可以基于钩子实现自定义UI。比方说默认状态是思考中显示一个转圈动画但有的客户希望显示一个AI正在翻阅知识库的动态提示那么只需要监听思考状态并渲染自己的组件即可。另一个关键设计是消息协议。前端与服务端之间的消息不是简单的文本流而是一个带类型的增量数据包消息标识、内容片段、引用来源、工具调用记录、token用量。这样前端可以精准控制每一段的渲染方式知识库引用的来源可以单独展示为小卡片工具调用的过程可以折叠起来token消耗可以在调试面板里显示。前端还内置了主题系统。客户不需要改组件源码只需要提供几个颜色变量和字体配置就能把聊天界面调整成和自身品牌风格一致的样子。一个售后服务网站的聊天框长成客服工单的样式一个金融网站的聊天框则是专业冷酷的黑金风格交互层内核不需要做任何调整。5. 实操过程与踩坑实录5.1 项目骨架搭建目录结构和核心模块这个项目的工程规划我们花了很大力气因为涉及多人协作模块边界必须清晰到谁写的代码一眼就能归属到哪个层。目录结构大致是这样的├── packages/ │ ├── core/ # 内核层会话、消息、模型适配、工具调用 │ ├── server/ # 服务层HTTP网关、鉴权、限流、SSE输出 │ ├── sdk/ # 交互层前端SDK和可嵌入组件 │ └── plugins/ # 插件库知识库、企业微信桥接、任务调度等 ├── apps/ │ ├── demo-web/ # 一个可直接部署的完整Web应用 │ └── embed-site/ # 一个展示如何嵌入任意网站的示例 ├── docs/ └── examples/core包里是所有技术团队成员最看重的地方。会话管理使用了一个会话对象池每个会话维护独立的消息历史、上下文窗口和工具执行记录。模型适配器注册表让我们可以在运行时动态切换模型供应商不用重启服务。服务层基于Node.js实现选型时考虑过Python的FastAPI但为了前后端统一语言降低团队维护成本还是留在了Node.js阵营。实践中可以选择更顺手的技术栈关键是分层架构保持一致。5.2 踩坑实录一SSE连接的断线与重试第一个大坑是SSE连接在弱网环境下的稳定性。开发环境一切正常但客户公司网络拓扑复杂经常有代理服务器和防火墙SSE连接会被莫名掐断前端表现为AI说到一半就停了。排查后发现两个原因一是有些代理服务器空闲超时时间设置得很短长时间没有数据流动的连接会被主动断开二是前端EventSource API断线后默认会自动重连但重连后如果没有合适的标识信息后端会重新生成一个响应流导致前后文对不上。解决方案分两层。后端在SSE响应头里显式设置了Cache-Control: no-cache和X-Accel-Buffering: no避免中间层缓冲导致数据积压同时每隔一段时间发送心跳注释行维持连接活跃。前端则自定义了重连逻辑每次重连带着会话标识重新订阅后端根据标识恢复未完成的消息流。实测下来断线率从千分之几降到了可忽略的程度。5.3 踩坑实录二多轮对话的上下文管理为了追求对话效果开始我们设计的是只要不超过窗口尽量携带更多历史消息。结果发现一个问题上下文里塞满了与当前问题无关的历史信息模型抓不住重点回复质量明显下降而且token消耗飞速上涨。后来我们采用了一套组合策略。第一按时间衰减重置上下文窗口最新的5轮对话完整保留更早的历史消息只保留摘要极端陈旧的直接丢弃。第二引入关键信息抽取每轮对话结束后系统自动提取用户提到的关键实体和意图标签在后续对话中优先注入这些信息而不是把整段历史都塞进去。第三知识库查询结果有相关性分数门槛低于阈值的检索结果不进上下文。这套策略带来一个意外收获因为上下文更紧凑响应速度明显提升用户体感变好了。所以如果你在开发类似的对话系统不要盲目地完整保留所有历史做聪明的上下文管理比堆算力重要得多。5.4 踩坑实录三前端流式渲染的性能流式输出时后端每秒推送几十个token前端如果每个token都触发一次React重新渲染页面会明显卡顿尤其在低端设备上几乎是灾难。我们用了三个优化手段。第一是批处理把短时间内到达的多个token合并成一批进行一次渲染而不是每个token渲染一次。第二是虚拟列表聊天记录很长时只渲染可视区域内的消息超过一定数量后自动折叠成查看更早的消息。第三是重MD轻HTML消息内容优先用纯文本和简单的结构化对象表示避免复杂的HTML渲染开销。现在即使在低端手机上连续对话几十轮界面依然流畅。5.5 踩坑实录四并发与限流开源项目被公开后很快遇到了一个之前没有的问题有人拿我们的服务直接跑压力测试或者部署后忘记配置鉴权导致服务被外部刷爆。解决思路是三层防线。第一层是网关限流每个租户按照API Key维度的令牌桶限流超出配额直接返回429。第二层是队列削峰对话请求先进入队列按优先级排序模型实例空闲时再消费队列避免突发流量直接打穿模型服务。第三层是会话级并发控制同一个会话同时只允许一个请求在途重复请求直接返回上一次回答还在生成中。这三层防线加完系统稳定性有了质的提升。也从侧面说明一个道理做开源项目必须有上线即裸奔的心理准备安全性和稳定性要从第一天就作为一等公民对待。6. 常见问题速查与排查思路问题现象可能原因排查方案前端连不上服务控制台报CORS错误服务端未正确配置跨域白名单检查服务端CORS配置确认前端域名已加入白名单SSE流式输出时断时续代理服务器空闲超时或中间层缓冲设置Cache-Control: no-cache和X-Accel-Buffering: no增加心跳机制对话回复质量明显偏差上下文管理中历史消息过多或过少检查上下文窗口策略确认关键信息抽取是否生效本地模型接入后回复速度极慢内网GPU资源不足或模型量化等级过低检查模型推理服务的吞吐指标必要时升级硬件或换用更小的模型工具调用没生效Function Calling格式与模型不兼容确认模型是否支持工具调用协议检查工具注册时参数Schema是否正确多租户共用同一会话池导致数据串扰会话隔离逻辑实现不完整检查会话维度上的租户隔离确保每次请求都携带了正确的租户标识前端页面在低端设备上卡顿流式渲染频繁触发重绘引入批渲染机制合并短时间内到达的多个token后再做一次渲染部署后访问返回404静态资源路径配置错误检查前端构建产物是否部署到预期路径检查Nginx静态目录映射7. 重写之后我们验证了什么7.1 数据层面的一些变化重写完成上线三个月后我们拉过一次数据。旧版获客工具一共积累了三十多个集成需求大部分做不动。重写后同样的需求在一个月内就完成了一半因为很多能力在架构层面天然支持只需要写少量业务适配代码。社区方面开源仓库发布后第一周star数涨得比预期快很多收到了将近两百个issue和五十多个PR。其中好几个PR来自我们完全不认识的外部开发者他们完善了文档、修复了移动端的样式问题、还补了一个对接飞书的插件。企业采用方面有家零售客户把我们的交互层嵌进了内部培训系统员工每天用AI询问产品知识上线首月就节省了几百个小时的问答时间。另一家金融客户把交互层接入到合规审查流程AI根据历史案例库帮助审查员起草初步意见效率提升非常明显。7.2 工具思维与平台思维的边界这段经历给我最深刻的启发是做项目之前先想清楚你在做工具还是做平台。工具解决一个具体问题闭环快价值直观但天花板也低。平台解决一类问题需要更长的投入周期但边际成本递减复用价值高。判断标准很简单用户是在使用你的产品还是在基于你的能力构建自己的产品前者是工具后者是平台。在AI能力快速普及的窗口期平台化的价值远大于单一工具。如果你正在做AI应用方向我的建议是先想清楚你和通用AI助手之间的差距在哪里。如果只是换了个UI、换了个提示词那你的护城河很浅。想办法把自己定位成可被集成的交互层让业务系统而不是用户直接来接触你你才能真正成为AI时代的基础设施。7.3 给想尝试同类方向团队的三个建议第一一开始就要把模型接入做成插件式的。别把某一个模型的API写死在代码里模型这是目前技术圈变化最快的变量今天最强的模型三个月后可能就落后了适配层做得越薄你的系统寿命越长。第二开源版与商业版要划清边界。开源版保留核心能力可以支撑中小团队和独立开发者商业版提供企业级能力比如私有化部署、高可用集群、统一运维控制台、专属技术支持。边界划清楚了社区和商业才不会互相内耗。第三把文档当作产品一样对待。代码写得好但文档读不懂用户只会放弃。我们实测过一个外部开发者从零开始接入从看到文档到跑起来用了三十分钟。如果这个时间超过半天大概率是文档出了问题。最后说一点个人感受。做AI应用这几年最大的体会是技术迭代真的太快了但需求本身其实很朴素人们只是想跟机器说话的时候机器能听懂、记住、做出靠谱的回应。围绕这个朴素需求把交互层做扎实让更多人能够在上面的上层建筑发挥创造力这件事本身就有长久价值。