2026年Vibe Coding 做了一个基于 Java 的 AI IM 系统:Spring Boot + LangChain4j + WebSocket + Skills + RAG 可商用的全栈落地实战

Vibe Coding 做了一个基于 Java 的 AI IM 系统:Spring Boot + LangChain4j + WebSocket + Skills + RAG 可商用的全栈落地实战这不是一篇 让 AI 帮我生成了几个文件 的流水账 而是一篇完整的工程复盘 这篇文章聚焦的是我最终落地的 Java 技术栈版本 核心工程是 java im 基于 Spring Boot Spring WebSocket Spring Data JPA Spring AI LangChain4j Redis 和 Qdrant 做出来的 AI IM 系统 我把 AI

大家好,我是讯享网,很高兴认识大家。这里提供最前沿的Ai技术和互联网信息。



这不是一篇“让 AI 帮我生成了几个文件”的流水账,而是一篇完整的工程复盘。

这篇文章聚焦的是我最终落地的 Java 技术栈版本,核心工程是 java-im:基于 Spring BootSpring WebSocketSpring Data JPASpring AILangChain4jRedisQdrant 做出来的 AI IM 系统。 我把 AI 当成结对工程师,用 vibe coding 的方式,把这个 Java 项目逐步打磨成一个带有 AI Agent、Tools、Context Compression、Skills 路由和 RAG 优化能力的智能 IM 系统。 整个过程中,AI 不只是代码生成器,更像是实现加速器、重构搭子、Prompt 调参器、日志分析助手和测试补全器。


为了让读者一开始就知道这篇文章讲的到底是什么工程,我先把这个项目涉及到的核心技术栈和框架全部摆在最前面。

层次 技术栈 / 框架 在项目中的职责 后端语言与构建 Java 17Maven 作为 java-im 的运行时与构建工具 应用框架 Spring Boot 3.5.8 提供整体应用启动、依赖注入、配置管理和组件装配能力 Web 接口层 Spring Web MVCspring-boot-starter-validation 提供 REST API、参数校验、控制器层和统一请求处理 实时通信 Spring WebSocket 提供 /api/v1/ws 长连接能力,承担 IM 实时消息收发 流式输出 Spring Web MVC + Flux > 在 Spring MVC 控制器中返回响应式 SSE 事件流,承载 AI 增量输出与工具过程事件 数据持久化 Spring Data JPAHibernateMySQL 8 持久化用户、消息、群组、群成员、上下文等核心业务数据 缓存与状态 RedisSpring Data Redis 承担部分缓存与运行时辅助状态 安全认证 JWTJwtAuthFilterspring-security-crypto(BCrypt) 处理 HTTP 鉴权、密码加密、用户身份解析 WebSocket 鉴权 HandshakeInterceptor 在 WebSocket 握手阶段校验 token,并注入当前用户身份 AI 模型编排 LangChain4j 1.13.0 负责聊天消息结构、tool calling、模型请求封装和对话编排 AI 接入基础设施 Spring AI 1.1.2 提供 embedding、vector store、模型配置和生态集成能力 大模型接入 OpenAI-Compatible APIqwen-turboglm-4.7-flash 提供主备模型能力,支持普通问答与工具调用 RAG 向量库 Qdrant 存储 IM 业务知识向量,支撑检索增强 知识库形态 MarkdownTXT 作为 RAG 的源知识文档,沉淀业务规则、排障 runbook、产品策略 提示词与工具定义 TXT PromptYAML Tool SchemaSnakeYAML 管理系统提示词、摘要提示词、工具定义与规则约束 JSON/序列化 Jackson 处理请求体、WebSocket 消息、SSE 事件和上下文持久化结构 API 文档 springdoc-openapiSwagger UI 提供接口调试与文档展示 测试 Spring Boot TestJUnit 5Mockito 验证 skills 路由、WebSocket 断链清理、关键服务逻辑 前端 Vue 3TypeScriptVitePiniaElement Plus 提供聊天界面、会话列表、AI 面板、管理端交互 工程方法 Vibe Coding 不是框架,但它是我推动这套系统快速迭代和重构的核心工作方式

如果把这张表压缩成一句话,这个项目本质上是:

一个基于 Spring Boot + Spring WebSocket + LangChain4j + Spring AI + Qdrant + MySQL + Redis 的 Java AI IM 系统。


我想做的不是一个“能发几条消息”的聊天 Demo,而是一个足够接近真实业务的 IM 系统:

  • 有用户体系、JWT 认证、联系人、群聊、消息 ACK、离线补拉、会话列表。
  • 有基于 Spring WebSocket 的长连接,也有 HTTP 和 SSE。
  • 有用户侧接口,也有管理侧接口和管理员 AI 能力。
  • 有 AI,不只是普通聊天,而是能执行工具、能理解上下文、能基于业务知识回答问题。

更重要的是,这个项目不是一次性设计完再照着写,而是在持续迭代里长出来的。

我最终选择的是 Java 技术栈实现,也就是现在这套 java-im 工程。 整个实现过程中,我持续用 vibe coding 的方式把它往更工程化的方向推进,尤其是:

  • 让 AI 不再死板地绑定单一 skill,而是能自动判断:

