编程导航技术话题讨论

技术

544 参与
分享

快来分享你的内容吧~

点击登录,快来和大家讨论吧~
表情
图片
话题
打卡
综合
交流
文章
问答

交通行业工程师-缺乏AI落地场景困惑-求推荐可快速验证的AI应用方向

### 资源类型 交通行业的ai应用能做什么呢,感觉像这种工程领域并没有什么合适的ai应用。求广大网友提提建议!!

AI全栈或者普通全栈还考不考算法?-AI时代算法考察权重变化

### 求职目标 现在在找AI全栈岗 ### 个人情况 3年经验,前端、Java后端,Python工程化都已掌握并能独立开发项目部署上线。 ### 求职困惑 想知道现在全栈岗的面试中算法考察的比例怎么样,还考不考算法了。特别是中大厂。想了解一下情况好分配准备面试的权重。 ### 期望帮助 最好能有真实的面试案例分享,说说现在AI全栈岗面试的侧重点

耄耋专属AI Loop全自动编码器技术笔记

> 写给想了解「多AI Agent 在大LOOP时代怎么真上线」的同学 ## 一句话 **Loop Engineering 时代,别只在聊天里写代码——用多 AI Agent,跑一条自己干活的交付 Loop。** 先给这套 Loop 代码编排器定一条简单主线: **需求 Issue → 多角色 AI 协作 → 脚本验证与独立审阅 → 合并 → 固定脚本部署 → 看板盯进度。** 人定目标和验收;AI 负责中间大部分实现与自检;模型不直接登录生产。 --- ## 为什么不只「开个 Cursor 窗口」 聊天式助手擅长帮你改文件,但上线仍要人拉分支、补测、发版、盯结果。Loop 把这些环节收成固定流水线: | | 聊天式 AI 编程 | Loop | |---|---|---| | 起点 | 粘贴上下文 | Issue / 看板目标 | | 过程 | 人来回追问 | 多角色自动推进 | | 质量 | 靠自觉 | 脚本验证 + 独立审阅 | | 上线 | 手工发版 | 确定性发布脚本 | | 可见性 | 对话记录 | 看板阶段与应用入口 | 一句话:**助手帮你写;Loop 帮你把需求送到可点开的版本。** --- ## 技术骨架:三层分工 ```text 触发层 Forgejo Issue(ai-ready) / 看板「新建项目」 编排层 Worker + loopctl + Hermes 五角色 Profile 落地层 verify.sh → PR 合并 → SSH/Compose 发布 → sslip 域名跳转 ``` - **触发层**:习惯还是 Git Issue;新产品也可在看板填「仓库名 + 目标」一键建仓。 - **编排层**:分析 → 架构 → 编码 → 测试 → 审阅;审阅打回最多返工 3 轮;单阶段约 30 分钟超时。 - **落地层**:过门后由脚本部署,看板只监控和跳转,不托管业务前端。 密钥只在服务器 `secrets/`,不进仓库。 --- ## 多 Agent:故意「拆开」,不让一个模型既当运动员又当裁判 | 角色 | 做什么 | 典型产出 | |---|---|---| | 分析 | 收成可验收目标 | `spec.md` | | 架构 | 拆任务、定边界 | `plan.md` | | 编码 | 改业务代码与测试 | 仓库 diff | | 测试 | 独立补测、跑验证 | 测试报告 | | 审阅 | 只评不改,给通过/打回 | `review.json` | 执行侧用 Hermes 多 Profile,工具集刻意收窄(文件 / 终端 / 必要时代码执行),并设轮次上限,避免一轮对话无限烧 Token。 模型也可分层:编码侧重代码模型,测试用更快模型,审阅用更稳的模型——**质量门与写代码的人不是同一个「脑」**。 ## 配置落在哪 ### 编排层(仓库) `config/loop.json` 只规定: - 五角色各自用哪个 Profile 名 - 工具集(分析 / 架构 / 审阅:`file,terminal`;编码 / 测试再加 `code_execution`) - 单阶段轮次上限、YOLO、返工次数、超时 其中 `hermes.models` 可以按角色覆盖模型名;**留空则用 Profile 默认值**。 ### 执行层(服务器 Hermes) 真正的模型 ID、`base_url`、API Key 写在各 Profile 的 `config.yaml`(例如 `/root/.hermes/profiles/loop-coder/config.yaml`)。 线上现行大致是: | 角色 | 模型 | |---|---| | 分析 / 架构 / 编码 | `ark-code-latest`(火山方舟选GLM5.2) | | 测试 | `deepseek-v4-flash` | | 审阅 | `deepseek-v4-pro` | ![image.png](https://pic.code-nav.cn/post_picture/1949837726039801857/qyhrlcPGr0p4yuOR.webp) ### 小技巧 如果想真正的分饰多角,完全可以多买几个便宜的Agent Plan分别挂不同对应角色能力的**LLM**,但这里由于成本控制的原因,最少两个**LLM**对应不同能力就可以完成基本工作了。 ### 人格层(SOUL) 仓库 `roles/*.md` 同步进各 Profile 的 `SOUL.md`,约束「只做什么、不做什么」: - **模型**负责能力 - **SOUL**负责边界 ### 调用链 ```text Issue Worker → loopctl → 对应 Profile 的 Hermes(--oneshot,瘦工具集)→ 该 Profile 配置的模型 ``` --- ## 有边界的自治(比「全自动」更重要) **会自动做:** 读仓、改码、跑校验、开/合 PR、按脚本部署。 **不会自动做:** 没目标乱开需求、跳过质量门上生产、把密钥写进 Git。 **人能介入:** 看板看卡点、一键重跑;紧急可停 Worker。 发布永远走 `release.sh` / SSH 一类确定性路径,而不是让 Agent 临时拼命令登生产。 --- ## 看板:把黑盒变成进度条 看板回答三件事: 1. 现在跑到哪(分析 / 架构 / 编码 / 测试 / 审阅 / 发布) 2. 有没有被打回、卡在哪 3. 应用在哪打开(当前交付 + 历史项目各自地址) 多项目时:**一仓一应用、独立端口、独立 `*.sslip.io` 域名**。改哪个程序,Issue 就开在哪个仓库。 ![屏幕截图 2026-07-17 101839.png](https://pic.code-nav.cn/post_picture/1949837726039801857/vzxWZOa0ms6Yb4jN.webp) ![image.png](https://pic.code-nav.cn/post_picture/1949837726039801857/1ocJMbOLzlYeaFoH.webp) --- ## 一次真实路径长什么样 1. 看板新建,或在目标仓开 Issue 并打 `ai-ready` 2. Worker 领取 → 工作区拉出 `ai/issue-N` 分支 3. 五角色流水线跑完,产物落在 `.loop/current/` 4. 质量门通过 → PR 合并 5. `deploy_once` 部署 Staging/Production,写好跳转域名 6. 打开 `{项目名}.xxx.sslip.io` 验收 试点里我们用这条链路交付过多智能体对话应用、内容创作平台等——从「一句话目标」到「浏览器能点开」,中间大部分由流水线完成。 ![屏幕截图 2026-07-17 135748.png](https://pic.code-nav.cn/post_picture/1949837726039801857/QeDPNBsgcC0eGPRp.webp) --- ## 适合什么,不适合什么 **适合:** 中小功能、脚手架增量、内部试点、需求边界写得清的迭代。 **暂不适合:** 无人值守改核心账务、无评审的大重构、强合规唯一发布通道。 产出质量仍然取决于:需求是否写清、模板是否匹配、模型额度与提示词。 ## 目前Loop编排器自动完成的项目展示 ![屏幕截图 2026-07-17 173309.png](https://pic.code-nav.cn/post_picture/1949837726039801857/iIJFRoDbuEHBwOyS.webp) ![image.png](https://pic.code-nav.cn/post_picture/1949837726039801857/ecCh8tkQL47ZOger.webp) ![image.png](https://pic.code-nav.cn/post_picture/1949837726039801857/9rE2dyaB0kfRO0iU.webp) --- ## 结语 Loop 的技术选择可以概括成三句: 1. **角色分离**,降低「自写自审」幻觉; 2. **脚本守门**,模型负责想和改,脚本负责过不过、能不能上; 3. **看板可见**,自动跑也不变成黑盒。 它不是取代工程师,而是把重复的「领任务 → 改代码 → 验证 → 发版」收成一条可观测流水线,让人把时间花在目标与验收上。

新手SpringSecurity6.0源码流程总结