只是普通回答;

  • 需要单 skill;

  • 还是需要多 skill 协同。

  • 让 RAG 不再只是“查一下向量库”,而是有查询扩展、历史感知、skill 感知、去重、重排、多样化、预览和可观测能力。
  • 这篇文章会把这条演进路径讲清楚。


    我这里说的 vibe coding,不是“给 AI 一句需求,等它自动把整个系统写完”。

    我真正采用的方式更像这样:

    1. 我先确定架构边界和关键语义。
    2. 把一个复杂问题拆成可验证的小任务,让 AI 分段实现。
    3. 用日志、测试、接口行为和真实对话去验证结果。
    4. 发现不对,就继续让 AI 帮我重构、补测试、修 Prompt、加约束。
    5. 我始终保留最终决策权,AI 提高的是实现速度,不替代工程判断。

    这套方法特别适合做这种跨层系统:

    • 一头连着 Spring WebSocket、MySQL、Redis、SSE。
    • 另一头连着大模型、Tool Calling、Prompt、RAG、上下文压缩。

    如果没有 AI 辅助,很多“试错成本很高但又必须亲手打磨”的细节会推进得非常慢。 而在 vibe coding 模式下,我能很快做出第一版,再不断把它从“能跑”打磨到“工程上说得过去”。


    这里我要先说清楚一件事,避免读者误解:

    • 我早期确实参考和尝试过 Go 版 IM 原型,用它验证过一些 IM 核心语义,比如 ACK、离线消息、群未读。
    • 但本文不是在写 Go WebSocket 项目复盘。
    • 本文真正讲述、也是最终落地发布的版本,是 java-im 这套 Java / Spring Boot 技术栈实现

    也就是说,读这篇文章时,你可以直接把它理解成:

    • 一个基于 Spring Boot 的 IM + AI 系统;
    • WebSocket 连接层是 Spring WebSocket
    • HTTP 接口和 SSE 流式输出都在同一个 Java 服务里;
    • AI 编排基于 LangChain4j + Spring AI + Qdrant
    • Skills、RAG、上下文压缩、工具调用都围绕这套 Java 工程展开。

    如果要说项目演进,那更准确的表达应该是:

    1. 先把 IM 核心语义做对

    • 私聊消息的 ACK 语义;
    • 离线消息补拉;
    • 群成员未读水位;
    • 会话列表和消息持久化。

    2. 再把 AI 从“能聊”做成“能落地”

    • Prompt 文件化;
    • Tool Calling;
    • Skills 自动规划;
    • RAG 检索增强;
    • 上下文压缩;
    • 可观测与预览接口;
    • 安全边界和失败兜底。

     

    Vue3 / TypeScript Frontend

    HTTP API

    Spring WebSocket (/api/v1/ws)

    SSE Streaming (/api/v1/ai/*/stream)

    Java IM Service (Spring Boot)

    MySQL + JPA/Hibernate

    Redis

    LangChain4j / OpenAI-Compatible LLM

    Spring AI VectorStore + Qdrant

    RAG Markdown Knowledge Base

    如果把它简化成一句话:

    • 这不是一个 Go WebSocket 服务外加一个 Java AI 服务的拼接方案;
    • 而是一套以 java-im 为核心的 Java 单体工程
    • IM 主链路、AI 对话、SSE、Tools、Skills、RAG 都在同一个 Spring Boot 项目里完成。

    这也是我最终选择 Java 来承载这套系统的原因:

    • Spring 生态在 Web、WebSocket、JPA、Filter、配置管理这类基础设施上非常成熟;
    • Spring AILangChain4j 让模型接入、向量库接入、工具调用编排更顺手;
    • 对这种“业务系统 + AI 编排”混合场景来说,Java 的工程化优势非常明显。

    这一部分是整个系统最基础、也最不能出错的地方。

    1. 技术栈选择

    当前 Java 版的核心栈大致是:

    • Spring Boot 3.5.x:作为整体应用框架。
    • Spring Web MVC:承担 HTTP API。
    • Spring WebSocket:承担长连接。
    • Spring Data JPA + Hibernate + MySQL:承担消息、群组、成员、上下文等持久化。
    • Spring Data Redis:缓存与辅助状态。
    • Spring Web MVC + Flux > :承担 AI 流式输出。
    • Spring AI + Qdrant:承担向量检索基础设施。
    • LangChain4j:承担模型调用、tool calling 和消息结构编排。
    • 自定义 OpenAiCompatibleClient:统一对接 DashScope、智谱等 OpenAI-Compatible 模型。
    • JwtAuthFilter + HandshakeInterceptor:分别处理 HTTP 和 WebSocket 鉴权。

    2. 分层设计与依赖注入

    Java 版没有把逻辑都堆在 controller 或 handler 里,而是走了明确的分层:

    • controller:只负责参数和协议层。
    • service:承载业务语义。
    • repository:只做数据访问和聚合查询。
    • websocket:单独维护握手鉴权、会话注册、消息分发和断链清理。
    • prompt + rag + skill + ai client:单独维护 AI 编排层。

    实际实现上,这套结构的一个直接好处是:

    • WebSocket 收到 @AI 时走的是同一个 AiServiceImpl
    • HTTP /api/v1/ai/chat/stream 走的也是同一个 AiServiceImpl
    • 管理侧 AI 走的仍然是同一套技能规划和检索逻辑,只是 adminMode 不同。

    这件事很小,但很能说明一个现实: AI 功能接进系统以后,依赖注入不只是“代码优雅”,而是直接影响用户对“记忆”和“连续对话”的感知。

    3. WebSocket 长连接模型

    这里必须特别说明一下: 当前对外发布和本文聚焦的实现,不是 Go 里的 gorilla/websocket Hub 模型,而是 Java / Spring WebSocket 的 Handler 模式。

    当前 Java 版的实时链路是:

    • @EnableWebSocket + WebSocketConfigurer 注册 /api/v1/ws
    • ImHandshakeInterceptor 在握手前校验 JWT
    • ImWebSocketHandler extends TextWebSocketHandler
    • WebSocketSessionRegistryConcurrentHashMap 维护在线会话
    • afterConnectionEstablished / handleTextMessage / afterConnectionClosed 分别处理建连、消息和断链

    换句话说,这里的设计更贴近 Spring 生态的习惯:

    • 握手鉴权交给 HandshakeInterceptor
    • 连接生命周期交给 TextWebSocketHandler
    • 在线会话管理交给 SessionRegistry

    这种实现方式的优点是:

    • 结构更贴近 Spring Boot 应用整体;
    • 和 HTTP Filter、Controller、Service 层的协作更自然;
    • 更容易跟 JPA、AI Service、系统通知服务做集成。

    4. 消息 ACK 与离线语义

    我没有把消息状态简单做成“成功 / 失败”,而是拆成了更贴近 IM 语义的状态:

    • delivered:对方在线并收到;
    • stored:对方离线,但消息已经落库,等待补拉。

    这样做的意义是:

    • 发送端能知道消息到底是“没发出去”,还是“已存储待送达”;
    • 用户体验上不会把离线误判成消息丢失;
    • 后续客服和 AI 排障也有真实依据。

    这套语义在 Java 版里直接体现在 ImWebSocketHandler 的处理流程里:

    • 私聊先落库;
    • 如果目标用户在线,就立即投递并标记 delivered
    • 如果不在线,就保留为 stored
    • 然后给发送方回 ACK。

    5. 群聊离线补拉与未读水位

    群聊是 IM 里最容易被低估的复杂点。

    私聊的 stored/delivered 语义并不能直接复用到群聊,因为群聊不是一对一状态,而是每个成员都有自己的阅读进度。

    所以我最终采用的是:

    • group_members.joined_at
    • group_members.last_read_at

    来表达成员级读水位。

    核心规则是:

    • 只返回 created_at > COALESCE(last_read_at, joined_at) 的群消息;
    • 新成员没有 last_read_at 时,用 joined_at 兜底;
    • 这样不会收到入群前的旧消息。

    这是一个很关键的产品语义修正: 如果这里做错,用户看到的不是“偶发 bug”,而是整个群聊未读逻辑都不可信。

    而且这个规则也直接进入了后面的 AI 知识库。 也就是说,RAG 不是在讲“理想中的 IM 设计”,而是在讲这个 Java 系统自己真实采用的群未读语义。

    6. 会话列表统一排序

    我还专门修过一个会话列表的小坑:

    • 私聊和群聊会话原本分开查询;
    • 结果 append 到同一个切片后顺序错乱;
    • 最终必须按统一的 last_time 再排序一次。

    这个问题很像真实业务里常见的那类 bug: 单个模块都没错,但一组合起来,最终表现就不对。

    7. JWT 鉴权与 WebSocket 握手

    Java 版里,HTTP 和 WebSocket 的鉴权不是混在一起处理的,而是各自走最适合自己的链路。

    HTTP 侧
    • JwtAuthFilter extends OncePerRequestFilter
    • 统一拦截 /api/*
    • Authorization: Bearer xxx 里解析 token
    • 把当前用户写进 request attribute
    WebSocket 侧
    • 浏览器原生 WebSocket 不方便自定义 Authorization Header
    • 所以握手阶段通过 query 参数拿 token
    • ImHandshakeInterceptor 在握手前解析 JWT
    • 解析成功后,把 usernamerole 写进 WebSocket session attributes

    这两套链路并存,是因为:

    • HTTP 请求和 WebSocket 握手本来就不是同一个协议阶段;
    • 如果硬凑一套统一方式,最终只会让实现更绕。

    8. SSE 流式输出与 AI 接入

    除了 WebSocket,我还保留了 SSE 作为 AI 对话输出通道:

    • /api/v1/ai/chat/stream
    • /api/v1/ai/tools/chat/stream
    • /api/v1/ai/compress/stream
    • /api/admin/ai/chat/stream

    这样做有两个好处:

    • IM 主消息链路仍然由 WebSocket 承担,协议边界更清晰;
    • AI 生成链路天然适合 text/event-stream,前端更容易做逐段渲染、取消生成和过程可视化。

    这也是我比较认同的一种工程边界:

    • WebSocket 更适合 IM 主消息流;
    • SSE 更适合 AI 生成流;
    • 二者在同一个 Java 服务里共存,但各自只处理最擅长的职责。

    在 Java 版里,这部分对应的是:

    • AiControllerAdminController 直接返回 Flux >>
    • OpenAiCompatibleClient.chatStream() 基于 LangChain4jOpenAiStreamingChatModel + StreamingChatResponseHandler 透传上游增量输出;
    • 工具对话现在不只推 tool_call / tool_result / chunk / done,还新增了 status 事件,让前端先看到“规划中 / 生成中 / 整理工具结果中”;
    • chatWithToolsStream() 增加了“明显无需工具时直接流式回答”的快路径,避免用户在首个 token 前白等一轮 tool planning;
    • Vite 开发代理和后端响应头都会显式关闭缓存 / buffering,避免浏览器最终看到的是“伪流式”。

    这里还有一个很重要的工程现实:

    • 当前版本采用的是 Spring Web MVC + Flux > 的响应式写法来做 SSE;
    • 它已经可以把 LangChain4j 的流式 chunk 和工具过程事件按 SSE 实时推给前端;
    • 但项目主运行时目前仍然是 spring-boot-starter-web + Tomcat,而不是整站切到 spring-boot-starter-webflux + Reactor Netty。

    也就是说,我这次优先解决的是“真实流式链路”和“前端体感问题”,而不是为了追求名义上的 WebFlux,把整个应用一次性重构掉。

    这比“只给最终答案”更适合做 AI 调试和用户体验反馈。


    很多 AI IM 项目的问题是: 它们只有一个“聊天框”,并没有真正把模型能力和业务系统打通。

    我这里做的是更偏 Agentic 的集成方式。

    1. Prompt 从文件加载,不写死在代码里

    在当前 Java 实现里,我没有把系统提示词直接硬编码在业务逻辑里,而是:

    • 用独立的 txt 文件管理系统 Prompt;
    • yaml 文件管理工具定义;
    • 运行时读取并拼装。

    这样做的价值很直接:

    • Prompt 调整不需要大改业务代码;
    • 工具 schema 更容易维护;
    • 用户侧和管理侧可以拥有不同约束。

    2. 工具不是“口头调用”,而是真执行

    我在工具 Prompt 里明确约束了模型:

    • 用户要求建群、邀请、踢人、解散群,必须真实调工具;
    • 不能没调工具就说“已经完成”;
    • 依赖返回值的工具必须串行;
    • 互不依赖的工具才允许并发;
    • group_id 必须来自真实工具返回值;
    • 批量操作必须走批量工具;
    • 生活服务问题要调用 get_weather / get_news / get_route

    这套约束的目标只有一个: 让模型不要假装自己有系统权限,而是变成一个严格遵守业务边界的执行代理。

    3. 用户侧工具与生活服务工具

    用户侧工具不只有 IM 操作,也有生活服务工具:

    • get_news
    • get_weather
    • get_route

    这背后其实对应了一个设计取舍:

    • 我不希望 AI 只能回答“IM 说明书”;
    • 但也不希望生活服务问题被误送到 IM RAG 里乱检索。

    这正好就引出了后面 skills 的设计。


    项目做到一定阶段以后,我发现只靠一个大 Prompt 会越来越难控制。

    典型问题有几个:

    1. 同一个系统里,其实存在多种“思考视角”

    比如这些问题,其实关注点完全不一样:

    • “为什么收不到私聊消息?” 这是客服 / 排障视角。
    • “怎么做群未读数才准确?” 这是产品 / 会话语义视角。
    • “帮我把这几个人拉进群里。” 这是工具执行 / 群组操作视角。
    • “怎么给 AI 助手接入 RAG 和上下文压缩?” 这是 AI 工程视角。

    如果都让一个通用 Prompt 去同时处理,模型很容易:

    • 回答不聚焦;
    • 检索错知识;
    • 该调用工具时没调;
    • 不该调用时又乱调。

    2. 不是所有问题都值得走 RAG

    用户问一句:

    我想去河北赵州桥玩,现在在广州天河智慧城,这两天天气适合去吗?

    这其实是生活服务问题,应该优先走天气工具,而不是:

    • 去检索 IM 知识库;
    • 匹配什么群聊、消息、上下文;
    • 最后给出完全不相关的回答。

    所以我后来把判断拆开了:

    • 先做 skill planning
    • 再决定是否需要 RAG;
    • 工具调用和 skill 绑定也分离处理。

    这一步是整个系统从“能用”走向“可控”的关键。


    这一部分是我现在最想讲清楚的,因为它正好回应了一个真实需求:

    AI 不能死板地只走一个 skill,必须能自动判断是基础回答、单 skill、还是多 skill 协同。

    当前实现核心在三个对象里:

    • ImSkillDefinition
    • SkillCatalogService
    • SkillPlan

    1. Skill 定义层:把“领域关注点”结构化

    每个 skill 目前都包含这些信息:

    • code
    • name
    • description
    • promptFragment
    • domains

    当前注册了 6 个技能:

    1. im_customer_service
    2. im_group_operations
    3. im_admin_operations
    4. im_product_operations
    5. im_risk_control
    6. im_ai_assistant

    注意这里的 skill 不是独立 Agent,也不是独立模型。 它本质上是一个“业务关注点包”:

    • 一部分用于路由匹配;
    • 一部分用于 Prompt 注入;
    • 一部分用于 RAG 查询扩展。

    补充:当前 6 个 Skill 的职责矩阵

    为了避免 skills 讲得过于抽象,我把当前 6 个 skill 的职责拆成一个更具体的矩阵。

    Skill Code 业务角色 主要处理的问题 典型关键词 Prompt 注入后的关注点 对 RAG 的影响 im_customer_service 客服 / 排障 收不到消息、登录异常、ACK 状态解释、会话异常 客服、登录、异常、消息、已读、离线、撤回 用客服口吻解释现象、定位故障、区分 stored / delivered / failed 更偏向客服 runbook、消息投递链路、常见排障知识 im_group_operations 群组操作 建群、加群、邀请、踢人、退群、解散群 群、建群、邀请、踢人、解散、group_id 强调真实 group_id、群主权限、成员校验、批量工具规则 更偏向群生命周期、成员关系、群操作规则 im_admin_operations 管理后台 封禁、解禁、删号、重置密码、统计、角色管理 管理员、后台、封禁、统计、角色 强调权限、审计、高风险操作、后台边界 更偏向后台治理、审计、管理规则 im_product_operations 产品 / 运营 会话列表、未读数、通知策略、埋点指标、状态机 产品、运营、未读、会话、通知、策略 让回答更像产品经理,而不是通用客服 更偏向会话体验、未读逻辑、投递指标、运营判断 im_risk_control 安全 / 风控 骚扰、刷屏、黑名单、权限滥用、审核 风控、安全、审核、黑名单、刷屏、审计 强调频控、最小权限、违规策略、审计留痕 更偏向风控规则、风险边界、审核与滥用治理 im_ai_assistant AI 工程 Prompt、RAG、tools、上下文压缩、模型路由、智能助手设计 AI、RAG、tool、agent、prompt、上下文、skills 强调 AI 在 IM 中的落地方法,而不是泛泛聊大模型 更偏向 AI/RAG/skills/上下文治理相关知识

    这张表的价值在于,它把 skill 从“一个代码名”变成了“一个有工程职责的路由单元”。

    换句话说,当前的 skills 体系并不是为了炫技,而是为了回答下面这类现实问题:

    • 用户现在是在问功能解释、产品设计、后台操作、风控边界,还是 AI 工程?
    • 这个问题应该注入哪类 Prompt 关注点?
    • 这个问题应该优先检索哪类知识?
    • 这个问题到底要不要走 RAG?

    2. SkillPlan:先规划模式,再决定是否启用 RAG

    SkillPlan 当前有 3 种模式:

    • BASIC_RESPONSE
    • SINGLE_SKILL
    • MULTI_SKILL

    同时它还带一个关键标记:useRag

    也就是说,当前实现把两个判断拆开了:

    1. 当前是普通回答、单技能还是多技能。
    2. 这一轮是否值得启用 RAG。

    这是一个非常重要的设计点,因为:

    • BASIC_RESPONSE 不等于不能调工具;
    • 它只是表示“本轮不强行绑定 IM 业务 skill,不额外注入 skill prompt,不启用 IM RAG”。

    例如天气、路线、新闻这些生活服务问题:

    • /chat/stream 里会被当成普通回答处理;
    • /tools/chat/stream 里仍然可以由工具 Prompt 驱动模型去调用 get_weather/get_route/get_news

    也就是说:

    • skill 负责业务视角与知识边界;
    • tool 负责是否真正执行动作;
    • 两者被有意识地解耦了。

    3. SkillCatalogService 的路由步骤

    当前的 skill 路由逻辑可以概括成下面这几步:

    输入用户消息 -> 如果前端显式指定 requestedSkillCode,直接尊重 -> 否则先做 normalize -> 判断是不是非 IM 的生活服务问题 -> 对所有 skill 做关键词打分 -> 判断是否存在多意图 -> 选出 1~3 个最合适的 skill -> 产出 SkillPlan(mode, skills, useRag)

    如果把它写成更接近代码的伪代码,大概是这样:

    SkillPlan plan(String requestedSkillCode, String userMessage, boolean adminMode) ​    normalized = normalize(userMessage);    if (normalized is blank) {        return BASIC_RESPONSE(no rag);   } ​    if (isLifeServiceQuery(normalized) && !looksImRelated(normalized)) {        return BASIC_RESPONSE(no rag);   } ​    ranked = rankSkills(normalized, adminMode);    if (ranked is empty)        return fallbackSingleSkill(with rag);   } ​    selected = selectSkills(ranked, normalized);    if (selected.size() > 1) {        return MULTI_SKILL(with rag);   }    return SINGLE_SKILL(with rag); }

    这个伪代码有两个很关键的点:

    • 生活服务问题优先被挡在 IM skill / IM RAG 之外。
    • 只有“看起来像 IM 领域问题”的请求,才会继续走 skill ranking 和 RAG。

    4. 具体打分策略

    当前并没有上一个专门的小模型来做 skill router,而是采用了“规则可控 + 结果可解释”的方案:

    • 每个 skill 注册一组关键词;
    • 如果命中关键词,就累计分数;
    • 某些场景再加额外 bonus:

    群操作相关词给 im_group_operations 加权;

  • AI/RAG/Prompt/上下文相关词给 im_ai_assistant 加权;

  • 管理端词汇在 adminMode 下给 im_admin_operations 加权。

    这个方案的优点是:

    • 可解释;
    • 容易调;
    • 很适合早期迭代。

    缺点也很明显:

    • 对隐式意图和复杂语义的泛化能力有限;
    • 仍然依赖人工维护关键词。

    但在当前阶段,这是一种我认为非常务实的折中。

    5. 多 Skill 判定逻辑

    模型什么时候应该进多 skill?

    当前实现里,多 skill 的触发主要依赖两类信号:

    • 文本中有明显的复合意图词:

    “并”

  • “同时”

  • “以及”

  • “并且”

  • “还要”

  • “又要”

  • 排名靠前的 skill 分数足够接近,说明问题跨领域。
  • 最终最多选择 3 个 skill 协同。

    这其实非常贴近真实问法,比如:

    怎么给客服排障回答接入 RAG 和 tools,同时保留上下文压缩?

    这类问题天然跨了:

    • 客服排障;
    • AI 工程;
    • 上下文治理。

    如果硬压成单 skill,回答一定会变窄。

    补充:用三个真实问题看当前 Skill 路由结果

    为了让这套规则更直观,我用三个非常典型的问题来解释当前会怎么路由。

    示例 1:纯生活服务问题

    用户问题:

    我想去河北赵州桥玩,现在在广州天河智慧城,这两天天气适合去吗?

    当前规划结果:

    • mode = BASIC_RESPONSE
    • skills = []
    • useRag = false

    为什么?

    • 它命中了“天气 / 适合出行 / 路线”这类生活服务词;
    • 但没有明显 IM 业务词;
    • 所以不应该把它送进 IM 知识库;
    • 如果用户走的是 tools 接口,那么后续仍然可以由工具 Prompt 驱动模型调用天气工具。
    • 当前实现里,chatWithToolsStream() 还会先用 shouldUseToolPlanning() 做快路径判断:明显只是普通问答时直接流式回答,明显是天气 / 新闻 / 路线 / 群操作时再进入工具规划循环。

    这正是我前面说的:

    • BASIC_RESPONSE 不等于系统什么都不做;
    • 它只是明确告诉系统:别把这个问题当 IM 业务知识问答。
    示例 2:典型单 Skill IM 问题

    用户问题:

    为什么我收不到私聊消息,登录也没报错?

    当前规划结果:

    • mode = SINGLE_SKILL
    • primarySkill = im_customer_service
    • useRag = true

    为什么?

    • “收不到私聊消息”明显是客服 / 排障问题;
    • “登录没报错”进一步强化了客服定位;
    • 这类问题通常不能只靠模型猜,需要检索排障 runbook 和消息状态规则。
    示例 3:典型多 Skill AI 工程问题

    用户问题:

    怎么给客服排障回答接入 RAG 和 tools,同时保留上下文压缩?

    当前规划结果通常会是:

    • mode = MULTI_SKILL
    • skills 至少包含:

    im_customer_service

  • im_ai_assistant

  • useRag = true
  • 为什么?

    • “客服排障回答”是客服知识域;
    • “RAG / tools / 上下文压缩”又是 AI 工程域;
    • 文本里还有“同时”,说明这是典型复合问题;
    • 这时单 skill 反而会丢信息。

    6. Skill 结果是怎么影响最终 Prompt 的

    AiServiceImpl 里,skillPlan 并不是只用于“看一眼”,而是真正参与了系统 Prompt 的构造。

    核心逻辑是:

    • 普通聊天走 buildChatSystemPrompt
    • 工具对话走 buildToolSystemPrompt
    • 两者都会把 buildSkillPlanPrompt(skillPlan) 拼进去

    最终注入效果大概是:

    • 当前模式:基础回答 / 单 skill / 多 skill 协同
    • 如果是单 skill:

    注入该 skill 的关注点 promptFragment

  • 如果是多 skill:
  • 逐个列出多个 skill 的关注点

  • 告诉模型先拆解,再综合回答

  • 只有互不依赖的子任务才能并发

    这一步的价值在于:

    • skill 不再是代码里一个“标签”;
    • 而是转化成了模型真正能感知的推理约束。

    7. Skills 当前执行链路

    把 skills 放进整个请求生命周期里,可以理解成下面这条链路:

    用户请求 -> SkillCatalogService.plan() -> 产出 SkillPlan -> chat / tool 系统 Prompt 注入 skill planning 规则 -> 如果 useRag = true,则再做 skill-aware RAG 检索 -> 模型生成回答或发起工具调用 -> 工具结果回灌到消息历史 -> 保存上下文 / 触发压缩

    也就是说,skills 不是单独运行的一层,而是连接了:

    • Prompt;
    • RAG;
    • Tool Use;
    • Context。

    这就是为什么我会说它不是“一个分类器”,而是当前 AI 编排层的入口。

    补充:为什么我当前没有把 6 个 Skill 做成 6 个独立 Agent

    很多人看到 skills 之后,第一反应会是:

    那为什么不直接做 6 个子 Agent,让一个总控 Agent 去调度它们?

    我当前没有这么做,原因主要有四个。

    第一,当前问题的复杂度还没高到必须多 Agent

    现在大多数问题,本质上还是:

    • 需要一个统一对话上下文;
    • 需要一个统一工具调用历史;
    • 需要一个统一的最终回答出口。

    也就是说,当前更需要的是“单模型下的规划与约束”,而不是“多模型并发协作”。

    第二,多 Agent 会放大上下文和状态同步成本

    如果每个 skill 都变成独立 Agent,那么要解决:

    • 每个 Agent 看哪些历史;
    • 工具结果怎么共享;
    • RAG 命中结果谁负责解释;
    • 最终答案如何聚合;
    • 多 Agent 之间是否会互相矛盾。

    这会让系统复杂度一下子从“工程可控”变成“调试地狱”。

    第三,当前阶段我更重视可解释和可验证

    现在 skills 的好处是:

    • 能直接看到为什么命中;
    • 可以通过规则快速修正;
    • 能在 preview 和日志里验证结果。

    而多 Agent 一旦做得不够好,问题就会变成:

    • 路由错了?
    • 检索错了?
    • 子 Agent Prompt 错了?
    • 聚合错了?

    定位成本会明显上升。

    第四,当前这版更适合作为下一阶段多 Agent 的地基

    我现在更愿意把当前 skills 看成一个“中间形态”:

    • 先把 skill 定义、路由、Prompt 注入、RAG 联动做扎实;
    • 之后再逐步演进到 planner + workers 的结构。

    也就是说,我并不是否定多 Agent,而是有意把演进顺序排成:

    规则化 skill 路由 -> 可解释的多 skill 协同 -> planner 化 -> 多 Agent 化

    8. 当前 Skills 设计的意义

    这一版 skills 虽然还不是最终形态,但已经解决了几个很实际的问题:

    • 避免所有问题都被死板地套进 IM RAG。
    • 避免所有问题都走一个大而全 Prompt。
    • 给多意图问题提供了一个可控拆解入口。
    • 让技能选择、检索选择、提示词选择之间形成联动。

    我这里的 RAG,不是“把文档扔进向量库就完了”,而是一步一步做成现在这样的。

    1. 为什么要做 RAG

    AI 在 IM 场景里最怕的是“看起来很会说,但说的不是系统真实规则”。

    比如这些问题:

    • 为什么消息显示 stored
    • 为什么群未读不对?
    • 为什么解散群失败?
    • 后台封禁用户要注意什么?

    这些都不是通用百科知识,而是系统特有的业务规则。 所以如果不用 RAG,模型很容易“讲得像那么回事,但跟系统实现不一致”。

    2. 知识源怎么组织

    我把知识源整理成了按主题拆分的 Markdown 文档,比如:

    • IM AI / RAG / Skills
    • 客服与排障 Runbook
    • 风控与审计
    • 产品指标与运营判断

    这样做有两个好处:

    • 文档本身可读,适合长期维护;
    • 检索时能保留足够强的来源和标题语义。

    3. 种库流程:先把知识切块再入向量库

    当前的种库服务是 RagSeedService,核心步骤是:

    1. 从外部目录或 classpath 加载 .md/.txt 知识文件。
    2. 按标题和空行做分块。
    3. 控制单 chunk 大小,当前上限约 1200 字符。
    4. 为每个 chunk 打 metadata:

    source

  • heading

  • domain

  • chunk_index

  • 分批写入向量库,当前 batch size 为 10
  • 写入 marker,避免重复 seed。
  • 这里我选择了 Spring AI VectorStore + Qdrant 的组合。

    这很重要,因为它让我把系统分成了两层:

    • 上层是自己的 RAG 编排逻辑;
    • 下层是可替换的向量存储实现。

    补充:为什么我按 Markdown 标题切块,而不是简单按固定长度切块

    这一点其实对检索质量影响非常大。

    当前 RagSeedService 的切块思路不是纯粹按长度硬切,而是:

    • 遇到标题优先切分;
    • 在段落自然结束处优先切分;
    • 只有 chunk 过长时,才在空行处截断;
    • 过短的片段直接过滤,不进向量库。

    这样做的好处是:

    1. 每个 chunk 更接近“一个完整业务语义单元”

    比如:

    • “为什么群未读不对”
    • “为什么群操作失败”
    • “AI 回答不准确的常见原因”

    这些标题本身就构成了很强的检索锚点。 如果按固定长度乱切,很容易把一个规则拆散,最终模型看到的是半截话。

    2. heading metadata 变得非常有价值

    因为切块围绕标题组织,所以后面重排时:

    • 标题命中可以额外加分;
    • 预览接口里也更容易读懂;
    • 最终定位问题时,人一眼就知道命中了什么知识块。
    3. 更适合长期维护知识库

    我希望知识库本身是“人能读、也适合检索”的。 Markdown 标题分层刚好满足这个需求。

    补充:当前 RAG 的关键配置参数是怎么设计的

    application.yml 里,我把当前这版 RAG 的关键参数显式配出来了,这一点我非常建议在工程里保留,因为参数不透明的 RAG 系统几乎没法调。

    参数 当前值 作用 我为什么这么设 top-k 5 最终返回给模型的命中数 太少容易丢信息,太多会污染 Prompt per-query-top-k 4 每个查询变体先召回多少条 保持扩展 query 有探索空间,但不把噪声带太多 max-merged-results 8 去重前最大保留命中数 给去重和多样化留出候选池 max-chunks-per-source 2 同一来源最多取几个 chunk 防止某一个文档霸榜 similarity-threshold -1 相似度阈值 当前让系统不过早过滤,先依赖 rerank 再收敛 expand-query true 是否开启查询扩展 解决用户口语化提问和文档表达不一致的问题 history-aware true 是否引入历史上下文 解决“这个/刚才那个”之类的上下文依赖问题 history-query-messages 3 最近多少条消息参与历史提示 太多容易引入噪声,3 条是当前比较平衡的值 history-query-max-chars 160 历史提示最大长度 控制 query 膨胀,避免上下文干扰过大 max-query-variants 4 最多构造多少个查询变体 控制检索成本和召回噪声 seed-on-startup true 启动时是否自动种库 开发环境和单机部署更方便

    这些参数看起来都是小开关,但实际上它们共同决定了:

    • 检索召回的范围;
    • 检索噪声的大小;
    • Prompt 中证据的密度;
    • 每次请求的成本和延迟。

    4. 检索不是只查一次,而是查询扩展

    当前 LangChainRagRetriever 并不会只拿用户原句做一次向量检索。

    它会按规则构造多组查询:

    • 原始 query;
    • normalize 后的 query;
    • 追加 “IM 即时通讯 业务规则” 之类的 query;
    • 追加 skill 名称和 domains 的 query;
    • 如果开启历史感知,再把 summary / recent history 拼进 contextHint;
    • 根据 admin/user 模式追加不同的领域词。

    默认控制参数大致是:

    • perQueryTopK = 4
    • topK = 5
    • maxMergedResults = 8
    • maxQueryVariants = 4

    这背后的思路是:

    • 不要指望用户每次都把问题问得足够标准;
    • 系统要主动把 query 拉回到“业务知识可以理解的表达”上。

    补充:当前查询扩展具体扩了什么

    为了让这块更具体,我把当前查询扩展逻辑拆开说一下。

    如果原始问题是:

    怎么给客服排障回答接入 RAG 和 tools,同时保留上下文压缩?

    系统可能构造出这些 query 变体:

    1. 原始 query 怎么给客服排障回答接入 RAG 和 tools,同时保留上下文压缩
    2. normalize 后 query 去掉多余标点和空白,让表述更规整
    3. 加入 IM 业务提示词 原始 query + IM 即时通讯 业务规则
    4. 加入 skill 名称和领域 原始 query + IM智能助手 AI RAG tools 上下文
    5. 如果启用历史感知,再拼上最近历史摘要 contextHint + 原始 query

    这背后有一个经验判断:

    • 向量检索擅长找语义近似;
    • 但业务知识文档往往更“规整”和“书面化”;
    • 所以 query 必须适度往知识表达方式上靠。

    补充:当前重排为什么不直接上 Cross-Encoder

    理论上更强的 reranker 当然可以提高排序质量,但我当前没有直接上更重的 cross-encoder,原因是:

    • 当前系统还在快速迭代阶段;
    • 我更需要一套足够轻、足够透明、足够容易调的方案;
    • 规则重排已经能解决不少“向量相似但业务不相关”的问题;
    • 真到下一阶段,再引入更强 reranker 会更合适。

    这也是我做这个系统的一贯原则:

    • 先把可解释的基础设施做对;
    • 再逐步堆更贵、更复杂的能力。

    5. 历史感知:RAG 不只看当前一句话

    如果只看当前 query,很多问题其实是上下文依赖的,比如:

    • “这个群怎么处理?”
    • “刚才那个为什么失败?”
    • “这个情况要怎么答?”

    所以当前实现里,RAG 会把两类历史信息用起来:

    • 最近若干条用户/助手消息;
    • 已经压缩好的历史摘要。

    然后构造成 contextHint 参与检索。

    这一步特别关键,因为它让 RAG 不再是“本轮问什么就查什么”,而是带着会话上下文去理解问题。

    6. Skill 感知:先知道自己在哪个领域,再去检索

    这也是当前实现里我很看重的一点:

    • SkillPlan 决定了当前涉及哪些业务领域;
    • RAG 在扩展 query 和做重排时会利用这些 skill 信息;
    • skill 的 name/code/domains 都会参与关键词加权。

    这意味着同样一句“为什么失败了”,在不同 skill 上下文里,检索结果会不一样:

    • 群组问题偏向群成员、权限、group_id;
    • 客服问题偏向 ACK、登录、消息状态;
    • AI 工程问题偏向 Prompt、上下文压缩、tools、RAG。

    这就是 skill 和 RAG 真正联动起来的地方。

    7. 检索结果不是直接用,而是会去重、重排、多样化

    向量检索的一个常见问题是:

    • 同一份文档的多个相邻 chunk 可能一起冲到前面;
    • 明明有更有价值的来源,却被重复内容挤掉了。

    所以我在当前实现里做了三层优化:

    第一层:合并去重

    使用:

    • source
    • heading
    • chunk_index

    组成唯一 key,把重复命中的 chunk 合并,并记录它命中了哪些 query 变体。

    第二层:规则重排

    不是只看向量分数,而是做了一个轻量 rerank:

    • 原始 vectorScore
    • 命中 query 关键词加分
    • 命中标题关键词再加分
    • 命中 skill 名称 / domains 再加分
    • admin / user 模式下再加不同 bonus

    最终得到 rerankedScore

    这套方法很朴素,但很有效,因为它把“语义相似”进一步拉回“业务相关”。

    第三层:来源多样化

    我加了 maxChunksPerSource 限制,默认单来源最多取 2 个 chunk。

    这样可以避免:

    • 前 5 条结果全来自同一篇文档;
    • 模型看到的信息视角过于单一。

    这一步很像搜索里的 result diversification,只不过我用的是一种更轻量、可控的工程实现。

    8. RAG 结果不是黑盒,我做了可预览和可观测

    很多 RAG 系统有一个问题: 你只能看最终回答,但不知道中间到底检索到了什么。

    所以我专门做了:

    • /api/v1/ai/rag/status
    • /api/v1/ai/rag/rebuild
    • /api/v1/ai/rag/preview

    尤其 preview 很有价值,它可以直接看到:

    • 当前命中的 skillMode
    • 是否 useRag
    • 命中的 skills
    • contextHint
    • expandedQueries
    • 每个 hit 的:

    source

  • heading

  • vectorScore

  • rerankedScore

  • matchedQueries

  • 预览文本

    这使得 RAG 调试不再靠猜,而是可以直接看证据链。

    9. RAG 与上下文压缩是配套设计,不是两套孤立机制

    当前实现里,上下文不会无限增长。

    做法是:

    • 普通聊天和工具对话分别维护上下文;
    • 超过窗口后,只保留最近若干条消息;
    • 更早的历史交给模型压缩成摘要;
    • 后续 RAG 检索再使用这个摘要作为 summaryHint

    这件事非常重要,因为它解决了两个问题:

    1. 上下文窗口成本失控。
    2. 早期关键历史完全丢失。

    也就是说:

    • 历史摘要是“记忆压缩层”;
    • RAG 是“知识补充层”;
    • 两者一起工作,模型才能既记得会话,又不胡编系统规则。

    为了更直观,我把当前链路整理成一个可执行的步骤图:

    用户发起请求 -> SkillCatalogService.plan() -> 得到 SkillPlan(mode / skills / useRag) -> 加载当前上下文 summary + recent history -> buildRagHistoryHints() -> LangChainRagRetriever.buildExpandedQueries() -> 对每个 query variant 做向量检索 -> 合并重复 chunk -> 结合关键词 / skill / adminMode 做 rerank -> 按 source 做 diversification -> 把结果注入 system prompt -> 模型回答 / 调工具 -> 保存历史上下文 -> 超窗后做 context compression

    这就是我现在理解的“工程上靠谱的 RAG”:

    • 它不是单一 API 调用;
    • 它是一条受控、可调、可观测、可验证的链路。

    补充:一条真实请求在当前系统里是怎么跑完的

    这里我用一个更贴近真实开发的问题,串起 skills、RAG、Prompt、tools 和上下文。

    用户问题:

    怎么给客服排障回答接入 RAG 和 tools,同时保留上下文压缩?

    第一步:Skill Planning

    系统先做 SkillCatalogService.plan()

    • 命中“客服 / 排障”
    • 命中“RAG / tools / 上下文压缩”
    • 命中“同时”这样的复合意图词

    因此得到:

    • mode = MULTI_SKILL
    • skills = [im_customer_service, im_ai_assistant]
    • useRag = true
    第二步:加载上下文

    系统从 AiContextEntity 里取出:

    • summary
    • recent

    并从 recent 里抽出:

    • 最近用户消息
    • 最近助手消息

    生成 historyHints

    第三步:构造检索词

    LangChainRagRetriever 基于:

    • 原始问题
    • summaryHint
    • historyHints
    • skill 列表
    • 当前是否 adminMode

    生成一组 expandedQueries

    第四步:向量召回

    系统对每个 query variant 调一次向量检索,得到候选 chunk 列表。

    这些 chunk 可能分别来自:

    • 05-im-ai-rag-skills.md
    • 10-im-support-diagnosis-runbook.md
    • 12-im-product-metrics-and-operations.md
    第五步:去重、重排、多样化

    系统会:

    • 合并重复命中的 chunk
    • 叠加 skill / keyword / heading bonus
    • 对来源做多样化限制

    最后得到更适合注入 Prompt 的 hit 列表。

    第六步:拼装系统 Prompt

    系统 Prompt 最终不再只是原始 chat_system.txt,而是:

    • 基础系统规则
    • 历史摘要
    • skill planning 结果
    • RAG 检索结果

    拼接之后的结果。

    也就是说,模型此时看到的并不是“裸问题”,而是:

    • 你应该从哪些业务视角回答;
    • 你可以优先参考哪些系统知识;
    • 当前的会话历史重点是什么。
    第七步:模型生成回答或发起工具调用

    如果是普通聊天接口,模型会直接回答。 如果是工具接口,模型还可能:

    • 先决定要不要调工具;
    • 再根据工具执行结果生成最终回答。
    第八步:写回上下文

    最终用户消息、助手回复、工具调用记录、工具结果都会写回上下文:

    • chat 上下文
    • tool / admin-tool 上下文

    如果窗口超过阈值,就触发压缩。

    这个过程看起来长,但每一步都是在为“最终回答更贴近真实系统”服务。

    补充:上下文压缩的具体实现步骤

    很多文章一写到上下文压缩,就只说一句“定期做 summary”。 但如果不讲具体步骤,这部分其实很容易被说虚。

    我当前的实现是这样的:

    1. 每个用户、每种上下文类型分别存

    当前至少分了几类上下文:

    • chat
    • tool
    • admin-tool

    这样做的原因是:

    • 普通问答历史和工具执行历史的结构不同;
    • 管理后台的问题又有不同的安全边界;
    • 如果混在一起,Prompt 很容易被污染。
    2. 上下文结构不是一整段文本,而是 summary + recent

    我没有直接存“一大段完整历史”,而是拆成:

    • summary:更早历史的压缩摘要
    • recent:最近还没被压缩的消息列表

    这是非常经典但也非常有效的结构。

    3. 超窗后不是直接截断,而是先切头尾

    recent 超出窗口后:

    • 把较老的一段消息作为 head
    • 把最近的一段消息作为 tail
    • tail 直接保留
    • head 交给模型压缩

    这样做的结果是:

    • 最新上下文原文不丢;
    • 旧上下文也不会彻底消失。
    4. 不同类型上下文用不同的摘要 Prompt

    当前实现里:

    • 普通对话走 summarize_chat.txt
    • 工具对话走 summarize_tools.txt

    原因很简单:

    • 普通聊天更关心讨论主题和结论;
    • 工具对话更关心操作结果、真实 ID、失败原因、关键参数。

    如果共用一个摘要 Prompt,很容易把工具历史压坏。

    5. 压缩后的摘要不仅用于记忆,也用于 RAG

    压缩结果不是只给模型“下次参考一下”,而是会进一步参与:

    • buildRagHistoryHints
    • summaryHint
    • contextHint

    也就是说,摘要不仅服务于“记忆”,还服务于“检索理解”。

    这是我觉得当前实现里非常关键的一点:

    • 上下文压缩不是独立附属功能;
    • 它已经进入了 RAG 的主链路。

    除了主功能,我还很重视那些“看起来小,但真的会把系统打崩”的细节。

    1. 断链清理不能把 WebSocket 收尾流程搞崩

    我最近修了一个很典型的问题:

    • WebSocket 关闭后,服务端会尝试更新用户所在群组的 last_read_at
    • 但如果清理过程中数据库查询报错,整个收尾逻辑会抛异常;
    • 最终在日志里出现一串 Unhandled exception after connection closed

    后来我把逻辑改成:

    • 断开连接先正常打印日志;
    • 如果用户名缺失,直接跳过清理;
    • 清理失败只记 warning,不再往外抛。

    这个修复的本质不是“吞异常”,而是把清理流程从主链路故障里隔离出去。

    2. groups 是 MySQL 保留字,表名要显式转义

    还有一个很真实的坑:

    • JPA 查询 groups 表时报 SQL syntax error;
    • 根因是 groups 在 MySQL 里是保留字;
    • 最后通过 @Table(name = “groups”) 解决。

    这类问题特别适合用 vibe coding 配合日志排查:

    • AI 可以很快帮我定位“像保留字问题”;
    • 但最终还是要我结合实际 SQL 和 ORM 生成语句来确认。

    3. AI 调用要有主备兜底

    在当前 Java 实现里,我保留了主备模型切换能力:

    • 主模型失败先打日志;
    • 再切到备用模型;
    • 尽量保证对话不中断。

    这件事在 AI 功能里非常重要,因为模型服务的不稳定性远高于传统数据库和缓存。

    4. Tool Loop 必须限制轮数

    当前工具调用轮数有上限,比如现在这版实现设置了 MAX_TOOL_ROUNDS = 5

    原因很简单:

    • 不限制,模型可能在异常情况下来回试探;
    • 一旦工具结果不符合预期,可能出现无穷循环调用。

    所以工程上必须给 Agent 一个硬边界。

    5. 需要测试,而不是只靠人工聊天验证

    我补了几类很关键的测试:

    • SkillCatalogServiceTest

    验证生活服务问题不误入 IM skill / RAG;

  • 验证单 skill 和多 skill 路由是否符合预期。

  • ImWebSocketHandlerTest
  • 验证断链清理失败时不会继续抛异常。

    AI 可以帮忙生成测试框架,但“测什么最值钱”仍然是人的判断。


    如果回头看整个实现过程,我觉得最有价值的不是某一段代码,而是这种协作方式本身。

    我是怎么用 AI 的

    我主要把 AI 用在这些地方:

    • 快速生成第一版模块骨架;
    • 帮我把自然语言需求翻成 Prompt 规则;
    • 根据日志反推问题可能出在哪一层;
    • 对已有代码做局部重构;
    • 补测试、补异常分支、补边界处理;
    • 帮我把“一个模糊目标”拆成一组可实现步骤。

    补充:我实际采用的一轮 Vibe Coding 工作流

    很多人会问:

    你说你是用 vibe coding 做的,那一轮完整协作到底长什么样?

    我把自己现在比较稳定的一轮协作方式总结成 8 步:

    第 1 步:先定边界,不让 AI 自由发挥

    我会先明确告诉 AI:

    • 现在要改的是哪一层;
    • 不能改什么;
    • 成功标准是什么;
    • 如果有旧实现,要优先兼容什么。

    比如:

    • 这次只改 skills 路由,不要顺手改工具 schema;
    • 这次要让生活服务问题不进入 IM RAG;
    • 这次要把断链清理失败改成 warn 日志,不允许再抛异常。
    第 2 步:让 AI 先读代码,再动手

    我不会一上来就让 AI 直接写。 因为在这种项目里,“先理解代码现状”远比“直接生成一份看起来正确的新代码”重要。

    第 3 步:让 AI 提一个局部实现方案

    这一步通常不是为了听思路,而是为了看它是否真的理解了问题边界。 如果方案里开始飘,我就能及时把它拉回来。

    第 4 步:让 AI 改一小段,立即验证

    我更偏好:

    • 一次只改一块;
    • 一次只修一个逻辑问题;
    • 一次只验证一条主链路。

    因为这种做法最适合快速回滚和快速定位。

    第 5 步:拿真实日志和真实请求打它

    这一点我特别依赖真实输入:

    • HTTP exchange 日志
    • WebSocket 断链日志
    • 模型请求体
    • 工具调用结果
    • RAG 命中结果

    很多问题不是代码静态看不出来,而是必须用真实请求才能打出来。

    第 6 步:如果结果不对,不是只改代码,还要改 Prompt 和规则

    AI 系统和传统 CRUD 不一样。 很多问题不在业务代码本身,而在:

    • Prompt 约束不够强;
    • 工具 schema 不够清晰;
    • 路由规则不够细;
    • 检索查询不够稳定。

    所以我经常会同时改:

    • Java 代码
    • Prompt 文件
    • YAML 工具定义
    • 测试样例
    第 7 步:补一层可观测性

    如果一个 AI 功能调不好,通常不是“模型太笨”,而是你根本看不见它中间发生了什么。

    所以我会优先补:

    • 请求日志
    • 检索预览
    • 工具调用事件
    • 失败原因
    • 测试断言
    第 8 步:把这轮经验沉淀成规则

    比如最后沉淀出来的就可能是:

    • 生活服务问题直接 BASIC_RESPONSE,不进 IM RAG;
    • 群名必须先解析真实 group_id;
    • 批量操作必须走批量工具;
    • 断链清理失败只打 warning;
    • RAG 结果必须可 preview。

    这一步很重要,因为它决定了系统是“修完一个问题”,还是“真的长出更强的工程能力”。

    我没有把 AI 用成什么

    我没有把 AI 当成:

    • 不看代码的自动提交机;
    • 不验证结果的万能工程师;
    • 一句需求就能替我负责系统正确性的黑盒。

    因为这类项目一旦跨到:

    • WebSocket 状态语义;
    • 数据一致性;
    • 工具执行边界;
    • RAG 命中质量;
    • Prompt 安全性;

    你就会很清楚地知道,人必须在环。

    Vibe Coding 最适合解决什么问题

    我认为它最适合:

    • 初版原型很复杂,但不想手敲每个样板的场景;
    • 系统有很多“半结构化知识”要转成代码和 Prompt;
    • 需要频繁试错、快速验证、持续重构的项目。

    这个 AI IM 系统恰好就是这种典型场景。


    当前这版已经能跑、能控、能调,但离“更强的生产级智能层”还有不少空间。

    我认为后面最值得继续做的方向有这些。

    1. Skill Router 从规则路由升级到轻量模型路由

    现在的 skill 选择还是关键词 + bonus。

    下一步可以考虑:

    • 做一个小型 intent classifier;
    • 或者让一个便宜模型先做 route,再交给主模型回答。

    这样可以提升对隐式意图和复杂语义的识别能力。

    2. Skill 体系从内置注册升级成配置化 / 插件化

    当前 skill 是代码内注册的。

    未来可以把它演进成:

    • skill 配置文件;
    • skill 对应独立 prompt fragment;
    • skill 对应独立工具白名单;
    • skill 对应独立知识域。

    这样扩展新领域时,不必改核心代码。

    3. 多 Skill 从“并列协同”升级成分层规划

    现在的多 skill 仍然偏平面。

    后续可以做成更明确的 Planner:

    • 先拆子任务;
    • 再判断哪些串行、哪些并行;
    • 最后做结果汇总。

    也可以把工具规划和 skill 规划进一步合并。

    4. RAG 从纯向量检索升级到混合检索

    当前还是以向量召回为主,辅以规则重排。

    未来可以继续加:

    • BM25 / keyword hybrid retrieval;
    • metadata filter;
    • 按 skill / domain / role 分 collection;
    • 基于 query type 的动态 topK;
    • 更强的 reranker。

    这会让检索质量更稳定。

    5. 增加答案引用与证据对齐

    现在模型已经能拿到 source / heading,但最终回答还没有强制输出引用。

    未来如果做得更严格,可以:

    • 在回答里显式给出知识来源;
    • 或至少输出内部证据 ID;
    • 让“为什么这样回答”更容易审计。

    6. 做离线评测集与回归测试

    这会是后面非常值钱的一步。

    比如建立一批标准问答:

    • 客服问题;
    • 群操作问题;
    • 管理后台问题;
    • AI 工程问题;
    • 生活服务问题。

    每次改:

    • skill 路由规则;
    • RAG 参数;
    • Prompt;
    • 工具定义;

    都跑一遍回归评测,才能避免“修一个点、坏一大片”。

    7. 做语义缓存和分层成本控制

    对高频重复问题,可以增加:

    • query normalization;
    • semantic cache;
    • 不同模型分层调用;
    • 低成本模型做 route / summarize,高成本模型做最终回答。

    这对成本和延迟都会更友好。

    8. 让工具调用再加一层风险控制

    现在已经有不少规则约束了,但未来还可以继续加强:

    • 高风险工具需要二次确认;
    • 不同 skill 绑定不同工具白名单;
    • 根据用户角色动态裁剪工具集;
    • 工具失败后进入明确的降级策略,而不是让模型反复试。

    如果只看表面,这像是一个:

    • 基于 Java 技术栈实现的 IM 项目;
    • 在同一个 Spring Boot 工程里做了 AI 能力增强;
    • 接了向量库;
    • 加了几个 tools;
    • 能回答问题、能拉群、能做天气查询。

    但如果从工程视角去看,它真正有价值的地方在于:

    1. 它把 IM 语义、AI 能力、知识系统和上下文治理放到了同一个设计里。
    2. 它不是用一个大 Prompt 硬撑,而是开始走向可规划、可观测、可维护。
    3. 它展示了 vibe coding 在复杂系统里的正确打开方式:

    不是替代工程判断;

  • 而是加速实现、加快验证、加深迭代。

    我现在越来越相信一件事:

    未来很多真正有价值的项目,都不是“AI 自动生成完毕”的产物,而是人和 AI 作为双人组,一起把一个系统从模糊想法推到工程成品。

    这个 AI IM 系统,就是我用这种方式做出来的一个完整样本。


    Skills 当前结论

    • 不是独立 Agent,而是“领域关注点 + 路由规则 + Prompt 片段 + RAG 边界”的组合。
    • 当前自动判断三种模式:

    BASIC_RESPONSE

  • SINGLE_SKILL

  • MULTI_SKILL

  • 最多协同 3 个 skill。
  • skill 决定业务视角和是否启用 RAG。
  • tool 决定是否真正执行动作。
  • RAG 当前结论

    • 采用 Markdown 知识库 -> chunk -> metadata -> Qdrant -> vector retrieval -> rerank -> diversify -> prompt grounding 的完整链路。
    • 不是单 query 检索,而是:

    query expansion

  • history-aware retrieval

  • skill-aware retrieval

  • dedupe

  • rerank

  • source diversification

  • 提供 preview/status/rebuild,方便调试和回归。
  • 下一步最值得做的事

    • skill router 小模型化;
    • 多 skill planner 化;
    • hybrid retrieval;
    • 答案引用;
    • 评测集;
    • 语义缓存;
    • 风险分级工具调用。

    小讯
    上一篇 2026-04-20 14:00
    下一篇 2026-04-20 13:58

    相关推荐

    版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
    如需转载请保留出处:https://51itzy.com/kjqy/271857.html