自己看的源码理解总结的,后端代码抄别人的能运行听不赖。 认证: ![认证.jpg](https://pic.code-nav.cn/post_picture/1929133079952228353/R0OaqErtfNLEDBzq.webp) 鉴权: ![鉴权.jpg](https://pic.code-nav.cn/post_picture/1929133079952228353/iXzRdpDtJwELIOfX.webp)

我的AI编程工作流:从需求判断到上线复盘,搭一套 AI 项目自动化流水线

我的AI编程工作流不是让 AI 帮我写一段代码,而是让它围绕需求、原型、方案、任务、开发、测试、部署、日志和复盘持续运转。 ```text 需求判断 → 原型设计 → 技术方案 → 任务拆解 → 前后端开发 → 测试联调 → 代码审查 → 部署上线 → 日志排查 → 复盘沉淀 ``` 这不是一个“问一句答一句”的 AI 聊天窗口,而是一个后台开发助理。 ![image.png](https://pic.code-nav.cn/post_picture/1813254542264233985/NnAizoM9C1PyTiA1.webp) 它应该能: ```text 每天自动检查项目 发现问题进入 inbox 没有问题自动归档 低风险问题生成修复建议 中高风险问题等待人工确认 PR 自动做风险审查 部署前自动生成检查清单 上线后自动看日志 每周自动生成复盘和技术债 ``` 这篇文章就完整拆一件事: **如何把 AI 编程能力放进一个有边界、有触发、有产物、有审批、有复盘的软件工程流水线里。** 这不是“全自动开发”。 我的核心判断是: **AI 编程的下一步,不是更长的 Prompt,而是更清晰的工程边界。** **AI 不应该直接接管项目,而应该进入有触发、有产物、有审批的流水线。** **能自动化的是巡检、整理、初稿、检查和低风险修复;不能自动化的是方向判断、风险审批和上线责任。** **真正有价值的 AI 工作流,不是让 AI 多写一点代码,而是让项目过程变得可追踪、可审查、可复盘。** --- ## 一、先说清楚:这不是普通 AI 工具流 这套系统叫: ### AI 项目交付流水线 它不是一个软件,也不是一个插件,而是一套项目组织方式。 ```text 项目自己有一套流水线 每个阶段有固定输入 每个阶段有固定产物 每个阶段有触发器 每个阶段有 Skill 每个阶段有风险等级 每个阶段有人工 Gate 每个阶段结束后能沉淀到下一轮 ``` 底层用: ```text Codex Automations Codex / Claude Code Agent Skills AGENTS.md MCP GitHub Actions GitHub PR Review 本地脚本 人工审批 ``` Skill 可以把指令、资源和可选脚本打包成任务专用能力,让 Codex 更稳定地执行某类工作流。 编码Agent 会在开始工作前读取项目里的 `AGENTS.md`,把这些指令作为项目上下文和行为规范。 MCP 定义为连接 AI 应用和外部系统的开放标准,可以让 AI 应用连接本地文件、数据库、搜索引擎、工具和工作流。 同时,MCP Tools 规范也明确建议为了安全和信任,应保留 human-in-the-loop,让用户能够拒绝工具调用,并清楚看到哪些工具暴露给模型。 这套系统的底层原则是: ```text 能只读,就先只读。 能生成报告,就先生成报告。 能进入 inbox,就不要直接改生产。 能人工确认,就不要自动上线。 ``` --- ## 二、总架构:AI 项目交付流水线的 6 层 先看整体架构。 ![image.png](https://pic.code-nav.cn/post_picture/1813254542264233985/pSlCv8mOHqQ4gbOj.webp) 每一层负责的事情不一样。 | 层级 | 作用 | 例子 | | ----- | --------------- | ------------------------ | | 产物层 | 所有阶段必须留下可追踪产物 | PRD、AC、任务卡、测试报告、日志报告 | | 触发层 | 决定什么时候让 AI 介入 | 每日巡检、PR 触发、手动触发 | | 能力层 | 给 AI 方法、规则和外部能力 | Skills、MCP、AGENTS.md | | 执行层 | 生成、检查、修复、汇报 | Codex、Claude Code、Cursor | | 审批门层 | 控制风险,防止 AI 越权 | 人工审批、风险分级 | | 复盘反馈层 | 把每次执行沉淀成经验 | 周报、复盘、更新 Skill | 普通 AI 编程工具流只停留在 Execution Layer。 这套流水线的重点是:**每一次 AI 介入都必须有输入、有输出、有边界、有审批、有复盘。** --- ## 三、Codex常用功能 #### 1.插件安装 ![image.png](https://pic.code-nav.cn/post_picture/1813254542264233985/smsyXwJbJiaOqy6k.webp) 在Codex左上角插件处即可下载以上所有所需的MCP包括computer use,FIgma,Playwright,Github MCP等。 #### 2.自动化工作流安排 ![image.png](https://pic.code-nav.cn/post_picture/1813254542264233985/7MQgbZKUCGYTYeTh.webp) 在codex的左上角即可进行以上所有工作流的编排,可以自行选择执行时间、重复次数、模型调用和项目所在的工作数分支还是本地执行等。也可以直接将本文放进对话框中后续再进行根据自己的实际情况微调直接执行实现AI编程的工作流。 #### 3.Plan模式 需求分析和分阶段任务的阶段建议使用Plan Mode模式,会给出项目的阶段方案和测试方案等,参考如下: ![image.png](https://pic.code-nav.cn/post_picture/1813254542264233985/28lv21ONY2wdXrYe.webp) #### 4.Goal模式 如果你有了具体的Plan想让Codex长时间往一个目标执行任务,你就可以使用Goal模式,他会长时间进行工作直到达到你的目标,参考图如下: ![image.png](https://pic.code-nav.cn/post_picture/1813254542264233985/G0m1pcLe9E1tbJY6.webp) #### 5.关于本文的前端原型图设计 具体可以使用文章提到过的product design和fronted design 以及结合Figma设计前端原型图,参考图如下: ![image.png](https://pic.code-nav.cn/post_picture/1813254542264233985/NYy8bUWaJz7ZRAQ2.webp) ## 四、先定义三类触发器 成熟的 AI 流水线不是要所有事情都自动化应该有三类触发器 |触发器|适合什么|例子| |---|---|---| |定时触发|巡检、报告、提醒、复盘|每天 9 点测试巡检,每周日复盘| |事件触发|PR、push、issue、CI 失败|PR 自动审查,构建失败分析| |手动触发|高判断、高风险、高成本任务|需求确认、技术方案、生产部署| 这对应到 AI 项目流水线里,就是: ```text 定时触发:让 AI 定期巡检 事件触发:让 AI 响应项目变化 手动触发:让 AI 处理高价值但需要人确认的节点 ``` 一句话: **自动化不是让 AI 自己做决定,而是让 AI 在合适的时间自动收集证据、生成初稿、发现风险,然后把需要人判断的东西推到我面前。** --- ## 五、再定义五个风险等级 没有风险等级,AI 自动化迟早会失控。 我把任务分成五级: |等级|含义|AI 可以做什么|是否需要人工确认| |---|---|---|---| |L0|只读分析|读文档、读日志、读 diff、生成报告|不需要| |L1|低风险产物|生成 PRD 初稿、任务卡、README、复盘|需要审核后进入下一阶段| |L2|低风险修改|改文档、补测试、修文案、轻量 UI|合并前确认| |L3|中高风险修改|改接口、改权限、改数据库、重构模块|修改前和合并前都确认| |L4|生产高风险|部署生产、删除数据、改密钥、迁移库|AI 禁止自动执行| 这个表是整套流水线的安全底座。 每张任务卡都要写: ``` 风险等级:L0 / L1 / L2 / L3 / L4 自动化等级:只读 / 仅生成草稿 / AI 可修改 / AI 可修改但必须审查 / 仅人工处理 ``` 比如: ```text README 更新:L1,draft_only 补单元测试:L2,ai_can_patch_with_review 新增登录接口:L3,ai_can_patch_with_review 修改生产 Nginx:L4,manual_only 数据库迁移:L4,manual_only ``` 这就体现了第一条核心原则: **AI 编程的下一步,不是更长的 Prompt,而是更清晰的工程边界。** --- ## 六、第一步:搭项目目录 先在项目根目录建这套结构。 ```text project-root/ ├── AGENTS.md ├── .agents/ │ └── skills/ │ ├── 01-idea-review/ │ │ └── SKILL.md │ ├── 02-product-design/ │ │ └── SKILL.md │ ├── 03-tech-spec/ │ │ └── SKILL.md │ ├── 04-task-breakdown/ │ │ └── SKILL.md │ ├── 05-implementation/ │ │ └── SKILL.md │ ├── 06-test-triage/ │ │ └── SKILL.md │ ├── 07-review-gate/ │ │ └── SKILL.md │ ├── 08-deploy-readiness/ │ │ └── SKILL.md │ ├── 09-log-triage/ │ │ └── SKILL.md │ └── 10-postmortem/ │ └── SKILL.md ├── .github/ │ ├── workflows/ │ │ ├── ci.yml │ │ ├── codex-pr-review.yml │ │ └── release-check.yml │ └── codex/ │ └── prompts/ │ ├── pr-review.md │ ├── release-check.md │ ├── deploy-check.md │ └── weekly-review.md ├── ideas/ ├── docs/ ├── prototype/ ├── tasks/ ├── inbox/ ├── reports/ └── deploy/ ``` 这套目录是我为了让 AI 工作流可沉淀、可追踪、可复盘设计的项目结构。 核心目的只有一个: ```text 不要让 AI 产物散落在聊天记录里。 所有东西必须落到文件系统。 ``` 需求审查进 `docs/idea-review/`。 任务卡进 `tasks/`。 自动巡检结果进 `inbox/`。 部署检查进 `deploy/`。 复盘进 `reports/`。 这就是 Artifact Layer。 --- ## 七、第二步:写 AGENTS.md,先给 AI 立规矩 没有 `AGENTS.md`,AI 每次进项目都在猜。 先把项目技术栈、命令、风险边界、审批规则先写清楚。 ```` # AGENTS.md ## 项目角色 你是这个项目的后台开发助理。 你可以协助完成: - 想法审查 - 产品原型 - 技术方案 - 任务拆解 - 低风险实现 - 测试联调 - 代码审查 - 部署前检查 - 日志排查 - 每周复盘 在没有人工批准之前,你不能做高风险决策。 ## 项目技术栈 后端: - Java 17 - Spring Boot 3 - MyBatis-Plus - MySQL - Redis 前端: - Vue 3 - TypeScript - Vite - Element Plus 部署: - Docker Compose - Nginx - HTTPS - Cloudflare 或反向代理 ## 常用命令 后端: bash mvn test mvn spring-boot:run 前端: cd frontend npm install npm run build npm run dev Docker: docker compose up -d --build docker compose ps docker compose logs -f backend ## 工作流规则 修改代码前: 1. 先阅读相关文档和文件。 2. 说明你的执行计划。 3. 列出你打算修改的文件。 4. 判断风险等级。 5. 如果任务属于中风险或高风险,必须等待人工确认。 修改代码后: 1. 总结修改了哪些文件。 2. 运行必要的测试。 3. 报告测试结果。 4. 说明剩余风险。 5. 未经过验证,不能声称任务完成。 ## 风险等级 L0:只读分析 - 读取文档 - 读取日志 - 读取 diff - 生成报告 L1:仅生成草稿 - 生成 PRD 初稿 - 生成任务卡 - 生成 README 初稿 - 生成周报 L2:低风险修改 - 文档更新 - 测试补充 - UI 文案调整 - 非生产脚本的小改动 L3:中高风险修改 - 新增 API 接口 - 认证相关逻辑 - 权限相关逻辑 - 数据库访问逻辑 - 业务模块重构 L4:生产高风险 - 生产部署 - 密钥 - 数据库迁移 - Nginx / Docker / CI 变更 - 支付 / 积分 / 扣费逻辑 - 安全策略变更 ## 硬性规则 - 不能提交密钥、API Key、密码、Token 或 `.env` 文件。 - 不能删除测试,除非得到明确批准。 - 不能削弱认证或权限控制。 - 不能擅自修改已经应用过的历史迁移文件。 - 不能把数据库、Redis、Docker API 或内部服务暴露到公网。 - 不能自动部署到生产环境。 - 不能修改生产数据。 - 如果不确定,必须先请求确认。 ## 代码审查关注点 审查变更时,重点关注: - 是否缺少测试 - 是否引入安全回退 - 是否绕过权限 - 是否记录了用户敏感信息 - 是否泄露密钥 - 是否出现 N+1 查询 - 是否存在数据库迁移风险 - 是否存在部署风险 - 是否修改了无关文件 ```` 这份文件是整个流水线的“施工规范”。 Codex、Claude Code 会读取 `AGENTS.md` 作为项目级指导,把里面的技术栈、命令、风险边界和 Review 规则带入后续任务。 --- ## 八、第三步:写 10 个 Skill Skill 的价值是把同类任务的经验固化下来。 注意:这里的路径不要写成 `skills/xxx/SKILL.md`。 Codex 仓库级 Skill 默认应该放在: ```text .agents/skills/{skill-name}/SKILL.md ``` ### 1. idea-review-skill(需求分析skill) 路径: ```text .agents/skills/01-idea-review/SKILL.md ``` 内容: ```md --- name: idea-review-skill description: 在实现项目之前审查想法。用于判断真实痛点、目标用户、MVP 范围、风险和简历价值。 --- # 想法审查 Skill ## 目标 帮助判断一个想法是否值得进入开发流水线。 ## 输入 - 项目想法描述 - 目标用户 - 开发者背景 - 可投入时间 - 已有替代品,如果有的话 ## 输出 生成一份想法审查报告,包含: 1. 判断结果:执行 / 等待 / 放弃 2. 真实痛点 3. 目标用户 4. 现有替代品 5. MVP 范围 6. 7 天可行性 7. 技术风险 8. 产品风险 9. 简历价值 10. 前 7 天计划 ## 规则 - 不能自动创建开发任务。 - 不能修改源代码。 - 如果缺少证据,标记为“需要调研”。 - 审查要严格。如果想法模糊,优先给出“等待”或“放弃”。 ``` ### 2. product-design-skill(前端原型设计图skill) 路径: ```text .agents/skills/02-product-design/SKILL.md ``` 内容: ```md --- name: product-design-skill description: 在前端编码之前,设计产品流程、页面结构、用户状态和交互逻辑。 --- # 产品设计 Skill ## 目标 不要直接生成前端代码。 先设计产品流程、信息结构和页面状态。 ## 输出 1. 用户目标 2. 核心用户流程 3. 页面列表 4. 页面信息结构 5. 核心组件 6. 加载 / 空状态 / 错误 / 成功状态 7. 表单校验规则 8. 危险操作和二次确认 9. 桌面端 / 移动端图片预览 10. 交给前端实现的说明 ## 规则 - 不要编造业务逻辑。 - 不清楚的需求标记为“待确认”。 - 每个页面都必须包含加载、空状态、错误和成功状态。 - 优先使用简单流程,不要一开始设计复杂页面。 ``` ### 3. tech-spec-skill(技术选型和后端架构skill) 路径: ```text .agents/skills/03-tech-spec/SKILL.md ``` 内容: ```md --- name: tech-spec-skill description: 把已确认的 PRD 和原型转换成技术方案、接口设计、数据库草案、风险清单和任务边界。 --- # 技术方案 Skill ## 目标 在正式实现之前生成技术方案。 ## 输出 1. 模块设计 2. 后端包结构 3. 前端页面和组件结构 4. API 接口 5. 请求和响应结构 6. 数据库表和索引 7. 认证和权限边界 8. 错误处理 9. 日志和 traceId 10. 缓存策略 11. 限流策略 12. 测试策略 13. 部署影响 14. 风险清单 ## 规则 - 不能修改源代码。 - 不能创建数据库迁移文件。 - 不确定的决策标记为“需要人工确认”。 ``` ### 4. task-breakdown-skill(分阶段实现任务skill) 路径: ```text .agents/skills/04-task-breakdown/SKILL.md ``` 内容: ```md --- name: task-breakdown-skill description: 把已确认的技术方案拆成可执行任务卡,包含范围、风险等级、自动化等级和验证命令。 --- # 任务拆解 Skill ## 目标 创建 AI 编码 Agent 可以安全执行的任务卡。 ## 每张任务卡必须包含 1. 任务目标 2. 任务范围 3. 允许修改的文件 4. 禁止修改的文件 5. 验收标准 6. 验证命令 7. 风险等级 8. 自动化等级 9. 人工 Gate ## 自动化等级 - 仅人工处理 - AI 辅助分析 - AI 可以修改 - AI 可以修改但必须审查 ## 规则 - 不能修改源代码。 - 每个任务必须足够小,方便审查。 - 高风险文件必须标记为“仅人工处理”或“AI 辅助分析”。 ``` ### 5. implementation-skill(每阶段开发、测试及验收skill) 路径: ```text .agents/skills/05-implementation/SKILL.md ``` 内容: ```md --- name: implementation-skill description: 按已批准任务卡小步执行开发,并输出测试结果和变更摘要。 --- # 开发实现 Skill ## 目标 只实现 `tasks/ready/` 中已经批准的任务。 ## 工作流程 1. 读取 AGENTS.md。 2. 读取任务卡。 3. 读取相关文件。 4. 提出执行计划。 5. 判断风险等级。 6. 如有需要,等待人工确认。 7. 只修改允许修改的文件。 8. 运行验证命令。 9. 总结变更和风险。 ## 规则 - 不能从 backlog 中自行挑任务。 - 不能修改禁止修改的文件。 - 验证没有通过,不能把任务标记为完成。 - 不能自动修改生产配置。 ``` ### 6. test-triage-skill(测试和修复skill) 路径: ```text .agents/skills/06-test-triage/SKILL.md ``` 内容: ```md --- name: test-triage-skill description: 运行或分析项目验证检查,并生成测试排查报告。 --- # 测试排查 Skill ## 目标 发现构建失败、测试失败和有风险的 TODO/FIXME 变更。 ## 检查项 1. 后端测试 2. 前端构建 3. Docker Compose 配置 4. 健康检查接口,如果有的话 5. TODO/FIXME 扫描 6. 最近变更文件是否缺少测试 ## 输出 - 状态:自动归档 / 需要查看 / 阻塞发布 / 需要人工处理 - 证据 - 疑似原因 - 最小下一步 - 建议负责人 ## 规则 - 除非明确批准,否则不能修改源代码。 - 如果全部通过,标记为“自动归档”。 ``` ### 7. review-gate-skill(代码合并前审查skill) 路径: ```text .agents/skills/07-review-gate/SKILL.md ``` 内容: ```md --- name: review-gate-skill description: 审查 diff 和 PR,关注安全、正确性、测试、部署和无关改动。 --- # 代码审查门禁 Skill ## 目标 在合并前审查变更。 ## 重点关注 1. 安全回退 2. 认证 / 权限绕过 3. 密钥泄露 4. 用户敏感信息日志 5. 删除或削弱测试 6. 数据库迁移风险 7. 部署配置风险 8. 意外的无关改动 9. N+1 查询 10. 缺少错误处理 ## 审查结论 - 可以合并 - 需要继续审查 - 禁止合并 ## 规则 - 除非明确要求,否则不要重写代码。 - 优先关注 P0 / P1 高风险问题。 - 如果涉及部署、认证、数据库、密钥或 CI 变更,至少标记为“需要继续审查”。 ``` ### 8. deploy-readiness-skill(上线前部署skill) 路径: ```text .agents/skills/08-deploy-readiness/SKILL.md ``` 内容: ```md --- name: deploy-readiness-skill description: 检查部署准备情况、回滚计划、冒烟测试、端口、密钥、日志和生产风险。 --- # 部署准备检查 Skill ## 目标 准备部署检查清单和回滚计划。不能自动部署。 ## 检查项 1. CI 结果 2. docker-compose.yml 3. Nginx 配置 4. .env.example 5. 暴露端口 6. 数据库迁移 7. 数据卷挂载 8. 日志 9. 备份策略 10. 回滚命令 11. 冒烟测试 12. 安全风险 ## 输出 - deploy-readiness.md - rollback-plan.md - smoke-test.md ## 规则 - 不能部署到生产环境。 - 不能打印密钥。 - 有风险的内容必须标记为“需要人工处理”。 ``` ### 9. log-triage-skill(日志排查skill) 路径: ```text .agents/skills/09-log-triage/SKILL.md ``` 内容: ```md --- name: log-triage-skill description: 在部署后或定时任务中分析日志和健康信号。 --- # 日志排查 Skill ## 目标 从日志和健康检查中发现运行问题。 ## 检查项 1. 5xx 错误 2. Nginx 404 / 502 异常增长 3. 认证失败 4. AI 模型超时 5. JSON 解析失败 6. 限流错误 7. PDF 导出失败 8. 容器重启 9. 磁盘 / 内存告警 ## 输出 - 严重程度:信息 / 警告 / 错误 / 严重 - 证据 - 疑似原因 - 建议下一步 - 是否需要人工处理 ## 规则 - 默认只读。 - 不能删除日志。 - 不能修改生产配置。 ``` ### 10. postmortem-skill(复盘和沉淀skill) 路径: ```text .agents/skills/10-postmortem/SKILL.md ``` 内容: ```md --- name: postmortem-skill description: 生成周报、发布复盘、技术债、简历表达和面试问题。 --- # 复盘沉淀 Skill ## 目标 把项目活动转化成可复用经验和下一步行动。 ## 输入 - tasks/done - inbox/test - inbox/review - inbox/deploy - inbox/logs - 已合并 PR - 本周提交记录 ## 输出 1. 本周交付了什么 2. 哪些地方失败了 3. 剩余风险 4. 技术债 5. 下周计划 6. AGENTS.md 更新建议 7. Skill 更新建议 8. 简历表达候选 9. 面试问题 10. 博客选题 ## 规则 - 不能修改源代码。 - 区分事实和建议。 - 不确定的结论标记为“需要复查”。 ``` 这 10 个 Skill 就是能力层。 --- ## 九、第四步:写 10 个流水线节点 下面开始搭真正的流水线。 每个节点都要包含: ```text 触发方式 输入 Skill AI 动作 输出 人工 Gate ``` --- ## 节点 1:需求判断 Pipeline 目标:不让自己冲动开项目。 触发方式: ```text 手动触发:新建 ideas/xxx.md 定时触发:每周扫描 ideas/ ``` 输入: ```text ideas/*.md ``` 输出: ```text docs/idea-review/{idea-name}.md ``` 自动化 Prompt: ```text 使用 idea-review-skill。 扫描 ideas/ 目录。 对于每一个还没有在 docs/idea-review/ 里生成审查报告的想法,创建一份想法审查报告。 不能自动创建开发任务。 不能修改源代码。 每个想法都要输出: - 执行 / 等待 / 放弃 - 真实痛点 - 目标用户 - 现有替代品 - MVP 范围 - 7 天可行性 - 简历价值 - 技术风险 - 产品风险 如果没有新的想法,把本次运行标记为“自动归档”。 ``` 人工 Gate: ```text 只有我把判断结果改成“执行”,才允许进入 PRD 阶段。 ``` 核心判断: ```text AI 是立项审计员,不是产品老板。 ``` --- ## 节点 2:原型设计 Pipeline 目标:先把产品流程想清楚,不让 AI 直接写前端。 触发方式: ```text 手动触发 ``` 输入: ```text docs/PRD.md docs/AC.md ``` 输出: ```text prototype/page-flow.md prototype/DESIGN.md prototype/wireframe.html ``` 自动化 Prompt: ```text 使用 product-design-skill。 读取 docs/PRD.md 和 docs/AC.md。 生成: - prototype/page-flow.md - prototype/information-architecture.md - prototype/states.md - prototype/DESIGN.md - prototype/wireframe.html 不能修改前端源代码。 如果可以使用 browser MCP: - 打开 prototype/wireframe.html。 - 检查主流程是否清晰可见。 - 检查加载、空状态、错误、成功状态是否完整。 - 报告页面布局问题。 最后输出检查清单: - 核心流程是否清晰? - 所有状态是否覆盖? - 哪些部分需要人工确认? ``` 人工 Gate: ```text 我确认原型后,才能进入技术方案阶段。 ``` 核心判断: ```text 前端自动化不是先写 Vue,而是先生成产品结构和页面状态。 ``` --- ## 节点 3:技术方案 Pipeline 目标:把产品需求转成可开发的技术合同。 触发方式: ```text 手动触发 ``` 输入: ```text docs/PRD.md docs/AC.md prototype/DESIGN.md 当前代码结构 ``` 输出: ```text docs/tech-spec.md docs/api-spec.md docs/db-schema-draft.sql docs/risk-checklist.md ``` 自动化 Prompt: ```text 使用 tech-spec-skill。 读取: - docs/PRD.md - docs/AC.md - prototype/DESIGN.md - 当前项目代码结构 生成: - docs/tech-spec.md - docs/api-spec.md - docs/db-schema-draft.sql - docs/risk-checklist.md 技术方案必须包含: 1. 模块设计 2. 后端包结构 3. 前端页面和组件结构 4. API 接口 5. 请求和响应结构 6. 数据库表和索引 7. 认证和权限边界 8. 错误处理 9. 日志和 traceId 10. 缓存策略 11. 限流策略 12. 测试策略 13. 部署影响 14. 风险清单 不能修改源代码。 不能创建数据库迁移文件。 所有不确定的决策都标记为“需要人工确认”。 ``` 人工 Gate: ```text 未审批 tech-spec.md,不允许拆任务。 ``` 核心判断: ```text 技术方案是 AI 开发前的边界合同。没有合同就开工,本质是让 AI 猜架构。 ``` --- ## 节点 4:任务拆解 Pipeline 目标:把技术方案拆成任务卡,并给每个任务标注风险等级。 触发方式: ```text 手动触发 docs/tech-spec.md 变更后触发 ``` 输入: ```text docs/tech-spec.md docs/api-spec.md docs/risk-checklist.md ``` 输出: ```text tasks/backlog/*.md ``` 任务卡模板: ````md # 任务:001-login-api ## 目标 实现用户登录接口。 ## 范围 允许修改的文件: - backend/src/main/java/.../AuthController.java - backend/src/main/java/.../AuthService.java - backend/src/main/java/.../dto/LoginRequest.java - backend/src/main/java/.../vo/LoginResponse.java 禁止修改的文件: - docker-compose.yml - nginx.conf - 生产环境 .env - 已经应用过的历史迁移文件 ## 验收标准 - 正确账号密码返回 accessToken - 错误密码返回统一错误码 - 空字段返回参数校验错误 - 密码不明文存储 - 测试通过 ## 验证命令 ```bash mvn test ``` ## 风险等级 L3:中高风险修改 ## 自动化等级 AI 可以修改,但必须审查。 ## 人工 Gate 需要人工看 diff 后合并。 ```` 自动化 Prompt: ```text 使用 task-breakdown-skill。 读取 docs/tech-spec.md、docs/api-spec.md、docs/risk-checklist.md。 在 tasks/backlog/ 下创建任务卡。 每张任务卡都必须包含: - 任务目标 - 任务范围 - 允许修改的文件 - 禁止修改的文件 - 验收标准 - 验证命令 - 风险等级 - 自动化等级 - 人工 Gate 不能修改源代码。 ``` 人工 Gate: ```text 只有我把任务从 tasks/backlog 移到 tasks/ready,Codex 才能执行。 ``` 核心判断: ```text AI 不是自动抢任务,而是领取已批准的任务卡。 ``` --- ## 节点 5:前后端开发 Pipeline 目标:让 AI 小步执行,不接管整个项目。 触发方式: ```text 任务被人工移动到 tasks/ready GitHub issue 加 codex-ready 标签 ``` 输入: ```text tasks/ready/*.md AGENTS.md 相关源码 ``` 输出: ```text branch / worktree diff test result tasks/done/{task}.md ``` 自动化 Prompt: ```text 先读取 AGENTS.md。 从 tasks/ready/ 中选择一个任务。 不要立刻开始写代码。 先输出: 1. 任务摘要 2. 需要读取的文件 3. 计划修改的文件 4. 风险等级 5. 验证命令 6. 是否需要人工确认 如果任务风险等级是 L3 或 L4,必须停止并等待确认。 如果已经批准: - 创建或使用独立 branch / worktree。 - 只修改任务卡允许修改的文件。 - 运行验证命令。 - 写出变更摘要。 - 只有测试通过,才能把任务移动到 tasks/done。 ``` 人工 Gate: ```text 所有代码进入 main 前必须 PR。 L3 任务修改前必须确认。 L4 任务 AI 不允许自动执行。 ``` 核心判断: ```text AI 可以执行任务,但不能突破任务卡边界。 ``` --- ## 节点 6:测试联调 Pipeline 目标:让失败不再沉默。 触发方式: ```text 每日定时 PR 更新 手动触发 ``` 输入: ```text 源码 测试命令 构建命令 健康检查接口 ``` 输出: ```text inbox/test/test-report-{date}.md ``` 自动化 Prompt: ```text 使用 test-triage-skill。 运行项目验证检查清单: 1. 后端测试 2. 前端构建 3. Docker Compose 配置验证 4. 如果有健康检查接口,就检查健康接口 5. 扫描 TODO / FIXME 变更 把报告写入 inbox/test/test-report-{today}.md。 如果所有检查都通过: - 标记为“自动归档”。 如果任意检查失败: - 标记为“需要人工查看”。 - 解释失败原因。 - 给出最小修复建议。 - 除非明确批准,否则不能修改代码。 ``` 处理规则: ```text 全部通过:自动归档 低风险失败:需要人工查看 影响部署:阻塞发布 涉及安全/数据库:需要人工处理 ``` 核心判断: ```text 测试联调的自动化价值,不是让 AI 修所有 Bug,而是让失败不再沉默。 ``` --- ## 节点 7:代码审查 Pipeline 目标:AI 写的代码必须过 Review Gate。 触发方式: ```text pull_request opened pull_request synchronize @codex review ``` 输入: ```text PR diff AGENTS.md review guidelines ``` 输出: ```text PR review comment inbox/review/pr-{number}.md ``` Codex GitHub integration 支持在 PR 评论中使用 `@codex review` 请求审查,也可以开启 automatic reviews。Codex 会读取仓库里的 `AGENTS.md` review guidance,并优先标出 P0/P1 高优先级风险。 PR Review Prompt: ```md # .github/codex/prompts/pr-review.md 你正在审查一个 Pull Request。 请先读取 AGENTS.md,并遵守其中的代码审查规范。 重点关注: 1. 是否引入安全回退 2. 是否绕过认证或权限 3. 是否泄露密钥 4. 是否记录用户敏感信息 5. 是否删除或削弱测试 6. 是否存在数据库迁移风险 7. 是否存在部署配置风险 8. 是否出现意外的无关改动 9. 是否引入 N+1 查询 10. 是否缺少错误处理 输出: - 结论:可以合并 / 需要继续审查 / 禁止合并 - P0 问题 - P1 问题 - P2 问题 - 必须修复项 - 需要人工确认的问题 除非明确要求,否则不要重写代码。 ``` GitHub Action 示例: ```yaml name: Codex PR Review on: pull_request: types: [opened, synchronize, reopened] jobs: codex-review: runs-on: ubuntu-latest permissions: contents: read pull-requests: write steps: - uses: actions/checkout@v5 with: ref: refs/pull/${{ github.event.pull_request.number }}/merge fetch-depth: 0 persist-credentials: false - name: Run Codex Review uses: openai/codex-action@v1 with: openai-api-key: ${{ secrets.OPENAI_API_KEY }} prompt-file: .github/codex/prompts/pr-review.md output-file: codex-output.md ``` GitHub Actions workflow 可以通过 `pull_request` 等事件触发,适合把审查、测试、构建等流程接进 PR 生命周期。 人工 Gate: ```text 禁止合并:不能合并 需要继续审查:必须处理 可以合并:仍需人工最终确认 ``` 核心判断: ```text 代码审查不是整套系统的全部,它只是 AI 项目流水线里的风险门禁。 ``` --- ## 节点 8:部署上线 Pipeline 目标:AI 做 readiness,人做 deploy。 触发方式: ```text release 分支 tag 创建前 手动触发 ``` 输入: ```text CI 结果 docker-compose.yml nginx.conf .env.example migration ``` 输出: ```text deploy/deploy-readiness.md deploy/rollback-plan.md deploy/smoke-test.md ``` 自动化 Prompt: ```text 使用 deployment-readiness-skill。 检查这个项目是否具备部署条件。 检查: 1. CI 结果 2. docker-compose.yml 3. Nginx 配置 4. .env.example 5. 暴露端口 6. 数据库迁移 7. 数据卷挂载 8. 日志 9. 备份策略 10. 回滚命令 11. 冒烟测试 12. 安全风险 生成: - deploy/deploy-readiness.md - deploy/rollback-plan.md - deploy/smoke-test.md 不能自动部署。 所有有风险的项目都必须标记为“需要人工处理”。 ``` 部署前清单: ```text [ ] 后端构建通过 [ ] 前端构建通过 [ ] Docker Compose 配置通过 [ ] 数据库迁移可回滚或已备份 [ ] Nginx 配置通过 nginx -t [ ] 80/443 暴露合理 [ ] MySQL / Redis 不暴露公网 [ ] .env.example 字段完整 [ ] 生产密钥未进入 Git [ ] 备份可用 [ ] rollback-plan.md 已生成 [ ] smoke-test.md 已生成 ``` 人工 Gate: ```text 生产部署必须人工执行。 AI 只能生成检查清单和命令。 ``` 核心判断: ```text AI 可以帮我准备上线,但不能替我承担生产事故。 ``` --- ## 节点 9:日志排查 Pipeline 目标:上线后让 AI 值班。 触发方式: ```text 每天 9 点 部署后 30 分钟 出现错误激增时 ``` 输入: ```text 后端日志 Nginx 日志 AI 调用日志 Docker 日志 健康检查接口 ``` 输出: ```text inbox/logs/log-triage-{date}.md ``` 自动化 Prompt: ```text 使用 log-triage-skill。 分析最近 24 小时日志。 检查: - 5xx 错误 - Nginx 404 / 502 异常增长 - 认证失败 - AI 调用失败 - 模型超时 - JSON 解析失败 - PDF 导出失败 - 容器重启 - 磁盘 / 内存告警 输出: - 严重程度:信息 / 警告 / 错误 / 严重 - 主要发现 - 证据 - 疑似原因 - 建议下一步 - 是否需要人工处理 把报告写入 inbox/logs/log-triage-{today}.md。 不能修改生产配置。 不能删除日志。 ``` 处理规则: ```text 信息:归档 警告:进入技术债 错误:进入 inbox 严重:人工处理 ``` 核心判断: ```text 上线后,AI 不是继续写代码,而是开始值班。 ``` --- ## 节点 10:复盘沉淀 Pipeline 目标:把项目过程变成下一轮规则。 触发方式: ```text 每周五 每次 release 后 每次 incident 后 ``` 输入: ```text tasks/done inbox/test inbox/review inbox/deploy inbox/logs merged PR weekly commits ``` 输出: ```text reports/weekly/weekly-dev-review.md reports/release/release-postmortem.md reports/weekly/resume-bullets.md reports/weekly/interview-questions.md ``` 自动化 Prompt: ```text 使用 postmortem-skill。 生成每周开发复盘。 读取: - tasks/done/ - inbox/test/ - inbox/review/ - inbox/deploy/ - inbox/logs/ - 本周 git commits 输出: 1. 本周交付了什么 2. 哪些地方失败了 3. 还剩哪些风险 4. 下周应该修什么 5. AGENTS.md 哪些规则应该更新 6. 哪些 Skill 应该优化 7. 简历表达候选 8. 面试问题 9. 博客文章选题 不能修改源代码。 ``` 核心判断: **复盘不是写总结,而是把一次项目经验重新写进规则、Skill 和下一轮流水线。** --- ## 十、第五步:配置 Codex Automations(自动化) ### Automation 1:Daily Test Triage(24小时测试bug评审) 名称: ```text Daily Test Triage ``` 频率: ```text 每天 9:00 ``` Prompt: ```text 使用 test-triage-skill。 运行每日验证检查清单: 1. 检查 git status。 2. 如果最近修改了后端文件,运行后端测试。 3. 如果最近修改了前端文件,运行前端构建。 4. 如果修改了部署文件,检查 Docker Compose 配置。 5. 扫描最近 24 小时新增的 TODO / FIXME。 把报告写入 inbox/test/test-report-{today}.md。 如果所有检查通过,标记为“自动归档”。 如果有任何检查失败,标记为“需要人工查看”,并说明最小下一步。 不能修改源代码。 ``` --- ### Automation 2:Daily Log Triage(24小时日志审计) 名称: ```text Daily Log Triage ``` 频率: ```text 每天 10:00 ``` Prompt: ```text 使用 log-triage-skill。 分析最近 24 小时日志。 检查: - 后端错误 - 认证失败 - AI 调用失败 - 模型超时 - JSON 解析失败 - PDF 导出失败 - 容器重启 - Nginx 502 / 404 异常增长 把报告写入 inbox/logs/log-triage-{today}.md。 如果没有明显问题,标记为“自动归档”。 如果存在警告或错误,生成一份简短行动清单。 不能修改生产配置。 不能删除日志。 ``` --- ### Automation 3:Weekly Tech Debt Scan(7天技术债务扫描) 名称: ```text Weekly Tech Debt Scan ``` 频率: ```text 每周五 18:00 ``` Prompt: ```text 扫描项目技术债。 检查: 1. TODO / FIXME 2. 最近变更文件是否缺少测试 3. 文档是否过期 4. tasks/doing 中是否有长期未完成任务 5. 是否存在可疑重复代码 6. README 是否缺少关键部分 7. 部署文档是否和当前配置不一致 8. 风险项是否长期未解决 把报告写入 reports/weekly/tech-debt-{date}.md。 不能修改源代码。 只有当问题明确可执行时,才在 tasks/backlog/ 下创建任务建议。 ``` --- ### Automation 4:Weekly Dev Review(7天开发复盘) 名称: ```text Weekly Dev Review ``` 频率: ```text 每周日 21:00 ``` Prompt: ```text 使用 postmortem-skill。 基于以下内容生成每周复盘: - tasks/done/ - inbox/test/ - inbox/review/ - inbox/deploy/ - inbox/logs/ - 本周 git commits 输出: - 本周交付了什么 - 哪些地方失败了 - 还剩哪些风险 - 下周要做什么 - AGENTS.md 哪些规则应该更新 - 哪些 Skill 应该优化 - 简历表达候选 - 面试问题 - 博客文章选题 写入 reports/weekly/weekly-dev-review-{date}.md。 ``` 这 4 个自动化已经能覆盖: ```text 项目有没有坏 PR 能不能合 上线有没有风险 经验有没有沉淀 ``` 不要一上来配置 20 个 automation。 自动化越多,越需要治理。先把 MVP 跑通。 --- ## 十一、第六步:MCP 权限怎么控制 ### 建议安装的 MCP | 推荐顺序 | MCP | 是否建议第一版安装 | 主要解决什么问题 | 推荐权限 | 对应流水线阶段 | | ---- | ---------------------------- | --------- | --------------------------------- | ------------------------- | ---------------------- | | 1 | filesystem MCP | 建议安装 | 读取项目文档、任务卡、源码、报告、inbox | 限定项目目录;优先只读;源码修改必须走任务卡 | 需求判断、技术方案、任务拆解、测试巡检、复盘 | | 2 | GitHub MCP | 建议安装 | 读取 issue、PR、diff、review、仓库状态 | 第一版只读;写 PR 评论前需要确认 | 任务拆解、代码审查、PR 风险检查、复盘 | | 3 | browser / Playwright MCP | 建议安装 | 打开页面、检查白屏、截图、跑前端流程 | 仅开发环境或测试环境;禁止自动操作生产后台 | 原型设计、前端联调、冒烟测试 | | 4 | logs MCP / 日志读取工具 | 建议安装 | 读取后端日志、Nginx 日志、Docker 日志、AI 调用日志 | 只读;禁止删除日志;禁止修改配置 | 部署后巡检、每日日志排查、事故复盘 | | 5 | search / fetch MCP | 可选但推荐 | 查询官方文档、错误原因、依赖变更、竞品信息 | 只读;优先官方文档;搜索结果不能直接作为改代码依据 | 需求判断、技术调研、故障排查 | | 6 | database MCP | 谨慎安装 | 查询表结构、排查错误数据、辅助分析 SQL 问题 | 只读;优先开发库/测试库;禁止生产写入 | 技术方案、测试联调、日志排查 | | 7 | Figma MCP | 视项目而定 | 读取设计稿、辅助前端还原和 UI 审查 | 只读;不自动修改正式设计稿 | 原型设计、前端开发 | 不要一上来就给 AI: ```text 生产数据库写权限 服务器 root 权限 删除文件权限 部署生产权限 密钥读取权限 ``` 应该写清楚边界: ```text MCP 第一版只读优先。 任何写操作都必须人工确认。 生产环境默认不允许 AI 直连写入。 数据库、密钥、部署、删除、迁移必须标记为仅人工处理。 ``` MCP 的价值不是让 AI 权限更大,而是让 AI 在受控边界内拿到更真实的上下文。有了边界,MCP 才能真正把编码Agent从“写代码工具”变成“后台开发助理”。 最后验收: ## 真实可用验收清单 搭完以后,不要只看目录有没有创建,要检查是否真的能识别和运行。 ```text [ ] Skill 是否放在 .agents/skills/ 下 [ ] 每个 Skill 是否都有 SKILL.md [ ] 每个 SKILL.md 是否有 name 和 description [ ] 在 Codex 输入 $ 时,是否能看到对应 Skill [ ] Automation Prompt 是否显式使用 $skill-name [ ] Automation 是否选择了正确项目 [ ] Git 仓库里的 Automation 是否优先使用 worktree 隔离 [ ] inbox/ 是否能收到自动化结果 [ ] GitHub PR Review 是使用 @codex review,还是 GitHub Action,二者是否讲清楚 [ ] GitHub Action 是否配置 OPENAI_API_KEY [ ] AGENTS.md 是否写了 Review guidelines [ ] MCP 是否只开放必要权限 [ ] 部署、数据库、密钥、删除操作是否明确标记 manual_only ``` --- ## 十二、这套流程怎么写进简历? 不要只写: ```text 熟练使用 Codex / Claude Code / Cursor 辅助开发。 ``` 可以写成: 设计并实践 AI 项目交付流水线,基于 Codex Automations、AGENTS.md、Agent Skills、MCP 和 GitHub PR Review,将测试巡检、PR 风险审查、部署前检查、日志排查和复盘沉淀等环节结构化为可触发、可追踪、可审批的工程流程。通过 AI Dev Inbox 统一收口自动化发现的问题,并按“自动归档 / 需要人工查看 / 创建任务 / 需要人工处理”分流,降低 AI 编程中的无序修改、重复排查和上线风险。 如果面试官问: > 这不就是会用 AI 工具吗? 可以这样回答: 不是。我做的不是单点 AI 编程,而是把 AI 能力放进软件工程流水线。每个阶段都有输入、触发器、产物、风险等级和人工 Gate。比如测试巡检可以定时触发,PR 审查可以事件触发,部署检查必须人工确认,日志排查会进入 inbox,复盘会反向更新 AGENTS.md 和 Skill。AI 负责重复检查、初稿生成和低风险执行,人负责方向判断、审批和上线结果。 这就不是“会用工具”,这是“会设计 AI 工程流程”。 --- ## 十三、总结:AI 工作流真正值钱的地方 如果只会让 AI 写代码,门槛会越来越低。 真正有价值的是你能不能回答这些问题: ```text AI 在什么时候介入? 它读什么上下文? 它调用什么工具? 它输出什么产物? 它能不能改代码? 它能改哪些文件? 它改完怎么验证? 它什么时候必须停下来等人? 它的发现进入哪里? 它的经验怎么沉淀到下一轮? ``` 这就是我理解的 AI 工作流一体化。 不是让 AI 替我从 0 到 1 做完项目。 而是把 AI 放进一个有边界、有触发、有产物、有审批、有复盘的软件工程流水线里。 最后再重复这 4 句话: **AI 编程的下一步,不是更长的 Prompt,而是更清晰的工程边界。** **AI 不应该直接接管项目,而应该进入有触发、有产物、有审批的流水线。** **能自动化的是巡检、整理、初稿、检查和低风险修复;不能自动化的是方向判断、风险审批和上线责任。** **真正有价值的 AI 工作流,不是让 AI 多写一点代码,而是让项目过程变得可追踪、可审查、可复盘。** 这就是我这套 AI 项目交付流水线真正的价值。 我是Ryan,记录真实的AI应用工程,下一篇文章分享我用Codex从立项到完整构建项目的全流程经验。 跨境电商客服项目(进行中):[OmniMerchant](https://github.com/RyanCoreAI/spring-ai-crossborder-customer-service)

视频上传和视频播放慢的问题

想请教一下鱼皮,大视频课件一般都是怎么做的 我目前的方案是: 1. 前端采用分片上传,大视频(1~2GB 甚至更大)切片上传到后端。 2. 后端收到所有分片后,再合并成一个完整的视频文件进行存储。 3. 播放时直接播放这个完整的视频。 但是现在遇到了两个问题: ① 上传慢 * 大视频上传耗时很长。 * 目前只是分片上传,最后还是合并成一个完整的视频 ② 播放定位慢 * 用户充1分钟快进到 30分钟的时候特别慢 * 用户观看过程中会记录上次播放时间,例如看到 30 分钟。 * 下次进入课件时,会自动定位到 30 分钟继续播放。 * 但是由于播放的是完整视频,拖到 30 分钟需要等待很久才能开始播放,用户体验比较差。 我在想是不是应该改方案,比如: * 每个视频都生成切片 * 用户恢复播放时直接从对应时间点的切片开始播放 AI给出的方案: ``` 大文件分片上传 ↓ 后端合并原始视频 ↓ 后台 FFmpeg 转码/切片 ↓ 生成 HLS(m3u8 + ts)播放资源 ↓ PC/H5 播放 m3u8 ↓ 记录学习进度 ↓ 断点续播直接跳到对应时间点 ``` 我测试了一下,发现 FFmpeg 转码/切片耗时也比较长,尤其是 1~5GB 的大视频。

小程序、網頁端、app端開發同一個項目,如何使用AI Coding開發

### 个人情况 我有一個項目涉及小程序、網頁端、app端,我想使用ai coding在三端開發同一個功能,如何保證AI編寫一致性 ### 已有尝试 現在我的處理方式是:相同prompt,同一個對話框,相同測試案例,分三層(小程序、網頁端、app端),讓他逐步開發 ### 期望帮助 我想知道魚哥那邊是如何開發的,感覺這不是很好

Agent 不是多角色聊天,而是让大模型在边界内完成任务

Agent 这个词现在被用得很乱。有人把它理解成“会自己思考的 AI”,有人把它理解成“多个角色互相讨论”,也有人认为只要给大模型接几个工具,就已经是在做 Agent。 但从工程角度看,Agent 最重要的不是“看起来多自主”,而是: **它能不能在明确边界内,稳定完成一个多步骤任务。** 这才更接近真实 AI 应用开发。 ![image.png](https://pic.code-nav.cn/post_picture/1813254542264233985/YU8GcCXZlpGalo74.webp) ## 一、普通大模型调用解决的是“回答”,Agent 解决的是“任务” 普通 LLM 调用一般是这样的: 用户输入一个问题,模型返回一段文本。 比如用户问: > 帮我写一段求职信。 模型直接生成一段求职信。 这个过程没有问题,但它本质上还是一次文本生成。模型并不知道用户的真实简历细节有没有支撑这段求职信,也不知道目标 JD 的关键要求是什么,更不知道哪些内容不能夸大。 RAG 往前走了一步:它会先检索知识库、文档或数据库,再让模型基于材料回答。它主要解决的是“回答有没有依据”。 但 Agent 要解决的不是单次回答,而是任务推进。它不只是“查资料再回答”,而是要根据任务目标,在多个步骤之间做选择: - 它可能需要先判断用户意图,再决定要不要查询订单; - 它可能需要先检索售后政策,再判断是否能退款; - 它可能需要生成一个草稿,但不能直接提交; - 它可能需要发现证据不足,然后暂停,要求用户补充信息。 也就是说,Agent 面向的不是单次回答,而是一个可执行的任务过程。 所以我更愿意把 Agent 看成一个系统问题: **Agent 不是把 prompt 写得更像人,而是把模型放进一个可以行动、可以观察、可以被约束的系统里。** ## 二、Agent 和 Workflow 的区别 我不太建议一上来就从“自主规划”理解 Agent。 Anthropic 在《Building effective agents》里把 agentic systems 分成 workflow 和 agent。Workflow 是 LLM 和工具按照预设代码路径被编排;Agent 则是 LLM 可以动态决定自己的流程和工具使用方式。它们都属于 agentic system,但工程含义不同。 这个区分很重要。 如果你的任务路径本来就很清楚,比如: 1. 解析 JD 2. 解析简历 3. 匹配能力项 4. 找出缺口 5. 生成定制简历 6. 做证据检查 7. 输出风险提示 那它更适合先做成 workflow,而不是一上来就交给 Agent 自己规划。 Workflow 的好处是可控、可测、可复盘。 Agent 的价值在于处理那些路径不固定、步骤数量不确定、需要根据中间结果不断调整的任务。 比如客服场景里,用户可能问的是物流、退款、发票、商品参数、投诉、优惠券、售后政策,也可能把多个问题混在一起。系统很难提前写死所有路径,这时 Agent 才有更明确的价值。 换成代码视角,区别更明显: **Workflow 的下一步主要由开发者写死。 Agent 的下一步主要由模型根据当前状态决定。** 这不是说 Agent 没有代码控制,而是说代码负责提供工具、状态和边界,模型负责在这些边界里选择下一步。 但这不代表 Agent 可以无限自由。恰恰相反,Agent 越能行动,越需要边界。 ## 三、用代码理解 Agent 从代码角度看,Agent 不是一个神秘概念。 它可以先理解成: **Agent = 一个由 LLM 驱动下一步决策的任务循环。** 这里的“循环”不是说所有框架源码都必须写成 while,而是说 Agent 的运行语义大致相同:模型决定下一步,应用执行工具,工具结果写回状态,模型再基于新状态继续判断。 ```java while (!state.isFinished()) { AgentDecision decision = llm.decideNextStep(state, availableTools); if (decision.isFinalAnswer()) { return decision.answer(); } if (decision.isToolCall()) { policyGate.check(user, decision.toolName(), decision.arguments()); ToolResult result = toolExecutor.execute( decision.toolName(), decision.arguments() ); state.addToolResult(result); traceLog.record(decision, result); } if (decision.needHumanApproval()) { return pauseForHumanReview(state); } } ``` 这不是某个 Agent 框架的完整源码,而是 Agent 的核心运行语义。 普通 LLM 是: ```java String answer = llm.chat(userQuestion); return answer; ``` RAG 是: ```java List<Document> docs = retriever.search(userQuestion); String answer = llm.chat(userQuestion, docs); return answer; ``` Workflow 是: ```java JdInfo jd = parseJd(input); ResumeInfo resume = parseResume(input); MatchResult match = matchResumeToJd(jd, resume); RewriteResult rewrite = rewriteResume(match); RiskReport risk = checkEvidence(rewrite); return result; ``` Agent 是: ```java AgentState state = new AgentState(userGoal); while (state.canContinue()) { AgentDecision decision = model.decide(state, tools); switch (decision.type()) { case CALL_TOOL -> { ToolResult result = executeTool(decision); state.observe(result); } case ASK_USER -> { return askUserForMoreInfo(decision.question()); } case NEED_APPROVAL -> { return waitForHumanApproval(decision.action()); } case FINAL -> { return decision.finalAnswer(); } } } ``` 区别就在这里: **Workflow 的下一步是代码写死的。 Agent 的下一步是模型根据当前状态决定的。** 这也对应了 Anthropic 对 workflow 和 agent 的区分:前者按预设路径执行,后者由模型动态决定流程和工具使用。 --- ### 代码上,一个 Agent 至少有 5 个对象 不要先想 LangGraph、CrewAI、AutoGen。先想这 5 个类。 #### (1)AgentState:任务状态 ```java public class AgentState { private String goal; private int step; private List<Message> messages; private List<ToolCallRecord> toolCalls; private Map<String, Object> facts; private boolean finished; private boolean needHumanApproval; } ``` 它回答一个问题: **任务现在进行到哪一步了?** 没有 `AgentState`,就不是连续任务,只是一次问答。 --- #### (2)AgentTool:工具 ```java public interface AgentTool { String name(); String description(); ToolResult execute(Map<String, Object> arguments); } ``` 比如电商客服 Agent 里可以有: ```java public class GetOrderTool implements AgentTool { public String name() { return "get_order"; } public String description() { return "根据 orderId 查询当前用户的订单状态"; } public ToolResult execute(Map<String, Object> args) { String orderId = (String) args.get("orderId"); // 查数据库 / 调接口 return orderService.getOrder(orderId); } } ``` Spring AI 的 Tool Calling 也是类似思想:模型提出工具调用请求,应用程序负责解析、执行工具、再把结果返回给模型。模型不是直接操作你的数据库。 --- #### (3)AgentDecision:模型决定下一步 ```java public sealed interface AgentDecision permits CallTool, FinalAnswer, NeedHumanApproval { } public record CallTool( String toolName, Map<String, Object> arguments ) implements AgentDecision { } public record FinalAnswer( String answer ) implements AgentDecision { } public record NeedHumanApproval( String action, String reason ) implements AgentDecision { } ``` 这里就是 Agent 和普通程序最大的区别。 普通程序是你写: ``` 先查订单,再查政策,再生成回复。 ``` Agent 是模型返回: ``` { "type": "CALL_TOOL", "toolName": "get_order", "arguments": { "orderId": "123456" } } ``` 然后你的后端决定: - 这个工具存不存在? - 这个用户有没有权限? - 这个 orderId 是不是他的? - 这个动作需不需要人工确认? --- #### (4)PolicyGate:权限边界 ```java public class PolicyGate { public void check(User user, String toolName, Map<String, Object> args) { if (toolName.equals("submit_refund")) { throw new NeedHumanApprovalException("退款提交必须人工确认"); } if (toolName.equals("get_order")) { String orderId = (String) args.get("orderId"); if (!orderService.belongsToUser(orderId, user.id())) { throw new AccessDeniedException("不能查询他人订单"); } } } } ``` 边界不是 prompt 里写一句“不要越权”,而是代码里真的拦住。 OWASP 的 Excessive Agency 风险,本质就是 LLM 系统被授予过多功能、权限或自主性后,可能造成破坏性动作;所以工具权限、人工确认、最小授权必须在系统层实现。 --- #### (5)AgentRunner:核心循环 ```java public class AgentRunner { private final LlmClient llm; private final ToolRegistry toolRegistry; private final ToolExecutor toolExecutor; private final PolicyGate policyGate; private final TraceLog traceLog; public AgentResult run(User user, String goal) { AgentState state = new AgentState(goal); while (!state.isFinished() && state.getStep() < 10) { AgentDecision decision = llm.decideNextStep(state, toolRegistry.availableTools()); if (decision instanceof FinalAnswer finalAnswer) { state.finish(); return AgentResult.success(finalAnswer.answer()); } if (decision instanceof NeedHumanApproval approval) { return AgentResult.waitingForApproval(approval.action(), approval.reason()); } if (decision instanceof CallTool callTool) { policyGate.check(user, callTool.toolName(), callTool.arguments()); ToolResult result = toolExecutor.execute( callTool.toolName(), callTool.arguments() ); state.addToolCall(callTool, result); traceLog.record(state, callTool, result); state.nextStep(); } } return AgentResult.failed("Agent stopped: max steps reached."); } } ``` OpenAI Agents SDK 里所谓 agent loop,本质也是:处理工具调用,把结果送回 LLM,然后继续运行直到任务完成;同时配套 guardrails、sessions、tracing 等工程能力。 --- ### 用电商客服理解一次完整执行 用户问: > 我的订单签收三天了,商品破损,可以退吗? Agent 第一次判断: ```json { "type": "CALL_TOOL", "toolName": "get_order", "arguments": { "orderId": "A1001" } } ``` 后端执行: ```java policyGate.check(user, "get_order", args); ToolResult order = getOrderTool.execute(args); state.addToolResult(order); ``` 工具返回: ```json { "orderId": "A1001", "status": "SIGNED", "signedDaysAgo": 3, "productCategory": "electronics" } ``` Agent 第二次判断: ```json { "type": "CALL_TOOL", "toolName": "get_refund_policy", "arguments": { "category": "electronics" } } ``` 工具返回: ```json { "category": "electronics", "returnWindowDays": 7, "needDamagePhoto": true, "autoRefundAllowed": false } ``` Agent 第三次判断: ```json { "type": "FINAL", "answer": "你的订单签收 3 天,仍在 7 天售后窗口内。但商品破损需要上传图片凭证,我可以先帮你生成退款申请说明,提交退款前需要你确认。" } ``` 这就是一个最小 Agent 的运行过程。 它不是一次性回答,而是: ```text 看当前状态 → 决定查订单 → 得到订单结果 → 决定查政策 → 得到政策结果 → 判断不能直接退款 → 给出受边界约束的答案 ``` 所以,从代码视角看,Agent 不是让模型直接控制系统,而是让模型提出下一步动作,再由应用程序判断、执行、记录和约束。 ## 四、Agent 的核心不是“自主”,而是工具、状态和边界 一个更工程化的 Agent 定义可以是: **Agent = LLM + Tools + State + Control Loop + Guardrails** 拆开看: - LLM 负责理解任务和选择下一步。 - Tools 负责连接数据库、订单系统、知识库、搜索服务等外部能力。 - State 负责记录任务进展、工具调用历史和中间结果。 - Control Loop 负责把模型决策、工具执行和结果反馈串起来。 - Guardrails 负责限制模型不能越权、不能乱调用工具、不能直接执行高风险动作。 这里最容易被忽略的是工具调用的安全边界。 Spring AI 官方文档对 tool calling 的说明很清楚:虽然通常说 tool calling 是模型能力,但实际执行工具调用逻辑的是客户端应用。模型只能请求工具调用并提供参数,真正执行工具、返回结果的是应用程序本身;模型并不会直接获得工具背后的 API 权限。 这句话非常关键。 因为它说明 Agent 不是让模型直接控制系统,而是让模型提出行动意图,再由应用层判断是否执行。 比如电商客服 Agent 面对“我的订单能退吗?”这个问题时,它不应该直接回答“可以退”。 更合理的方式是:先查询订单,再查询售后政策,再结合订单状态、商品类目和平台规则生成回复。如果涉及退款提交,就只能生成草稿,不能直接执行。 这才是可上线的 Agent。 ## 五、Agent 的真正风险:从“回答错误”变成“行动越界” 普通大模型回答错,风险主要在内容层。 Agent 一旦接入工具,风险就进入系统层。 它可能查错数据,调用错工具,传错参数,泄露敏感信息,执行不该执行的操作,或者在错误前提下连续行动。 OWASP LLM Top 10 里专门列出了 Prompt Injection、Insecure Plugin Design、Excessive Agency 等风险。其中 Excessive Agency 指的是给 LLM 过多、未受约束的行动自主权,可能破坏可靠性、隐私和信任。 这也是为什么我不赞成一开始就做“全自动 Agent”。Agent 安全不能只靠 prompt,而必须靠工具权限、参数校验、人工确认和审计日志这些系统层设计。 真正的工程顺序应该是: - 先让模型会读。 - 再让模型会查。 - 再让模型会生成草稿。 - 再让模型在低风险场景自动执行。 - 最后才考虑高风险动作的半自动化或自动化。 对于工具权限,我更倾向于分四层: **READ:只读查询,可以自动执行。** 例如查询订单状态、查询物流、检索政策文档。 **DRAFT:只生成草稿,不真正提交。** 例如生成退款说明、工单回复、邮件草稿。 **WRITE_REVIEW:会改变系统状态,必须人工确认。** 例如提交退款申请、修改用户资料、发送正式邮件。 **DANGEROUS:默认禁止。** 例如删除数据、批量修改权限、执行任意命令。 如果一个 Agent 系统没有这个权限分层,那它本质上还不能算生产级 Agent,只是一个接了工具的聊天机器人。 ## 六、一个更接近真实项目的 Agent 示例 假设我们要做一个跨境电商客服 Agent。 用户问: > 我这个订单已经签收三天了,商品有破损,能不能退? 一个不可靠的 AI 可能会直接回答: > 一般情况下,签收七天内可以退货。 这句话听起来合理,但它不一定正确。 因为真实判断至少需要几个条件: - 订单是否真实存在? - 订单是不是当前用户的? - 商品类目是什么? - 是否支持无理由退货? - 破损是否需要上传凭证? - 当前是否超过售后时间? - 平台政策和商家政策是否冲突? - 是否需要人工客服介入? 所以更合理的 Agent 流程应该是: ```text 用户问题 → 意图识别:售后 / 退款 / 商品破损 → 查询订单状态 → 检索售后政策(RAG 在这里是证据工具) → 检查商品类目 → 判断是否需要图片凭证 → 生成客服回复 → 标注证据来源 → 如果涉及退款提交,进入人工确认 → 记录整次 Agent Run ``` 在这个流程里,模型不是凭常识回答,而是在工具和证据的约束下完成任务。 这就是 Agent 相比普通聊天机器人的真正区别:它不是凭常识直接回答,而是在工具、证据和权限边界内推进任务。 ## 七、为什么不要一开始做多 Agent 很多 Agent 教程喜欢从多 Agent 开始: 一个产品经理 Agent,一个架构师 Agent,一个开发者 Agent,一个测试 Agent,大家互相讨论,最后输出结果。 这种演示很容易吸引眼球,但工程价值未必高。 多数真实业务问题,不是靠“多几个角色说话”解决的,而是靠更清楚的任务边界、更可靠的工具、更稳定的状态记录和更严格的输出校验解决的。 多 Agent 只有在这些场景下才更有必要: - 不同角色确实需要不同职责; - 不同 Agent 拥有不同工具权限; - 需要一个 Agent 审查另一个 Agent 的输出; - 任务路径复杂到单一 workflow 难以维护; - 系统需要长期运行和异步协作。 否则,多 Agent 很可能只是增加延迟、成本和调试难度。 所以第一篇不应该从多 Agent 开始,而应该先讲清楚单个 Agent 如何完成任务、如何调用工具、如何记录状态、如何控制边界。 ## 八、我对 Agent 的第一性原则 如果让我用一句话定义 Agent,我不会说它是“自主智能体”。 我更愿意说: **Agent 是一个让大模型在工具、状态和权限边界内完成任务的工程系统。** 这个定义不够炫,但更适合真实开发。 因为 Agent 的难点从来不只是“让模型更聪明”,而是: - 它能调用什么工具; - 它不能调用什么工具; - 它每一步是否有依据; - 它的工具调用是否可追踪; - 它在证据不足时能不能停下来; - 它在高风险动作前能不能交给人; - 它失败后能不能复盘。 所以 Agent 专题不应该从框架开始,不应该从多 Agent 开始,也不应该从“让 AI 自己干活”开始。 它应该先回答一个更基础的问题: **当我们说要做 Agent 时,我们到底是在做一个聊天机器人,还是在做一个可控的任务执行系统?** - 如果只是聊天,普通 LLM 就够了。 - 如果只是查资料,RAG 就够了。 - 如果路径固定,Workflow 就够了。 - 如果任务路径不确定,需要工具、状态、反馈和权限边界,才真正进入 Agent。 下一篇,我会解释 Agent 核心术语:ReAct、Tool Calling、State、Memory、Workflow 到底是什么? ## 参考资料 - Anthropic:Building effective agents - OpenAI Agents SDK:Agent loop、Tools、Sessions、Tracing、Guardrails - Spring AI Reference:Tool Calling - OWASP LLM Top 10:Excessive Agency - ReAct:Synergizing Reasoning and Acting in Language Models - Toolformer:Language Models Can Teach Themselves to Use Tools 我是 Ryan,一个专注于可信 AI 应用工程的开发者,技术博客:[yanxai.com](https://yanxai.com),研究如何让 AI 生成从“看起来对”走向“有证据、可追溯、可验证”。

Spring AI 从入门到精通:构建你的 AI 开发知识体系

# Spring AI 从入门到精通:构建你的 AI 开发知识体系 ![Spring AI 从入门到精通:构建你的 AI 开发知识体系.png](https://pic.code-nav.cn/post_picture/1609766978631761921/6YqOgrwkNNIW7tnK.webp) --- ## 前言:为什么需要 Spring AI? 在过去的两年里,大语言模型(LLM)以惊人的速度渗透到了软件开发的每一个角落。从 ChatGPT 的横空出世,到如今各类 AI 原生应用的遍地开花,开发者面临的核心挑战已经悄然发生了变化 —— 不再是"能不能调用一个 AI 模型",而是"如何将 AI 模型与企业的现有数据、业务系统、API 体系深度融合,构建出真正有价值的生产级应用"。 Java 生态在这股浪潮中曾经一度显得有些落后。Python 凭借 LangChain、LlamaIndex 等框架抢占了 AI 应用开发的先机,而 Java 开发者要么被迫切换到 Python 技术栈,要么只能通过简陋的 HTTP 客户端封装来调用大模型接口 —— 这两种方案都不够理想。 Spring AI 的出现彻底改变了这一局面。它将 Spring 生态二十年来积累的设计哲学 —— 依赖注入、面向接口编程、约定优于配置、自动装配、可移植性抽象 —— 完整地注入了 AI 应用开发领域。如果你熟悉 Spring Boot,你会发现 Spring AI 的使用体验几乎是"零学习成本"的:你不需要学习新的编程范式,不需要理解复杂的 Pipeline 概念,只需要像使用 Spring Data 或 Spring Security 一样,引入 Starter 依赖、配置几行属性、注入一个 Bean,就能开始构建强大的 AI 应用。 截至本文编写时,Spring AI 已经演进到了 1.1.x 和 2.0.0 里程碑版本,支持包括 OpenAI、Azure OpenAI、Anthropic Claude、Google Gemini、Amazon Bedrock、Ollama 在内的几乎所有主流 AI 服务提供商,并且提供了对数十种向量数据库的统一抽象。更重要的是,它不仅仅是一个"API 封装层",而是一个完整的 AI 应用开发框架 —— 它内置了 RAG(检索增强生成)、Function Calling(函数调用)、多模态支持、对话记忆管理、ETL 数据管道、MCP(模型上下文协议)等几乎所有现代 AI 应用开发所需的关键能力。 本文将按照"从浅到深,逐层拆解"的方式,带你全面理解 Spring AI 的每一个核心组件。我们会从最基础的 ChatClient 开始,逐步深入到 Prompt 工程、Embeddings 与向量存储、ETL 数据管道、RAG、Function Calling、多模态、对话记忆、MCP 协议以及可观测性。每一个组件我都会用代码示例配合讲解,让你不仅"知道是什么",更能"看懂怎么做"。 --- ## 第一部分:概念铺垫 —— Spring AI 是什么,它的设计哲学是什么 ### 1.1 Spring AI 的核心定位 在开始写代码之前,我们有必要先花一些篇幅理解 Spring AI 在整个 AI 技术栈中的位置。这个问题很重要,因为很多开发者第一次接触 Spring AI 时,会把它和 LangChain、LlamaIndex 等框架直接对标,而这种对标其实是不完全准确的。 Spring AI 的核心定位可以概括为:**一个连接企业数据与 API 到 AI 模型的、符合 Spring 设计哲学的应用框架**。注意这里的关键词是"连接"—— Spring AI 的目标不是重新发明一套 AI 的开发范式,而是让已经精通 Spring 生态的 Java 开发者,能够用最自然、最符合 Spring 习惯的方式来构建 AI 应用。 具体来说,Spring AI 提供了以下几个层面的能力: 1. **可移植的 API 抽象层**:你不需要关心底层是调用 OpenAI 的 GPT-4、Azure 的模型还是 Anthropic 的 Claude,统一的 `ChatModel` 接口可以让你的代码在任何模型提供商之间平滑切换。 2. **企业数据与 AI 的桥梁**:通过 Embeddings、VectorStore、ETL Pipeline、RAG 等组件,Spring AI 解决了"如何让 AI 模型访问企业私有数据"这一核心问题。 3. **AI 模型与外部工具的连接器**:通过 Function Calling(Tool Calling)和 MCP 协议,Spring AI 让 AI 模型能够调用你的业务 API、数据库、外部服务。 4. **生产级的工程实践**:自动配置、可观测性(Metrics/Tracing)、对话记忆管理、流式响应等,这些是"能跑"和"能上生产"之间的差距。 ### 1.2 Spring AI 的设计哲学 理解 Spring AI 的设计哲学至关重要,因为它决定了整个框架的使用体感。如果你是一个有 Spring 开发经验的工程师,以下三点会让你的学习曲线变得非常平坦: **第一,坚持 Spring 的"可移植性抽象"传统。** 这和 Spring Data 的设计思路完全一致 —— 你操作的是 `JpaRepository` 接口,底层的具体实现可以是 MySQL、PostgreSQL、MongoDB,你切换数据库时业务代码完全不需要改动。Spring AI 也是如此:你操作的是 `ChatModel` 接口,底层可以是 OpenAI、Anthropic、Ollama 或者任何其他实现。 **第二,拥抱 Spring Boot 的自动配置体系。** 引入一个 Starter 依赖,配置 `application.yml` 中的几行属性,然后直接 `@Autowired` 注入你需要的 Bean,一切就绪。这是 Spring Boot 开发者最熟悉的体验,Spring AI 忠实地继承了下来。 **第三,提供"简单场景简单做,复杂场景可以深度控制"的分层 API。** 对于最常见的场景(比如一次性问答),ChatClient 可以让你在一行调用链中完成所有操作;而对于复杂的场景(比如多轮对话、RAG、Function Calling),你可以通过 Advisors 链逐步叠加能力,每一步都是可插拔的、可定制的。 --- ## 第二部分:起步 —— 配置环境与第一个 AI 调用 ### 2.1 添加依赖 Spring AI 使用独立的 BOM(Bill of Materials)来管理所有依赖版本。首先在你的 `pom.xml` 中添加 Spring AI 的 BOM: ```xml <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>1.1.2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> ``` 然后添加 OpenAI 的 Starter(你可以替换为其他模型提供商的 Starter): ```xml <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> </dependency> ``` 如果你使用的是 Gradle,相应的配置如下: ```groovy dependencyManagement { imports { mavenBom("org.springframework.ai:spring-ai-bom:1.1.2") } } dependencies { implementation 'org.springframework.ai:spring-ai-starter-model-openai' } ``` ### 2.2 配置 API Key 在 `application.yml` 中配置你的 OpenAI API Key: ```yaml spring: ai: openai: api-key: ${OPENAI_API_KEY} chat: options: model: gpt-4o-mini temperature: 0.7 ``` Spring AI 支持通过环境变量、配置文件、命令行参数等多种方式注入 API Key,这种灵活性让你在不同环境(开发、测试、生产)之间切换变得非常容易。你还可以为不同的模型提供商配置各自的参数,所有配置项都有合理的默认值。 ### 2.3 第一个 AI 调用:使用 ChatClient 现在我们来写第一个真正意义上的 AI 调用。Spring AI 提供了两个层面的 API:底层的 `ChatModel` 和更高层的 `ChatClient`。对于绝大多数场景,我推荐你直接从 `ChatClient` 开始。它是一个 Fluent API 风格的构建器,提供了链式调用的优雅体验。 ```java @RestController class AIController { private final ChatClient chatClient; AIController(ChatClient.Builder chatClientBuilder) { this.chatClient = chatClientBuilder.build(); } @GetMapping("/ai") String chat(@RequestParam(defaultValue = "用三句话介绍 Java 语言的特点") String message) { return this.chatClient.prompt() .user(message) .call() .content(); } } ``` 启动 Spring Boot 应用,访问 `http://localhost:8080/ai?message=什么是面向对象编程`,你就会收到 AI 模型的回复。整个调用过程不到 20 行代码。 我来解释一下这段代码背后发生了什么:当你调用 `chatClient.prompt().user(message).call().content()` 时,Spring AI 在内部完成了以下步骤: 1. 将 `user(message)` 封装为一个 `UserMessage` 对象(Spring AI 的消息模型); 2. 创建一个 `Prompt` 对象,包含这个消息; 3. 通过 `ChatModel`(根据你的配置自动装配了 OpenAI 的实现)调用远程 API; 4. 将 API 返回的 `ChatResponse` 转换为 `Generation` 对象; 5. 最终通过 `.content()` 提取出纯文本响应。 这个过程中,你完全不需要关心 HTTP 请求的构造、JSON 的序列化和反序列化、错误处理、重试策略等底层细节 —— 这些都被 Spring AI 封装好了。 --- ## 第三部分:核心入口 —— ChatClient 与 ChatModel 深度解析 ### 3.1 ChatModel:模型抽象层 `ChatModel` 是 Spring AI 中最重要的抽象接口之一。它类似于 JDBC 中的 `DataSource` 或者 Spring Data 中的 `Repository` —— 它定义了一套统一的 API 规范,而具体的实现由各个模型提供商提供。目前 Spring AI 支持的 ChatModel 实现包括: | 模型提供商 | Maven Starter | ChatModel 实现类 | |-----------|-------------|-----------------| | OpenAI | `spring-ai-starter-model-openai` | `OpenAiChatModel` | | Azure OpenAI | `spring-ai-starter-model-azure-openai` | `AzureOpenAiChatModel` | | Anthropic Claude | `spring-ai-starter-model-anthropic` | `AnthropicChatModel` | | Google Gemini | `spring-ai-starter-model-google-genai` | `GeminiChatModel` | | Amazon Bedrock | `spring-ai-starter-model-bedrock-converse` | `BedrockProxyChatModel` | | Ollama (本地) | `spring-ai-starter-model-ollama` | `OllamaChatModel` | | 智谱/DeepSeek 等 | `spring-ai-starter-model-openai` (兼容) | `OpenAiChatModel` | `ChatModel` 接口中最核心的方法是 `call(Prompt prompt)`,它接收一个 `Prompt` 对象,返回一个 `ChatResponse` 对象。此外,它还提供了 `stream(Prompt prompt)` 方法用于流式响应。值得强调的是,由于 `ChatModel` 是一个接口,你的业务代码完全不会绑定到任何具体的模型提供商 —— 这是 Spring 面向接口编程思想在 AI 领域的典型体现。 在底层使用 `ChatModel` 的例子: ```java @RestController public class LowLevelController { private final ChatModel chatModel; public LowLevelController(ChatModel chatModel) { this.chatModel = chatModel; } @GetMapping("/chat/low-level") public String chat(@RequestParam String message) { Prompt prompt = new Prompt(new UserMessage(message)); ChatResponse response = chatModel.call(prompt); return response.getResult().getOutput().getText(); } } ``` 你可以看到,使用 `ChatModel` 需要你手动构造 `Prompt` 和 `UserMessage` 对象,并且需要自己处理 `ChatResponse` 中的 `Generation`。这个 API 层级提供了最大的控制力,但也带来了更多的样板代码。 ### 3.2 ChatClient:Fluent API 的优雅封装 `ChatClient` 是 Spring AI 推荐的默认入口。它在 `ChatModel` 之上提供了更加符合开发者直觉的 Fluent API,让你可以用链式调用的方式完成从 Prompt 构建到响应提取的整个过程。 `ChatClient` 的核心方法是 `.prompt()`,它返回一个 `ChatClient.PromptSpec` 对象,这个对象提供了一系列用于构建 Prompt 的方法: - `.system(String text)` / `.system(SystemMessage message)` —— 设置系统消息 - `.user(String text)` / `.user(UserMessage message)` —— 设置用户消息 - `.messages(Message... messages)` —— 设置多个消息 - `.options(ChatOptions options)` —— 覆盖默认的模型参数(温度、Top-P 等) - `.advisors(Advisor... advisors)` —— 注册 Advisor 链 - `.tools(ToolCallback... tools)` —— 注册可调用的工具 - `.call()` —— 执行同步调用,返回 `ChatClient.CallSpec` - `.stream()` —— 执行流式调用,返回 `Flux<String>` `.call()` 之后的 `CallSpec` 提供了几个提取响应的方法: - `.content()` —— 直接返回纯文本内容 - `.chatResponse()` —— 返回完整的 `ChatResponse` 对象 - `.entity(Class<T> type)` —— 将响应映射为指定类型的 Java 对象 - `.entities(ParameterizedTypeReference<T> type)` —— 映射为泛型集合类型 这种设计的美妙之处在于,你可以根据场景的复杂程度,自由选择在调用链的哪个层次"停下来"。如果只需要一段文本,`.content()` 就够了;如果需要完整的元数据(Token 用量、Finish Reason 等),再用 `.chatResponse()`;如果需要结构化的 JSON 输出,则使用 `.entity()`。 --- ## 第四部分:Prompt 工程 —— 与 AI 模型高效沟通的艺术 ### 4.1 理解 Spring AI 的消息模型 在深入 Prompt 工程之前,我们必须先理解 Spring AI 的消息模型。所有的 Prompt 最终都是由一个或多个 `Message` 对象组成的,而 `Message` 接口有两个核心子类: - **`UserMessage`**:代表用户的输入,这是对话的主体内容。 - **`SystemMessage`**:代表系统级的指令,用于设定 AI 的角色、行为约束和输出格式。System Message 不会直接显示给用户,但它对模型的行为有着至关重要的影响。 - **`AssistantMessage`**:代表 AI 模型之前的回复,主要用于维护多轮对话的上下文。 这三类消息共同构成了 Prompt。一个典型的 Prompt 通常至少包含一个 SystemMessage 和一个 UserMessage: ```java String userText = """ 请介绍三位黄金海盗时代中最著名的海盗,并说明他们的特点。 至少为每位海盗写一句话。 """; Message userMessage = new UserMessage(userText); String systemText = """ 你是一个乐于助人的 AI 助手,帮助人们查找信息。 你的名字是 {name} 请以 {voice} 的风格回复用户的问题,并在回复中提及你的名字。 """; SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemText); Message systemMessage = systemPromptTemplate.createMessage( Map.of("name", "Jack", "voice", "海盗的口吻")); Prompt prompt = new Prompt(List.of(userMessage, systemMessage)); List<Generation> response = chatModel.call(prompt).getResults(); ``` ### 4.2 System Prompt 的角色与最佳实践 System Prompt 是整个 Prompt 工程中最重要的一环。它扮演着三个关键角色: 1. **角色设定**:告诉模型它是谁("你是一个资深 Java 架构师")、它的知识范围("你精通 Spring 全家桶")、它的行为准则("你总是给出可运行的完整代码")。 2. **格式约束**:要求模型以特定格式输出("返回合法 JSON"、"使用 Markdown 格式"、"代码注入使用 ```java 标记")。 3. **边界设定**:限制模型的行为范围("只回答 Java 相关的问题"、"如果不知道就如实说不知道")。 Spring AI 通过 `SystemPromptTemplate` 支持在 System Prompt 中使用占位符,这使得你可以动态注入参数: ```java SystemPromptTemplate template = new SystemPromptTemplate(""" 你是一个 {role},精通 {expertise}。 回答问题时请遵循以下准则: - {guideline_1} - {guideline_2} 当前对话的上下文是:{context} """); Message systemMessage = template.createMessage(Map.of( "role", "Java 全栈架构师", "expertise", "Spring AI、Spring Boot、微服务架构", "guideline_1", "始终提供完整的、可直接运行的代码示例", "guideline_2", "在解释概念时,优先使用类比和实际场景", "context", "为一个 5 人团队设计 AI 聊天机器人后端" )); ``` ### 4.3 结构化输出(Structured Output) 在实际的生产应用中,我们通常不希望 AI 返回一段自由格式的文本,而是希望它返回结构化的 JSON 数据,这样我们的程序才能可靠地解析和处理。Spring AI 通过 `ChatClient` 的 `.entity()` 方法原生支持这一点。 ```java record MovieReviews(Movie[] movie_reviews) { enum Sentiment { POSITIVE, NEUTRAL, NEGATIVE } record Movie(Sentiment sentiment, String name) {} } MovieReviews reviews = chatClient.prompt() .system(""" Classify movie reviews as positive, neutral or negative. Return valid JSON. """) .user(""" Review: "Her" is a disturbing study revealing the direction humanity is headed if AI is allowed to keep evolving, unchecked. It's so disturbing I couldn't watch it. JSON Response: """) .call() .entity(MovieReviews.class); ``` 这段代码的工作流程非常清晰:首先通过 System Prompt 告诉模型"你的输出必须是合法的 JSON",然后在 User Prompt 中提供待分类的文本并提示"JSON Response:",最后通过 `.entity(MovieReviews.class)` 告诉 Spring AI 要将模型返回的 JSON 自动反序列化为指定的 Java 记录类。 当你需要将格式化指令动态注入到 Prompt 中时(这在某些需要结构化的 Prompt 模板中非常有用),可以使用 `StructuredOutputConverter`: ```java StructuredOutputConverter outputConverter = ...; String userInputTemplate = """ ... user text input .... {format} """; Prompt prompt = new Prompt( PromptTemplate.builder() .template(userInputTemplate) .variables(Map.of("format", outputConverter.getFormat())) .build() .createMessage() ); ``` 这种做法的底层原理是:`StructuredOutputConverter.getFormat()` 会返回具体的格式化指令(比如"返回一个 JSON 对象,包含字段 X、Y、Z"),然后将这段指令嵌入到 Prompt 的 `{format}` 占位符中,使得模型能够明确理解输出格式的要求。 --- ## 第五部分:Embeddings —— 让 AI 理解你的数据 ### 5.1 什么是 Embedding,为什么它至关重要? 如果说 ChatClient 解决的是"让 AI 和我们对话"的问题,那么 Embeddings 解决的就是"让 AI 理解我们的数据"的问题。这是整个 RAG(检索增强生成)体系的基石。 Embedding(嵌入向量)的核心思想是将文本(无论是单词、句子、段落还是整篇文档)转换为一个高维空间中的数值向量。在这个向量空间中,语义相近的文本会被映射到彼此靠近的位置。比如"今天天气真好"和"阳光明媚,万里无云"这两个句子虽然用词不同但语义相似,它们的向量距离会很近;而"今天天气真好"和"如何配置数据库连接池"语义完全不相关,它们的向量距离就会很远。 这个特性有什么实际价值呢?当你有一个包含大量文档的知识库时,你想找到"和用户当前问题最相关的那些文档片段",你不需要做关键词匹配(那会漏掉同义词、近义词),也不需要全文检索(那太慢),你只需要: 1. 将用户的查询文本转换为一个向量; 2. 在向量数据库中搜索与之距离最近的 N 个向量; 3. 返回这些向量对应的文档片段。 这就是向量相似度检索,它是 RAG 的检索部分的核心机制。 ### 5.2 使用 Spring AI 生成 Embedding Spring AI 提供了 `EmbeddingModel` 接口来统一抽象文本到向量的转换过程。和 `ChatModel` 一样,`EmbeddingModel` 也有面向不同提供商的实现(`OpenAiEmbeddingModel`、`AzureOpenAiEmbeddingModel` 等)。 ```java @RestController public class EmbeddingController { private final EmbeddingModel embeddingModel; public EmbeddingController(EmbeddingModel embeddingModel) { this.embeddingModel = embeddingModel; } @GetMapping("/ai/embedding") public Map<String, Object> embed(@RequestParam String message) { EmbeddingResponse embeddingResponse = this.embeddingModel.embedForResponse(List.of(message)); return Map.of("embedding", embeddingResponse); } } ``` `embedForResponse(List.of(message))` 方法接收一个文本列表,返回一个 `EmbeddingResponse` 对象,其中包含了每条文本对应的浮点数向量。你可以根据自己的需要调整输入文本的批处理大小,Spring AI 会负责与模型提供商的高效通信。 这里需要特别注意一点:**用于生成 Embedding 的模型和用于对话的模型通常是不同的**。比如 OpenAI 的 `text-embedding-3-small` 和 `text-embedding-3-large` 是专门优化的 Embedding 模型,它们的输出向量比 GPT 对话模型更适合做相似度计算。在 Spring AI 的配置中,你需要分别配置 Chat Model 和 Embedding Model: ```yaml spring: ai: openai: api-key: ${OPENAI_API_KEY} chat: options: model: gpt-4o-mini # 对话模型 embedding: options: model: text-embedding-3-small # Embedding 模型 ``` --- ## 第六部分:ETL 数据管道 —— 把原始文档变成 AI 可用的知识 ### 6.1 ETL 管道的整体架构 ETL(Extract, Transform, Load)管道是 RAG 的数据准备阶段。它的任务是将各种格式的原始文档(PDF、Word、Markdown、JSON、网页等)转化为向量数据库中的、可以被高效检索的结构化数据。Spring AI 的 ETL 管道由三个核心组件组成: 1. **DocumentReader(提取)**:从不同的数据源读取原始文档。 2. **DocumentTransformer(转换)**:对文档进行分片、清洗、元数据增强等处理。 3. **DocumentWriter(加载)**:将处理后的文档写入向量数据库。 这三个组件的串联形成了一个完整的数据流水线: ``` 原始文档 → DocumentReader → List<Document> → DocumentTransformer → List<Document> → DocumentWriter → 向量数据库 ``` 在 Spring AI 中,这三个组件被设计为函数式接口,既可以用最简洁的代码串联使用,也可以在每个阶段单独定制。 ### 6.2 DocumentReader:从各种来源读取文档 Spring AI 提供了多种开箱即用的 DocumentReader 实现: | Reader | 描述 | |--------|------| | `TextReader` | 读取纯文本文件 | | `JsonReader` | 读取 JSON 文件,可按字段提取内容 | | `PagePdfDocumentReader` | 按页读取 PDF 文件 | | `ParagraphPdfDocumentReader` | 按段落读取 PDF 文件 | | `TikaDocumentReader` | 通过 Apache Tika 支持几乎所有文档格式 | 以下是使用 `ParagraphPdfDocumentReader` 读取 PDF 的完整示例: ```java @Component public class MyPagePdfDocumentReader { List<Document> getDocsFromPdfWithCatalog() { ParagraphPdfDocumentReader pdfReader = new ParagraphPdfDocumentReader( "classpath:/sample1.pdf", PdfDocumentReaderConfig.builder() .withPageTopMargin(0) .withPageExtractedTextFormatter( ExtractedTextFormatter.builder() .withNumberOfTopTextLinesToDelete(0) .build()) .withPagesPerDocument(1) .build() ); return pdfReader.read(); } } ``` `PdfDocumentReaderConfig` 提供了丰富的配置选项:你可以设置页边距、删除顶部行(比如页眉)、指定每份 Document 包含多少页。这种细粒度的控制对于处理格式复杂的 PDF 非常有价值。 下面是使用 `JsonReader` 读取 JSON 文件的示例: ```java @Component class MyJsonReader { private final Resource resource; MyJsonReader(@Value("classpath:bikes.json") Resource resource) { this.resource = resource; } List<Document> loadJsonAsDocuments() { JsonReader jsonReader = new JsonReader( this.resource, "description", "content"); return jsonReader.get(); } } ``` `JsonReader` 的参数允许你指定 JSON 中哪些字段作为文档的内容、哪些作为元数据。比如你的 JSON 数据是这样的: ```json [ { "id": 1, "title": "山地自行车选购指南", "description": "本指南覆盖入门级到专业级", "content": "在选择山地自行车时,你需要考虑..." } ] ``` 你可以指定 `content` 字段作为向量化的正文内容,`description` 字段作为可检索的元数据。 ### 6.3 DocumentTransformer:文本分片与清洗 `DocumentTransformer` 是将原始文档转换为适合向量检索的格式的关键步骤。它的核心任务是**将过长的文档切分为适当大小的片段**,这是一个非常微妙的工程问题,直接决定了你后续 RAG 的检索质量。 为什么需要分片? 1. **Token 限制**:大多数 Embedding 模型都有输入长度限制(比如 OpenAI 的 `text-embedding-3-small` 最大支持 8191 个 Token),超长文本无法直接向量化。 2. **检索精度**:如果一个"Document"包含了 20 页的内容,即使它和用户的查询高度相关,也很难从 20 页中找到最相关的那一段。而分段后的每个片段粒度更细,检索结果更精准。 3. **上下文窗口**:当检索到的文档片段被填入 LLM 的 Prompt 时,你希望填入的是最相关的那一小段,而不是一大篇。 Spring AI 提供了 `TokenTextSplitter` 作为默认的文本分片器: ```java List<Document> documents = textReader.get(); List<Document> splitDocuments = new TokenTextSplitter().apply(documents); ``` `DocumentTransformer` 的接口定义为: ```java public interface DocumentTransformer extends Function<List<Document>, List<Document>> { default List<Document> transform(List<Document> transform) { return apply(transform); } } ``` 它是一个标准的 `Function<List<Document>, List<Document>>`,这意味着你可以用 Java 的 `andThen()` 方法轻松组合多个 Transformer: ```java DocumentTransformer pipeline = new KeywordMetadataEnricher(keywords) .andThen(new SummaryMetadataEnricher(summaryModel)) .andThen(new TokenTextSplitter()); List<Document> processed = pipeline.apply(rawDocuments); ``` ### 6.4 从读取到写入:一个完整的 ETL 流程 将三个组件串联起来,一个完整的 ETL 流程如下: ```java // Step 1: 读取 PDF ParagraphPdfDocumentReader pdfReader = new ParagraphPdfDocumentReader( "classpath:/knowledge-base.pdf", PdfDocumentReaderConfig.builder() .withPagesPerDocument(1) .build() ); List<Document> documents = pdfReader.read(); // Step 2: 分片 TokenTextSplitter splitter = new TokenTextSplitter(); List<Document> chunks = splitter.apply(documents); // Step 3: 写入向量数据库 vectorStore.accept(chunks); ``` 也可以使用更简洁的链式写法: ```java vectorStore.accept( new TokenTextSplitter().apply( new ParagraphPdfDocumentReader("classpath:/knowledge-base.pdf").read() ) ); ``` 这里 `vectorStore.accept(chunks)` 的工作是:对每一个 Document 分片调用 Embedding 模型生成向量,然后将文本内容和对应的向量一起存入向量数据库。这一切都在 Spring AI 内部自动完成。 --- ## 第七部分:Vector Store —— 向量数据库的抽象层 ### 7.1 Spring AI 支持的向量数据库 如果说 Embeddings 是 RAG 的"引擎",那么 Vector Store 就是 RAG 的"仓储"。Spring AI 通过 `VectorStore` 接口提供了对数十种向量数据库的统一抽象,这其中包括: - **Milvus** —— 高性能开源向量数据库,适合大规模场景 - **Pinecone** —— 全托管向量数据库服务 - **Weaviate** —— 自带向量化和模式管理的开源方案 - **Qdrant** —— Rust 编写的高性能向量搜索引擎 - **Chroma** —— 轻量级、适合开发和原型 - **PGVector** —— PostgreSQL 扩展,在关系数据库中实现向量搜索 - **Redis Stack** —— 基于 Redis 的向量搜索 - **Elasticsearch** —— 全文检索与向量搜索的融合 - **MongoDB Atlas** —— 文档数据库的向量搜索扩展 - **Oracle、Cassandra、Neo4j** 等 无论你选择哪个数据库,只需要引入对应的 Starter 依赖并配置连接信息,你的业务代码完全不需要改动。例如使用 Qdrant: ```xml <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-vector-store-qdrant</artifactId> </dependency> ``` ```yaml spring: ai: vectorstore: qdrant: host: localhost port: 6334 collection-name: my-knowledge-base ``` ### 7.2 相似度检索 将文档存入向量数据库之后,最核心的操作就是相似度检索。`VectorStore` 接口提供了 `similaritySearch` 方法: ```java List<Document> similarDocuments = vectorStore.similaritySearch( SearchRequest.builder() .query("如何配置 Spring Boot 的数据源?") .topK(5) .similarityThreshold(0.7) .build() ); ``` 三个关键参数: - **`query`**:用户的查询文本。Spring AI 会自动将其转换为 Embedding 向量,然后在数据库中搜索。 - **`topK`**:返回最相似的 K 个文档片段。这个值需要根据你的上下文窗口大小来设置,通常 3-5 个是最平衡的选择。 - **`similarityThreshold`**:相似度阈值(0.0-1.0)。只有相似度高于此阈值的文档才会被返回。这个参数非常重要 —— 如果没有阈值限制,向量数据库会返回"看起来最像但可能完全不相关"的结果,而 0.7 是一个经过大量实践验证的合理默认值。 --- ## 第八部分:RAG —— 检索增强生成 ### 8.1 RAG 的工作原理 RAG(Retrieval Augmented Generation,检索增强生成)是 Spring AI 中最引人注目的能力之一。它解决了一个 AI 应用中的根本性矛盾:**大语言模型的知识截止于训练数据,它们不知道你企业内部的文档、最新的业务数据、今天刚发布的产品规格。** RAG 的核心思想非常优雅:在把用户的问题发给 AI 模型之前,先去你的知识库(向量数据库)中检索与问题最相关的文档片段,然后把"用户的问题"和"检索到的相关资料"一起打包放到 Prompt 里发给模型。这样,模型在生成回答时就有了"参考材料",可以基于你的数据给出准确的、有时效性的回答。 RAG 的完整流程可以分为两个阶段: **阶段一:数据准备(离线)** ``` 原始文档 → DocumentReader → DocumentTransformer → EmbeddingModel → VectorStore ``` **阶段二:查询回答(在线)** ``` 用户提问 → EmbeddingModel → VectorStore.similaritySearch → 将检索结果注入 Prompt → ChatModel → 带上下文的回答 ``` ### 8.2 使用 Spring AI 实现 RAG Spring AI 通过 `RetrievalAugmentationAdvisor` 将 RAG 能力封装为一个可插拔的 Advisor,可以轻松地挂载到 ChatClient 的调用链上: ```java Advisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder() .documentRetriever( VectorStoreDocumentRetriever.builder() .similarityThreshold(0.50) .vectorStore(vectorStore) .build() ) .build(); String answer = chatClient.prompt() .advisors(retrievalAugmentationAdvisor) .user(question) .call() .content(); ``` 这段代码背后发生了什么: 1. `RetrievalAugmentationAdvisor` 拦截了用户的问题。 2. 它调用 `VectorStoreDocumentRetriever`,从 Vector Store 中检索与问题相关的文档片段(相似度阈值设为 0.50)。 3. 检索到的文档片段被自动注入到 Prompt 的上下文中(通常放在 System Message 或作为额外消息插入)。 4. 增强后的 Prompt 被发送给 ChatModel,模型基于这些参考资料生成回答。 5. 返回给用户的回答中包含了来自知识库的准确信息。 这种设计的优雅之处在于:RAG 逻辑完全被隔离在 Advisor 中,你的业务代码不需要任何修改。你可以随时切换到不同的检索策略、调整相似度阈值、替换底层的向量数据库,而业务调用代码保持完全不变。 --- ## 第九部分:Function Calling —— 让 AI 调用你的代码 ### 9.1 什么场景需要 Function Calling? Function Calling(在 Spring AI 中也称为 Tool Calling)是实现 AI Agent 的关键技术。它让 AI 模型能够"意识到"外部工具的存在,并在合适的时机选择调用这些工具来获取信息或执行操作。 考虑以下典型场景: - **查询天气**:"明天的北京天气怎么样?"—— 模型自身没有实时天气数据,但它可以调用你的 `getCurrentWeather` 函数。 - **发送邮件**:"帮我把会议纪要通过邮件发给张三"—— 模型可以调用你的 `sendEmail` 函数。 - **数据库查询**:"上个月销售额最高的产品是什么?"—— 模型可以调用你的 `querySalesData` 函数。 - **调用企业内部 API**:"帮我把这个订单的状态改为已发货"—— 模型可以调用你的 `updateOrderStatus` 函数。 在这些场景中,AI 模型不需要自己"知道"天气、销售额、订单状态,它只需要知道**有哪些工具可用、每个工具需要什么参数**,然后像一个"调度中心"一样决定调用哪个工具、传什么参数。实际的执行由你的 Java 代码完成,AI 模型只是"决策者"。 ### 9.2 使用 Spring AI 实现 Function Calling Spring AI 提供了两种方式来定义工具函数,这里分别介绍。 **方式一:使用 `FunctionToolCallback`(编程式定义)** ```java ToolCallback weatherCallback = FunctionToolCallback .builder("getCurrentWeather", new WeatherService()) .description("Get the weather in location") .inputType(WeatherService.Request.class) .build(); String response = ChatClient.create(chatModel) .prompt() .user("What's the weather in Paris, Tokyo, and New York?") .tools(weatherCallback) .call() .content(); ``` 当用户问"What's the weather in Paris, Tokyo, and New York?"时,模型会识别出需要调用 `getCurrentWeather` 函数,然后自动发起工具调用、获取天气数据,并基于这些数据生成最终的回复。注意,模型可能会对一个请求并发调用多个工具 —— 比如这个例子中,模型可能会同时查询三个城市的天气。 **方式二:使用 `@Tool` 注解(声明式定义)** 如果你的类已经封装了业务逻辑,可以使用 Spring AI 的注解方式让它变成 AI 可调用的工具: ```java public class WeatherService { @Tool(description = "Get the weather in location") public String weatherByLocation( @ToolParam(description = "City or state name") String location) { // 实际的天气查询逻辑 return "The weather in " + location + " is sunny, 25°C"; } } // 使用时直接传入实例,Spring AI 会自动解析 @Tool 注解 String response = ChatClient.create(chatModel) .prompt() .user("What's the weather like in Boston?") .tools(new WeatherService()) .call() .content(); ``` 当一个 ChatClient 调用链中注册了多个工具时,模型会根据用户的提问自动判断是否需要调用工具、调用哪个工具、传什么参数。这个过程被称为"工具调用循环"(Tool Call Loop),Spring AI 的 `ToolCallingAdvisor` 会自动管理这个循环 —— 你不需要写任何循环或判断逻辑。 Tool Calling 的强大之处在于它的**组合性**。你可以在一个调用链中注册多个工具,模型会根据用户的意图智能选择: ```java String response = ChatClient.create(chatModel) .prompt() .user("帮我查一下北京明天的天气,然后给张三发邮件告诉他明天要不要带伞") .tools( new WeatherService(), // 天气查询工具 new EmailService(), // 邮件发送工具 new CalendarService() // 日历查询工具 ) .call() .content(); ``` 模型会自动先调用天气工具获取北京的天气预报,然后分析结果判断是否需要带伞,最后调用邮件工具向张三发送通知 —— 整个过程在 `ToolCallingAdvisor` 的管理下自动完成,开发者只需要定义工具的能力。 --- ## 第十部分:多模态支持 —— 不止于文本 ### 10.1 Spring AI 的多模态能力概述 多模态(Multimodal)是指 AI 模型理解和处理多种信息形式(文本、图像、音频、视频等)的能力。Spring AI 的 `Message` 接口通过 `Media` 类型来支持多模态数据: ```java // 从 Classpath 加载图片 var imageResource = new ClassPathResource("/multimodal.test.png"); var userMessage = new UserMessage( "Explain what do you see on this picture?", List.of(new Media(MimeTypeUtils.IMAGE_PNG, imageResource)) ); ChatResponse response = chatModel.call( new Prompt(userMessage, OpenAiChatOptions.builder() .model("gpt-4o") // 必须使用支持视觉的模型 .build() ) ); ``` 或者使用图片 URL: ```java var userMessage = new UserMessage( "Explain what do you see on this picture?", List.of(new Media(MimeTypeUtils.IMAGE_PNG, URI.create("https://docs.spring.io/spring-ai/reference/_images/multimodal.test.png"))) ); ChatResponse response = chatModel.call(new Prompt(userMessage)); ``` 对于 PDF 文件,同样可以作为多模态输入发送给 AI 模型进行理解和总结: ```java var pdfResource = new ClassPathResource("/document.pdf"); var userMessage = UserMessage.builder() .text("Please summarize this document.") .media(List.of(new Media(new MimeType("application", "pdf"), pdfResource))) .build(); ChatResponse response = chatModel.call(new Prompt(List.of(userMessage))); ``` ### 10.2 多模态支持的模型 目前支持视觉多模态的主流模型包括: | 模型 | 支持的模态 | |------|----------| | GPT-4o / GPT-4o-mini | 文本、图像 | | GPT-4 | 文本、图像 | | Anthropic Claude 3 / 3.5 | 文本、图像、PDF | | Google Gemini 系列 | 文本、图像、音频、视频 | 使用多模态功能时务必注意:**你必须选择支持对应模态的模型**,比如使用 `gpt-4o` 而不是 `gpt-3.5-turbo`,否则会收到错误。 --- ## 第十一部分:对话记忆(Chat Memory)—— 让 AI 记住上下文 ### 11.1 对话记忆的必要性 默认情况下,每一次 `chatClient.prompt().user(message).call()` 都是独立的、无状态的。模型不知道你上一轮说了什么,也不记得你之前让它扮演了什么角色。这对于"一次性问答"的场景没问题,但对于任何需要多轮交互的场景 —— 聊天助手、客服系统、代码助手 —— 都会造成体验上的断裂。 对话记忆(Chat Memory)就是来解决这个问题的。它的核心原理非常简单:**将每一轮对话的消息(用户的输入和模型的回复)追加到一个消息列表中,在下一次对话时把这个列表作为历史消息传给模型。** 这样一来,模型就有了"记忆"。 ### 11.2 使用 Spring AI 的 Chat Memory Spring AI 提供了 `ChatMemory` 接口来实现对话记忆,`MessageWindowChatMemory` 是其默认实现(使用滑动窗口策略): ```java // 创建一个记忆实例 ChatMemory chatMemory = MessageWindowChatMemory.builder().build(); String conversationId = "007"; // 第一轮对话 UserMessage userMessage1 = new UserMessage("My name is James Bond"); chatMemory.add(conversationId, userMessage1); ChatResponse response1 = chatModel.call(new Prompt(chatMemory.get(conversationId))); chatMemory.add(conversationId, response1.getResult().getOutput()); // 第二轮对话(不需要再次告知名字) UserMessage userMessage2 = new UserMessage("What is my name?"); chatMemory.add(conversationId, userMessage2); ChatResponse response2 = chatModel.call(new Prompt(chatMemory.get(conversationId))); // 模型会回答 "James Bond" ``` 如果你使用的是 ChatClient,记忆管理通过 `MessageChatMemoryAdvisor` 来实现,更加简洁: ```java chatClient.prompt() .user("Do I have license to code?") .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)) .call() .content(); ``` `ChatMemory.CONVERSATION_ID` 是一个关键参数,**每次调用都必须提供**,否则会抛出 `IllegalArgumentException`。这个 ID 用于区分不同的会话 —— 每个用户、每个对话窗口都应该有一个唯一的会话 ID。 ### 11.3 多个 Advisor 的组合使用 `MessageChatMemoryAdvisor` 可以和 `RetrievalAugmentationAdvisor`、`QuestionAnswerAdvisor` 等其他 Advisor 组合使用,形成一个 Advisor 链: ```java chatClient.prompt() .advisors(a -> a .advisors( MessageChatMemoryAdvisor.builder(chatMemory).build(), QuestionAnswerAdvisor.builder(vectorStore).build() ) .param(ChatMemory.CONVERSATION_ID, conversationId)) .user("根据我们之前的讨论,帮我找到相关的技术文档") .call() .content(); ``` 在这个链中,`MessageChatMemoryAdvisor` 负责注入历史对话上下文,`QuestionAnswerAdvisor` 负责从向量数据库检索相关文档。两者协作,模型在生成回答时既有对话历史又有知识库的支撑。 --- ## 第十二部分:Advisors —— Spring AI 的插件化架构 ### 12.1 Advisors 的设计理念 Advisors 是 Spring AI 中最重要的架构概念之一,它是整个框架"可插拔、可组合"设计哲学的集中体现。你可以把 Advisor 理解为一个拦截器链(Interceptor Chain):每个 Advisor 在 Prompt 被发送到模型之前、模型返回响应之后,都可以对数据进行拦截和处理。 Spring AI 内置了多个 Advisors: | Advisor | 功能 | |---------|------| | `MessageChatMemoryAdvisor` | 管理对话历史记忆 | | `RetrievalAugmentationAdvisor` | 从向量数据库检索相关文档注入 Prompt | | `QuestionAnswerAdvisor` | 基于知识库的问答 | | `ToolCallingAdvisor` | 管理 Function Calling 的调用循环 | | `DynamicToolSearchAdvisor` | 动态搜索并注册工具 | ### 12.2 构建 Advisor 链 Advisors 的真正威力在于它们可以链式组合: ```java ChatClient chatClient = ChatClient.builder(chatModel) .defaultAdvisors( MessageChatMemoryAdvisor.builder(chatMemory).build() ) .build(); String answer = chatClient.prompt() .advisors(a -> a .advisors( RetrievalAugmentationAdvisor.builder() .documentRetriever(VectorStoreDocumentRetriever.builder() .vectorStore(vectorStore) .similarityThreshold(0.50) .build()) .build() ) .param(ChatMemory.CONVERSATION_ID, sessionId)) .user("Spring AI 中如何配置向量数据库?") .tools(new DocumentationSearchTool()) .call() .content(); ``` 在这个示例中,ChatClient 同时使用了三个能力: 1. **Chat Memory**(通过默认 Advisor):记住这个会话之前的对话内容。 2. **RAG**(通过按需注册的 Advisor):从向量数据库检索相关文档。 3. **Tool Calling**(通过 `.tools()` 注册):提供文档搜索工具给模型调用。 这三个能力完全独立、互不干扰,但它们可以被无缝组合到一个调用链中。这就是 Spring AI Advisors 架构的魅力 —— 你可以像搭积木一样逐步叠加 AI 应用的能力,每个"积木"都独立可测试、独立可替换。 --- ## 第十三部分:MCP —— 模型上下文协议 ### 13.1 什么是 MCP? MCP(Model Context Protocol)是由 Anthropic 提出的一种开放协议,旨在标准化 AI 模型与外部工具、数据源之间的通信方式。Spring AI 完整实现了 MCP 协议,支持同时作为 MCP Client 和 MCP Server。 MCP 的核心价值在于**标准化**。在没有 MCP 之前,每个 AI 框架、每个工具都有自己的一套工具注册和调用机制,导致生态割裂。MCP 定义了一套统一的协议,使得: - 任何支持 MCP 的工具都可以被任何支持 MCP 的 AI 应用调用; - AI 应用可以将自己的内部能力通过 MCP 暴露给其他 AI 应用; - 工具的发现、注册、调用、安全控制都有了统一的标准。 ### 13.2 配置 MCP Client Spring AI 通过 Spring Boot 的自动配置体系来管理 MCP 连接: ```yaml spring: ai: mcp: client: enabled: true name: my-mcp-client version: 1.0.0 request-timeout: 30s type: SYNC # 或 ASYNC 用于响应式应用 streamable-http: connections: server1: url: http://localhost:8083 endpoint: /mcp stdio: connections: server1: command: /path/to/server args: - --port=8080 - --mode=production env: API_KEY: your-api-key DEBUG: "true" ``` Spring AI 支持三种 MCP 传输方式: - **SSE(Server-Sent Events)**:适用于服务端推送事件。 - **Streamable HTTP**:基于 HTTP 的流式传输,最常用的方式。 - **Stdio**:通过标准输入输出进行进程间通信,适用于本地工具。 ### 13.3 构建 MCP Server 如果你想让自己的应用能够被其他 AI 应用通过 MCP 协议调用,你可以构建一个 MCP Server。Spring AI 通过 `@McpTool` 和 `@McpResource` 注解来声明式定义暴露的工具和资源: ```java @SpringBootApplication public class McpServerApplication { public static void main(String[] args) { SpringApplication.run(McpServerApplication.class, args); } } @Component public class MyMcpTools { @McpTool(description = "查询指定用户的完整订单历史") public List<Order> getUserOrders( @McpToolParam(description = "用户唯一标识符") String userId, @McpToolParam(description = "查询最近 N 天的订单") int days) { // 实际的数据库查询逻辑 return orderRepository.findByUserIdAndDateRange(userId, days); } @McpTool(description = "生成指定时间范围内的销售报表") public SalesReport generateSalesReport( @McpToolParam(description = "报表开始日期(ISO 格式)") String startDate, @McpToolParam(description = "报表结束日期(ISO 格式)") String endDate) { // 实际的报表生成逻辑 return reportService.generate(startDate, endDate); } } @Component public class MyMcpResources { @McpResource(description = "公司的产品目录信息") public String getProductCatalog() { return productService.getCatalog(); } } ``` 配置 MCP Server 的协议: ```yaml spring: ai: mcp: server: name: my-cool-mcp-server protocol: STREAMABLE # 或 STATELESS ``` Spring Boot 的自动配置会自动扫描带有 `@McpTool` 和 `@McpResource` 注解的 Bean,将它们注册为 MCP Server 的工具和资源。任何支持 MCP 协议的 AI 客户端都可以发现并调用这些能力。 --- ## 第十四部分:可观测性 —— 生产环境中的 AI 监控 ### 14.1 为什么 AI 应用需要可观测性? AI 应用的可观测性比传统应用更加重要,原因有三: 1. **成本敏感**:每次调用 AI 模型都有实际的费用支出(Token 计费),你需要精确追踪每次调用的 Token 消耗量。 2. **延迟不可控**:AI 模型的响应时间波动很大(受负载、Prompt 复杂度、Function Calling 往返次数等因素影响),你需要监控 P50/P95/P99 延迟。 3. **行为不可预测**:模型的输出质量可能因为 Prompt 的微小变化而产生显著差异,你需要追踪调用的成功率和错误类型。 ### 14.2 Spring AI 的观测能力 Spring AI 无缝集成了 Spring 生态的可观测性体系(Micrometer + OpenTelemetry),提供了以下观测维度: **核心组件的指标和追踪覆盖:** - **ChatClient**:每次调用的延迟、Token 消耗(Input/Output)、成功率 - **ChatModel**:底层模型调用的延迟、Token 使用量 - **EmbeddingModel**:Embedding 生成的延迟和维度 - **ImageModel**:图片生成的延迟和尺寸 - **VectorStore**:向量检索的延迟和返回结果数量 **数据的分类策略:** - **低基数键(Low-cardinality keys)**:如模型名称、提供商名称等,同时添加到 Metrics 和 Traces 中。 - **高基数键(High-cardinality keys)**:如具体的 Prompt 内容、会话 ID 等,仅添加到 Traces 中。 你只需要引入对应的 Spring Boot Actuator 和 Micrometer 依赖,Metrics 和 Tracing 就会自动启用。这种"零配置自动观测"的设计,让 AI 应用的运维也变得和传统 Spring Boot 应用一样简单。 --- ## 第十五部分:总结 —— Spring AI 的学习路径与知识体系 ### 15.1 知识体系全景图 经过了从"第一个 AI 调用"到"MCP 协议"的完整旅程,现在让我们把这些组件串联成一个有机的整体。Spring AI 的知识体系可以按照以下五个层次来理解: ``` ┌─────────────────────────────────────────────────────────┐ │ 应用层 (Application) │ │ REST Controller / GraphQL / 消息队列 / 定时任务 │ ├─────────────────────────────────────────────────────────┤ │ 编排层 (Orchestration) │ │ ChatClient · Advisors · Chat Memory · Tool Calling │ ├─────────────────────────────────────────────────────────┤ │ 核心层 (Core) │ │ ChatModel · EmbeddingModel · Prompt · Messages │ │ Structured Output · Streaming · Multimodal │ ├─────────────────────────────────────────────────────────┤ │ 数据层 (Data) │ │ ETL Pipeline · VectorStore · DocumentReader │ │ DocumentTransformer · DocumentWriter │ ├─────────────────────────────────────────────────────────┤ │ 基础设施层 (Infrastructure) │ │ MCP Protocol · Observability · Auto-configuration │ └─────────────────────────────────────────────────────────┘ ``` ### 15.2 推荐学习路径 根据你的背景和需求,我推荐以下学习路径: **第一阶段:入门(1-2 天)** - 搭建 Spring Boot + Spring AI 项目 - 理解 ChatClient 的基本用法 - 完成第一个"发送消息、接收回复"的完整流程 - 理解 `application.yml` 中模型参数的配置 **第二阶段:对话与 Prompt(2-3 天)** - 深入理解 Message 模型(UserMessage、SystemMessage、AssistantMessage) - 掌握 System Prompt 的编写技巧 - 学习结构化输出(`.entity()` 映射 Java 对象) - 理解 PromptTemplate 的占位符机制 **第三阶段:RAG 体系(3-5 天)** - 理解 Embedding 的概念和 EmbeddingModel 的使用 - 学习 ETL Pipeline(DocumentReader → DocumentTransformer → DocumentWriter) - 选一个向量数据库(推荐从 Chroma 或 PGVector 开始),理解 VectorStore 的基本操作 - 使用 RetrievalAugmentationAdvisor 实现端到端的 RAG **第四阶段:AI Agent 能力(3-5 天)** - 掌握 Function Calling / Tool Calling - 使用 @Tool 和 @ToolParam 注解构建工具 - 理解 Chat Memory 和 MessageChatMemoryAdvisor - 学习 Advisors 链的组合使用 **第五阶段:生产级实践(持续)** - 配置可观测性(Metrics + Tracing) - 理解 MCP 协议、搭建 MCP Server - 流式响应的实现与优化 - 多模态支持的集成 ### 15.3 最后的建议 学习 Spring AI 的过程,本质上是在学习"如何用工程化的方式构建 AI 应用"。Spring AI 的设计让这个学习过程变得非常平滑 —— 你不需要在一开始就理解 RAG、Function Calling 这些高级概念,你只需要从 ChatClient 开始,写几个简单的调用,逐渐积累体感,然后根据实际需求逐步向调用链上叠加能力。 记住 Spring AI 的核心设计哲学:**简单场景简单做,复杂场景可以深度控制**。当你只需要一段 AI 回复时,`.call().content()` 就够了;当你需要让 AI 访问企业内部数据时,加上 `RetrievalAugmentationAdvisor`;当你需要 AI 执行操作时,注册 `ToolCallback`。每一次能力的叠加都是可插拔的,每一个组件都是可替换的。 这就是 Spring 生态二十年来一直坚持的工程哲学 —— **当你掌握了核心抽象,你就掌握了整个生态**。Spring AI 将这个哲学完整地带入了 AI 时代。 --- > **参考资料**:本文内容基于 Spring AI 官方参考文档(`docs.spring.io/spring-ai/reference`),代码示例来自官方文档中的实际使用场景,经过重新组织和补充说明。建议配合官方文档阅读以获取最新的 API 变更和版本差异信息。

小白用Codex和Claudecode也能做产品,程序员的出路在哪里?

最近用 Codex 和 Claude Code 写项目时,我越来越明显地感受到一种割裂:一边是效率真的变高了,一个想法可以很快变成能跑的产品;另一边是焦虑也变强了,因为“能跑”开始变得不再稀缺。真正刺痛我的问题不是 AI 能不能写代码,而是当一个项目大部分代码都由 AI 生成后,我还能不能解释它、修改它、验证它、回滚它,并在它出问题时真正负责?如果不能,这个项目看起来再完整,也更像是 AI 的产物,而不是我的工程能力。 ## 1.能做出产品,但不等于完全具备工程能力 小白能用AI做产品,但开发的时候他是否知道什么场景用什么技术栈,架构如何设计,如何用工作树和Git提交或回滚代码,如果一个系统真的上线,并发怎么处理?数据库事务边界在哪里?缓存失效怎么办?权限绕过怎么防?日志怎么设计?成本异常怎么定位?AI 改了鉴权逻辑但测试没覆盖怎么办?线上出错谁回滚?这些问题不是“生成代码”本身,而是**工程责任**。 未来最危险的岗位不是“程序员”,而是“只会接明确需求、写普通 CRUD、不会判断架构、不懂测试、不懂安全、不懂业务的人”。因为这类工作最适合被AI批量生成。 AI 让代码产出变快,必然让review、测试、安全和治理变成瓶颈。 AI 时代的高阶程序员价值链变成:定义问题 → 拆解任务 → 设计架构 → 给 AI 上下文 → 审查 diff → 写测试和评测 → 控制权限和安全边界 → 监控线上行为 → 复盘成本和质量 → 长期维护系统。 **AI 时代越往后,“能生成代码”越不稀缺,“能证明代码可信”越稀缺。** 我所做的项目: PatchBrake 不是为了再做一个代码扫描器,而是盯住 AI-generated diff 里的工程风险:删测试、放大权限、弱化鉴权、修改规则文件、引入危险脚本。 Token Studio ROI 不是为了记 token,而是把 AI 编程从“感觉效率很高”变成可复盘的工作证据:不同项目、不同模型、不同任务到底消耗了多少,产出了什么,值不值得继续。 OmniMerchant 不是客服 demo,而是在跨境电商场景里处理 RAG、tool calling、多租户、流式响应、fallback、成本和权限边界。 简喵不是泛泛的简历评分器,而是证据约束型岗位竞争力诊断:JD里哪些能匹配,哪些有证据,哪些不能硬补,改写后能不能经得起面试追问。 这些项目表面不同,底层是同一个问题: AI 生成之后,如何留下证据、边界和责任? 我把它叫作可信交付。 它至少包含五件事: - 生成结果有证据。 - 系统行为有边界。 - 质量风险可验证。 - 线上问题可追溯。 - 长期演进可复盘。 在AI应用工程中: RAG系统不是接一个向量库。 它要评估retrieval recall、context precision、faithfulness、answer relevance。 Agent系统不是让模型调用工具。 它要设计tool permission、approval flow、audit log、failure fallback。 AI observability不是看一眼 token 数。 它要记录模型、延迟、成本、trace、tool call、错误、用户反馈和版本变化。 这些才是AI应用开发的真实壁垒。 所以AI时代真正的出路是:**能把AI生成的软件纳入一套可验证、可审计、可交付的工程流程,建立一套自己的AI工作流。AI时代不缺代码生成,缺的是可信交付。** ## 2.小白也能做产品,程序员的出路在哪里? ### (1)做 AI 应用工程,而不是“套壳应用”。 用上Codex和Claude Code确实可以做一个“看起来不错”的工具,但多数人做不出稳定的 AI 应用。因为 AI 应用真正难的地方不是页面,也不是调 API,而是这些问题: - 模型输出不稳定怎么办? - 结构化 JSON 解析失败怎么办? - RAG 检索到错误证据怎么办? - 用户输入包含 prompt injection 怎么办? - tool calling 调错工具怎么办? - 多租户数据串了怎么办? - token 成本失控怎么办? - 流式响应中途失败怎么办? - 模型降级后质量怎么保证? **AI 生成内容如何可追溯、可解释、可复盘?** 这些才是AI应用开发的真实壁垒。 普通小白能上线 demo,但很难建立一套 evidence、eval、observability、guardrail、fallback、audit trail。 出路不是“我也会用 Claude Code 做项目”,而应该是:“**我能把 Claude Code、Codex 生成的软件纳入一套可验证、可审计、可交付的工程流程。”** - 不只是我的项目能跑,而是我能解释核心链路和关键实现。 - 不只是我的技术栈很丰富,而是我选择了有取舍、有边界、有替代方案。 - 不只是我用了 Claude Code / Codex,而是我能审查、约束、验证 AI 产物。 - 不只是我做了几个单元测试,而是我的测试覆盖了异常、边界、回归和 CI。 - 不只是我实现了登录鉴权,而是我能处理越权、注入、敏感信息和多租户隔离。 - 不只是我本地运行成功,而是我有日志、错误码、重试、回滚和监控。 - 不只是我的README很漂亮,而是我有 changelog、benchmark、failure cases 和复盘。 - 不只是我会背项目介绍,而是我能现场改需求、debug、解释取舍。 ### (2)掌握领域,而不是只掌握工具。 AI 最擅长的是通用模式。越通用,越容易被生成;越需要领域判断,越不容易被替代。 比如跨境电商客服系统,不只是写一个聊天框。它涉及订单、物流、售后、退换货政策、多语言、商家规则、平台合规、用户情绪、风控和成本。求职材料生成也不是“润色简历”,而是 JD → 证据 → 改写 → 风险控制 → 面试可追问。AI 可以帮你写代码,但它不知道你的产品到底要对谁负责。 所以程序员的长期出路之一,是变成“某个领域里的工程师”,而不是“只会某个技术栈的人”。Java 后端、Spring AI、RAG、Agent 都只是武器;真正值钱的是你能把它们用于一个真实问题,并且知道哪些地方不能乱生成。 AI 可以生成代码、README、测试样例、架构图,但很难伪造你对系统的真实理解、取舍过程、排障能力和长期维护能力。 ## 3.AI 时代,项目本身会贬值,工程证据会升值 ### (1)设计取舍证据。 面试官问: - “为什么用这个架构?” - “为什么不用更简单的方案?” - “这个模块的边界在哪里?” - “如果用户量扩大10倍,哪个地方先出问题?” - “这个设计最失败的地方是什么?” 真正做过工程的人,回答里会有取舍:性能、复杂度、开发时间、可维护性、安全、成本、团队能力。只靠 AI 堆出来的人,通常只能复述架构名词,比如“用了 Redis、用了 MQ、用了 RAG、用了微服务”,但说不出为什么。 ### (2)debug 证据。 AI 能生成新代码,但真实工程能力很大一部分体现在出问题后能不能定位。 面试官会问: - “线上接口变慢,你怎么排查?” - “Redis 缓存命中率下降,你看什么指标?” - “数据库偶发死锁,你怎么复现?” - “用户说 AI 回答错了,你怎么判断是 prompt 问题、检索问题、模型问题还是数据问题?” - “流式响应中途断了,前后端分别怎么处理?” 这类问题很难靠背诵解决。因为它考的是排查路径:日志、traceId、metrics、SQL explain、异常栈、请求链路、复现条件、最小化变量、回滚策略。 所以项目里要有日志、错误码、traceId、监控截图、故障复盘。哪怕是个人项目,也可以写一份“线上事故排查文档”。 ### (3)测试和验证证据。 未来 AI 生成代码越多,测试越重要。面试官会越来越看重你有没有能力验证 AI 产物。 他会问: - “你怎么证明这个功能是对的?” - “边界条件测了哪些?” - “单测、集成测试、端到端测试分别覆盖什么?” - “AI 生成代码你怎么 review?” - “你怎么防止修改 A 功能时破坏 B 功能?” 小白做项目通常是“能跑就行”。工程师要回答的是“为什么我相信它长期能跑”。 项目需要的东西: 单元测试、集成测试、异常测试、边界测试、benchmark、CI、测试覆盖范围说明、失败用例说明。 除此之外还应该有 AI eval:比如 RAG 回答是否引用了正确证据、简历改写是否夸大事实、客服 Agent 是否越权调用工具、AI 生成 diff 是否删除测试或放大权限。 ### (4)代码审查证据。 面试官不一定只让你写代码,可能直接给你一段AI生成代码,让你 review。 他会看你能不能发现: - 并发问题。 - 权限绕过。 - 事务边界错误。 - N+1 查询。 - 空指针和异常吞噬。 - SQL 注入。 - 缓存穿透/击穿/雪崩。 - 日志泄露敏感信息。 - 测试只测 happy path。 - 代码过度抽象。 AI 最容易写出“看起来完整,但边界很脆”的代码。会工程的人,能看出这些脆点。 我会把 PatchBrake 这类项目继续往“AI diff 风险审查”方向做。它不只是一个工具,而是能力定位的证据:你不是只会用 AI 写代码,你还能审计 AI 写出来的代码。 ### (5)系统边界和安全证据。 AI 写代码后,最容易被忽视的是边界。 面试官问: - “用户输入恶意内容怎么办?” - “多租户数据怎么隔离?” - “普通用户能不能越权访问别人的资源?” - “tool calling 有没有权限控制?” - “模型输出不合法怎么办?” - “敏感信息会不会进日志?” - “AI 生成内容错了,系统怎么兜底?” ### (6)长期维护证据。 很多 AI 项目是一次性 demo。面试官会越来越警惕这种“短期堆出来”的项目。 更关注: - 有没有版本演进记录。 - 有没有 changelog。 - 有没有 issue/roadmap。 - 有没有重构记录。 - 有没有测试随功能增长而增长。 - 有没有配置说明。 - 有没有部署说明。 - 有没有已知问题。 - 有没有从 v1 到 v2 的设计变化。 真正的工程能力不是“做出来”,而是“维护得住”。一个项目连续迭代 3 个月,比 5 个一次性AI demo更有说服力。 ### (7)表达和复盘证据。 AI 能帮你写项目介绍,但很难替你讲清楚真实经历。面试官会通过追问判断你是不是 owner。 面试官问: - “这个项目里你最难的一个 bug 是什么?” - “你做过最错误的设计是什么?” - “哪一部分后来推翻了?” - “你最不满意的地方是什么?” - “如果再做一遍,你会怎么改?” - “这个项目真正服务了谁?” - “有没有用户反馈?” 这些问题非常关键。因为没真正做过的人,通常只会讲正面包装,不会讲失败、返工、妥协和权衡。 所以文章里可以考虑主动写失败点。不是自曝缺点,而是证明你真的经历过工程过程。 | 能力 | 低质量证据 | 高质量证据 | | ----- | ----------------------- | ----------------------------- | | 写代码 | 项目能跑 | 能解释核心链路和关键实现 | | 架构设计 | 技术栈很丰富 | 有取舍、有边界、有替代方案 | | AI 使用 | 用 Claude Code/Codex 做项目 | 能审查、约束、验证 AI 产物 | | 测试 | 有几个 happy path 单测 | 覆盖异常、边界、回归、CI | | 安全 | 登录鉴权 | 越权、注入、敏感信息、多租户隔离 | | 可靠性 | 本地运行成功 | 日志、错误码、重试、回滚、监控 | | 项目深度 | README 很漂亮 | 有 changelog、benchmark、复盘、失败案例 | | 面试可信度 | 会背项目介绍 | 能现场改需求、debug、解释取舍 | 这些都是我后面做内容和产品会坚持的方向。 ## 4.关于我个人对AI时代的思考 我不会把自己的产品和文章做成“手把手复制一个爆款项目”的流量模板。 更不会承诺“学完某个项目就能找到工作”。 这种话听起来很有吸引力,但很多时候只是另一种自我安慰。 AI 应用开发不是背几个框架名词,也不是照着教程部署一个项目。 真正重要的是:**你有没有在一个具体问题里做过判断,踩过坑,推翻过方案,修过错误,理解过边界,并且能把这些过程沉淀成自己的工程认知。** 我更在意长期品牌,而不是短期热点。 热点可以带来流量,但很难带来信任。 **真正能让别人相信你的,不是你追上了多少话题,而是你是否长期围绕一个方向持续输出,是否能在项目、代码、文章和复盘里看见稳定的判断力。** 我现在做的事情很简单: 记录自己从 0 到 1 学习 AI 应用开发的过程。 记录项目踩坑、架构取舍、错误判断、重构过程和复盘。 **记录我如何从一个学生慢慢建立起对 AI 应用工程、可信交付、证据链、评测、安全和长期产品判断的理解。** 这不是速成路线。 也不是求职捷径。 它更像是一条长期训练路径。 **我希望我的内容不是让人看完以后产生虚假的兴奋,而是让真正愿意思考的人看到:一个项目为什么这样做,哪里容易错,哪些地方不能骗自己,什么才算真正有工程价值。** 我相信,**一个人需要在某个领域建立足够稳定的判断,才不会被网上的情绪、热点和速成叙事随意带偏。** 我也相信,**真正有判断力的人,最终会通过长期内容找到彼此。** 不是因为标题足够刺激。 而是因为**文字里有真实经历,有取舍,有问题意识,有持续迭代的痕迹。** 我不会只做“看起来很热”的内容。 我会继续做有我自己工程路径、成长痕迹和判断密度的深度内容。 短期流量会过去。 长期品牌会留下。 我是 Ryan,一个专注于可信 AI 应用工程的开发者,个人技术博客:[yanxai.com](https://yanxai.com/),研究如何让 AI 生成从“看起来对”走向“有证据、可追溯、可验证”。

下载 APP