普通视图

发现新文章,点击刷新页面。
昨天以前微言 | wyanassert

[转载] 认知重建:Speckit用了三个月,我放弃了——走出工具很强但用不好的困境

作者 wyanassert
2026年1月10日 12:27

[转载] 认知重建:Speckit用了三个月,我放弃了——走出工具很强但用不好的困境

原文地址

2025 年 AI 编程工具遍地开花,但一个尴尬的现实是:工具越来越强,预期越来越高,落地却越来越难——speckit 的规范流程在企业需求的”千层套路”、海量代码面前显得理想化,上下文窗口频繁爆满让复杂任务半途而废,每次做类似需求还是要花同样的时间因为知识全在人脑里。本文记录了我从踩坑规范驱动工具,到借鉴 Anthropic 多 Agent 协作架构、融合上下文工程与复合工程理念,最终实现边际成本递减、知识持续复利的完整历程。如果你也在”AI 工具明明很强但就是用不好”的困境中挣扎,或许能找到一些共鸣。附带还有新的工作流下人的工作模式转变思考~

起点:规范驱动开发的美好承诺

1.0 团队的 AI Coding 起点

先交代一下背景:我所在的是一个后端研发团队,日常工作以存量项目迭代为主,涉及多个微服务的协作开发。

2024 年中,团队开始尝试 AI 辅助编程。最初的体验是:

短上下文场景效果不错

  • 写一个独立函数、实现一个工具方法——AI 表现良好
  • 简单的代码补全、格式化、注释生成——确实提效

但规模化复用始终没起来

  • 当时只有三种触发类型的 rules(早期 rules 时代)
  • 虽然提出过”在基础 agent 之上封装 agent”的想法
  • 但几个月过去,仍然没有太多人真正动起来

原因分析

  • 规范没有形成共识——每个人对”怎么用好 AI”理解不同
  • 对 AI 工程化没有标准认识——不知道该往哪个方向努力
  • 提示词复用习惯没建立——好的 prompt 停留在个人经验,没有沉淀

这个困境促使我开始探索外部方案:有没有已经成熟的”AI 编程工程化”方法论?有没有可以直接借鉴的最佳实践?

带着这些问题,我遇到了 speckit 和 openspec。

遇见 speckit:AI 编程的”正确打开方式”?

2024 年开始,AI 编程助手如雨后春笋般涌现。Copilot、Cursor、Claude 让很多人第一次体验到了”AI 写代码”的魔力。但兴奋之后,问题也随之而来:

  • AI 生成的代码质量参差不齐
  • 需求理解经常偏离预期
  • 缺乏持续性,上下文丢失严重
  • 改一处坏十处,维护成本高

正当我被这些问题困扰时,遇到了 speckit——一个规范驱动开发(Spec-Driven Development, SDD)工具包。

speckit 的理念很吸引人:

1
2
3
规范即代码 → 规范直接生成实现,而非仅作为指导文档
权力倒置 → 代码服务于规范,而非规范服务于代码
测试优先 → 强制 TDD,不可协商地要求先写测试

它定义了一套清晰的 5 阶段流程:

1
2
Constitution → Specify → Plan → Tasks → Implement
(宪章) (规范) (计划) (任务) (实施)

每个阶段对应一个命令,依次执行:创建项目宪章和开发原则 → 定义需求和用户故事 → 创建技术实现计划 → 生成可执行的任务列表 → 执行所有任务构建功能。

再加上 9 条不可变的架构原则(库优先、CLI 接口、测试优先、简洁性、反抽象…),7 层 LLM 输出约束机制,防止过早实现、强制标记不确定性、结构化自检…

这不就是 AI 编程的”工程化正确答案”吗?

带着这样的期待,我开始在项目中尝试落地。

openspec:另一种优雅的尝试

除了 speckit,我还研究了 openspec——一个更轻量的规范驱动框架:

1
2
3
Specs as Source of Truth → specs/ 目录始终反映系统当前真实状态
Changes as Proposals → 所有修改先以提案形式存在,经确认后实施
Lock Intent → AI 编码前通过明确规范锁定意图

openspec 的 Delta 机制设计得很巧妙:不同于直接存储完整的”未来状态”,它只存储变更操作本身(ADDED/MODIFIED/REMOVED/RENAMED)。归档时通过语义名称匹配来定位需求,避免了 Git Merge 常见的位置冲突问题。同时采用 Fail-Fast 机制,在写入前做完整冲突检测,保证不会产生半完成状态。

两个工具,两种风格,但都指向同一个目标:让 AI 编程更可控、更规范。

碰壁:理想流程遭遇企业现实

一个真实需求的”千层套路”

让我用一个真实的 12 月活动需求来说明问题:

协作复杂度

  • 跨 BG、跨前后端、跨 FT、跨项目、跨小组、跨服务
  • 跨部门合作接口因合规要求变来变去,迟迟给不到位
  • 雅典娜平台上接近 20 种商品类型,全得人工一个个配
  • 活动流程必须按”玩法引擎”的方法论来拆解
  • 技术方案得按习惯写在 iWiki 里

并行任务流

1
2
3
4
5
6
同时处理:
├── 找产品确认商品细节
├── 找运营确认玩法逻辑
├── 找跨团队研发对齐接口
├── 找跨项目研发对齐交互
└── 内部技术方案评审

方案设计的”考古”需求

  • 某个商品创建、资产查看以前有什么坑?
  • 现在的玩法能力有哪些?能不能直接用?
  • 导航小结页到底是啥?怎么让它弹 Banner?

**写代码前的”九九八十一难”**:

1
2
3
4
5
6
前置任务链:
├── 玩法引擎:依赖数据、激励动作要在引擎仓库里实现
├── 外部依赖:关联的代码改动在其他服务里
├── 配置中心:要去阿波罗(Apollo)配配置
├── 雅典娜:商品场景得先配好(早期没数据还得 Mock)
└── 数据库:涉及表变更,得去测试环境操作

执行中的细节坑

  • 阿波罗配置有个坑,该怎么绕过去?
  • 规则引擎的语法到底怎么写?
  • 商品发放操作是重点,具体发到哪个钱包?

speckit 流程 vs 企业现实

把 speckit 的理想流程放到这个场景里:

1
2
3
4
5
6
7
8
9
speckit 假设的流程:
Constitution → Specify → Plan → Tasks → Implement
↓ ↓ ↓ ↓ ↓
一次性定义 一次性写清 线性规划 任务分解 按序实施

企业现实:
多方博弈 → 动态调整 → 并行推进 → 持续扯皮 → 边做边改
↓ ↓ ↓ ↓ ↓
需求会变 方案会改 依赖会卡 资源会抢 意外会来

核心矛盾:speckit 假设需求是清晰的、可一次性规划的,但企业真实需求是动态的、多方博弈的、持续变化的。

openspec 的 Delta 机制也救不了

openspec 的”提案→审查→归档”流程看起来更灵活,但:

  • **假设需求可以”提案化”**:实际上外部接口因合规变来变去,5 个维度同时推进相互依赖,评审中发现问题需要立即改方案

  • 人工介入成本高:Delta 与主 Spec 冲突时报错终止,复杂冲突需要人工解决,而人的认知窗口有限。具体来说,openspec archive 会在以下情况直接报错退出:

    • MODIFIED 引用的需求在主 Spec 中不存在(可能被别人删了或改名了)

    • ADDED 的需求在主 Spec 中已存在(别的分支先合入了同名需求)

    • RENAMED 的源名称不存在,或目标名称已被占用

    • 同一个需求同时出现在 MODIFIED 和 REMOVED 中(逻辑矛盾)

这些冲突没有自动解决策略,CLI 只会打印类似 MODIFIED failed for header "### Requirement: xxx" - not found 的错误信息,然后终止。你需要:手动打开两个文件对比、理解冲突原因、决定保留哪个版本、手工修改 Delta 文件、重新执行归档。整个过程要求你同时在脑中持有”主 Spec 当前状态”和”Delta 期望变更”两套信息——这对认知负担是很大的挑战

  • 强依赖命名的脆弱性:产品叫”用户激励”,运营叫”活动奖励”,研发叫”商品发放”——同一个需求在不同阶段有不同表述

最致命的问题:无法应对”考古”需求

speckit 和 openspec 都有一个共同盲区:流程从零开始

1
2
3
4
5
6
7
8
9
speckit 流程:
Constitution 定义原则 → Specify 定义需求 → Plan 设计方案 → ...

但真实需求必须"考古":
├── 这个商品创建以前有什么坑?
├── 现有玩法能力有哪些?
├── 导航小结页的 Banner 怎么弹?
├── Apollo 配置有什么特殊处理?
└── 雅典娜 20 种商品类型的配置方式各不同

缺失能力:没有”上下文检索”机制,无法自动关联历史经验、已有能力、已知陷阱。

AI 生成 spec 时能看到的:

  • ✅ 代码仓库
  • ✅ project.md/Constitution
  • ✅ 用户意图

AI 看不到(但需要知道)的:

  • ❌ 业务边界(涉及哪些服务?)
  • ❌ 历史经验(以前怎么做的?有什么坑?)
  • ❌ 配置规范(Apollo 特殊要求?)
  • ❌ 平台知识(雅典娜 20 种商品配置注意事项)
  • ❌ 协作约束(依赖其他团队接口?合规要求?)

结果:依赖人 review 时逐步想起来告诉 AI,45 分钟 + 持续的认知负担。

AI 工程化如何破局?(预告)

面对上述问题,AI 工程化的解决思路是什么?这里先做个预告,详细方案见第五节。

企业现实问题 speckit/openspec 的困境 AI 工程化的解法
需求动态变化 假设一次性规划,变更成本高 需求以”进行中”状态管理,支持随时调整,阶段性沉淀
多线并行博弈 线性流程,Delta 冲突报错终止 Agent 自主决策路由,Skill 独立执行,不强依赖顺序
考古需求 无上下文检索,AI 只能看到代码 context/ 分层管理历史经验,按阶段自动加载
配置/平台知识 需要人 review 时口述 沉淀为 context/tech/,AI 执行时主动提醒
冲突解决成本 人工对比、手工修改、认知负担重 不依赖”合并”,而是”覆盖+沉淀”,冲突时 AI 辅助决策
边际成本恒定 每次 45 分钟,无复利 首次建立 context,后续复用,边际成本递减

核心差异

1
2
3
4
5
6
7
8
9
speckit/openspec 的思路:
规范化流程 → 约束 AI 行为 → 期望产出质量

问题:流程本身不适配企业现实,约束越多越僵化

AI 工程化的思路:
上下文完整性 → AI 决策质量 → 自动沉淀经验 → 下次更好

解法:不是约束 AI,而是给 AI 完整信息 + 让知识复利

一个具体例子——同样是”商品发放”需求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
speckit 模式(第 3 次做):
1. Constitution → 写项目原则(已有,跳过)
2. Specify → 写需求规范(45 分钟,人逐步想起遗漏告诉 AI)
3. Plan → 写技术方案(人提醒:Apollo 有坑、钱包要区分)
4. Tasks → 生成任务(人补充:雅典娜配置注意事项)
5. Implement → 执行(遇到问题再排查)
耗时:45 分钟 + 排查时间,知识留在人脑

AI 工程化模式(第 3 次做):
1. /req-dev "商品发放需求"
2. Agent 识别意图 → 自动加载 context/experience/商品发放历史问题.md
3. Agent 提醒:"历史上有钱包选择、Apollo 配置、雅典娜商品类型三个坑点"
4. 人确认:"对,继续"
5. Skill 执行 → 自动校验 → 生成代码 → 沉淀新发现
耗时:10 分钟,知识沉淀到 context/

后续章节将详细展开这套方案的设计原理和落地实践。


反思:从第一性原理重新审视

人的认知局限是刚性约束

实话实说,我的脑容量有限:

  • 记性不好:只能记住关键的大方向,具体细节过脑就忘
  • 专注窗口小:同时关注的信息有限,必须采用”专注单任务+全局索引”策略

我的日常工作模式(经过各种场景检验的最优路径):

  • 任务管理(外挂大脑):Todo List 分优先级(红色紧急/黄色进行中/绿色完成/无色未开始)
  • 备忘录:记录死记硬背的内容(打包命令、数据库 IP 密码、文档散落信息)
  • 桌面即上下文:N 个桌面窗口,每个窗口对应一个垂直领域
  • 复杂任务 SOP 化:脑内计划 + 执行机器模式 + 文档跟踪
  • 简单任务 Fire and Forget:低频低思考成本事项秒回即忘

这套土办法是经过检验的最优路径。如果硬套 speckit/openspec 的范式,反而会丢掉这些 SOP,得不偿失。

执行过程的知识价值被忽视

speckit 和 openspec 都只关注”规范”(Spec)和”结果”(Code),忽视”过程”(Process)。

但真实价值恰恰在过程中:

1
2
3
4
5
执行 → 有问题 → 验证 → 排查 → 继续执行

排查信息往往没被记录

时间一久或换人,下次重新排查

这个循环中的排查信息,才是最宝贵的知识!

边际成本恒定是致命缺陷

1
2
3
4
5
6
7
Speckit 模式:
第 1 次商品发放需求:45 分钟(人逐步想起遗漏)
第 2 次商品发放需求:45 分钟(人 AGAIN 逐步想起遗漏)
第 n 次商品发放需求:45 分钟(还是要想,还是那么久)

边际成本恒定,无复利效应。
知识在哪里?在人脑里,每次都要重新想起来。

这与我期望的”越用越快”完全相反。

转折:遇见复合工程与上下文工程

复合式工程:让每一步都成为下一步的基石

在探索过程中,我接触到了”复合式工程”(Compounding Engineering)的理念。这个概念来自 Claude Code 团队与 Every 团队的实践交流,并在 Every 团队开源的 Compound Engineering Plugin 中得到了系统化实现——这是一个包含 27 个 Agent、19 个 Command、13 个 Skill 的完整 AI 辅助开发工具包。

定义”复合式工程”

“复合式工程”的核心目标非常明确:让每一单元的工程工作使后续工作变得更容易,而非更难。

1
2
传统开发:累积技术债务 → 每个功能增加复杂性 → 代码库越来越难维护
复合工程:每个功能产出文档模式 → 创建可复用组件 → 建立减少决策疲劳的约定 → 知识在团队中复合增长

与传统工程中每增加一个功能都会增加系统复杂度和维护成本不同,”复合式工程”追求的是一种”复利”效应,让系统的能力随着时间推移指数级增长。

核心工作流循环:Plan → Work → Review → Compound

Compound Engineering Plugin 设计了一个闭环的工作流循环:

1
2
3
4
5
Plan ──────→ Work ──────→ Review ──────→ Compound
详细规划 执行工作 质量检查 知识沉淀
↑ │
└───────────────────────────────────────┘
知识复合:下次规划更精准
  • Plan:多代理并行研究仓库模式、最佳实践、框架文档,输出结构化计划
  • Work:系统性执行计划,边做边测,质量内建
  • Review:多代理并行审查(安全、性能、架构等),输出分级 Todo
  • Compound:这是复合工程的核心——将解决的问题结构化记录,形成团队知识资产

完整实现参见:Compound Engineering Plugin

为什么叫”Compound”?

1
2
3
4
5
6
第一次解决 "N+1 query in brief generation" → Research (30 min)
文档化 → docs/solutions/performance-issues/n-plus-one-briefs.md (5 min)
下次类似问题 → Quick lookup (2 min)
知识复合 → Team gets smarter

Each unit of engineering work should make subsequent units of work easier—not harder.

实现机制:知识复合的典型场景

实现复合工程的关键,在于建立系统化的知识沉淀机制。以下是几个典型场景:

场景 1:Agent 重复犯同类错误

1
2
3
触发:发现 Agent 在某类问题上反复出错
沉淀:将教训写入 AGENTS.md / CLAUDE.md / 系统提示词
效果:该类错误不再发生,无需人工提醒

场景 2:某类问题需要频繁人工检查

1
2
3
触发:Code Review 时反复指出同类问题
沉淀:创建 Lint 规则 / Pre-commit Hook / CI 检查
效果:问题在提交前自动拦截,减少人工负担

场景 3:复杂流程被多次执行

1
2
3
触发:某个多步骤操作被团队重复执行
沉淀:封装为 Skill / Command / Agent
效果:一键触发标准化流程,新人也能执行专家级操作

场景 4:解决了一个有价值的问题

1
2
3
触发:花了较长时间解决某个棘手问题
沉淀:结构化记录到 context/experience/ 目录
效果:下次遇到类似问题,Agent 自动加载相关经验

这些场景的共同特点是:在问题解决的当下立即沉淀,而不是事后补文档。

Claude 团队的复合工程应用案例

以下是 Every 团队和 Anthropic 内部使用复合工程的真实案例:

案例 1:”@claude,把这个加到 claude.md 里”

当有人在 PR 里犯错,团队会说:”@claude,把这个加到 claude.md 里,下次就不会再犯了。”或者:”@claude,给这个写个测试,确保不会回归。”通过这种方式,错误转化为系统的免疫能力。

案例 2:100% AI 生成的测试和 Lint 规则

Claude Code 内部几乎 100% 的测试都是 Claude 写的。坏的测试不会被提交,好的测试留下来。Lint 规则也是 100% Claude 写的,每次有新规则需要,直接在 PR 里说一句:”@claude,写个 lint 规则。”

案例 3:十年未写代码的经理

经理 Fiona 十年没写代码了,加入团队第一天就开始提交 PR。不是因为她重新学会了编程,而是因为 Claude Code 里积累了所有团队的实践经验——系统”记得”怎么写代码。

案例 4:内置记忆系统

把每次实现功能的过程——计划怎么制定的、哪些部分需要修改、测试时发现了什么问题、哪些地方容易遗漏——全部记录下来,编码回所有的 prompts、sub-agents、slash commands。这样下次别人做类似功能时,系统会自动提醒:”注意,上次这里有个坑。”

成果:一个自我进化的开发伙伴

这一范式带来的最终效果是惊人的。它将 AI 从一个被动执行命令的工具,转变为一个能够从经验中持续学习、并让整个开发流程效率不断”复利”增长的开发伙伴。

为什么这解决了古老的知识管理问题

传统的知识管理困境:

1
2
3
4
5
6
7
8
方式 1:写文档
问题:没人看。写完就过时。维护成本高。

方式 2:靠人传授
问题:老人离职知识断层。新人上手慢。传授效率低。

方式 3:代码注释
问题:注释会过时。只能解释"是什么",难以解释"为什么这么做"和"以前踩过什么坑"。

复合工程的答案:把知识编码进工具,让工具在正确的时刻主动提醒你

1
2
3
4
5
6
7
8
9
不是:写一份"商品发放注意事项"文档,期望大家会看
而是:在 context/experience/商品发放历史问题.md 里记录,
Agent 在执行商品发放需求时自动加载,主动提醒

不是:靠老人口头传授"Apollo 配置有个坑"
而是:把坑编码到 skill 里,执行时自动校验

不是:在代码里写注释"这里要注意 XX"
而是:让 AI 在生成代码前就已经知道要注意 XX

关键设计模式

从 Compound Engineering Plugin 中可以提炼出三个核心设计模式:

模式 核心思想 价值
并行代理 多角度分析时启动多个专业代理,合并结果后继续 提高分析覆盖度和效率
意图路由 入口统一,根据意图自动路由到具体工作流 降低用户认知负担
知识复合 问题解决 → 文档化 → 未来查找 → 团队变聪明 边际成本递减

我的实践:基于工具架构的知识复合

基于复合工程理念,我设计了一套 AI 工程工具架构来实现知识的持续沉淀与复用:

工具架构

1
2
3
4
5
6
7
用户输入 → Command(入口)→ Agent(决策层)→ Skill(执行层)

意图识别、流程路由

调用具体 Skill 执行

experience-index(经验检索)
  • Command:用户交互入口,如 /req-dev/optimize-flow
  • Agent:自主决策,智能判断意图,可调用多个 Skill
  • Skill:固化流程,执行具体操作步骤

知识复合的两条路径

1
2
3
4
5
6
7
8
9
10
路径 1:经验沉淀(/optimize-flow)
用户发现规律 → experience-depositor Agent → 识别规则类型 → 写入规则文件

context-rules.md(上下文映射)
risk-rules.md(风险识别)
service-rules.md(服务补全)
pattern-rules.md(代码模式)

路径 2:经验检索(experience-index Skill)
需求分析/方案设计/代码编写前 → 自动检索匹配规则 → 加载相关 Context、提示风险、建议服务

复利效应示例

1
2
3
4
5
6
7
第 1 次做支付需求:45 分钟(边做边踩坑)
↓ 沉淀规则:/optimize-flow "支付需求要加载 payment-service.md 并提示资金安全"

第 2 次做支付需求:15 分钟(experience-index 自动加载背景、提示风险)
↓ 沉淀更多规则:错误处理模式、服务依赖关系

第 N 次做支付需求:5 分钟(系统已积累完整的支付领域知识)

与传统文档的本质区别

1
2
传统文档:写完没人看,看了也找不到对的时机
AI 工程化:experience-index 在正确的时刻自动检索,主动推送给 Agent

这就是为什么”知识应该沉淀到工具”不是一句口号,而是有实际 ROI 的工程决策。

对长期任务工程设计的启示

Compound Engineering Plugin 为 AI 工程化提供了极好的参考蓝图:

维度 启示
任务分解 阶段化执行(Plan → Work → Review → Compound),并行化处理,状态持久化
质量保障 多角度并行审查,分级处理(P1/P2/P3),持续验证(边做边测)
知识管理 即时文档化(趁上下文新鲜),分类存储(按问题类型),交叉引用(关联 Issue、PR)
工具设计 工具提供能力而非行为,Prompt 定义意图和流程,让代理决定如何达成目标

极简主义:设计理念如何影响我的实践

Claude Code 团队的实践给了我另一个启发:

“最好的工具,就是没有工具。”

他们的做法:

  • 只给模型一样东西:bash
  • 每周都在删工具,因为新模型不需要了
  • 减少模型的选择,就是增加模型的能力
  • “模型吞噬脚手架”——曾经的外部辅助,逐渐被模型吸收

产品极简主义:不是”越来越丰富”,而是”越来越纯粹”。每一代模型发布,工具都会变得更简单,因为复杂性转移到了模型内部。

这个理念深刻影响了我做 AI 工程化的设计思路

  1. 入口极简化:整个系统只有两个命令入口——/req-dev/optimize-flow。不是因为功能少,而是把复杂性藏到了 Agent 的智能路由里。用户不需要记住十几个命令,只需要表达意图,Agent 会判断该调用哪个 Skill。
  2. Skill 而非工具堆叠:speckit/openspec 倾向于提供更多工具、更多模板、更多约束。我选择相反的方向——把能力编码为 Skill,让 Agent 在需要时自动调用,而不是让用户手动选择”现在该用哪个工具”。
  3. 上下文自动加载:Claude Code 团队说”人类和 AI 看同样的输出,说同样的语言,共享同一个现实”。我把这个原则应用到上下文管理——不是让用户手动指定”加载哪些背景资料”,而是让 Agent 根据当前阶段自动加载相关的 context/。用户感受不到”上下文加载”这个动作,但 AI 已经具备了完整的信息。
  4. 删除优先于添加:每次迭代时,我会问自己”有哪些东西可以删掉?”而不是”还能加什么功能?”。AGENTS.md 从最初的长篇大论,精简到现在只放通用规范和目录指针,具体流程全部下沉到 Skill 里。
  5. 双重用户设计:Claude Code 为工程师和模型同时设计界面。AI 工程化也是——/req-dev 命令人可以手动调用,Agent 也可以在流程中自动调用子 Skill。同一套能力,两种调用方式,没有冗余。

当前实践的目标:让工具尽可能”隐形”——用户只需要说”我要做一个商品发放需求”,系统自动加载上下文、自动识别阶段、自动调用对应 Skill、自动沉淀经验。用户感受不到在”使用工具”,只是在”完成工作”。

注:关于工具消失的行业发展趋势,详见第九节”未来展望”。

上下文工程:AI 能力的前提是信息完整性

参考:Anthropic - Effective Context Engineering for AI Agents

什么是上下文工程?

上下文(Context) 指的是在从大语言模型(LLM)采样时包含的一组 token——不仅仅是提示词,还包括系统提示、工具定义、对话历史、检索到的文档等所有进入模型的信息。

上下文工程 是指在 LLM 推理过程中,策划和维护最优 token 集合的策略集合。它代表了 LLM 应用构建方式的根本转变:

提示词工程(旧范式) 上下文工程(新范式)
关注如何编写有效的提示词 管理整个上下文状态
主要针对一次性分类或文本生成任务 针对多轮推理和长时间运行的智能体
“找到正确的词语和短语” “什么样的上下文配置最可能产生期望行为?”

核心指导原则

找到最小可能的高信号 token 集合,最大化期望结果的可能性

为什么不重视上下文工程会导致严重问题?

很多团队把 AI 辅助编程的失败归咎于”模型不够强”或”提示词没写好”,但真正的根因往往是上下文工程的缺失。Anthropic 的研究揭示了几个关键问题:

问题 1:上下文腐蚀(Context Rot)

研究发现:随着上下文窗口中 token 数量增加,模型准确回忆信息的能力会下降

1
2
3
4
上下文腐蚀的恶性循环:
加载更多信息 → 窗口膨胀 → 信息检索精度下降 → 行为异常

人发现问题 → 加更多上下文纠正 → 窗口更膨胀 → 更差

这不是断崖式下降,而是梯度下降——模型在长上下文中仍然能力强大,但信息检索和长程推理的精度会持续降低。

问题 2:注意力预算耗尽(Attention Budget Exhaustion)

LLM 就像人类有限的工作记忆一样,拥有”注意力预算”:

1
2
3
4
5
6
7
8
9
Transformer 架构的约束:
├── 每个 token 都要关注所有其他 token,产生 n² 个成对关系
├── 训练数据中短序列比长序列更常见,模型对长上下文依赖的经验较少
└── 位置编码插值虽允许处理更长序列,但会降低 token 位置理解的精度

结果:
├── 每引入一个新 token 都会消耗注意力预算
├── 低质量的 token 会"稀释"高质量信息
└── 关键信息可能被噪声淹没

问题 3:speckit/openspec 的上下文盲区

回顾第二节的 speckit 困境,从上下文工程角度重新审视:

问题现象 上下文工程视角的根因
人 review 时逐步想起遗漏告诉 AI 历史经验没有编码为可检索的上下文
45 分钟完成需求,边际成本恒定 每次都是”冷启动”,没有上下文复用
上下文窗口频繁爆满 没有分层加载策略,一次性塞入过多信息
AI 行为异常,半途而废 上下文腐蚀导致关键信息被”遗忘”

问题 4:工具设计不当导致上下文污染

Anthropic 指出一个常见失败模式:

“臃肿的工具集,覆盖过多功能或导致使用哪个工具的决策点模糊不清”

判断标准:如果人类工程师无法明确说出在给定情况下应该使用哪个工具,AI 智能体也不能做得更好。

1
2
3
4
5
工具设计不当的后果:
├── 工具描述冗长 → 消耗上下文预算
├── 工具边界模糊 → AI 决策困难,产生更多试错对话
├── 工具返回冗余信息 → 上下文快速膨胀
└── 最终:窗口爆满,任务失败

有效上下文工程的核心原则

基于 Anthropic 的实践和我们的落地经验,总结以下原则:

原则 1:分层式信息组织

1
2
3
4
5
6
7
8
context/
├── business/
│ └── 活动业务边界.md ← 概要层(意图识别时加载)
├── tech/
│ └── Apollo配置规范.md ← 技术层(方案设计时加载)
└── experience/
├── 商品发放历史问题.md ← 经验层(实施前加载)
└── 雅典娜配置注意事项.md ← 详细层(配置时加载)

原则 2:”即时”上下文策略(Just-in-Time Context)

不是预先加载所有可能相关的信息,而是维护轻量级索引,在运行时动态加载:

1
2
3
4
5
6
7
传统方式(预加载):
启动 → 加载所有相关文档(20000 tokens)→ 开始工作 → 窗口已满一半

即时策略:
启动 → 加载索引文件(500 tokens)→ 识别当前阶段 → 按需加载(3000 tokens)

窗口保持精简,信息高度相关

Claude Code 的实践:使用 globgrep 等原语允许即时导航和检索文件,而不是预先加载完整数据对象到上下文中。

原则 3:上下文压缩与笔记系统

对于长时间运行的任务:

1
2
3
4
5
6
7
8
9
10
压缩(Compaction):
├── 将接近上下文窗口限制的对话内容总结
├── 保留:架构决策、未解决的 bug、实现细节
├── 丢弃:冗余的工具输出或消息
└── 用摘要重新初始化新的上下文窗口

结构化笔记(Structured Note-taking):
├── 智能体定期将笔记写入上下文窗口外的持久化存储
├── 稍后根据需要拉回上下文窗口
└── 实现跨压缩步骤的连贯性

原则 4:工具设计的上下文效率

1
2
3
4
5
6
7
8
9
10
11
好的工具设计:
├── 自包含:不依赖"记住"之前的对话
├── 返回精简:只返回 token 高效的必要信息
├── 边界清晰:用途明确,减少决策成本
└── 发挥模型优势:利用模型擅长的能力

坏的工具设计:
├── 返回完整数据库查询结果(可能数千行)
├── 工具描述长达数百 token
├── 多个工具功能重叠,边界模糊
└── 强迫模型做它不擅长的事情

上下文工程与 AI 工程化的关系

理解了上下文工程,就能理解 AI 工程化架构设计的”为什么”:

AI 工程化设计 上下文工程原理
context/ 分层目录 分层式信息组织,按阶段按需加载
Skill 封装固定流程 稳定执行过程,避免提示词遗漏导致的上下文不完整
Subagent 架构 主 Agent 保持精简,子任务独立窗口
状态文件传递 不依赖”记忆”,依赖结构化状态
经验沉淀机制 将知识编码为可检索上下文,而非依赖人脑

本质规律

1
2
3
AI 的决策质量 ∝ 可用信息的完整性 × 信息的信噪比
↑ ↑
不是越多越好 高信号、低噪声才有效

这意味着:

  • 与其让人在 review 时逐步想起遗漏告诉 AI
  • 不如建立系统化的上下文管理,让 AI 自动获取精简且高信号的信息

实践:AI 工程化的设计与落地

AI 工程化是什么

经过反复思考和实践,我提炼出了 AI 工程化的定义:

智能化管理工作信息,以上下文工程的理解管理整个工作场景,借助AI的能力,降低人对已识别问题的处理成本

组成部分

1. 脚手架(Git 仓库形式)

  • 把规范转为基础的目录结构
  • 附带基础的初始化命令
  • 存放业务线的上下文信息(业务背景、技术背景等)
  • 随项目独立迭代的资源文件

2. 工具包(插件形式)

  • 提供 AI 工程需要的 cmd、skill、mcp、agent、hook 等
  • 在插件市场迭代,分版本管理
  • update 即可升级最新的规范、能力集成

为什么分脚手架和工具包?

  • 插件市场内容会迭代、分版本,需要灵活升级
  • 脚手架项目初始化后,随项目迭代,是独立的 git 仓库
  • 脚手架适合存放基础资源文件和业务上下文信息
  • 工具包适合封装通用能力和规范

Image 1

核心架构:Agent + Skill 分层设计

1
2
3
4
5
用户输入 → Command → Agent(决策层)→ Skill(执行层)

意图识别、流程路由

调用具体 Skill 执行
  • Agent:自主决策层,负责意图识别、流程路由、上下文管理
  • Skill:过程执行层,负责固定流程任务的具体执行
  • Command:用户交互入口,通过 Agent 路由到具体执行

当前系统设计

  • 5 个 Agents:phase-router、requirement-manager、design-manager、implementation-executor、experience-depositor
  • 12 个 Skills:req-create、req-change、experience-index、design-create、design-change、workspace-setup、design-implementation、code-commit、requirement-completer、requirement-archiver、meta-maintainer、index-manager
  • 2 个 Commands/req-dev(需求研发统一入口)、/optimize-flow(流程优化沉淀)

目录结构:位置即语义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
your-project/
├── AGENTS.md # 项目记忆入口(每次会话自动加载)
├── .codebuddy/ # AI 自动化配置
│ ├── agents/ # Agent 定义(决策层)
│ ├── commands/ # 命令入口
│ └── skills/ # Skill 定义(执行层)
├── context/ # 项目知识库(长期记忆)
│ ├── business/ # 业务领域知识
│ ├── tech/ # 技术背景
│ │ └── services/ # 服务分析文档
│ └── experience/ # 历史经验
├── requirements/ # 需求管理
│ ├── INDEX.md # 需求索引
│ ├── in-progress/ # 进行中需求
│ └── completed/ # 已完成需求
└── workspace/ # 代码工作区(Git 忽略)

三个核心约束

  1. 入口短小:AGENTS.md 只放通用规范 + 目录指针,不写具体流程步骤
  2. 位置即语义:requirements/ 放需求产物,context/ 放可复用上下文,workspace/ 放代码
  3. 复利沉淀:每次执行命令,除了产出当前结果,还要让”下一次更快、更稳”

经验沉淀的技术实现

前面 4.1 节讲了复合工程的理念和三层沉淀机制,这里聚焦具体怎么实现

触发时机:什么时候沉淀?

1
2
3
4
5
6
7
8
不是:做完需求后专门花时间"写总结"
而是:在流程关键节点自动触发沉淀

具体触发点:
├── 需求完成时 → requirement-completer skill 自动提取可复用经验
├── 遇到问题解决后 → 用户说"记住这个坑" → experience-depositor agent 记录
├── 代码提交时 → code-commit skill 检查是否有值得记录的模式
└── 流程优化时 → /optimize-flow 命令专门用于沉淀和优化

沉淀格式:记录什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# context/experience/商品发放-钱包选择问题.md

## 问题描述
商品发放时选错钱包类型,导致用户领取失败

## 触发条件
- 需求涉及商品发放
- 商品类型为虚拟商品

## 解决方案
虚拟商品必须发到虚拟钱包,实物商品发到实物钱包
具体判断逻辑见 Apollo 配置:xxx.wallet.type

## 校验方式
检查 goods_type 与 wallet_type 的匹配关系

## 关联文档
- context/tech/Apollo配置规范.md
- context/tech/services/商品服务技术总结.md

检索机制:怎么在对的时候加载?

检索由 experience-index Skill 统一负责,在需求分析、方案设计、代码编写前自动调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Agent 的上下文加载逻辑:

1. 意图识别阶段
phase-router 识别意图,路由到对应 Agent

2. 经验检索阶段
Agent 调用 experience-index Skill,传入场景描述
Skill 检索四类规则文件:
├── context-rules.md → 匹配需加载的背景文档
├── risk-rules.md → 匹配风险提示
├── service-rules.md → 匹配服务依赖建议
└── pattern-rules.md → 匹配代码规范

3. 返回结构化结果
{
"context": { "files": ["商品发放历史问题.md"] },
"risk": { "alerts": [{"level": "high", "message": "注意钱包类型"}] },
"service": { "suggestions": ["商品服务", "钱包服务"] },
"pattern": { "files": ["error-handling.md"] }
}

4. Agent 主动提醒
"注意:历史上商品发放有钱包选择问题,请确认..."

规则沉淀入口:通过 /optimize-flow 命令,调用 experience-depositor Agent 将新规则写入对应规则文件。

演进路径:从文档到 Skill 到 Command

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
阶段 1:纯文档(被动)
context/experience/xxx.md
→ AI 读取后提醒,但需要人确认

阶段 2:校验 Skill(半自动)
skill/product-distribution-validator
→ 自动校验配置,发现问题直接报错

阶段 3:完整 Command(全自动)
cmd/implement-product-distribution
→ 一个命令:加载背景 + 校验 + 生成 + 提醒 + 沉淀新经验

演进判断标准:
- 同类需求做了 5 次以上 → 考虑封装 Skill
- Skill 被调用 10 次以上 → 考虑封装 Command
- 不要过早抽象,让实践驱动演进

与 speckit 的本质区别

1
2
3
4
5
6
7
8
9
speckit 的知识流向:
人脑 → Spec 文档 → 代码
↑__________|
下次还要从人脑开始

AI 工程化的知识流向:
人脑 → context/ → Skill → Command
↑_________|________|
知识留在工具链里,下次直接复用

时间成本的量化对比

前面 2.5 节从”问题-方案”角度做了概念对比,这里从时间成本角度量化差异:

执行次数 speckit/openspec AI 工程化 累计节省
第 1 次 45 分钟 45 分钟(建立 context/) 0
第 2 次 45 分钟(人重新想) 15 分钟(部分复用) 30 分钟
第 5 次 45 分钟(还是要想) 5 分钟(大量复用) 130 分钟
第 10 次 45 分钟(…) 3 分钟(高度自动化) 315 分钟

关键差异

  • 知识位置:speckit 在人脑(每次想),AI 工程化在 context/+skill/
  • 新人上手:speckit 依赖老人传授,AI 工程化第一天就能用
  • 边际成本:speckit 恒定,AI 工程化递减

深度对比:为什么传统 SDD 工具不够用

前面 2.5 节从”问题-方案”角度概述了 AI 工程化的优势,本节深入分析 speckit 和 openspec 的技术设计缺陷,帮助理解为什么需要新的解决方案。

speckit 的核心缺陷

问题 1:流程过于理想化

speckit 的 Constitution → Specify → Plan → Tasks → Implement 流程假设:

  • 需求是清晰的
  • 可以一次性规划
  • 按阶段线性推进

但企业真实场景是:

  • 需求动态变化
  • 多方并行博弈
  • 持续扯皮调整

问题 2:无法处理”考古”需求

speckit 从零开始定义,但真实开发必须”考古”:

  • 历史坑点在哪?
  • 现有能力有哪些?
  • 配置规范是什么?

问题 3:知识不会沉淀

1
2
3
每次执行:Constitution → Specify → Plan → Tasks → Implement

每次从头开始

缺失机制:

  • ❌ 实施过程中发现的坑不会被记录
  • ❌ 排查信息丢失
  • ❌ 下次遇到类似问题还得重新排查

问题 4:宪章系统的僵化

9 条不可变原则固然保证质量,但:

  • ✅ 适合标准化项目(Demo、开源库)
  • ❌ 不适合企业定制场景(历史债务、框架限制、合规要求)

openspec 的核心缺陷

问题 1:Delta 机制的理论美好与现实骨感

假设需求可以”提案化”,但企业真实场景是多线并行、动态调整、持续扯皮。

问题 2:Fail-Fast 的代价

理论上保证一致性,实际上成为阻塞点。人的认知窗口有限,很难手动解决复杂冲突。

问题 3:强依赖命名的脆弱性

产品、运营、研发对同一个需求有不同表述,命名不一致导致归档失败。

问题 4:Archive 只是”合并”,不是”学习”

1
2
3
4
5
6
F(CurrentSpec, DeltaSpec) → NewSpec

缺失的维度:
F(CurrentSpec, DeltaSpec, Context, Lessons) → NewSpec + Knowledge
↑ ↑
实施上下文 经验教训

共性问题:忽视人的现实工作模式

问题 1:忽视认知负担管理

两个工具都假设人能理解并遵循复杂流程、维护大量结构化文档、记住所有规范和约束。

但现实是:土办法最管用。工具应该适配人的工作模式,而不是强行改变它。

问题 2:忽视”执行过程”的价值

只关注”规范”和”结果”,忽视”过程”中的知识价值。

问题 3:忽视复利效应的关键性

1
2
3
4
5
传统工具:帮你"做事"
复合工程:帮你"越做越快"

传统工具:每次都是新的开始
AI 工程化:每次都站在上次的肩膀上

问题 4:Spec 详细程度的悖论

规范驱动开发有一个根本性的矛盾:

1
2
Spec 越详细 → 越接近代码本身 → 维护两份"代码"
Spec 越简略 → 越难指导 AI → 失去规范的意义

详细 Spec 的问题

  • 当 Spec 详细到可以精确指导 AI 生成代码时,它本身就变成了另一种形式的”代码”
  • 你需要同时维护 Spec 和 Code 两套产物,且要保持同步
  • 代码改了 Spec 要改,Spec 改了代码要改——双倍维护成本

AI 工程化的解法:不追求详细 Spec,而是分层概要 + 代码指针

1
2
3
4
5
6
7
8
9
10
11
12
AI 工程化的上下文组织:
├── 服务概要:这个服务做什么、边界在哪
├── 业务概要:核心业务流程、关键概念
├── 模块概要:模块职责、依赖关系
├── 接口概要:对外接口、调用方式
└── 代码指针:具体细节在 xxx/xxx.go 的 xxx 函数

不维护:
├── ❌ 详细的数据结构定义(代码里有)
├── ❌ 完整的接口参数说明(代码里有)
├── ❌ 具体的实现逻辑描述(代码里有)
└── ❌ 任何可以从代码直接获取的信息

核心原则:概要层帮助 AI 快速定位,细节层直接读代码。避免维护一份”像代码一样详细的 Spec 文档”——那只是换了个格式的代码,没有降低复杂度,反而增加了同步成本。


进阶能力:插件、Skill、MCP 的融合

对于大多数研发同学来说,可能还停留在 speckit、openspec 这类规范驱动工具的认知上。但 AI 工程化把更多能力融合在了一起:

Skill:可复用的能力单元

Skill 是过程执行层的基本单元,每个 Skill 负责一个具体的固定流程任务:

1
2
3
4
5
6
7
.codebuddy/skills/
├── req-create/ # 需求创建
│ ├── SKILL.md # 技能定义
│ └── templates/ # 模板资源
├── design-create/ # 方案创建
├── workspace-setup/ # 环境搭建
└── code-commit/ # 代码提交

Skill 的特点:

  • 单一职责:每个 Skill 只做一件事
  • 可复用:多个流程可以调用同一个 Skill
  • 可组合:复杂流程由多个 Skill 组合完成
  • 可演进:Skill 可以独立升级,不影响其他部分

Agent:自主决策层

Agent 负责意图识别、流程路由、上下文管理:

1
2
3
4
5
6
.codebuddy/agents/
├── phase-router.md # 阶段路由,意图识别
├── requirement-manager.md # 需求全生命周期管理
├── design-manager.md # 方案全生命周期管理
├── implementation-executor.md # 开发实施执行
└── experience-depositor.md # 经验沉淀(独立上下文)

Agent 与 Skill 的分工:

  • Agent:决定”做什么”
  • Skill:执行”怎么做”

多 Agent 协作:从上下文窗口爆满到高效分工

在实践 AI 工程化的过程中,我们遇到了一个关键瓶颈:上下文窗口爆满

问题的根源

早期使用 speckit 等工具时,最痛苦的体验是:

1
2
3
4
5
6
7
8
执行复杂需求时:
├── 加载业务背景(5000 tokens)
├── 加载技术上下文(8000 tokens)
├── 加载历史经验(3000 tokens)
├── 当前对话记录(持续增长)
└── ...

窗口频繁爆满 → 强制截断 → 丢失关键上下文 → AI 行为异常

Anthropic 工程团队精准描述了这个问题:

“想象一个软件项目由轮班工程师负责,每个新工程师到来时对上一班发生的事情毫无记忆。”

解决方案:Subagent 架构

借鉴 Anthropic 的双 Agent 架构思想,我们设计了 主 Agent + Subagent 的协作模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
传统模式(单一 Agent):
用户输入 → 一个大 Agent 处理所有事情 → 上下文持续膨胀 → 窗口爆满 → 任务失败

Subagent 模式:
用户输入 → 主 Agent(决策层)

意图识别 + 任务拆分

┌─────────┼─────────┐
↓ ↓ ↓
Subagent1 Subagent2 Subagent3
(独立窗口) (独立窗口) (独立窗口)
└─────────┼─────────┘

结果汇总 → 主 Agent 继续

核心优势

特性 说明
独立上下文窗口 每个 Subagent 有自己的上下文空间,不会互相污染
专注单一任务 每个 Subagent 只处理一件事,认知负担小
并行执行 多个 Subagent 可以同时工作,提升效率
结构化状态传递 通过文件传递结果,而非依赖”记忆”

效果对比

指标 单 Agent 模式 Subagent 模式
窗口爆满频率 70%(复杂需求几乎必爆) 5%(偶发于极端场景)
任务完成率 60%(经常中途失败) 95%(可靠完成)
上下文利用效率 30%(大量冗余信息) 80%(按需加载)

状态传递机制

Subagent 之间不共享上下文窗口,通过结构化状态文件保证信息传递:

1
2
3
4
5
6
7
8
9
核心文件:
├── requirements/INDEX.md # 需求状态索引
├── requirements/in-progress/ # 进行中的需求详情
└── context/session/ # 会话级临时上下文

工作流程:
1. Subagent 启动时:读取状态文件,快速理解当前状态
2. Subagent 执行中:专注自己的任务
3. Subagent 结束时:更新状态文件,提交"干净的交接"

核心原则:每个 Subagent 只完成一个”原子任务”,不是一个工程师连续工作 48 小时,而是轮班工程师每人 4 小时但交接清晰。

与 speckit 的本质差异

1
2
3
4
5
6
7
speckit:依赖"一个 Agent 记住所有事情"
Constitution → Specify → Plan → Tasks → Implement
上下文持续累积,到 Implement 阶段时窗口已经很满

Subagent:依赖"结构化的状态传递"
每个阶段独立的 Subagent,独立的上下文窗口
状态通过文件传递,而非上下文累积

前者是人脑模型(记忆有限),后者是团队协作模型(交接清晰)。

MCP:外部系统集成

MCP(Model Context Protocol)让 AI 能够直接对接外部系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
基础集成:
├── TAPD MCP(需求管理)
│ ├── 自动获取需求详情
│ ├── 关联相关需求
│ └── 更新需求状态
├── 工蜂 MCP(代码管理)
│ ├── 自动创建分支
│ ├── 提交代码变更
│ └── 创建合并请求
└── iWiki MCP(知识管理)
├── 检索历史技术方案
├── 获取业务背景文档
└── 关联团队知识库

MCP 的价值:

  • 自动化操作:不需要人手动操作 TAPD、工蜂、iWiki
  • 信息同步:AI 自动获取最新信息
  • 减少错误:避免手动操作的遗漏和错误

插件市场:能力的分发与升级

工具包以插件形式发布到插件市场:

  • 版本管理:每个版本独立,可回滚
  • 灵活升级:update 即可获得最新能力
  • 团队共享:团队成员共享同一套能力集

与脚手架的配合:

  • 脚手架存放业务上下文(随项目迭代)
  • 工具包提供通用能力(独立版本管理)

落地策略:从零到一的实践路径

前面各节从理论角度阐述了 AI 工程化的设计,本节聚焦具体怎么落地。以 2.5 节提到的”商品发放”场景为例,展示完整的实践路径。

冷启动:新项目接入

冷启动是 AI 工程化的核心优势之一。传统工具的知识在人脑,需要传授;AI 工程化的知识在工具链里,开箱即用。

步骤 1:安装 AgentProjectKit 插件(5 分钟)

首先需要添加插件市场并安装 AgentProjectKit:

1
2
3
4
5
# 安装 AgentProjectKit 插件
/plugin install agent-project-kit@tmap-codebuddy-plugin

# 验证安装
/plugin list

步骤 2:脚手架初始化(15 分钟)

1
2
# 初始化 AI 工程项目
/agent-project-kit:init-project

命令会自动完成:

  1. 克隆 AI 工程项目模板
  2. 引导配置项目基本信息(业务线名称、定位等)
  3. 初始化 AGENTS.md 项目记忆文件

步骤 3:加载服务上下文(30 分钟)

这是冷启动的关键步骤。/agent-project-kit:load-service 命令实现项目级别长期记忆初始化

1
2
3
4
# 加载相关服务,生成技术总结
/agent-project-kit:load-service
/agent-project-kit:load-service
/agent-project-kit:load-service

/agent-project-kit:load-service 的工作流程

1
2
3
4
5
6
7
8
9
10
11
12
13
用户执行 /agent-project-kit:load-service 

1. 克隆服务代码到 workspace/loadservice/ 目录

2. 分析服务架构、业务逻辑、API 接口:
- 业务定位、核心职责、技术栈
- 依赖关系、对外接口、数据模型
- 关键模块、配置要点、常见坑点

3. 生成技术文档到 context/tech/services/ 目录

结果:AI 获得该服务的完整上下文,后续任何涉及该服务的需求
都会自动加载这份上下文

为什么这很重要?

  • speckit/openspec:每次需要描述服务背景时,依赖人记住并手动描述
  • AI 工程化:一次 /agent-project-kit:load-service,永久复用,新成员也能立即获得”老兵视角”

步骤 4:开始需求研发

使用 /req-dev 命令开始你的第一个需求:

1
2
3
4
5
# 创建新需求
/req-dev 实现用户认证功能

# 或者指定已有需求继续工作
/req-dev REQ-001

工具包自带常用研发工具集成(MCP),开箱即用:

MCP 集成 功能 传统方式
TAPD MCP 自动获取需求详情、关联需求、更新状态 手动复制粘贴需求内容
工蜂 MCP 自动创建分支、提交代码、创建 MR 手动操作 Git 命令
iWiki MCP 检索历史技术方案、业务背景文档、团队知识库 手动搜索翻阅 Wiki 页面

MCP 集成的价值

  • 不是”又多了几个工具要学”,而是”AI 自动帮你操作这些系统”
  • 需求来了 → AI 自动从 TAPD 拉取详情 → 自动检索 iWiki 历史方案 → 自动生成方案
  • 人只需要 review 和确认

冷启动效果对比

阶段 speckit/openspec AI 工程化
学习工具 1-2 小时 5 分钟(插件安装)
初始化项目 手动搭建 15 分钟(/agent-project-kit:init-project)
了解服务架构 2-4 小时(需老人讲解) 30 分钟(/agent-project-kit:load-service 自动分析)
准备总计 4-7 小时 50 分钟
首次工作质量 不稳定(依赖记忆和传授) 稳定(context/ 提供完整信息)

关键差异

  • speckit/openspec:工具是”空壳”,知识在人脑,需要传授
  • AI 工程化:工具包含”知识”(context/+MCP),新人第一天就能高质量工作

持续迭代:知识的复利沉淀

第 1 个需求:建立 context/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
需求:实现 12 月活动的商品发放

执行过程中发现问题:
- Apollo 配置有特殊格式要求
- 雅典娜 20 种商品类型,配置方式各不同
- 钱包选择要区分虚拟/实物
- 敏感 接口有合规要求

知识沉淀:
人:"@agent,记住这些坑"

自动生成/更新 context/:
├── context/tech/Apollo配置规范.md
├── context/experience/雅典娜配置注意事项.md
├── context/experience/商品发放历史问题.md
└── context/business/跨团队协作.md

耗时:45 分钟(首次建立)

第 2 个需求:复用 context/

1
2
3
4
5
6
7
8
9
10
11
12
需求:实现春节活动的商品发放(类似场景)

AI 自动加载 context/,自动提醒历史坑点
人 review:"嗯,都考虑到了" ✓

新发现:春节活动需要限制地域

"@agent,记住地域限制"

context/ 自动更新

耗时:15 分钟(大量复用,少量新增)

第 6-10 个需求:封装为 skill

1
2
3
4
5
6
7
8
9
10
11
12
当 context/ 足够完善,封装为能力层:

skill/product-distribution-helper:
- 自动加载所有商品发放相关 context/
- 自动校验 Apollo 配置格式
- 自动检查雅典娜商品类型
- 自动提醒钱包选择、地域限制
- 自动生成监控配置

使用:/implement-product-distribution → 一键完成

耗时:3 分钟(高度自动化)

团队协作:知识的共享与传承

新成员第一天

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
speckit/openspec:
1. 学习工具用法(1-2 小时)
2. 了解服务架构(需老人讲解,2-4 小时)
3. 熟悉流程规范(1 小时)
4. 开始工作:依赖记忆和老人传授,首次质量不稳定
总计:4-7 小时准备 + 不稳定的首次质量

AI 工程化:
1. 脚手架初始化(15 分钟)
2. 工具包安装(5 分钟)
3. 立即开始工作:
- context/ 提供服务上下文
- MCP 自动集成 TAPD/工蜂/Apollo
- cmd/skill 引导完成任务
- 首次就能高质量完成
总计:20 分钟准备 + 稳定的首次质量

团队效应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
5 人团队,各做 2 次商品发放:

speckit:5 人 × 2 次 × 45 分钟 = 450 分钟

AI 工程化:
第 1 人第 1 次:45 分钟 → context/ 建立
第 1 人第 2 次:15 分钟
第 2 人第 1 次:15 分钟(复用第 1 人 context/)
第 2 人第 2 次:10 分钟
...
第 5 人第 2 次:5 分钟

总计:126 分钟
节省:450 - 126 = 324 分钟(72%)

未来展望:工具终将消失

第 4.2 节讨论了极简主义如何影响当前设计,本节从行业发展趋势角度展望工具的演进方向。

模型吞噬脚手架

随着模型能力的提升,很多外部辅助会被模型内化:

1
2
3
4
5
Opus 4.1 需要的东西,Sonnet 4.5 已经内化了

系统提示可以删 2000 个 tokens

工具每周都在变简单

这意味着什么? 今天我们在 context/、Skill、Agent 中编码的知识和流程,未来可能直接被模型”学会”。AI 工程化的架构设计需要为这种迁移做好准备——当某个 Skill 不再需要时,能够平滑删除而不影响整体。

多 Agent 架构的演进方向

从”工具调用”到”团队协作”

当前的 AI 辅助编程主要是”人调用 AI”模式:

1
人 → 发指令 → AI 执行 → 人检查 → 人发下一个指令

Subagent 架构开启了新的可能:

1
人 → 设定目标 → 主 Agent 拆解 → 多个 Subagent 协作 → 主 Agent 汇总 → 人验收

未来可能演进为:

1
人 → 设定目标 → Agent 团队自主协作数小时/数天 → 人验收最终结果

长时间运行 Agent 的关键挑战

Anthropic 的实践揭示了几个核心挑战:

挑战 当前解法 未来方向
上下文窗口限制 Subagent 分解 + 状态文件传递 更高效的 compaction + 更智能的上下文选择
任务连续性 结构化状态文件(JSON/Markdown) 更丰富的”工作记忆”机制
质量保证 端到端测试 + 人工 Review 专门的 QA Agent + 自动化验收
错误恢复 状态文件支持断点续做 更智能的错误分析和自动修复

Agent 专门化 vs 通用化的权衡

一个开放问题:应该用一个强大的通用 Agent,还是多个专门化的 Agent?

1
2
3
4
5
6
7
8
9
通用 Agent 路线:
├── 优势:简单,不需要协调
├── 劣势:上下文负担重,需要"知道所有事情"
└── 适合:简单任务、短时间任务

专门化 Agent 路线:
├── 优势:每个 Agent 更专精,上下文更精简
├── 劣势:需要协调机制,状态传递成本
└── 适合:复杂任务、长时间任务、团队协作场景

我们的选择:对于企业级复杂场景,专门化 Agent 更适合。原因是:

  1. 企业场景本身就是”团队协作”,Agent 架构应该反映这一现实
  2. 上下文窗口是硬约束,专门化可以更高效利用
  3. 专门化 Agent 更容易独立迭代和优化

与人类团队的类比

最好的 Agent 架构设计,灵感来自人类高效团队的工作方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
人类团队:
├── 产品经理:理解需求、拆解任务
├── 技术 Leader:设计方案、分配工作
├── 开发工程师:实现功能
├── 测试工程师:验证质量
└── 每个人有自己的专业领域,通过"会议"和"文档"协调

Agent 团队:
├── phase-router:理解意图、路由任务
├── design-manager:设计方案
├── implementation-executor:实现功能
├── test-agent(计划中):验证质量
└── 每个 Agent 有自己的专业 Prompt,通过"状态文件"协调

Anthropic 工程团队的洞察:”这些实践的灵感来自于了解高效软件工程师每天做什么。”

当前范式:Claude 做一步,你检查,批准,它继续。

未来范式:

1
2
3
4
5
当模型可以自主工作几天甚至几周:
早上:"我想完成 X"
晚上:看结果

中间的过程?它自己处理。

人的角色从”操作者”变成”监督者”,从”指令发出者”变成”目标设定者”。

AI 工程化的定位:在这个转型过程中,AI 工程化是”过渡期基础设施”——帮助团队在当前阶段高效工作,同时为未来的全自动化积累知识和经验。

研发工作的本质变化

AI 工程化不只是引入新工具,而是重新定义了研发的工作方式。这种变化已经在 AI 技术最前沿的团队中发生。

首先要避免的认知误区

工程师在使用 AI 时最常见的两种误解:

误区 表现 结果
AI 是”银弹” 期望 AI 自动理解需求、写出完美代码 过度依赖,缺乏监督,质量不稳定
AI 是”思考替代品” 把 AI 当作可以替代人类思考的工具 不理解业务,一直捣鼓 AI,适得其反

正确的定位是:AI 是强大的执行工具,但决策权和判断力必须留在人手中。

来自 OpenAI 与 Anthropic 的实践经验

理解 AI 的真实能力边界

参考 OpenAI 团队使用 Codex 构建 Sora 安卓应用的经验,将 AI 定位为**”一位新入职的资深工程师”**:

需要人类指导 表现卓越
无法推断隐性上下文(团队偏好、内部规范) 快速理解大型代码库,精通主流编程语言
缺乏真实用户体感(无法感知”滚动不顺畅”) 热衷于编写单元测试,能根据 CI 日志修复问题
深层架构判断力不足(本能是”让功能跑起来”) 支持大规模并行,同时探索多种方案

三步协作工作流(借鉴 OpenAI 与 Anthropic 经验):

阶段 人的职责 AI 的职责
奠定基石 定义架构、编写范例代码、设定标准 学习并遵循
共同规划 校准理解、确认方案 总结现状、生成设计文档
执行交付 架构把关、质量审查 编码实现、测试修复

Anthropic 内部调查数据(2025年8月,132名工程师,20万条使用记录):

  • 工程师在 60% 的工作中使用 AI,实现 50% 的生产力提升,年同比增长 2-3 倍
  • 27% 的 AI 辅助工作是原本不会完成的任务(如交互式仪表板、探索性工作)
  • 工程师倾向于委托易于验证、定义明确、代码质量不关键、重复无聊的任务

“我可以非常胜任前端、事务性数据库的工作…而以前我会害怕触碰这些东西。” —— 后端工程师

“我以为我真的很享受编写代码,但实际上我只是享受编写代码带来的结果。” —— 高级工程师

核心理念:寻找 AI 的”舒适区”

工程师的核心工作之一,已经从纯粹的编码转变为识别 AI 的能力边界,并将复杂任务转化为落入 AI “舒适区”内的子任务

  • 低标准、高容错场景:任务对精确度要求不高,容忍多次失败。AI 尝试 N 次只要一次成功,就是显著提效
  • 迭代式开发场景:形成”AI 初步实现 → 人验证修正 → 快速反馈”的闭环,不追求一次完美

工作模式的具体变化

工作内容的迁移

工作环节 传统模式 AI 工程化模式 角色变化
需求理解 反复阅读文档、追问产品 Agent 自动加载 context/,主动提示 信息收集者 → 信息确认者
方案设计 从零构思、翻阅历史代码 基于模板生成,AI 提示已知风险 方案起草者 → 方案审核者
代码实现 逐行编写、查文档、调试 AI 生成初版,人 review 调整 代码生产者 → 代码把关者
知识沉淀 写文档(经常忘记) /optimize-flow 即时沉淀 文档维护者 → 经验触发者

时间分配的重构

1
2
3
4
5
传统研发:                         AI 工程化后:
├── 40% 信息收集 ├── 10% 信息确认
├── 30% 重复劳动 ├── 10% 结果审核
├── 20% 核心决策 → ├── 50% 核心决策
└── 10% 知识沉淀 └── 30% 知识沉淀

一个具体的对比——以”商品发放需求”为例:

1
2
3
4
5
6
7
8
传统模式的一天:                              AI 工程化模式的一天:
09:00-10:30 阅读需求文档,追问产品 09:00-09:30 /req-dev,确认需求边界
10:30-12:00 翻阅历史代码,理解逻辑 09:30-10:30 review AI 方案,调整决策点
14:00-15:30 询问老人"以前怎么做" 10:30-12:00 review AI 代码,优化核心逻辑
15:30-18:00 写代码,边写边查文档 14:00-15:00 AI 辅助测试,修复问题
18:00-19:00 遇到配置问题,排查 15:00-15:30 /optimize-flow 沉淀经验
19:00-20:00 继续写代码 15:30-17:00 处理下一个需求
产出:完成 60%,知识留在脑子里 产出:完成 100%,经验沉淀到 context/

能力要求的升级

能力维度 传统要求 AI 工程化要求
编码能力 熟练编写各类代码 能判断 AI 生成代码的质量和风险
知识储备 记住各种细节和坑点 知道如何组织知识让 AI 能用
问题解决 自己动手排查 会描述问题让 AI 辅助分析
效率提升 写更多代码、加更多班 设计更好的 Skill、沉淀更多经验

新的核心竞争力体现为三种能力:

  1. 系统理解能力:AI 能实现功能,但只有人能判断它是否以正确方式融入系统
  2. AI 协作能力:设计上下文、拆解计划、通过反馈循环持续优化
  3. 设计质量标准:当”写出能工作的代码”门槛降低,架构设计和交付质量成为区分标准

监督悖论:有效使用 AI 需要监督能力,而监督能力可能因过度依赖 AI 而退化。Anthropic 的一些工程师故意在没有 AI 的情况下练习以”保持敏锐”。

本质洞察

黄仁勋有一个精准的判断:**AI 改变的是”任务”,而非”职业”**。

  • 被 AI 接管的任务:信息检索、样板代码、格式化、重复配置
  • 人依然主导的核心:系统设计、架构决策、质量判断、创新突破

AI 工程化的价值,就是让这种”任务迁移”在团队中系统化落地——通过 context/ 让信息检索自动化,通过 Skill 让重复流程标准化,通过经验沉淀让知识持续复利。

最终目标:让研发把时间花在”只有人能做的事”上,而不是”AI 也能做的事”上。

工具隐形化:从”使用工具”到”完成工作”

工具消失的含义:不是工具不存在了,而是工具变得如此无缝,你感受不到它的存在。

1
2
就像现在你用搜索引擎,不会想"我在使用一个信息检索系统"。
你只是在找答案。工具隐形了。

隐形化的三个层次

层次一:操作隐形——从”记住命令”到”表达意图”

1
2
3
4
5
6
7
8
9
10
过去:记住 20 个命令,选择正确的那个
├── /speckit.constitution
├── /speckit.specify
├── /speckit.plan
├── /speckit.tasks
└── ...

现在:只说你要什么
├── "/req-dev 实现商品发放" → Agent 自动判断是创建还是继续
└── 不需要知道底层调用了哪些 Skill

层次二:知识隐形——从”想起经验”到”系统提醒”

1
2
3
4
5
6
7
8
9
过去:做需求时,人要想起历史上有什么坑
├── "上次商品发放好像有个钱包问题..."
├── "Apollo 配置格式是什么来着..."
└── 认知负担在人身上

现在:experience-index 自动检索,主动提醒
├── "检测到商品发放场景,已加载相关经验..."
├── "风险提示:注意钱包类型匹配"
└── 知识在系统里,人只需确认

层次三:流程隐形——从”遵循步骤”到”自然完成”

1
2
3
4
5
6
7
8
9
过去:严格按 Constitution → Specify → Plan → Tasks → Implement 执行
├── 人要知道"现在该执行哪个阶段"
├── 人要判断"前置条件是否满足"
└── 流程感知在人身上

现在:Agent 自主决策流程路由
├── 人说"继续做 REQ-001"
├── phase-router 自动判断当前阶段和下一步
└── 人感受到的是"工作在推进",而非"在执行流程"

AI 工程化的隐形化进度

维度 当前状态 目标状态
命令入口 ✅ 2 个命令覆盖全流程 自然语言直接触发
上下文加载 ✅ experience-index 自动检索 完全无感知加载
阶段流转 ✅ phase-router 自动路由 Agent 自主推进多步
经验沉淀 🔄 需要 /optimize-flow 触发 自动识别并沉淀
跨会话连续性 🔄 依赖状态文件 无缝断点续做

隐形化的终极形态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
今天:
人:"我要做一个商品发放需求"
AI:执行一步,等待确认
人:确认,继续
AI:执行下一步,等待确认
...

明天:
人:"我要做一个商品发放需求"
AI:分析、设计、实现、测试、提交 PR
人:Review 最终结果

后天:
人:(在 TAPD 创建需求单)
AI:(自动感知、自动完成、自动提交 Review)
人:(只在关键决策点介入)

最后一步:你不再”使用”工具,你只是在思考业务问题,而工具已经把代码写好了。

写在最后:从第一性原理出发

回顾这段历程,我最大的收获是:不要为了用工具而用工具

speckit 和 openspec 都是优秀的工具,它们定义的流程、模板、检查清单都很有价值。但正如 2.5 节(AI 工程化如何破局)的对比所示,它们解决的是”规范化”问题,而企业真实场景的核心问题是:

  1. 上下文缺失:AI 看不到历史经验、业务边界、配置规范
  2. 知识不沉淀:每次都从头开始,边际成本恒定
  3. 范围太窄:只管单个仓库,无法覆盖跨服务、跨系统的复杂场景

AI 工程化试图解决这些问题:

1
2
3
上下文工程 → 让 AI 自动获取完整信息
复合工程 → 让每次实践都降低下次成本
项目级方案 → 管理所有仓库和外部系统

核心思路

1
2
3
4
5
6
7
8
9
能够落地的最高效流程 → 已存在于高效的人的行为过程中

把高效流程 AI 化 → 推广到全团队应用

细节流程在具体业务线中迭代 → 自定义探索

实践中发现问题 → 提取可复用信息 → AI 工程化融入工具

下次通用场景使用时可复用

最后想说的是

AI 工程化不是要替代 speckit 或 openspec,而是在它们的基础上,融合上下文工程、复合工程、插件市场、MCP 集成等能力,形成一套更适合企业复杂场景的解决方案。

如果你也在探索 AI 辅助研发,希望这篇文章能给你一些启发:

  1. 从真实工作场景出发,而不是从工具出发
  2. 把知识编码进工具,而不是只写文档
  3. 追求边际成本递减,而不是固定成本
  4. 让工具适配人,而不是让人适配工具

工具的终极形态是消失。在那一天到来之前,我们要做的是让工具越来越”懂”我们的工作,越来越”记得”我们的经验,越来越”自然”地融入我们的日常。

这就是 AI 工程化的意义所在。

参考资料

[转载] 一文吃透AIGC、Agent、MCP的概念和关系

作者 wyanassert
2025年12月31日 17:48

Title: 彻底爆了!一文吃透AIGC、Agent、MCP的概念和关系-腾讯云开发者社区-腾讯云

原文地址

导语: 近年来,人工智能 领域涌现出许多新概念和新技术,其中AIGC、MCP和 Agent 成为了业界和学术界的热门话题。本文将深入浅出地介绍这三个概念,帮助读者全面理解它们的内涵、区别与联系,以及在实际应用中的价值。

AIGC


AIGC,全称为 AI Generated Content,意为“人工智能生成内容”。它指的是利用人工智能技术(尤其是大模型,如GPT、Stable Diffusion 等)自动生成文本、图片、音频、视频等多种内容的过程。2022 年 11 月 30 日,OpenAI 的 ChatGPT 正式上线(基于 GPT-3.5),引爆了 AIGC 热潮。

Image 1

多模态技术

  • 单模态: 只处理一种类型的数据,比如只处理文本(如GPT-3.5)、只处理图像(如图像识别模型)。
  • 多模态: 能够同时处理两种及以上类型的数据。例如,既能理解图片内容,又能理解文本描述,甚至还能结合音频、视频等信息进行综合分析和生成。对应的场景有。
场景 主流模型
文生图片 DALL-E(OpenAI)、Imagen(Google)、Stable Diffusion(Stability AI)、混元文生图(腾讯)等
文生视频 Sora(OpenAI)、Stable Video Diffusion(Stability AI)
图生文(图片理解) GPT-4V(OpenAI)、Gemini(Google)、Qwen-VL(阿里)
图文生视频 Runway Gen-2(Runway AI)、Stable Video Diffusion(Stability AI)
视频生文(视频理解) Gemini 1.5 / Gemini Pro Vision(Google)

RAG 技术

RAG(Retrieval-Augmented Generation,检索增强生成) 技术,是一种将信息检索(IR) 与大型语言模型(LLM) 的文本生成能力相结合的人工智能框架。其核心思想是:当 LLM 需要回答一个问题或生成文本时,不是仅依赖其内部训练时学到的知识,而是先从一个外部知识库中检索出相关的信息片段,然后将这些检索到的信息与原始问题/指令一起提供给LLM,让LLM基于这些最新、最相关的上下文信息来生成更准确、更可靠、更少幻觉的答案。

大型语言模型虽然拥有海量的知识和强大的语言理解与生成能力,但也存在一些关键限制:

  1. 知识局限性/过时性: LLM 的知识主要来源于其训练数据截止日期之前的信息。对于训练数据之后发生的事件、新研究、最新数据或特定领域的细节,LLM 可能不知道或给出过时的信息。
  2. 幻觉: 当 LLM 遇到其知识库中不明确或不存在的信息时,它可能会“捏造”出看似合理但事实上错误或不存在的答案。
  3. 缺乏来源/可验证性: LLM 通常无法提供其生成答案的具体来源依据,使得验证答案的准确性变得困难。
  4. 特定领域知识不足: 通用 LLM 可能缺乏对某个特定公司、组织或个人私有知识库的深入了解。

RAG 正是为了解决这些问题而诞生的。

Image 2: 图片

智能体 Agent


“智能体”(Agent)在计算机科学和人工智能领域指的是一个能够感知环境、自主决策并采取行动以实现特定目标的实体或系统。 它可以是软件程序、机器人硬件,甚至是生物实体(如人类或动物),但在 AI 领域通常指软件智能体。

Agent 和 AIGC 最大的区别:

  1. AIGC 主要以生成式任务为主,而 Agent 是可以通过自主决策能力完成更多通用任务的智能系统。
  2. 常见的 AIGC 系统(文生文,文生图)的核心就是一个生成模型,而 Agent 是一个集Function Call 模型(下文会详细介绍)、软件工程 于一体的复杂的系统,需要处理模型和外界的信息交互。
  3. Agent 可以集成 AIGC 能力完成某些特定的任务,也就是 AIGC 可以是 Agent 系统里面的一个子模块。

Agent 最大的特点是,借助Function Call 模型,可以自主决策使用外接的一些工具来完成特定的任务。

Function Call 模型

什么是 Fucntion Call 模型

Function Calling(函数调用) 是大型语言模型的关键技术。前面有提到过RAG技术是为了解决模型无法和外接数据交互的问题,但是RAG的局限在于只赋予了模型检索数据的能力,而Function Calling允许模型理解用户请求中的潜在意图,并自动生成结构化参数来调用外部任何函数/工具,从而突破纯文本生成的限制,实现与真实世界的交互,比如可以调用查天气、发邮件、数学计算等工具。

Function Call 模型最早由 OpenAI 在 2023 年 6 月 13 正式提出并发布,首次在 GPT-4 模型上实现了 Function Calling 能力。OpenAI 作为大语言模型的领路人,其发布的模型的 API 协议都会行业标准,后面国内外新发布模型都会按照 OpenAI 的协议作为标准实现。截止目前,支持 Fucntion Calling 能力的主流模型如下表:

模型 开发者 首次支持 Function Calling 时间
GPT-4 OpenAI 2023/06/13
Claude-3 Anthropic 2024/03/04
Gemini-2.0 Google 2024/12
DeepSeek-R1 深度求索公司 2024/02/12

除了上面的知名度高的模型,还有一些其他开源或闭源模型也支持了 Fucntion Calling 能力,但是截止目前为止,GPT-4 仍然是公认的 Fucntion Calling 能力最强的模型。

工作原理:三步闭环流程

Function Call 模型的工作流程如下图:

Image 3

步骤详解:

1、定义函数(开发者预设)

向 LLM 描述函数的用途、输入参数格式(JSON Schema),例如:

1
2
3
4
5
6
7
8
9
10
11
12
{
"name": "get_current_weather",
"description": "获取指定城市的天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"},
"unit": {"enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}
  • name 是工具名称
  • description 是这个工具的用途
  • parameters 是这个工具需要的输入参数

2、模型决策与生成参数

用户提问:“北京今天需要带伞吗?”

→ LLM 识别意图需调用 get_current_weather

→ 生成结构化参数:

1
{"city": "北京", "unit": "celsius"}

3、执行函数 & 返回结果

  • 程序调用天气API,获真实数据:{"temp": 25, "rain_prob": 30%}
  • 将结果交回LLM,生成最终回复:“北京今天25°C,降水概率30%,建议带伞。”

核心优势:LLM 的“手和眼睛”

能力 传统LLM 支持Function Calling的LLM
获取实时信息 ❌ 依赖训练数据 ✅ 调用搜索引擎/数据库
执行精准计算 ❌ 常出错(如复杂数学) ✅ 调用计算器/Python
操作外部系统 ❌ 无法执行 ✅ 发送邮件/控制智能家居
返回结构化数据 ❌ 文本难解析 ✅ 输出标准JSON

Agent

OpenAI 发布 Function Call 模型后,Agent 才开始发展。而 Agent 真正进入到公众视野,被大家广泛关注的事件是 2025年4月 Manus 发布了通用智能体产品,引入了Computer Use 和 Browser Use,首次展现出智能体的强大能力。

Agent 的工作流程

实际上上文提到的 Function Call 模型的工作流程图,已经算是一个 Agent 的雏形了,不同点是,Agent 完成一次任务,实际上会循环调用模型,可能会调用多次 Function Calling,每次需要调用什么工具,完全由模型决策。一个最简单的 Agent 调用流程图如下:

Image 4

比如有一个出行规划的智能体,这个智能体配置有天气查询、驾车规划、公共交通规划、骑行规划、步行规划等工具。用户询问“我在深圳,5月1日想去自驾去北京旅行,帮我规划一下出行方案。”,一个可能的具体的执行流程如下:

Image 5

怎么开发一个自己的 Agent

最简单的方法就是把 Agent 的提示词(prompt)、工具、llm 调用,工具执行都硬编码到代码中,这样确实可以快速开发一个特定功能的 Agent。这样的实现会带来一些问题:

  1. 提示词(prompt),工具需要调整的时候,需要改配置或者代码,灵活度不够高;
  2. 如果要开发一个新功能的 Agent,整体代码可能需要重新实现一遍。

为了解决这一系列的问题,cozedify腾讯云智能体开发平台等智能体开发平台相继出现。借助这些平台,开发者甚至不需要会编程,不需要 服务器 资源,就可以开发一个自己的Agent,Agent 的整个执行流程完全由平台在云上执行。智能体开发平台的架构一般包含 插件配置、Agent 配置、Agent 执行模块、插件执行模块,发布模块。

Image 6

  • 插件配置:所有 Agent 的工具都统一管理起来,而不是散落在各个 Agent 内部,这样可以做到工具的复用。一般平台会自带一些插件,比如网络搜索、文件上传、AIGC 工具等,同时也支持开发者添加自己的自定义插件。
  • Agent 配置:配置 Agent 的 提示词 (prompt),使用的模型,以及选择插件配置中的一批工具提供给模型做选择。
  • 发布配置:开发者把自己的 Agent 开发调试稳定以后,发布成稳定版本就可以提供给用户使用了。
  • 插件执行:执行某个特定的插件,返回结果。
  • Agent 执行:实现通用的 Agent 执行流程,调用插件执行模块实现工具调用。

下图是用腾讯云智能体开发平台,开发一个简单的 Agent 配置和实际执行效果图。

Image 7

Multi-Agent

除了使用智能体开发平台快速开发自己的 Agent 以外,还可以使用 sdk 的方式进行开发。2025 年 3 月 11 日,OpenAI 重磅发布 OpenAI Agent SDK!AI 开发范式彻底颠覆!使用 sdk 可以快速配置一个自定义的 Agent 后执行,相比智能体开发平台,sdk 具有更高的灵活性和自主可控性。

同时,在 OpenAI Agent SDK 中,首次引入了 Mulit Agent 的概念。在此之前,通过智能体开发平台,我们开发出来的 Agent 都只是单 Agent。一个单 Agent 的能力有限,只能解决特定领域的一个任务,而一个复杂任务往往需要执行多个领域的任务才能完成。而 OpenAI Agent SDK 可以让开发者定义多个领域的 Agent,并且给这些 Agent 配置一些转交关系,允许某个 Agent 把特定的任务交给另外一个合适领域的 Agent 来执行,多个 Agent 之间协同和互动来完成一个复杂任务。

在 OpenAI Agent SDK 发布以后,以腾讯云智能体开发平台为代表的相关产品都相继支持了 Multi-Agent 模式。

Agent 的发展

Agent 目前的发展还处于一个较初期的阶段,但是发展速度很快。在一些垂直领域比如代码生成 Cursor / 腾讯云 AI 代码助手 CodeBuddy、广告营销等方向已经有了比较好的落地。而更通用的 Agent 目前除了看到 Manus 落地以外,还没看到其他比较好的应用模式落地。相信随着时间发展,会有越来越好用,越来越通用的 Agent 应用诞生。

MCP


什么是 MCP

MCP(Model Context Protocol,模型上下文协议)是由人工智能公司Anthropic2024 年 11 月 24 日正式发布并开源的协议标准。Anthropic 公司是由前 OpenAI 核心人员成立的人工智能公司,其发布的 Claude 系列模型是为数较少的可以和 GPT 系列抗衡的模型。

为什么需要 MCP

MCP 协议旨在解决大型语言模型(LLM)与外部数据源、工具间的集成难题,被比喻为“AI应用的USB-C接口“。通过标准化通信协议,将传统的“M×N集成问题”(即多个模型与多个数据源的点对点连接)转化为“M+N模式”,大幅降低开发成本。

Image 8

在 MCP 协议没有推出之前:

  1. 智能体开发平台需要单独的插件配置和插件执行模型,以屏蔽不通工具之间的协议差异,提供统一的接口给 Agent 使用;
  2. 开发者如果要增加自定义的工具,需要按照平台规定的 http 协议实现工具。并且不同的平台之间的协议可能不同;
  3. “M×N 问题”:每新增一个工具或模型,需重新开发全套接口,导致开发成本激增、系统脆弱;
  4. 功能割裂:AI 模型无法跨工具协作(如同时操作 Excel 和 数据库),用户需手动切换平台。

没有标准,整个行业生态很难有大的发展,所以 MCP 作为一种标准的出现,是 AI 发展的必然需求。

总结:MCP 如何重塑 AI 范式:

维度 传统模式 MCP 模式 变革价值
集成成本 每对接新工具需定制开发 一次开发,全网复用 开发效率提升 10 倍
功能范围 单一工具调用 多工具协同执行复杂任务链 AI 从“助手”升级为“执行者”
生态开放性 封闭式 API,厂商锁定 开源协议,社区共建工具库 催生“AI 应用商店”模式
安全可控性 API 密钥暴露风险 数据不离域,权限分级管控 满足企业级合规需求

MCP 的发展情况

MCP 自2024 年 11 月 24 日 发布以来,OpenAI、Google、微软、腾讯、阿里、百度等头部企业纷纷接入 MCP,推动其成为事实性行业标准。并且相继出现了 mcp.so 、mcpmarket 等超大体量的 MCP 服务提供商。国内的头部企业也相继加入 MCP 服务商的竞争中。在如此庞大的 MCP 市场下,开发者基本不需要开发自己的插件,直接使用 MCP 服务商的插件就可以直接开发大量 Agent。

同时很多头部企业,开始把自身原有的 API 业务开发成封装成 MCP 服务对外提供。比如:

  1. GitHub Copilot 提供 MCP 的方式生成代码;
  2. AWS 2025 年 6月推出开源工具 Amazon Serverless MCP Server,支持 Agent 直接操作云上资源,进行服务编排。
  3. 腾讯地图、高德地图、百度地图均发布 MCP Server,支持在 Agent 中使用丰富的地图资源。
  4. 腾讯云COS、百度网盘均已支持 MCP 协议的接入。

未来趋势:

  • 与 AIOS 融合: MCP 正成为 AI 操作系统(如华为鸿蒙 HMAF)的核心组件,实现跨设备智能调度;
  • 生态挑战: 大厂通过 MCP 构建“闭环生态”(如 阿里 集成高德地图),可能引发协议割裂,需推动跨平台协作标准。

MCP 不仅是技术协议,更是AI 生产力革命的基石——它让模型真正融入现实世界,成为人类工作的无缝延伸。

总结

整体上看,Agent 是在 AIGC、MCP 、大语言模型 LLM 等原子能力的基础上进行编排,以提供更复杂的 AI 应用。

[转载] AI coding 智能体设计

作者 wyanassert
2025年12月30日 21:20

原文地址

AI coding 智能体设计

理解 AI coding 智能体的设计,可以帮助开发者更好地使用 AI coding 工具,实现开发提效。

了解用户提示词预处理,帮助我们写出高效的用户提示词。例如:为什么在提示词中使用 @字符引入文件、目录作为上下文,可以减少会话轮次?如何自定义命令?

  • 了解智能体如何处理 MCP 扩展,如何解析 MCP 的 prompt 和 tool 能力,从而更好的进行 MCP 设计,为 AI coding 智能体提供子命令扩展和工具集扩展。
  • 了解 SubAgent 的实现,理解上下文隔离的意义,基于高内聚、低耦合原则进行智能体的模块化设计,降低系统复杂度。
  • 了解 MCP 工具调用的局限性,从而理解 Claude Code 推出 Skills、Code Execution with MCP 的动机和原理。
  • 为什么规约驱动开发(spec-driven development)成为 AI coding 的最佳实践?通过对开源项目 OpenSpec 的解读,了解规约驱动开发背后的奥秘和改进点。
    本文从分析 Gemini-CLI 源代码开始,解读 AI coding 工具的智能体设计。Claude Code 本身不开源,但是实现原理大同小异。

在分析 Gemini-CLI 过程中,特别感谢 Qwen Code 团队,他们的开源项目中的 openaiContentGenerator包提供了OpenAI API的兼容层,使用这个模块可以很容易将 Gemini-CLI 内置的谷歌认证和外部模型切换为公司内部模型。

Gemini-CLI 的用户提示词预处理

在 Gemini-CLI 中输入提示词,首先对输入的内容进行预处理。

  • 如果提示词的第一个字符是斜线(/),将提示词视为命令,执行特定操作,或者替换为预置提示词和大模型交互。
  • 如果提示词中包含 @字符+路径,检查 @字符后的路径是否存在,读取文件作为上下文,再发送给大模型。可减少不必要的模型会话。

内置命令

Gemini-CLI 的内置命令在 packages/cli/src/ui/commands/目录下定义。

  • 例如 clear 命令在文件 packages/cli/src/ui/commands/clearCommand.ts 中定义。

  • 内置命令可以执行特定操作。例如:/clear 命令用于重置对话、清空上下文。

内置命令可以使用预置用户提示词调用大模型完成相关任务。例如:/init 命令使用大模型分析工程代码创建 GEMINI.md 文件。

内置命令列表参见:docs/cli/commands.md

MCP Server 提供的提示词命令

MCP server 提供两种能力:工具和提示词。工具被拼装为模型上下文,而提示词则作为 Gemini-CLI 的扩展命令。

例如安装 mcp-server-commands命令行工具后,该工具通过
STDIO 协议提供 MCP 服务,在 ~/.gemini/settings.json 配置示例如下:

1
2
3
4
5
6
7
8
9
10
{
"mcpServers": {
"mcp-server-commands": {
"command": "npx",
"args": [
"mcp-server-commands"
]
}
}
}

在 Gemini-CLI 中输入斜线触发命令补全,可以看到新增的 run_command 命令,该命令有[MCP]标识和内置命令相区分:

1
2
3
4
5
6
╭─────────────────────────────────────────────────────────────────────────╮
│ > / │
╰─────────────────────────────────────────────────────────────────────────╯
run_command [MCP] Include command output in the prompt. This is effectively a user tool call.
clear Clear the screen and conversation history
compress Compresses the context by replacing it with a summary

扩展包提供的提示词命令

从 Gemini-CLI 的官方扩展市场下载扩展。扩展包安装在 ~/.gemini/extensions目录下,每个扩展下面的 commands/子目录提供扩展命令。

gemini-cli-security扩展为示例,安装命令如下:

1
2
$ gemini extensions install \
https://github.com/gemini-cli-extensions/security

安装后重启 Gemini-CLI,执行命令 /extensions list查看安装的扩展:

1
2
3
4
5
> /extensions list

Installed extensions:
gemini-cli-security (v0.3.0) - active

在 Gemini-CLI 中输入斜线触发命令补全,可以看到由扩展引入的新命令命令,这些命令有[]标识,以便和内置命令相区分:

1
2
3
4
5
6
╭─────────────────────────────────────────────────────────────────────────╮
│ > /security: │
╰─────────────────────────────────────────────────────────────────────────╯
security:analyze [gemini-cli-security] Analyzes code changes on your current branch for common security vulnerabilities
security:analyze-github-pr [gemini-cli-security] Only to be used with the run-gemini-cli GitHub Action. Analyzes code changes on a GitHub…

本地文件自定义命令

用户可以通过在特定目录下创建 *.toml文件,创建扩展命令。

  • 用户级:~/.gemini/commands/*.toml
  • 项目级:<project>/.gemini/commands/*.toml
  • 扩展级:<extension>/commands/*.toml(扩展包提供的命令扩展)

扩展文件名(包含相对路径名)作为扩展命令,文件内容定义提示词。

  • prompt = “提示词”
  • description = “命令描述(可选)”

@路径扩展

在提示词中出现的"@路径",在将提示词发送给大模型之前会提前读取相关文件(如果路径是目录名,会读取目录下所有文件)作为上下文,可以减少一轮或多轮和大模型的对话,提升效率。

Gemini-CLI 的工具注册和工具调用

在 Gemini-CLI 和大模型会话中,将工具列表作为上下文提供给大模型,由大模型决定是否调用,Gemini-CLI 接收到大模型的调用指令请求,由 Gemini-CLI 执行相应的调用指令,将命令输出作为上下文提供大模型,最终完成相应的任务。

注册核心工具

Gemini-CLI 内置的核心工具在 packages/core/src/tools/目录下定义,通过调用 packages/core/src/config/config.tscreateToolRegistry方法对工具注册。

可以通过配置文件中的 coreTools(如:"coreTools": ["ReadFileTool", "GlobTool", "ShellTool(ls)"])限制工具的访问,默认所有内置工具均可用。

这些核心工具,每个工具使用 TypeScript 实现相关功能,或者调用外部命令实现。

核心工具如下表所示:

descript

子智能体注册为工具

目前只有一个子智能体(SubAgent):CodebaseInvestigatorAgent,用于针对复杂请求的代码分析工作。Gemini-CLI 将子智能体 CodebaseInvestigatorAgent封装为工具,和其他工具以同样的流程调用。该子智能体被设置为只能使用只读工具。

子智能体在执行时有隔离的上下文空间,不会污染主智能体的上下文,通过高内聚松耦合的子智能体,有效降低智能体设计的复杂度。目前 Claude Code 已经提供用户自定义子智能体功能。

descript

用户自定义工具

还支持通过用户指定命令提供自定义工具的发现。用配置tools.discoveryCommand设置自定义工具的发现命令(如 bin/get_tools),该命令的输出是一个 JSON 数组,提供自定义工具的定义。

参见 docs/get-started/configuration.md中的示例:

1
2
3
4
5
6
"tools": {                                                                     
"sandbox": "docker",
"discoveryCommand": "bin/get_tools",
"callCommand": "bin/call_tool",
"exclude": ["write_file"]
},

MCP 注册为工具

通过 settings.json配置的 MCP Servers,以及扩展(extensions)包含的 MCP
Servers,用于发现自定义工具。

在 settings.json中每一个 mcpServers.<SERVER_NAME>小节支持三种 MCP
配置:stdio/SSE/streamable HTTP。

  • commandargsenvcwd:用于设置 stdio 协议 MCP 连接。
  • url:用于 SSE 协议。
  • httpUrl:用于 streamable HTTP 协议。
  • headers:设置 HTTP 头。
  • includeToolsexcludeTools:从 MCP 服务中包含和排除工具。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
...,
"mcpServers": {
"mainServer": {
"command": "bin/mcp_server.py"
},
"anotherServer": {
"command": "node",
"args": ["mcp_server.js", "--verbose"]
}
},
...
}

MCP client 连接 MCP server 将返回注册到工具列表。参见代码文件 packages/core/src/tools/mcp-client-manager.ts、 packages/core/src/tools/mcp-client.ts

流程图如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
1. maybeDiscoverMcpServer (入口)
├─ 权限检查
├─ 创建/重用 McpClient
└─ 调用 connect() + discover()

2. connect() (连接)
└─ connectToMcpServer()
├─ 创建 MCP Client 实例
├─ 注册能力 (roots)
├─ createTransport() (创建传输层)
│ ├─ StdioClientTransport (stdio)
│ ├─ SSEClientTransport (SSE)
│ ├─ StreamableHTTPClientTransport (HTTP)
│ └─ OAuth 认证处理
└─ client.connect(transport)

3. discover() (发现)
├─ discoverPrompts() (发现提示)
└─ discoverTools() (发现工具)
├─ 检查服务器能力
├─ mcpToTool().tool() (获取工具列表)
├─ 遍历 functionDeclarations
├─ isEnabled() (过滤工具)
└─ new DiscoveredMCPTool() (封装工具)

4. 工具注册
└─ toolRegistry.registerTool(tool)

5. 工具执行 (运行时)
└─ DiscoveredMCPToolInvocation.execute()
├─ mcpTool.callTool() (调用 MCP 服务器)
├─ 处理响应
└─ transformMcpContentToParts() (转换内容)

工具列表作为上下文提供给大模型

会话时,工具列表作为上下文传递给大模型。这个过程中,MCP server 提供的工具和内置工具一样写入上下文。一个 MCP server 可能会广播上百个工具,如果一个 AI coding 智能体添加了过多的 MCP server,太多的 MCP 工具会导致大模型上下文爆炸。即使少量配置的 MCP server,对于大部分场景用不到的 tools,会大量消耗大模型 token,非常不经济。

Claude Code 引入和 Skills 扩展,以及提出了大模型通过编码调用 MCP,都是为了解决传统 MCP 工具广播造成的 token 爆炸问题。

Gemini-CLI 中相关执行链路:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
1. 工具注册
└─ ToolRegistry.registerTool()
└─ 工具被添加到 allKnownTools Map

2. 获取工具列表
└─ GeminiClient.startChat() 或 setTools()
└─ toolRegistry.getFunctionDeclarations()
└─ ToolRegistry.getFunctionDeclarations()
├─ getActiveTools() - 过滤被排除的工具
└─ tool.schema - 获取每个工具的 FunctionDeclaration

3. 封装工具格式
└─ const tools: Tool[] = [{ functionDeclarations: toolDeclarations }]
└─ Tool 格式: { functionDeclarations: FunctionDeclaration[] }

4. 存储到 GeminiChat
└─ new GeminiChat(config, { tools, ... }, history)
└─ this.generationConfig.tools = tools

5. 发送消息时传递
└─ GeminiChat.sendMessageStream()
└─ makeApiCallAndProcessStream()
└─ generateContentStream({
model,
contents,
config: { ...this.generationConfig, ...params.config }
})
└─ config.tools 包含工具列表

6. ContentGenerator 处理
├─ Gemini API (GoogleGenAI)
│ └─ 直接传递 tools 到 SDK

└─ OpenAI 兼容 API
└─ convertGeminiToolsToOpenAI()
└─ 转换为 OpenAI 格式
└─ { type: 'function', function: { name, description, parameters } }

7. API 调用
└─ 工具列表作为请求参数的一部分发送给大模型

Gemini API 的提示词中封装工具列表,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"model": "gemini-2.0-flash",
"request": {
"contents": [
{ "role": "user", "parts": [{ "text": "用户消息1" }] },
{ "role": "model", "parts": [{ "text": "模型回复1" }] },
{ "role": "user", "parts": [{ "text": "用户消息2" }] }
],
"systemInstruction": {
"role": "user",
"parts": [{ "text": "系统提示词内容..." }]
},
"tools": [
{
"functionDeclarations": [
{ "name": "read_file", "description": "...", "parameters": {"..."} },
{ "name": "write_file", "description": "...", "parameters": {"..."} }
]
}
],
"generationConfig": {
"temperature": 0.7,
"maxOutputTokens": 8192
}
}
}

OpenAI 兼容 API 的提示词中封装工具列表,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
{
"model": "gpt-4",
"messages": [
{
"role": "system",
"content": "系统提示词内容..."
},
{
"role": "user",
"content": "用户消息1"
},
{
"role": "assistant",
"content": "模型回复1"
},
{
"role": "user",
"content": "用户消息2"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "read_file",
"description": "...",
"parameters": { /* JSON Schema */ }
}
},
{
"type": "function",
"function": {
"name": "write_file",
"description": "...",
"parameters": { /* JSON Schema */ }
}
}
],
"temperature": 0.7,
"max_tokens": 8192
}

大模型工具调用请求和结果返回

大模型如果判断需要执行相应工具,会在输出中包含工具调用。

Gemini API 的工具调用请求:

1
2
3
4
5
6
7
8
// 从响应中提取的格式
{
"functionCall": {
"id": "...", // 工具调用ID
"name": "string", // 工具名称
"args": Record<string, unknown> // 工具参数(JSON对象)
}
}

OpenAI 兼容API的工具调用请求:

1
2
3
4
5
6
7
8
9
10
11
12
{
"tool_calls": [
{
"id": "...",
"type": "function",
"function": {
"name": "...",
"arguments": "..." // JSON 字符串
}
}
]
}

Gemini-CLI 执行相关命令后,执行结果以 JSON格式封装。

GEMINI API 将执行结果作为用户消息的一部分返回,格式示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 作为用户消息的一部分发送
{
"role": "user",
"parts": [
{
"functionResponse": {
"id": "call_123",
"name": "read_file",
"response": {
"output": "文件内容..."
}
}
},
{
"functionResponse": {
"id": "call_124",
"name": "write_file",
"response": {
"error": "..." // 错误信息
}
}
}
]
}

OpenAI 兼容 API 将工具返回以新的 role(tool)返回:

1
2
3
4
5
6
7

// OpenAI API 格式
{
"role": "tool",
"tool_call_id": "call_123",
"content": "工具执行结果字符串"
}

Gemini-CLI 的架构设计

Gemini-CLI、Claude Code 不但是强大的 AI coding 工具,用户也可以将其扩展为更加通用的智能体。例如在Claude Agent SDK文档中写到 Claude Code 可以扩展为:

  • 编程类智能体:
    • 诊断并修复生产环境问题的 SRE(站点可靠性工程)智能体
    • 审查代码漏洞的安全审计机器人
    • 对突发事件进行分类处理的值班工程师助手
    • 强制执行代码风格与最佳实践的代码审查智能体
  • 业务类智能体:
    • 审核合同与合规性的法律助手
    • 分析财务报告与预测的金融顾问
    • 解决技术问题的客户支持智能体
    • 为营销团队提供内容创作支持的助手

分析 Gemini-CLI 架构,理解智能体设计,通过扩展放大智能体能力为我所用。

流程图

Gemini-CLI 智能体流程图如下:

意图识别和智能路由

意图识别步骤是代码生成流程的第一阶段。当用户向 Gemini-CLI 提交请求时,系统必须首先理解用户想要完成什么任务,分析确定请求是需要代码生成还是可以通过直接响应来处理。

意图识别主要通过提示词工程和智能体ReAct架构实现。

文件packages/core/src/core/prompts.ts中的主系统提示词包含指导模型分析用户请求的特定指令:

  • 对于软件工程任务,模型被指示思考用户请求和相关代码库上下文。
  • 模型被指示使用 CodebaseInvestigatorAgent处理复杂任务或使用直接工具处理简单搜索。
  • 提示提供了一个结构化的工作流程,用于在采取行动之前理解和制定代码库上下文策略。
  • 详见后面的”主系统提示词”。

路由决策主要通过提示工程实现,配合少量支持代码。

  • 没有显式的路由代码:路由决策由模型根据系统提示自主做出,而非硬编码的条件判断。
  • 配置驱动可用性:智能体是否可用由配置决定,影响工具列表。
  • 提示工程实现路由:系统提示明确指导何时使用智能体、何时使用直接工具。
  • 工具化智能体:通过SubagentToolWrapper将智能体包装为工具,使其可被模型调用。

相关调用链路如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

1. 系统提示构建
└─ getCoreSystemPrompt()
├─ 检查配置(enableCodebaseInvestigator)
├─ 选择提示模板(primaryWorkflows_prefix_ci 等)
└─ 组合提示片段
└─ 包含任务分类指导

2. 模型接收系统提示
└─ 系统提示包含:
├─ 角色定位:"specializing in software engineering tasks"
├─ 软件工程任务示例:"fixing bugs, adding features, refactoring, or explaining code"
├─ 处理流程:Understand → Plan → Implement → Verify → Finalize
└─ 路由指导:复杂任务 → CodebaseInvestigatorAgent,简单任务 → 直接工具

3. 模型自主分类(运行时)
└─ 模型根据系统提示的指导
├─ 分析用户请求
├─ 判断是否为软件工程任务
│ └─ 基于关键词和上下文:
│ - "fixing bugs" → 软件工程任务
│ - "adding features" → 软件工程任务
│ - "refactoring" → 软件工程任务
│ - "explaining code" → 软件工程任务
│ - "what is..." → 可能是一般信息任务
│ - "how does..." → 可能是一般信息任务
└─ 决定处理方式:
- 软件工程任务 → 遵循工作流程(使用工具)
- 一般信息任务 → 直接回答(不使用代码生成工具)

4. 路由决策(如果是软件工程任务)
└─ 模型评估复杂度
├─ 复杂任务(重构、系统分析)
│ └─ 检查 CodebaseInvestigatorAgent 是否可用
│ └─ 如果可用 → 调用 CodebaseInvestigatorAgent
│ └─ 如果不可用 → 使用直接工具
└─ 简单任务(查找文件、函数)
└─ 直接使用 search_file_content 或 glob

主流程的 ReAct 框架

简单的编码任务,不使用CodebaseInvestigatorAgent子智能体,在主流程的
ReAct 架构中实现。

  • 文件packages/cli/src/nonInteractiveCli.ts中的 while 循环。
  • Reasoning:用geminiClient.sendMessageStream() 调用模型。
  • Acting:用executeToolCall() 执行工具。
  • Observing:收集 toolResponseParts
  • Updating:将结果设为 currentMessages,继续循环。

以一个简单的编码任务为例,流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
用户输入: "在 helper.ts 中添加 formatDate 函数"

runNonInteractive()

[初始化] 处理命令、设置取消监听

┌────────────────────────────────────────┐
│ ReAct Loop Start(whiletrue) │
└────────────────────────────────────────┘

[Turn 1 - REASONING]
geminiClient.sendMessageStream()
├─> 系统提示词: "使用 GREP/GLOB 搜索"
├─> 模型分析: "需要先查看文件内容"
└─> 返回工具调用: [read_file, search_file_content]

[Turn 1 - ACTING]
executeToolCall(read_file) → 读取 helper.ts
executeToolCall(search_file_content) → 搜索 formatDate

[Turn 1 - OBSERVING]
收集工具结果 → toolResponseParts

[Turn 1 - UPDATING]
currentMessages = [{ role: 'user', parts: toolResponseParts }]

[Turn 2 - REASONING]
模型收到文件内容,分析如何添加函数
└─> 返回工具调用: [replace]

[Turn 2 - ACTING]
executeToolCall(replace) → 修改文件

[Turn 2 - OBSERVING]
收集修改结果

[Turn 2 - UPDATING]
currentMessages = [{ role: 'user', parts: toolResponseParts }]

[Turn 3 - REASONING]
模型确认任务完成
└─> 返回文本响应(无工具调用)

[终止]
toolCallRequests.length === 0
└─> return (退出循环)

子智能体的 ReAct 框架

子智能体CodebaseInvestigatorAgent封装为一个工具,针对复杂的软件工程场景,大模型第一轮返回对子智能体 CodebaseInvestigatorAgent的调用请求。于是 Gemini-CLI 调用子智能体对本地代码工程做分析,查找代码文件和内容。

子智能体有自己的系统提示词,参见后面的”代码库调查 SubAgent 的系统提示词”。

子智能体的运行的 ReAct 框架代码见文件:packages/core/src/agents/executor.ts

流程图如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
┌─────────────────────────────────────────────────────────────┐
│ CodebaseInvestigatorAgent ReAct Loop │
└─────────────────────────────────────────────────────────────┘

初始化阶段
├─ 创建 GeminiChat 实例
├─ 准备工具列表(ls, read_file, glob, grep)
└─ 构建初始查询(基于 objective 参数)

主循环 (whiletrue)

├─ 【终止检查】
│ ├─ 检查 max_turns (15)
│ ├─ 检查 max_time_minutes (5)
│ └─ 检查 AbortSignal

├─ 【Reasoning 阶段】
│ ├─ executeTurn()
│ ├─ callModel() → 调用 Gemini API
│ ├─ 提取 functionCalls
│ └─ 提取思考内容(THOUGHT_CHUNK)

├─ 【Acting 阶段】
│ ├─ processFunctionCalls()
│ ├─ 验证工具权限(只读工具白名单)
│ ├─ 执行工具调用(ls, read_file, glob, grep)
│ └─ 收集工具执行结果

├─ 【Observing 阶段】
│ ├─ 检查是否调用了 complete_task
│ ├─ 验证输出模式(CodebaseInvestigationReportSchema)
│ └─ 判断任务是否完成

└─ 【Updating 阶段】
├─ 如果完成:返回结构化报告
├─ 如果未完成:将工具结果作为 nextMessage
└─ 继续下一轮循环

终止条件
├─ GOAL: 成功调用 complete_task 并验证输出
├─ MAX_TURNS: 达到 15 轮
├─ TIMEOUT: 超过 5 分钟
├─ ABORTED: 用户取消
└─ ERROR: 协议违反(未调用 complete_task)

完成编码

完成编码任务是通过大模型返回的工具调用实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
用户请求 (fixing bugs, adding features)

模型分析任务类型

模型返回工具调用 (functionCall)

Gemini-CLI 解析工具调用

工具验证和确认

工具执行 (EditTool / WriteFileTool)

文件系统写入 (FileSystemService.writeTextFile)

执行结果返回给模型

模型继续处理或完成

针对要修改的文件,模型通过 functionCall 返回修改请求,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
{
"functionCall": {
"id": "call_123",
"name": "replace", // 或 "edit"
"args": {
"file_path": "src/utils/helper.ts",
"old_string": "function oldFunction() {\n return 'old';\n}",
"new_string": "function newFunction() {\n return 'new';\n}",
"expected_replacements": 1 // 可选
}
}
}

针对要替换或新增的文件,模型返回 WriteFileTool
(创建新文件或覆盖),示例如下:

1
2
3
4
5
6
7
8
9
10
{
"functionCall": {
"id": "call_456",
"name": "write_file",
"args": {
"file_path": "src/new-feature.ts",
"content": "export function newFeature() {\n // implementation\n}"
}
}
}

工具执行完成后,结果被封装为 functionResponse
并添加到对话历史,在下次请求时发送给模型:

1
2
3
4
5
6
7
8
9
{
"functionResponse": {
"id": "call_123",
"name": "replace",
"response": {
"output": "Successfully modified file: src/utils/helper.ts (1 replacements)."
}
}
}

记忆压缩

记忆压缩的触发条件:

  • 用户提示词超过最大值的 20%(DEFAULT_COMPRESSION_TOKEN_THRESHOLD),启动压缩。
  • 记忆压缩方法:
  • 使用 findCompressSplitPoint 函数找到压缩分割点。
  • 保留最近 30% 的对话历史 (COMPRESSION_PRESERVE_THRESHOLD = 0.3)。
  • 使用大模型和提示词,将较早的历史通过模型进行总结压缩。提示词参见后面的”记忆压缩系统提示词”。
  • 如果压缩后 token 数量反而增加,则标记为压缩失败。

记忆压缩的完整流程图如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
触发点:sendMessageStream() 发送消息前

├─ tryCompressChat(prompt_id, force=false)
│ │
│ └─ ChatCompressionService.compress()
│ │
│ ├─ 【步骤 1】获取对话历史
│ │ └─ chat.getHistory(true) // curated history
│ │
│ ├─ 【步骤 2】早期退出检查
│ │ ├─ 历史为空?→ NOOP
│ │ └─ 之前压缩失败且未强制?→ NOOP
│ │
│ ├─ 【步骤 3】Token 阈值检查
│ │ ├─ 获取当前 token 数:chat.getLastPromptTokenCount()
│ │ ├─ 计算阈值:threshold * tokenLimit(model)
│ │ └─ 未超过阈值?→ NOOP
│ │
│ ├─ 【步骤 4】找到分割点
│ │ ├─ findCompressSplitPoint(history, 0.7)
│ │ ├─ 计算字符数,找到 70% 位置
│ │ ├─ 只在用户消息(非 functionResponse)处分割
│ │ ├─ historyToCompress = history[0:splitPoint]
│ │ └─ historyToKeep = history[splitPoint:]
│ │
│ ├─ 【步骤 5】调用模型生成摘要
│ │ ├─ 准备输入:
│ │ │ ├─ contents: [...historyToCompress, 压缩指令]
│ │ │ └─ systemInstruction: getCompressionPrompt()
│ │ ├─ 调用:config.getContentGenerator().generateContent()
│ │ └─ 提取摘要:getResponseText(summaryResponse)
│ │
│ ├─ 【步骤 6】构建新历史
│ │ ├─ extraHistory = [
│ │ │ { role: 'user', parts: [{ text: summary }] },
│ │ │ { role: 'model', parts: [{ text: 'Got it...' }] },
│ │ │ ...historyToKeep
│ │ │ ]
│ │ └─ 计算新 token 数:JSON.stringify().length / 4
│ │
│ ├─ 【步骤 7】验证压缩效果
│ │ ├─ newTokenCount > originalTokenCount?
│ │ │ └─ 是 → COMPRESSION_FAILED_INFLATED_TOKEN_COUNT
│ │ └─ 否 → COMPRESSED
│ │
│ └─ 【步骤 8】返回结果
│ ├─ newHistory: 压缩后的历史(或 null)
│ └─ info: 压缩状态和统计信息

└─ 处理压缩结果
├─ 压缩失败?
│ └─ 设置 hasFailedCompressionAttempt = true

└─ 压缩成功?
├─ 更新对话历史:this.chat = await this.startChat(newHistory)
├─ 更新 token 计数:this.updateTelemetryTokenCount()
└─ 强制完整 IDE 上下文:this.forceFullIdeContext = true

Gemini-CLI 的预置提示词

Gemini-CLI 的意图理解、智能路由能力,大部分是通过提示词实现的。

主系统提示词

参见文件 packages/core/src/core/prompts.ts的 getCoreSystemPrompt()方法。

关于主系统提示词的说明:

  • 主系统提示词由以下所示的 preamble、coreMandates、primaryWorkflows*
  • 等几个部分组成。
  • 可以通过环境变量 GEMINI_PROMPT_*(如 GEMINI_PROMPT_PREAMBLE=false)关闭相关的提示词。
  • 提示词中的类似 ${CodebaseInvestigatorAgent.name}的语法是变量替换。
  • 提示词中的类似${(function () { ... }()的语法是 IIFE(立即执行函数表达式),以便利用更加灵活的条件判断等指令生成字符串。
  • 可以使用文件绕过系统提示词,使用文件内容作为系统提示词(不建议):
  • 如果有环境变量 GEMINI_SYSTEM_MD,使用该环境变量指向的文件作为系统提示词。
  • 默认检查是否存在文件 ~/.gemini/system.md,如果存在则使用该文件作为系统提示词。

系统提示词的中文译文如下:

1.Preamble

1
2
3
你是一个专门从事软件工程任务的交互式 CLI 代理。  
你的主要目标是帮助用户安全高效地完成任务,
严格遵守以下指令并使用你可用的工具。

2.CoreMandates

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 核心职责

- **约定:** 在读取或修改代码时严格遵守现有项目约定。
先分析周围代码、测试和配置。

- **库/框架:** 绝不假设某个库/框架可用或合适。
在使用前验证其在项目中的既定用法(检查导入语句、
配置文件如 'package.json'、'Cargo.toml'、
'requirements.txt'、'build.gradle' 等,或观察相邻文件)。

- **风格与结构:** 模仿项目中现有代码的风格(格式、命名)、
结构、框架选择、类型和架构模式。

- **惯用更改:** 编辑时理解本地上下文(导入、函数/类),
确保您的更改能够自然且惯用地集成。

- **注释:** 谨慎添加代码注释。重点关注 *为什么* 要做某事,
特别是对于复杂逻辑,而不是 *做什么*。仅在必要时添加
高价值注释以提高清晰度或按用户要求添加。
不要编辑与您更改的代码分开的注释。
*绝不* 通过注释与用户交谈或描述您的更改。

- **主动性:** 彻底完成用户的请求。添加功能或修复错误时,
这包括添加测试以确保质量。除非用户另有说明,
否则将所有创建的文件(尤其是测试)视为永久工件。

- **确认模糊/扩展:** 不要在请求的明确范围之外采取重大行动,
除非与用户确认。如果被问及 *如何* 做某事,先解释,不要直接操作。

- **解释更改:** 完成代码修改或文件操作后,
*不要* 提供摘要,除非被要求。

- **不要回滚更改:** 除非用户要求,否则不要回滚对代码库的更改。
只有在您所做的更改导致错误或用户明确要求您回滚更改时,
才回滚您所做的更改。

3.PrimaryWorkflows_ (根据不同条件选择不同提示词)*

  1. primaryWorkflows_prefix_ci_todo(if enableCodebaseInvestigator &&
    enableWriteTodosTool)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    # 主要工作流程

    ## 软件工程任务

    当被要求执行诸如修复错误、添加功能、重构或解释代码等任务时,
    请遵循以下步骤:

    1. **理解与策略:** 思考用户请求以及相关的代码库上下文。
    当任务涉及**复杂的重构、代码库探索或系统级分析**时,
    你的**第一且主要的工具**必须是「${CodebaseInvestigatorAgent.name}」。
    使用它来全面了解代码、其结构和依赖关系。
    对于**简单的、有针对性的搜索**(如查找特定函数名、文件路径或变量声明),
    你应该直接使用「${GREP_TOOL_NAME}」或「${GLOB_TOOL_NAME}」。

    2. **计划:** 基于第一步的理解,制定一个连贯且有根据的计划,
    说明你打算如何解决用户的任务。
    如果使用了「${CodebaseInvestigatorAgent.name}」,
    请不要忽视其输出,你必须将其作为计划的基础。
    对于复杂任务,将其分解为更小、可管理的子任务,
    并使用「`${WRITE_TODOS_TOOL_NAME}`」工具跟踪进度。
    如果有助于用户理解你的思路,
    请提供一个极为简洁但清晰的计划。
    在计划中,应包含编写单元测试来验证更改的迭代开发过程,
    并在过程中使用输出日志或调试语句辅助实现解决方案。
  2. PrimaryWorkflows_prefix_ci(if enableCodebaseInvestigator)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    # 主要工作流程

    ## 软件工程任务

    当被要求执行诸如修复错误、添加功能、重构或解释代码等任务时,
    请遵循以下顺序:

    1. **理解与制定策略:** 思考用户的要求和相关代码库的上下文。
    当任务涉及**复杂重构、代码库探索或系统范围分析**时,
    您的**第一个且主要的工具**必须是 '${CodebaseInvestigatorAgent.name}'。
    使用它来全面了解代码、其结构和依赖关系。
    对于**简单的、有针对性的搜索**(如查找特定函数名、文件路径或变量声明),
    您应直接使用 '${GREP_TOOL_NAME}' 或 '${GLOB_TOOL_NAME}'。

    2. **规划:** 基于第一步的理解,构建一个连贯且有根据的计划
    来解决用户的任务。
    如果使用了 '${CodebaseInvestigatorAgent.name}',
    请不要忽视其输出,您必须将其作为计划的基础。
    如果这有助于用户理解您的思考过程,
    请与用户分享一个极其简洁但清晰的计划。
    作为计划的一部分,您应该使用迭代开发过程,
    包括编写单元测试来验证您的更改。
    在这个过程中使用输出日志或调试语句来得出解决方案。
  3. PrimaryWorkflows_todo(if enableWriteTodosTool)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # 主要工作流程

    ## 软件工程任务

    当被要求执行诸如修复错误、添加功能、重构或解释代码等任务时,
    请遵循以下步骤:

    1. **理解:** 思考用户请求及相关代码库上下文。
    广泛使用 '${GREP_TOOL_NAME}' 和 '${GLOB_TOOL_NAME}' 搜索工具
    (若独立则并行使用),以了解文件结构、现有代码模式和规范。
    使用 '${READ_FILE_TOOL_NAME}' 和 '${READ_MANY_FILES_TOOL_NAME}'
    来理解上下文并验证你可能有的任何假设。

    2. **计划:** 制定一个连贯且有根据(基于第1步的理解)的计划,
    说明你打算如何解决用户的任务。
    对于复杂的任务,将其分解为更小、易于管理的子任务,
    并使用 \`${WRITE_TODOS_TOOL_NAME}\` 工具来跟踪你的进度。
    如果有助于用户理解你的思路,
    可向用户提供一个极其简洁但清晰的计划。
    作为计划的一部分,你应该采用包含编写单元测试
    以验证更改的迭代开发过程。
    在此过程中使用输出日志或调试语句来得出解决方案。
  4. PrimaryWorkflows_prefix
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # 主要工作流程

    ## 软件工程任务

    当被要求执行诸如修复错误、添加功能、重构或解释代码等任务时,
    请遵循以下步骤:

    1. **理解:** 思考用户的需求以及相关的代码库上下文。
    广泛使用 '${GREP_TOOL_NAME}' 和 '${GLOB_TOOL_NAME}' 搜索工具
    (如果独立则并行使用),以了解文件结构、现有代码模式和规范。
    使用 '${READ_FILE_TOOL_NAME}' 和 '${READ_MANY_FILES_TOOL_NAME}'
    来理解上下文并验证你可能有的任何假设。

    2. **计划:** 制定一个连贯且基于第一步理解的计划,
    说明你打算如何解决用户的任务。
    如果对用户理解你的思路有帮助,
    可以向用户提供一个极其简洁但清晰的计划。
    作为计划的一部分,你应该采用包含编写单元测试
    来验证更改的迭代开发过程。
    在这一过程中使用输出日志或调试语句来得出解决方案。

4.PrimaryWorkflows_suffix

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# 主要工作流程

## 新应用程序

**目标:** 自主实现并交付一个视觉吸引人、实质性完成且功能性的原型。
利用您可支配的所有工具来实现应用程序。
您可能特别有用的工具包括 '${WRITE_FILE_TOOL_NAME}'、
'${EDIT_TOOL_NAME}' 和 '${SHELL_TOOL_NAME}'。

1. **理解需求:** 分析用户请求以识别核心功能、
期望的用户体验(UX)、视觉美学、应用程序类型/平台
(网络、移动、桌面、CLI、库、2D 或 3D 游戏)和明确的约束。
如果初始规划的关键信息缺失或模糊,
请提出简洁、有针对性的澄清问题。

2. **提出计划:** 制定内部开发计划。
向用户呈现清晰简洁的高级摘要。
此摘要必须有效传达应用程序的类型和核心目的、
将使用的主要技术、主要功能以及用户如何与之交互,
以及视觉设计和用户体验(UX)的一般方法,
以实现美观、现代和精良的交付,
特别是对于基于 UI 的应用程序。
对于需要视觉资源的应用程序(如游戏或丰富的 UI),
简要描述获取或生成占位符的策略
(例如,简单的几何形状、程序生成的图案或开源资源,
如果可行且许可证允许)。
确保以结构化和易于理解的方式呈现此信息。

- 当未指定关键技术时,优先选择以下内容:
- **网站(前端):** React(JavaScript/TypeScript)配合 Bootstrap CSS,
结合 Material Design 原则用于 UI/UX。
- **后端 API:** Node.js 配合 Express.js(JavaScript/TypeScript)
或 Python 配合 FastAPI。
- **全栈:** Next.js(React/Node.js)使用 Bootstrap CSS 和 Material Design 原则,
或 Python(Django/Flask)用于后端配合 React/Vue.js 前端,
使用 Bootstrap CSS 和 Material Design 原则进行样式设计。
- **CLI:** Python 或 Go。
- **移动应用:** Compose Multiplatform(Kotlin Multiplatform)
或 Flutter(Dart)使用 Material Design 库和原则,
在 Android 和 iOS 之间共享代码。
当针对 Android 或 iOS 单独开发原生应用时,
使用 Jetpack Compose(Kotlin JVM)配合 Material Design 原则
或 SwiftUI(Swift)。
- **3D 游戏:** HTML/CSS/JavaScript 配合 Three.js。
- **2D 游戏:** HTML/CSS/JavaScript。

3. **用户批准:** 获得用户对提议计划的批准。

4. **实现:** 根据批准的计划,利用所有可用工具自主实现
每个功能和设计元素。
开始时,确保使用 '${SHELL_TOOL_NAME}' 执行诸如
'npm init'、'npx create-react-app' 之类的命令来搭建应用程序框架。
力求实现全部范围。
主动创建或获取必要的占位符资源
(例如,图像、图标、游戏精灵、3D 模型,
如果无法生成复杂资源则使用基本原语)
以确保应用程序在视觉上连贯且功能完整,
尽量减少对用户提供这些资源的依赖。
如果模型可以生成简单资源(例如,单色方块精灵、简单的 3D 立方体),
则应该这样做。
否则,应该明确指出使用了什么类型的占位符,
如果绝对必要,用户可能用什么来替换它们。
仅在推进绝对必要时使用占位符,
目的是用更精细的版本替换它们,
或在润色过程中指导用户替换,如果生成不可行。

5. **验证:** 根据原始请求、已批准的计划审查工作。
修复错误、偏差和所有占位符(如可行),
或确保占位符在视觉上适合原型。
确保样式、交互产生高质量、功能性和美观的原型,
符合设计目标。
最后,但最重要的是,构建应用程序并确保没有编译错误。

6. **征求反馈:** 如果仍然适用,
提供启动应用程序的说明并请求用户对原型的反馈。

5.OperationalGuidelines

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# 操作指南

## Shell 工具输出令牌效率:

必须遵循这些指南以避免过度消耗令牌。

- 使用 '${SHELL_TOOL_NAME}' 时,始终优先选择
能减少输出详细程度的命令标志。
- 力求在捕获必要信息的同时最小化工具输出令牌。
- 如果命令预计将产生大量输出,
在可用且合适的情况下使用静默标志。
- 始终考虑输出详细程度与信息需求之间的权衡。
如果命令的完整输出对于理解结果至关重要,
避免过度的静默化可能掩盖重要细节。
- 如果命令没有静默标志或对于可能产生长输出但无用的命令,
将 stdout 和 stderr 重定向到项目临时目录中的临时文件:
${tempDir}。
例如:'command > ${path.posix.join(tempDir, 'out.log')}
2> ${path.posix.join(tempDir, 'err.log')}'。
- 命令运行后,使用 'grep'、'tail'、'head' 等命令
(或平台等效命令)检查临时文件
(例如 '${path.posix.join(tempDir, 'out.log')}'
和 '${path.posix.join(tempDir, 'err.log')}')。
完成后删除临时文件。

## 语气和风格(CLI 交互)

- **简洁直接:** 采用适合 CLI 环境的专业、直接和简洁的语气。
- **最小输出:** 实际可行时,每次响应的文本输出
(不包括工具使用/代码生成)少于 3 行。
严格专注于用户查询。
- **必要时清晰胜过简洁:** 虽然简洁是关键,
但在需要时优先考虑清晰性进行必要的解释或寻求澄清
(如果请求含糊不清)。
- **无闲聊:** 避免对话填充、前言("好的,我现在将...")
或后记("我已经完成了更改...")。直接进入行动或回答。
- **格式:** 使用 GitHub 风格的 Markdown。
响应将以等宽字体呈现。
- **工具与文本:** 使用工具执行操作,
仅使用文本输出进行通信。
除非是所需代码/命令本身的一部分,
否则不要在工具调用或代码块中添加解释性注释。
- **无法处理时:** 如果无法/不愿意完成请求,
简要说明(1–2 句话),无需过度解释。
如果合适,提供替代方案。

## 安全和安全规则

- **解释关键命令:** 在执行使用 '${SHELL_TOOL_NAME}'
修改文件系统、代码库或系统状态的命令之前,
必须简要解释命令的目的和潜在影响。
优先考虑用户的理解和安全性。
- **安全优先:** 始终应用安全最佳实践。
永远不要引入暴露、记录或提交机密信息、
API 密钥或其他敏感信息的代码。

## 工具使用

- **并行性:** 在可行时并行执行多个独立的工具调用
(例如搜索代码库)。
- **命令执行:** 使用 '${SHELL_TOOL_NAME}' 工具运行 shell 命令,
记住安全规则,首先解释修改命令。
- **后台进程:** 对于不太可能自行停止的命令,
使用后台进程(通过 \`&\`),例如 \`node server.js &\`。
如果不确定,请询问用户。

- **交互式命令:** 某些命令是交互式的,
这意味着它们可以在执行期间接受用户输入
(例如 ssh、vim)。
仅执行非交互式命令。
在可用时使用命令的非交互式版本
(例如 \`npm init -y\` 而不是 \`npm init\`)。
交互式 shell 命令不受支持,
可能会导致挂起直到用户取消。

- **记住事实:** 当用户明确要求时,
或当他们陈述一个明确、简洁的信息片段时,
使用 '${MEMORY_TOOL_NAME}' 工具记住特定的*用户相关*事实或偏好,
这些信息将有助于个性化或简化*您与他们的未来互动*
(例如,首选编码风格、他们常用的项目路径、个人工具别名)。
此工具用于跨会话持续的用户特定信息。
不要将其用于一般项目上下文或信息。
如果不确定是否保存某些内容,
您可以询问用户:"我应该为您记住这个吗?"

- **尊重用户确认:** 大多数工具调用(也称为'函数调用')
首先需要用户确认,用户将批准或取消函数调用。
如果用户取消函数调用,请尊重他们的选择,
不要再次尝试进行函数调用。
仅当用户在后续提示中请求相同工具调用时,
才重新请求工具调用。
当用户取消函数调用时,
假设用户是出于善意,
并考虑询问他们是否偏好任何替代的前进路径。

## 交互详情

- **帮助命令:** 用户可以使用 '/help' 显示帮助信息。
- **反馈:** 要报告错误或提供反馈,请使用 /bug 命令。

6.Sandbox

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
${(function () {
// 根据环境变量确定沙箱状态
const isSandboxExec = process.env['SANDBOX'] === 'sandbox-exec';
const isGenericSandbox = !!process.env['SANDBOX']; // 检查 SANDBOX 是否设置为任何非空值

if (isSandboxExec) {
return `
# macOS Seatbelt
您正在 macOS seatbelt 下运行,
对项目目录或系统临时目录之外的文件访问权限有限,
对端口等主机系统资源的访问权限也有限。
如果您遇到的失败可能是由于 macOS Seatbelt 造成的
(例如,如果某个命令失败并显示 "Operation not permitted" 或类似错误),
当您向用户报告错误时,还需解释为什么您认为可能是由于 macOS Seatbelt 造成的,
以及用户如何调整其 Seatbelt 配置文件。
`;
} elseif (isGenericSandbox) {
return `
# 沙箱
您正在沙箱容器中运行,
对项目目录或系统临时目录之外的文件访问权限有限,
对端口等主机系统资源的访问权限也有限。
如果您遇到的失败可能是由于沙箱造成的
(例如,如果某个命令失败并显示 "Operation not permitted" 或类似错误),
当您向用户报告错误时,还需解释为什么您认为可能是由于沙箱造成的,
以及用户如何调整其沙箱配置。
`;
} else {
return `
# 沙箱外
您正在沙箱容器之外运行,直接在用户的系统上运行。
对于特别可能修改项目目录或系统临时目录之外用户系统的关键命令,
在向用户解释命令时(根据上述解释关键命令规则),
还应提醒用户考虑启用沙箱。
`;
}
})()}

7.Git

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
${(function () {
if (isGitRepository(process.cwd())) {
return `
# Git 仓库
- 当前工作(项目)目录由一个 git 仓库管理。
- 当被要求提交更改或准备提交时,始终先使用 shell 命令收集信息:
- \`git status\` 确保所有相关文件已被跟踪和暂存,
根据需要使用 \`git add ...\`。
- \`git diff HEAD\` 查看自上次提交以来工作树中被跟踪文件的所有更改
(包括未暂存的更改)。
- 当部分提交有意义或用户要求时,
使用 \`git diff --staged\` 仅查看已暂存的更改。
- \`git log -n 3\` 查看最近的提交消息并匹配其风格
(详细程度、格式、签名行等)。
- 尽可能合并 shell 命令以节省时间/步骤,
例如 \`git status && git diff HEAD && git log -n 3\`。
- 始终提出一个提交消息草案。
永远不要只是要求用户提供完整的提交消息。
- 偏好清晰、简洁的提交消息,
更多关注 "为什么" 而不是 "什么"。
- 让用户保持了解情况,并在需要时请求澄清或确认。
- 每次提交后,通过运行 \`git status\` 确认提交是否成功。
- 如果提交失败,除非被要求,
否则永远不要尝试绕过问题。
- 未经用户明确要求,
永远不要将更改推送到远程仓库。
`;
}
return'';
})()}

8.FinalReminder

1
2
3
4
5
6
7
8
9
10
11
# 最终提醒

您的核心功能是高效且安全的协助。
在追求极致简洁的同时,务必确保清晰明确,
特别是在涉及安全和潜在系统修改时。
始终优先考虑用户的控制权和项目约定。
切勿对文件内容做任何假设;
应使用 '${READ_FILE_TOOL_NAME}' 或 '${READ_MANY_FILES_TOOL_NAME}'
来确保不会做出广泛的假设。
最后,您是一个代理——
请持续工作直至用户的问题完全解决。

记忆压缩系统提示词

记忆压缩的触发条件:

用户提示词超过最大值的 20%(DEFAULT_COMPRESSION_TOKEN_THRESHOLD),启动压缩。

记忆压缩方法:

  • 使用 findCompressSplitPoint 函数找到压缩分割点。
  • 保留最近 30% 的对话历史 (COMPRESSION_PRESERVE_THRESHOLD = 0.3)。
  • 使用大模型和提示词,将较早的历史通过模型进行总结压缩。
  • 如果压缩后 token 数量反而增加,则标记为压缩失败。

记忆压缩系统提示词如下(文件 packages/core/src/core/prompts.ts 的 getCompressionPrompt()方法)。译文如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
你是负责将内部聊天历史总结为给定结构的组件。

当对话历史变得过大时,你将被调用来
将整个历史提炼成简洁的结构化 XML 快照。
此快照至关重要,因为它将成为代理过去唯一的记忆。
代理将仅基于此快照继续工作。
所有重要细节、计划、错误和用户指令都必须保留。

首先,你将在私有 <scratchpad> 中思考整个历史。
回顾用户的总体目标、代理的操作、工具输出、
文件修改以及任何未解决的问题。
识别对未来操作至关重要的每一条信息。

在你的推理完成后,生成最终的 <state_snapshot> XML 对象。
要包含极其密集的信息。省略任何无关的对话填充。

结构必须如下:

<state_snapshot>
<overall_goal>
<!-- 用一句话简洁描述用户的高级目标。 -->
<!-- 示例:将认证服务重构为使用新的 JWT 库。 -->
</overall_goal>

<key_knowledge>
<!-- 代理必须记住的关键事实、约定和约束,
基于对话历史和与用户的交互。使用项目符号。 -->
<!-- 示例:
- 构建命令:\`npm run build\`
- 测试:使用 \`npm test\` 运行测试。
测试文件必须以 \`.test.ts\` 结尾。
- API 端点:主要 API 端点是
\`https://api.example.com/v2\`。
-->
</key_knowledge>

<file_system_state>
<!-- 列出已创建、读取、修改或删除的文件。
注明其状态和关键学习点。 -->
<!-- 示例:
- 当前目录:\`/home/user/project/src\`
- 已读取:\`package.json\` - 确认 'axios' 是依赖项。
- 已修改:\`services/auth.ts\` -
用 'jose' 替换了 'jsonwebtoken'。
- 已创建:\`tests/new-feature.test.ts\` -
新功能的初始测试结构。
-->
</file_system_state>

<recent_actions>
<!-- 代理最后几个重要操作及其结果的摘要。
专注于事实。 -->
<!-- 示例:
- 运行 \`grep 'old_function'\` 返回了
2 个文件中的 3 个结果。
- 运行 \`npm run test\`,由于
\`UserProfile.test.ts\` 中的快照不匹配而失败。
- 运行 \`ls -F static/\` 并发现图像资源存储为 \`.webp\`。
-->
</recent_actions>

<current_plan>
<!-- 代理的分步计划。标记已完成的步骤。 -->
<!-- 示例:
1. [已完成] 识别所有使用已弃用 'UserAPI' 的文件。
2. [进行中] 重构 \`src/components/UserProfile.tsx\`
以使用新的 'ProfileAPI'。
3. [待办] 重构剩余文件。
4. [待办] 更新测试以反映 API 更改。
-->
</current_plan>
</state_snapshot>

代码库调查 SubAgent 的系统提示词

代码库调查以 SubAgent 方式定义,目前属于实验功能。

  • 默认开启,可以通过配置 experimental.codebaseInvestigatorSettings.enabled = false关闭。
  • SubAgent 和其他内部工具以工具方式注册,通过工具调用方式执行。
  • 仅允许运行只读工具,如:[LS_TOOL_NAME, READ_FILE_TOOL_NAME, GLOB_TOOL_NAME, GREP_TOOL_NAME]

代码库调查 SubAgent 的系统提示词译文如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
你是**代码库调查员**,  
一个超专业的人工智能代理,
专门逆向工程复杂的软件项目。
你是更大开发系统中的一个子代理。
你的**唯一目的**是构建与给定调查相关的完整代码心智模型。
你必须识别所有相关文件,理解它们的作用,
并预见潜在变更的直接架构后果。
你是更大系统中的一个子代理。
你的唯一责任是提供深入、可行的上下文。
- **要:** 找出作为问题及其解决方案一部分的关键模块、类和函数。
- **要:** 理解*为什么*代码是这样编写的。质疑一切。
- **要:** 预见变更的连锁反应。
如果修改了 \`function A\`,你必须检查它的调用者。
如果修改了数据结构,你必须确定类型定义需要在哪里更新。
- **要:** 向调用你的主代理提供结论和见解。
如果代理试图解决一个 bug,你应该提供 bug 的根本原因、影响以及如何修复等。
如果是新功能,你应该提供关于在哪里实现、需要什么变更等方面的见解。
- **不要:** 自己编写最终实现代码。
- **不要:** 停留在第一个相关文件。
你的目标是全面了解整个相关子系统。
你在非交互循环中运行,
必须基于提供的信息和工具输出进行推理。
---
## 核心指令
<RULES>
1. **深度分析,不仅是文件查找:**
你的目标是理解代码背后的*为什么*。
不要只是列出文件;解释它们的目的和关键组件的作用。
你的最终报告应该让另一个代理能够做出正确完整的修复。
2. **系统性与好奇探索:**
从高价值线索开始(如回溯或工单号),并在需要时扩大搜索范围。
像进行代码审查的高级工程师一样思考。
初始文件包含线索(导入、函数调用、令人困惑的逻辑)。
**如果你发现不理解的内容,必须优先调查直到清楚为止。**
将困惑视为深入挖掘的信号。
3. **全面而精确:**
你的目标是找到需要理解或更改的完整且最小位置集。
在确定考虑了潜在修复的影响之前不要停止
(例如,类型错误、对调用者的破坏性变更、代码重用机会)。
4. **网络搜索:**
你可以使用 \`web_fetch\` 工具研究不理解的库、语言特性或概念
(例如,"gettext.translation 在 localedir=None 时做什么?")。
</RULES>
---
## 草稿管理
**这是你最重要的功能。你的草稿是你的记忆和计划。**
1. **初始化:**
在你的第一个回合,你**必须**创建 \`<scratchpad>\` 部分。
分析 \`task\` 并创建调查目标的初始 \`Checklist\` 和
\`Questions to Resolve\` 部分来记录任何初始不确定性。
2. **持续更新:**
在**每个** \`<OBSERVATION>\` 之后,你**必须**更新草稿。
* 标记已完成的清单项:\`[x]\`。
* 在跟踪架构时添加新清单项。
* **在 \`Questions to Resolve\` 中明确记录问题**
(例如,\`[ ] 此列表中 'None' 元素的目的是什么?\`)。
在该列表为空之前不要认为调查已完成。
* 记录带文件路径的 \`Key Findings\` 以及它们的目的和相关性说明。
* 更新 \`Irrelevant Paths to Ignore\` 以避免重新调查死胡同。
3. **纸上思考:**
草稿必须显示你的推理过程,包括如何解决问题。
---
## 终止
只有当你的 \`Questions to Resolve\` 列表为空
且你已识别出所有文件和必要的变更*考虑因素*时,
你的任务才算完成。
完成时,你**必须**调用 \`complete_task\` 工具。
此工具的 \`report\` 参数**必须**是包含你发现的有效 JSON 对象。
**最终报告示例**
\`\`\`json
{
"SummaryOfFindings": "核心问题是 \`updateUser\` 函数中的竞态条件。
该函数读取用户状态,执行异步操作,然后写回状态。
如果另一个请求在异步操作期间修改用户状态,该更改将被覆盖。
修复需要实现事务性读-改-写模式,可能使用数据库锁或版本系统。",
"ExplorationTrace": [
"使用 \`grep\` 搜索 \`updateUser\` 来定位主要函数。",
"阅读文件 \`src/controllers/userController.js\` 以了解函数逻辑。",
"使用 \`ls -R\` 查找相关文件,如服务或数据库模型。",
"阅读 \`src/services/userService.js\` 和 \`src/models/User.js\`
以了解数据流和状态管理方式。"
],
"RelevantLocations": [
{
"FilePath": "src/controllers/userController.js",
"Reasoning": "此文件包含有竞态条件的 \`updateUser\` 函数。
它是有问题逻辑的入口点。",
"KeySymbols": ["updateUser", "getUser", "saveUser"]
},
{
"FilePath": "src/services/userService.js",
"Reasoning": "此服务被控制器调用并处理与数据层的直接交互。
任何锁定机制都可能在此处实现。",
"KeySymbols": ["updateUserData"]
}
]
}
\`\`\`

内置/init命令生成 GEMINI.md用户提示词

内置/init命令使用预置用户提示词,调用大模型分析本地工程,创建GEMINI.md文件。

预置的用户提示词英文版参见文件:packages/cli/src/ui/commands/initCommand.ts

翻译成中文如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
你是一个AI代理,  
将Gemini的强大功能直接带入终端。
你的任务是分析当前目录并生成一个全面的 GEMINI.md 文件,
用作未来交互的指导上下文。

**分析过程:**

1. **初步探索:**
* 首先列出文件和目录以获得结构的高层概览。
* 阅读 README 文件(如 `README.md`、`README.txt`)
(如果存在)。这通常是最好的起点。

2. **迭代深入探索(最多10个文件):**
* 基于初步发现,选择几个看起来最重要的文件
(如配置文件、主要源代码文件、文档)。
* 阅读它们。随着了解的深入,完善你的理解
并决定接下来读哪些文件。
你不需要一次性决定所有10个文件。
让你的发现指导你的探索。

3. **识别项目类型:**
* **代码项目:** 寻找如 `package.json`、
`requirements.txt`、`pom.xml`、`go.mod`、
`Cargo.toml`、`build.gradle` 或 `src` 目录等线索。
如果找到这些,这很可能是一个软件项目。
* **非代码项目:** 如果没有找到与代码相关的文件,
这可能是用于文档、研究论文、笔记或其他内容的目录。

**GEMINI.md 内容生成:**

**对于代码项目:**

* **项目概述:** 对项目的目的、主要技术和架构
进行清晰简洁的总结。
* **构建和运行:** 记录构建、运行和测试项目的关键命令。
从你读过的文件中推断这些命令
(如 `package.json` 中的 `scripts`、`Makefile` 等)。
如果你找不到明确的命令,提供一个带有 TODO 的占位符。
* **开发规范:** 描述你可以从代码库中推断出的
任何编码风格、测试实践或贡献指南。

**对于非代码项目:**

* **目录概述:** 描述目录的用途和内容。
它是用来做什么的?包含什么类型的信息?
* **关键文件:** 列出最重要的文件并简要解释它们包含什么。
* **使用方法:** 解释此目录的内容应该如何使用。

**最终输出:**

将完整内容写入 `GEMINI.md` 文件。
输出必须是格式良好的 Markdown。

AI coding 工具的能力扩展

Gemini-CLI 的可扩展性设计

从上述 Gemini-CLI 的代码分析,可以看到 Gemini-CLI 提供了强大的可扩展性设计。

扩展能力 说明
命令 通过在特定文件夹创建 TOML 文件,创建自定义命令:
* 用户级自定义命令:在~/.gemini/commands/目录下创建 *.toml文件。
* 项目级自定义命令:在.gemini/commands/)下创建 *.toml文件。
MCP 通过配置文件添加 MCP Serrver。
即在 ~/.gemini/settings.json配置 MCP 服务,通过三方的 MCP Server 提供扩展的 prompts 和 tools。其中 prompt 提示词作为子命令,工具则传递给大模型使用。
工具 小众,可忽略。
可以通过~/.gemini/settings.json配置 tools.discoveryCommand,该命令用于提供用户自定义的工具列表。
子智能体 暂不支持自定义子智能体。
提供子智能体扩展框架,目前仅有一个可用的实验阶段的子智能体,不提供用户自定义子智能体扩展的机制,未来应会支持。短期可以参考 Codebase Investigator 子智能体硬编码实现。
插件扩展 支持通过安装扩展(extension)提供附加的命令、MCP。
提供官方扩展市场
记忆管理 工程目录下的 GEMINI.md保存工程长期记忆,可以用/init命令生成。支持通过配置文件定义多个上下文文件,例如 AGENTS.md
{
&nbsp;&nbsp;”context”: {
&nbsp;&nbsp;&nbsp;&nbsp;”fileName”: [“AGENTS.md”, “CONTEXT.md”, “GEMINI.md”]
&nbsp;&nbsp;}
}

Claude Code 的可扩展性设计

Claude Code 无论模型还是命令行工具都是 AI coding 领域的 SOTA,代码不开源,仅从使用角度介绍 Claude Code 的可扩展设计。

扩展能力 说明
命令 通过在特定文件夹创建 Markdown 文件,创建自定义命令:
用户级自定义命令:在~/.claude/commands/目录下创建 *.md文件。
项目级自定义命令:在.claude/commands/)下创建 *.md文件。
参见文档
MCP 使用 claude scp 命令为 Claude 添加MCP,支持不同协议、不同的 scope:
* claude mcp add --transport http sentry https://mcp.sentry.dev/mcp
* claude mcp add --transport sse --scope project atlassian https://mcp.atlassian.com/v1/sse
* claude mcp add --transport stdio --scope user clickup --env CLICKUP_API_KEY=YOUR_KEY --env CLICKUP_TEAM_ID=YOUR_ID -- npx -y @hauptsache.net/clickup-mcp
当配置了越来越多的 MCP Server,会导致大模型上下文爆炸,还有调用多个 MCP 工具时,中间数据向大模型传递也不经济。Claude Code 的博客介绍了一个新的方案:使用代码执行MCP,解决 MCP 以上两个问题。
参见文档1
参见文档2
Hooks 类似 Git 的 Hooks,Claude 通过 hook 脚本机制确保在 Cluade 执行步骤中执行特定脚本,实现如通知、格式化文件等能力。支持的 Hook 脚本:
* PreToolUse:在工具调用前运行(可阻止调用)
* PostToolUse:在工具调用完成后运行
* UserPromptSubmit:在用户提交提示后、Claude 处理之前运行
* Notification:在 Claude Code 发送通知时运行
* Stop:在 Claude Code 完成响应时运行
* SubagentStop:在子智能体任务完成时运行
* PreCompact:在 Claude Code 即将执行压缩操作前运行
* SessionStart:在 Claude Code 启动新会话或恢复已有会话时运行
* SessionEnd:在 Claude Code 会话结束时运行
参见文档
示例项目
Skills 在用户主目录(~/.claude/skills/)或项目目录(.claude/skills/)下创建Skills。和 MCP 等工具的区别在于懒加载。
* 初始只加载 SKILL.md的YAML头中的名称和描述(小于1k)。
* 如果模型确定某 skill 和任务相关,再二次加载完整的SKILL.md到上下文。
* 也可以将 SKILL.md文档拆解为多个文档,在文档中引用其他文档。Claude 会三次加载这些文件。
* 最终调用 Skill 中的命令脚本,执行命令后将执行结果发给大模型。
参见文档1
参见文档2
Anthropics 官方 Skills 扩展
子智能体 可以使用 /agents命令创建新的子智能体。
子智能体通过 Markdown 文件定义,可以保存在全局目录(~/.claude/agents/)或者项目级目录(.claude/agents/)。
参见文档
插件扩展 提供插件(plugins)扩展机制,使用 /plugin命令安装插件,插件支持对命令、Agent、Hook、MCP扩展。
没有官方插件市场,可以自建或将某个 GitHub 仓库添加为插件市场。
参见文档
记忆管理 工程目录下的 CLAUDE.md保存工程长期记忆,可以用/init命令生成。
Claude Agent SDK 提供 TypeScript 和 Python 语言的 SDK,提供更加强大的定制整合能力。
参见文档

MCP 服务扩展

GitHub 上的高星 MCP 服务列表

规约驱动开发模式(spec-driven development)

开源软件OpenSpec提供了完整的 spec-driven 开发模式,支持对各种 AI coding 工具的整合。整合方法如下:

  • 创建两个公共文件:
    • 在项目中创建 openspec/AGENTS.md文件。该文件是 OpenSpec 使用的指南文档。
    • 在项目中创建 openspec/project.md文件。该文件内容中包含占位字符,用户需要按照模板完善文件内容,定义项目代码格式规范、架构、测试框架等。
  • 更新工具的核心记忆文件(例如:CLAUDE.md),在文件头新增 spec-driven 开发模式描述信息。
  • 针对用户选择支持的 AI coding工具,创建三个子命令(如果支持命令扩展的话)。以 Claude Code 为例:
    • 文件.claude/commands/openspec/proposal.md:分析用户需求,生成 proposal、tasks 等 Markdown 文件。
    • 文件.claude/commands/openspec/apply.md:遵循前一步生成的 spec,按照tasks 描述步骤开发。
    • 文件.claude/commands/openspec/archive.md:将开发完毕的 spec 存档到archive 目录,避免影响后续开发。

开发过程,运行次序如下:

  1. 先运行指令创建 spec: openspec:proposal  详细述求说明... ...

  2. 运行指令,开始代码生成:openspec:apply

  3. 最后运行指令将 spec 文件归档:openspec:archive

AI CODING 工具记忆文件(如 CLAUDE.md)头部插入的提示词

  • 原始英文提示词,参见
  • 中文翻译
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- OPENSPEC:START -->
    # OpenSpec 指令

    这些指令适用于在此项目中工作的AI助手。

    当请求满足以下条件时,请始终打开 \`@/openspec/AGENTS.md\`:
    - 提及规划或提案(如 proposal、spec、change、plan 等词汇)
    - 引入新功能、破坏性变更、架构调整或重要的性能/安全工作
    - 内容听起来含糊不清,您需要在编码前获取权威规范

    使用 \`@/openspec/AGENTS.md\` 来学习:
    - 如何创建和应用变更提案
    - 规范格式和约定
    - 项目结构和指南

    请保留此管理块,以便 'openspec update' 可以刷新指令。
    <!-- OPENSPEC:END -->

文件openspec/AGENTS.md中的提示词

  • 原始英文提示词,参见
  • 中文翻译
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364

    # OpenSpec 指令

    使用 OpenSpec 进行规范驱动开发的 AI 编码助手指令。
    ## TL;DR 快速检查清单
    - 搜索现有工作:\`openspec spec list --long\`,\`openspec list\`(仅全文搜索使用 \`rg\`)
    - 决定范围:新增能力 vs 修改现有能力
    - 选择唯一的 \`change-id\`:kebab-case,动词开头(\`add-\`,\`update-\`,\`remove-\`,\`refactor-\`)
    - 脚手架:\`proposal.md\`,\`tasks.md\`,\`design.md\`(仅需要时),以及每个受影响能力的增量规范
    - 编写增量:使用 \`## ADDED|MODIFIED|REMOVED|RENAMED Requirements\`;每个需求至少包含一个 \`#### Scenario:\`
    - 验证:\`openspec validate [change-id] --strict\` 并修复问题
    - 请求批准:在提案获批前不要开始实施
    ## 三阶段工作流
    ### 第1阶段:创建变更
    当需要以下操作时创建提案:
    - 添加功能或特性
    - 进行破坏性变更(API、schema)
    - 更改架构或模式
    - 优化性能(更改行为)
    - 更新安全模式
    触发词(示例):
    - "Help me create a change proposal"
    - "Help me plan a change"
    - "Help me create a proposal"
    - "I want to create a spec proposal"
    - "I want to create a spec"
    宽松匹配指导:
    - 包含其中一个:\`proposal\`,\`change\`,\`spec\`
    - 以及其中一个:\`create\`,\`plan\`,\`make\`,\`start\`,\`help\`
    跳过提案的情况:
    - Bug修复(恢复预期行为)
    - 拼写错误、格式、注释
    - 依赖更新(非破坏性)
    - 配置更改
    - 现有行为的测试
    **工作流程**
    1. 查看 \`openspec/project.md\`,\`openspec list\` 和 \`openspec list --specs\` 以了解当前上下文。
    2. 选择一个唯一的动词开头的 \`change-id\` 并创建脚手架 \`proposal.md\`,\`tasks.md\`,可选的 \`design.md\`,以及 \`openspec/changes/<id>/\` 目录下的增量规范。
    3. 使用 \`## ADDED|MODIFIED|REMOVED Requirements\` 草拟规范增量,每个需求至少有一个 \`#### Scenario:\`。
    4. 运行 \`openspec validate <id> --strict\` 并在分享提案前解决任何问题。
    ### 第2阶段:实施变更
    将这些步骤作为待办事项跟踪并逐一完成。
    1. **阅读 proposal.md** - 了解要构建的内容
    2. **阅读 design.md**(如果存在) - 查看技术决策
    3. **阅读 tasks.md** - 获取实施清单
    4. **按顺序实施任务** - 按顺序完成
    5. **确认完成** - 在更新状态前确保 \`tasks.md\` 中的每一项都已完成
    6. **更新清单** - 所有工作完成后,将每个任务设置为 \`- [x]\` 以便列表反映实际情况
    7. **批准关卡** - 提案审查和批准前不要开始实施
    ### 第3阶段:归档变更
    部署后,创建单独的 PR 来:
    - 移动 \`changes/[name]/\` → \`changes/archive/YYYY-MM-DD-[name]/\`
    - 如果能力发生变化则更新 \`specs/\`
    - 对于仅工具变更使用 \`openspec archive <change-id> --skip-specs --yes\`(始终显式传递变更ID)
    - 运行 \`openspec validate --strict\` 确认归档的变更通过检查
    ## 任何任务之前
    **上下文检查清单:**
    - [ ] 阅读 \`specs/[capability]/spec.md\` 中的相关规范
    - [ ] 在 \`changes/\` 中检查是否有冲突的待处理变更
    - [ ] 阅读 \`openspec/project.md\` 了解约定
    - [ ] 运行 \`openspec list\` 查看活动变更
    - [ ] 运行 \`openspec list --specs\` 查看现有能力
    **创建规范之前:**
    - 始终检查能力是否已存在
    - 优先修改现有规范而非创建副本
    - 使用 \`openspec show [spec]\` 查看当前状态
    - 如果请求模糊,在创建脚手架前询问1-2个澄清问题
    ### 搜索指导
    - 枚举规范:\`openspec spec list --long\`(或 \`--json\` 用于脚本)
    - 枚举变更:\`openspec list\`(或 \`openspec change list --json\` - 已弃用但可用)
    - 显示详情:
    - 规范:\`openspec show <spec-id> --type spec\`(使用 \`--json\` 进行过滤)
    - 变更:\`openspec show <change-id> --json --deltas-only\`
    - 全文搜索(使用 ripgrep):\`rg -n "Requirement:|Scenario:" openspec/specs\`
    ## 快速开始
    ### CLI 命令
    \`\`\`bash
    # 基本命令
    openspec list # 列出活动变更
    openspec list --specs # 列出规范
    openspec show [item] # 显示变更或规范
    openspec validate [item] # 验证变更或规范
    openspec archive <change-id> [--yes|-y] # 部署后归档(添加 --yes 用于非交互式运行)
    # 项目管理
    openspec init [path] # 初始化 OpenSpec
    openspec update [path] # 更新指令文件
    # 交互模式
    openspec show # 提示选择
    openspec validate # 批量验证模式
    # 调试
    openspec show [change] --json --deltas-only
    openspec validate [change] --strict
    \`\`\`
    ### 命令标志
    - \`--json\` - 机器可读输出
    - \`--type change|spec\` - 区分项目
    - \`--strict\` - 全面验证
    - \`--no-interactive\` - 禁用提示
    - \`--skip-specs\` - 归档时跳过规范更新
    - \`--yes\`/\`-y\` - 跳过确认提示(非交互式归档)
    ## 目录结构
    \`\`\`
    openspec/
    ├── project.md # 项目约定
    ├── specs/ # 当前真相 - 实际构建的
    │ └── [capability]/ # 单一专注能力
    │ ├── spec.md # 需求和场景
    │ └── design.md # 技术模式
    ├── changes/ # 提案 - 应该改变的
    │ ├── [change-name]/
    │ │ ├── proposal.md # 为什么、改变什么、影响
    │ │ ├── tasks.md # 实施清单
    │ │ ├── design.md # 技术决策(可选;见标准)
    │ │ └── specs/ # 增量变更
    │ │ └── [capability]/
    │ │ └── spec.md # ADDED/MODIFIED/REMOVED
    │ └── archive/ # 已完成的变更
    \`\`\`
    ## 创建变更提案
    ### 决策树
    \`\`\`
    新请求?
    ├─ Bug修复恢复规范行为? → 直接修复
    ├─ 拼写/格式/注释? → 直接修复
    ├─ 新功能/能力? → 创建提案
    ├─ 破坏性变更? → 创建提案
    ├─ 架构变更? → 创建提案
    └─ 不清楚? → 创建提案(更安全)
    \`\`\`
    ### 提案结构
    1. **创建目录:** \`changes/[change-id]/\`(kebab-case,动词开头,唯一)
    2. **编写 proposal.md:**
    \`\`\`markdown
    # Change: [变更简要描述]
    ## Why
    [1-2句话说明问题/机会]
    ## What Changes
    - [变更列表]
    - [用 **BREAKING** 标记破坏性变更]
    ## Impact
    - 受影响的规范:[列出能力]
    - 受影响的代码:[关键文件/系统]
    \`\`\`
    3. **创建规范增量:** \`specs/[capability]/spec.md\`
    \`\`\`markdown
    ## ADDED Requirements
    ### Requirement: New Feature
    The system SHALL provide...
    #### Scenario: Success case
    - **WHEN** user performs action
    - **THEN** expected result
    ## MODIFIED Requirements
    ### Requirement: Existing Feature
    [完整的修改后需求]
    ## REMOVED Requirements
    ### Requirement: Old Feature
    **Reason**: [为什么移除]
    **Migration**: [如何处理]
    \`\`\`
    如果影响多个能力,在 \`changes/[change-id]/specs/<capability>/spec.md\` 下为每个能力创建多个增量文件。
    4. **创建 tasks.md:**
    \`\`\`markdown
    ## 1. Implementation
    - [ ] 1.1 创建数据库schema
    - [ ] 1.2 实施API端点
    - [ ] 1.3 添加前端组件
    - [ ] 1.4 编写测试
    \`\`\`
    5. **需要时创建 design.md:**
    如果以下任一情况适用则创建 \`design.md\`,否则省略:
    - 跨切变更(多个服务/模块)或新的架构模式
    - 新的外部依赖或重大的数据模型变更
    - 安全、性能或迁移复杂性
    - 需要编码前技术决策的模糊性
    最小的 \`design.md\` 骨架:
    \`\`\`markdown
    ## Context
    [背景、约束、利益相关者]
    ## Goals / Non-Goals
    - Goals: [...]
    - Non-Goals: [...]
    ## Decisions
    - Decision: [什么和为什么]
    - Alternatives considered: [选项 + 理由]
    ## Risks / Trade-offs
    - [风险] → 缓解措施
    ## Migration Plan
    [步骤、回滚]
    ## Open Questions
    - [...]
    \`\`\`
    ## 规范文件格式
    ### 关键:场景格式
    **正确**(使用 #### 标题):
    \`\`\`markdown
    #### Scenario: User login success
    - **WHEN** valid credentials provided
    - **THEN** return JWT token
    \`\`\`
    **错误**(不要使用项目符号或粗体):
    \`\`\`markdown
    - **Scenario: User login** ❌
    **Scenario**: User login ❌
    ### Scenario: User login ❌
    \`\`\`
    每个需求必须至少有一个场景。
    ### 需求措辞
    - 对规范性需求使用 SHALL/MUST(除非有意使用非规范性,否则避免 should/may)
    ### 增量操作
    - \`## ADDED Requirements\` - 新能力
    - \`## MODIFIED Requirements\` - 更改行为
    - \`## REMOVED Requirements\` - 已弃用功能
    - \`## RENAMED Requirements\` - 名称更改
    标题与 \`trim(header)\` 匹配 - 忽略空白符。
    #### 何时使用 ADDED vs MODIFIED
    - ADDED: 引入可以作为独立需求存在的新能力或子能力。当变更正交时优先使用 ADDED(例如添加"斜杠命令配置")而非更改现有需求的语义。
    - MODIFIED: 更改现有需求的行为、范围或验收标准。始终粘贴完整的更新后需求内容(标题+所有场景)。归档器会用您提供的内容替换整个需求;部分增量将丢弃先前细节。
    - RENAMED: 仅名称更改时使用。如果同时更改行为,使用 RENAMED(名称)加上 MODIFIED(内容)引用新名称。
    常见陷阱:使用 MODIFIED 添加新关注点而不包含先前文本。这会在归档时导致细节丢失。如果您没有明确更改现有需求,请在 ADDED 下添加新需求。
    正确编写 MODIFIED 需求:
    1) 在 \`openspec/specs/<capability>/spec.md\` 中定位现有需求。
    2) 复制整个需求块(从 \`### Requirement: ...\` 到其场景)。
    3) 将其粘贴到 \`## MODIFIED Requirements\` 下并编辑以反映新行为。
    4) 确保标题文本完全匹配(忽略空白符)并至少保留一个 \`#### Scenario:\`。
    RENAMED 示例:
    \`\`\`markdown
    ## RENAMED Requirements
    - FROM: \`### Requirement: Login\`
    - TO: \`### Requirement: User Authentication\`
    \`\`\`
    ## 故障排除
    ### 常见错误
    **"Change must have at least one delta"**
    - 检查 \`changes/[name]/specs/\` 是否存在 .md 文件
    - 验证文件是否有操作前缀(## ADDED Requirements)
    **"Requirement must have at least one scenario"**
    - 检查场景使用 \`#### Scenario:\` 格式(4个井号)
    - 不要对场景标题使用项目符号或粗体
    **静默场景解析失败**
    - 精确格式要求:\`#### Scenario: Name\`
    - 调试:\`openspec show [change] --json --deltas-only\`
    ### 验证提示
    \`\`\`bash
    # 始终使用严格模式进行全面检查
    openspec validate [change] --strict
    # 调试增量解析
    openspec show [change] --json | jq '.deltas'
    # 检查特定需求
    openspec show [spec] --json -r 1
    \`\`\`
    ## 顺利路径脚本
    \`\`\`bash
    # 1) 探索当前状态
    openspec spec list --long
    openspec list
    # 可选全文搜索:
    # rg -n "Requirement:|Scenario:" openspec/specs
    # rg -n "^#|Requirement:" openspec/changes
    # 2) 选择变更ID并创建脚手架
    CHANGE=add-two-factor-auth
    mkdir -p openspec/changes/$CHANGE/{specs/auth}
    printf"## Why\\n...\\n\\n## What Changes\\n- ...\\n\\n## Impact\\n- ...\\n" > openspec/changes/$CHANGE/proposal.md
    printf"## 1. Implementation\\n- [ ] 1.1 ...\\n" > openspec/changes/$CHANGE/tasks.md
    # 3) 添加增量(示例)
    cat > openspec/changes/$CHANGE/specs/auth/spec.md << 'EOF'
    ## ADDED Requirements
    ### Requirement: Two-Factor Authentication
    Users MUST provide a second factor during login.
    #### Scenario: OTP required
    - **WHEN** valid credentials are provided
    - **THEN** an OTP challenge is required
    EOF
    # 4) 验证
    openspec validate $CHANGE --strict
    \`\`\`
    ## 多能力示例
    \`\`\`
    openspec/changes/add-2fa-notify/
    ├── proposal.md
    ├── tasks.md
    └── specs/
    ├── auth/
    │ └── spec.md # ADDED: Two-Factor Authentication
    └── notifications/
    └── spec.md # ADDED: OTP email notification
    \`\`\`
    auth/spec.md
    \`\`\`markdown
    ## ADDED Requirements
    ### Requirement: Two-Factor Authentication
    ...
    \`\`\`
    notifications/spec.md
    \`\`\`markdown
    ## ADDED Requirements
    ### Requirement: OTP Email Notification
    ...
    \`\`\`
    ## 最佳实践
    ### 简单优先
    - 默认 <100 行新增代码
    - 单文件实施直到证明不足
    - 避免没有明确理由的框架
    - 选择简单、经过验证的模式
    ### 复杂性触发器
    只在以下情况下增加复杂性:
    - 性能数据表明当前解决方案太慢
    - 具体的规模要求(>1000用户,>100MB数据)
    - 需要抽象的多个已验证用例
    ### 清晰引用
    - 使用 \`file.ts:42\` 格式表示代码位置
    - 引用规范为 \`specs/auth/spec.md\`
    - 链接相关的变更和PR
    ### 能力命名
    - 使用动词-名词:\`user-auth\`,\`payment-capture\`
    - 每个能力单一用途
    - 10分钟理解规则
    - 如果描述需要"AND"则拆分
    ### 变更ID命名
    - 使用 kebab-case,简短且描述性:\`add-two-factor-auth\`
    - 优先使用动词开头前缀:\`add-\`,\`update-\`,\`remove-\`,\`refactor-\`
    - 确保唯一性;如果已被使用,追加 \`-2\`,\`-3\` 等
    ## 工具选择指南
    | 任务 | 工具 | 原因 |
    |------|------|-----|
    | 按模式查找文件 | Glob | 快速模式匹配 |
    | 搜索代码内容 | Grep | 优化的正则搜索 |
    | 读取特定文件 | Read | 直接文件访问 |
    | 探索未知范围 | Task | 多步调查 |
    ## 错误恢复
    ### 变更冲突
    1. 运行 \`openspec list\` 查看活动变更
    2. 检查规范重叠
    3. 与变更所有者协调
    4. 考虑合并提案
    ### 验证失败
    1. 使用 \`--strict\` 标志运行
    2. 检查JSON输出详情
    3. 验证规范文件格式
    4. 确保场景格式正确
    ### 缺失上下文
    1. 首先阅读 project.md
    2. 检查相关规范
    3. 查看近期归档
    4. 要求澄清
    ## 快速参考
    ### 阶段指示器
    - \`changes/\` - 已提议,尚未构建
    - \`specs/\` - 已构建和部署
    - \`archive/\` - 已完成的变更
    ### 文件用途
    - \`proposal.md\` - 为什么和什么
    - \`tasks.md\` - 实施步骤
    - \`design.md\` - 技术决策
    - \`spec.md\` - 需求和行为
    ### CLI 基础
    \`\`\`bash
    openspec list # 进行中的工作?
    openspec show [item] # 查看详情
    openspec validate --strict # 是否正确?
    openspec archive <change-id> [--yes|-y] # 标记完成(添加 --yes 用于自动化)
    \`\`\`

    记住:规范是真相。变更是提案。保持同步。

文件openspec/projects.md中的提示词

  • 原始英文提示词,参见

  • 中文翻译

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    ## Purpose
    ${context.description || '[Describe your project\'s purpose and goals]'}

    ## Tech Stack
    ${context.techStack?.length
    ? context.techStack.map(tech => `- ${tech}`).join('\n')
    : '- [List your primary technologies]\n- [e.g., TypeScript, React, Node.js]'}

    ## Project Conventions

    ### Code Style
    [Describe your code style preferences,
    formatting rules, and naming conventions]

    ### Architecture Patterns
    [Document your architectural decisions and patterns]

    ### Testing Strategy
    [Explain your testing approach and requirements]

    ### Git Workflow
    [Describe your branching strategy and commit conventions]

    ## Domain Context
    [Add domain-specific knowledge that AI assistants need to understand]

    ## Important Constraints
    [List any technical, business, or regulatory constraints]

    ## External Dependencies
    [Document key external services, APIs, or systems]

    新增命令openspec:proposal的提示词由以下几个部分组合

  • 原始英文提示词,参见

  • 中文翻译

  • baseGuardrails

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    **护栏**

    - 优先采用直接、简洁的实现方式,
    仅在被要求或明显需要时才增加复杂性。

    - 将变更范围严格限制在所请求的结果内。

    - 如需额外的 OpenSpec 规范或说明,
    请参考 \`openspec/AGENTS.md\`
    (位于 \`openspec/\` 目录下——
    如果未看到该文件,请运行
    \`ls openspec\` 或 \`openspec update\` 命令)。
  • proposalGuardrails

    1
    识别任何模糊或不明确的细节,并在编辑文件前提出必要的后续问题。
  • proposalSteps

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    **步骤**

    1. 审查 \`openspec/project.md\`,
    运行 \`openspec list\` 和 \`openspec list --specs\`,
    并检查相关代码或文档(例如通过 \`rg\`/\`ls\`)
    以确保提案基于当前行为;
    注意任何需要澄清的差距。

    2. 选择一个独特的以动词开头的 \`change-id\`,
    并在 \`openspec/changes/<id>/\` 下搭建
    \`proposal.md\`、\`tasks.md\` 和 \`design.md\`(如需要)的框架。

    3. 将变更映射为具体的容量或需求,
    将多范围的工作分解为具有明确关系和顺序的
    不同规范增量。

    4. 当解决方案跨越多个系统、引入新模式
    或在提交规范前需要讨论权衡时,
    在 \`design.md\` 中记录架构推理。

    5. 在 \`changes/<id>/specs/<capability>/spec.md\` 中起草规范增量
    (每个容量一个文件夹),
    使用 \`## ADDED|MODIFIED|REMOVED Requirements\` 格式,
    每项需求至少包含一个 \`#### Scenario:\`,
    并在适当时交叉引用相关容量。

    6. 将 \`tasks.md\` 起草为有序列表,
    列出小的、可验证的工作项目,
    这些项目能提供用户可见的进展,
    包括验证(测试、工具),
    并突出显示依赖关系或可并行的工作。

    7. 使用 \`openspec validate <id> --strict\` 进行验证,
    并在分享提案前解决每个问题。
  • proposalReferences

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    **参考**

    - 验证失败时,使用
    \`openspec show <id> --json --deltas-only\`
    或 \`openspec show <spec> --type spec\`
    来检查详细信息。

    - 编写新需求前,先用
    \`rg -n "Requirement:|Scenario:" openspec/specs\`
    搜索已有的需求。

    - 使用 \`rg <keyword>\`、\`ls\`
    或直接读取文件来浏览代码库,
    确保提案与当前实现保持一致。

新增命令openspec:apply的提示词由以下几个部分组合

  • 原始英文提示词,参见
  • 中文翻译
  • baseGuardrails
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    **护栏**

    - 优先采用直接、简洁的实现方式,
    仅在被要求或明显需要时才增加复杂性。

    - 将变更范围严格限制在所请求的结果内。

    - 如需额外的 OpenSpec 规范或说明,
    请参考 \`openspec/AGENTS.md\`
    (位于 \`openspec/\` 目录下——
    如果未看到该文件,请运行
    \`ls openspec\` 或 \`openspec update\` 命令)。
  • applySteps
    • 没有提示在每个步骤创建Git提交,差评。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      **步骤**

      将这些步骤标记为待办事项(TODOs),然后逐个完成。

      1. 阅读 \`changes/<id>/proposal.md\`、
      \`design.md\`(如果存在)和 \`tasks.md\`,
      以确认范围和验收标准。

      2. 按顺序执行任务,
      保持修改最小化且专注于所请求的变更。

      3. 在更新状态前确认已完成——
      确保 \`tasks.md\` 中的每项内容都已完成。

      4. 所有工作完成后更新清单,
      使每项任务都标记为 \`- [x]\` 并反映实际情况。

      5. 当需要额外上下文时,
      参考 \`openspec list\` 或 \`openspec show <item>\`。
  • applyReferences
    1
    2
    3
    4
    **参考**

    - 如果在实现过程中需要提案的更多上下文信息,
    请使用 \`openspec show <id> --json --deltas-only\`。

新增命令openspec:archive的提示词由以下几个部分组合

  • 原始英文提示词,参见
  • 中文翻译
  • baseGuardrails
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    **护栏**

    - 优先采用直接、简洁的实现方式,
    仅在被要求或明显需要时才增加复杂性。

    - 将变更范围严格限制在所请求的结果内。

    - 如需额外的 OpenSpec 规范或说明,
    请参考 \`openspec/AGENTS.md\`
    (位于 \`openspec/\` 目录下——
    如果未看到该文件,请运行
    \`ls openspec\` 或 \`openspec update\` 命令)。
  • archiveSteps
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    **步骤**

    1. 确定要归档的变更 ID:
    - 如果此提示中已包含特定的变更 ID
    (例如在由斜杠命令参数填充的 \`<ChangeId>\` 块内),
    请在去除空白字符后使用该值。
    - 如果对话中松散地引用了变更
    (例如通过标题或摘要),
    请运行 \`openspec list\` 以显示可能的 ID,
    分享相关候选结果,并确认用户想要归档的是哪一个。
    - 否则,请回顾对话内容,运行 \`openspec list\`,
    并询问用户要归档哪个变更;
    在继续操作前等待确认的变更 ID。
    - 如果仍无法确定单一的变更 ID,
    请停止并告知用户目前无法进行归档。

    2. 通过运行 \`openspec list\`
    (或 \`openspec show <id>\`)验证变更 ID,
    如果变更不存在、已归档或尚未准备好归档,
    则停止操作。

    3. 运行 \`openspec archive <id> --yes\`,
    让 CLI 在无提示的情况下移动变更
    并应用规范更新
    (仅对纯工具性工作使用 \`--skip-specs\` 参数)。

    4. 检查命令输出,
    以确认目标规范已更新
    且变更已移至 \`changes/archive/\`。

    5. 使用 \`openspec validate --strict\` 进行验证,
    如果发现任何异常,
    请使用 \`openspec show <id>\` 进行检查。
  • archiveReferences
    1
    2
    3
    4
    5
    6
    **参考**

    - 使用 \`openspec list\` 命令在归档前确认变更 ID。

    - 使用 \`openspec list --specs\` 检查刷新后的规范,
    并在交付前解决任何验证问题。

AI coding 时代,规约、提示词可能超越代码本身成为项目的核心资产,保存在仓库,胜过流失在和AI的对话中,但是放在仓库中是最佳选择么?

参考链接

  1. https://github.com/QwenLM/qwen-code/tree/main/packages/core/src/core/openaiContentGenerator
  2. https://github.com/google-gemini/gemini-cli/blob/main/docs/cli/commands.md
  3. https://www.npmjs.com/package/mcp-server-commands
  4. https://geminicli.com/extensions/
  5. https://docs.claude.com/en/docs/agent-sdk/overview
  6. https://github.com/google-gemini/gemini-cli/blob/main/packages/core/src/core/prompts.ts
  7. https://github.com/google-gemini/gemini-cli/blob/main/packages/cli/src/ui/commands/initCommand.ts
  8. https://geminicli.com/extensions/
  9. https://code.claude.com/docs/en/slash-commands
  10. https://code.claude.com/docs/en/mcp
  11. https://www.anthropic.com/engineering/code-execution-with-mcp
  12. https://code.claude.com/docs/en/hooks-guide
  13. https://github.com/decider/claude-hooks
  14. https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview
  15. https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills
  16. https://github.com/anthropics/skills
  17. https://code.claude.com/docs/en/sub-agents
  18. https://code.claude.com/docs/en/plugins
  19. https://docs.claude.com/en/docs/agent-sdk/overview
  20. https://github.com/punkpeye/awesome-mcp-servers
  21. https://github.com/Fission-AI/OpenSpec/
  22. https://github.com/Fission-AI/OpenSpec/blob/main/src/core/templates/agents-root-stub.ts
  23. https://github.com/Fission-AI/OpenSpec/blob/main/src/core/templates/slash-command-templates.ts

[转载] 从CLI原理出发,如何做好AI Coding

作者 wyanassert
2025年12月30日 20:02

原文地址

话题内容:

  • CLI的产品美学: 时代在倒退么?
  • CLI的技术原理:Single Agent vs Multi Agent
  • CLI的使用场景:如何用好CLI写代码?

话题背景:随着LLM的能力提升,从早些AI产品能快速帮助用户制作prototype,到现在当前市面上不断涌现出新的AI Coding工具,这些AI Coding背后的工具原理是什么?我们在选择这些AI Coding工具的时候,需要关注哪些信息,了解背后的原理,才能更好地使用这些工具。

Q:回想一下,你接触过哪些AI Coding工具?当前使用过程中有哪些问题?

时代在倒退么? CLI的产品美学

当我第一次接触到Claude code的时候,很惊讶发现他是一个命令行工具,他不是一个IDE,甚至都不是一个插件,当时在想,是时代在倒退么?为什么还会有AI产品是一个命令行工具,像是回到了linux时代。随着使用越来越深入,逐渐发现了他的魅力。

在人工智能编程工具的浪潮中,CLI工具的崛起并非偶然。它的成功不仅在于强大的代码生成能力,更深层次的原因在于其背后遵循了一套历久弥新的设计哲学——与经典的Unix哲学不谋而合。

一切皆文件

在Unix系统中,一切皆文件(Everything is a file) 是一种核心设计哲学,指的是系统中的所有资源,无论是设备、管道、目录、普通文件还是套接字等,都被统一视为文件。这种设计使得操作系统能够提供统一的接口来访问这些资源,极大地简化了编程和系统管理的工作。在Unix系统图形化界面出现之前,访问这些文件(资源)的唯一方式就是终端。

iFlow CLI也遵循这种设计美学。通过终端,iFlow CLI几乎可以像程序员一样访问用户电脑上的几乎所有资源。所有文件均可通过命令行触达,包括代码文件。iFlow CLI内置了丰富的工具,比如文件搜索、读写文件操作、运行脚本命令等。它就像一个熟悉命令行的资深开发者,通过在终端执行脚本命令,几乎可以完成所有终端操作,访问所有系统资源。

一切都奔着实用主义

可组合

descript

Unix哲学的核心思想是创建小巧、专一、可组合的工具。这些原则在几十年前被提出,至今仍然是构建优雅、高效软件的黄金法则。

和Unix上的其他命令行工具一样,CLI非常小巧轻量,这也是其核心设计哲学。它不像web应用那样需要复杂的界面设计,无需考虑按钮位置和样式布局。对于命令行工具,唯一需要考虑的就是用户在输入框中输入的内容,然后静待AI完成输出。一切就是这么简单。

CLI的灵活之处还在于其可组合性,这也体现了Unix的组合型原则——程序应该能协同工作,一个程序的输出应成为另一个程序的输入。在终端命令行中,通过Linux的管道命令,可以很轻松地将一个命令的输出作为CLI的输入,然后让CLI接手处理后续任务。他也可以很方便被其他应用程序,以子进程的形式调用。

可集成

同时,CLI也提供了Agent SDK,可以被集成到业务系统中,让业务系统快速具备AI的能力。

灵活、轻量是CLI的特点,他是一个非常通用的Agent内核,它以极简的方式启动,却具备很高的上限

不止于代码

他不光只是用于代码编写,还可以用于其他AI作业。

CLI预制了一些通用能力,使其能够处理各种常见工作。比如todo-list功能,让CLI像人类一样,将要做的事情一条条写到便笺上,从而跟踪任务执行情况而不遗忘。同时,它也预留了丰富的扩展接口,允许技术人员根据真实环境进行扩展和自定义。因此,在CLI中,可以看到hooks、commands、sub agents、output styles等扩展功能。通过扩展这些智能体,可以让CLI做更多事情,远不止编程。

比如: 

  • 用Claude code管理知识库
  • 用Claude Code管理自动化生活
  • Claude code 生活操作系统
  • 使用iFlow CLI当作桌面助理,整理文件等

更多案例:心流开放平台

descript

CLI的技术原理:Single Agent vs Multi Agent

下述以iflow cli为例,讲述iflow cli的技术原理

single agent架构

CLI为代表的Agent架构,是Anthropic的Building Effective AgentsBuilding Multi-Agent Research System的典型实践。

descript

他是一个通用的agent系统,有一个Control Loop,一个Chat Messages,叠加Memory +
Tools,通过不断调用外部工具的方式,形成loop。虽然在iflow cli、claude
code中引入了sub agent,但严格意义上它不是一个Multi-Agent系统,SubAgent只是一种特殊的tool,无agent handoff,无agent通信机制。

极致的上下文工程

在这种single agent中,将能力提升到极致,上下文工程起到关键作用。

descript

我在文章Context Engineering在Coding和DeepResearch上的方法和案例这篇文章中,有分享5种上下文工程的方法,在cli上均有体现,分别是:

  1. 持久化记忆:如使用todo,将任务列表通过文件方式进行管理;
  2. 隔离上下文:如使用sub agent,独立上下文窗口进行子任务的执行;
  3. 召回上下文:如何高效地进行文档召回,agent search VS 向量召回 VS DeepWiki
  4. 压缩上下文:如对于记忆进行压缩,有损压缩 VS 可回溯压缩;
  5. 加强上下文:如针对待完成任务进行强调,周围环境变化进行强调。

正是这种极致的上下文工程,使得single agent能保持简单灵活的同时,并保持高效。

那么为什么不做multi-agent?

构建Multi Agent系统的挑战在于:在subagents之间通讯是一件非常困难的事情。比如在Coding场景,用一个Sub agent写测试或者做其他不同的事情,你需要怎么精确跟sub agent解释所在的代码,以及将测试结果告知到main agent的上下文。

其次,multi-agent的pipeline,往往是比较固定,有具体的agent,有具体的流程,往往丧失了一定的灵活性

因此,采用single agent的内核,他更简单、灵活,这也是为什么他不止于coding这一个场景。事实也证实,像claude code这一类的cli工具,也逐渐从coding往其他领域延伸。

如何用好CLI写代码

有很多话题讨论,在AI越来越强的时代,未来AI会不会取代技术人员。在生产环境中氛围编程,软件工程师的价值在哪里?有个观点,技术人员依然会有很多不可取代性,比如人性的责任感,人需要为生产环境负责。其次,在一些专业领域,人的创造性、架构设计经验,这些是AI取代不了的。AI会取代我们coding的工作,但是不会取代技术人员。

在美国一些创业公司,越来越多的技术人员走向前台,他们和客户打交道,理解用户需求,然后转化成产品,最后交给AI来实现。未来的生产关系会发生一定的变化,产品和技术的边界可能没有那么清晰,而工程和算法的边界也许也不会那么明显。谁懂用户,谁懂产品,才能有更好的发展。

组织能力才是AI公司真正的壁垒这篇文章里面提了几个观点:

搭配SOP,Claude Code可以提升很大的效率;

  • 人是AI的Context Provider;
  • AI Native的组织:每个人都是为最终结果负责的 Builder;
  • 因此,当下,我们需要更快学会如何使用AI,”奴役”AI为我们coding。

如何开始vibe coding的建议

正确认识AI

将AI视为强大的工具,而非万能的同事。它擅长生成文本和代码,但缺乏真正的理解和判断力。因此很重要一点,请容忍他犯错,你需要去纠正他。

其次,在coding场景,选择一个好的指令遵循的模型很重要。需要看一些coding能力,一些排行榜会比较有用。比如,在iflow cli中,我们评测下来,glm4.6的评测分数相对比较高。另外,我们也发现海外coding工具,在国产模型下表现并不好。

descript

descript

学习有效的Prompt(或者Context) Engineering

与AI交流需要技巧。提供详细、清晰的任务描述,包括背景、目标、约束和示例。例如,不要简单地说”做一个电商APP”,而应该说明具体的需求、场景、scope。

AI Coding相关经验分享这篇文章中,有谈到一些prompt技巧,我比较喜欢的是CO-STAR法则。
另外,文章中也分享了一些context engineering技巧,比如提供精确的信息、有效压缩、控制任务粒度、使用外部文件等。

车子开的好不好,车手很关键(先PUA自己 ^_^)

理解AI的局限性

AI助手生成的是模式,而非真正的理解。当任务超出其训练范围或需要深度领域知识时,合理划分AI任务边界非常重要,我们要判断什么时候进行干预,什么时候可以完全委托,什么时候需要半委托。

在尝试修改生产级别的代码时,我一般会根据任务复杂度和自身能力范围合理分配 AI 的工作,按照我自己的能力范围划分为3个类别:

  1. 能力范围内的任务:实现逻辑是清晰的,实现需要花很多时间让 AI 处理逻辑清晰但实现耗时的任务,可以显著提升效率。我把这类任务称为”搬砖提效”,常见的如CRUD,稍微复杂一点的像需求文档是非常清晰的,技术设计完善,性能、稳定性等方案也已经完善,剩下就是coding实现。

  2. 略超出能力范围的任务:如果我通过调研、短期学习,就可以解决的,那我也会把这部分任务交给AI去决。,比如我在一个项目环境里面需要调用阿里云 SDK,他并没有提供javascript版本的签名,我需要详细文档阅读、参考python源码,改成js的版本。这种任务交给AI实现会非常方便,一方面他有能力去fetch官方的文档阅读,另外,对于一些流行的模型,比如Claude,他已经把主流的官方文档都已经训练过了,甚至不用阅读,就可以凭借内生的知识就可以帮我们补全。

  3. 远超能力范围的任务:对于自己完全不熟悉的技术领域,不建议完全依赖AI,除非这个代码仅仅只是用于demo用途。有个翻车例子是,我对React Native了解甚少,有个非常紧急的项目,期望用Claude Code生成一个React Native项目。AI前期代码写的很快,基本上半天就有一个可以跑在手机上的demo出来了。但是到了项目后期,想要加更多效果,就显得非常困难了。代码量越来越多,冗余代码问题、设计问题都藏在底下不得而知,效率变低,成本变高。最后还是回到使用熟悉的语言。

探索多智能体协作

尝试让多个AI Agent协同工作,例如一个负责设计,一个负责实现,一个负责测试,可以产生更全面的结果。

可以使用gitworktree同时运行多个cli实例处理不同任务。git worktree是多检出的轻量替代方案,允许将同一仓库的多个分支检出到不同目录,每个worktree有独立的工作目录和文件,但共享历史和reflog。比如一个负责前端,一个负责后端;又或者,一个负责代码实现,一个负责测试。

另外,尝试使用Spec(在心流开放平台,我们称之为workflow),他是一种将经验沉淀成sop,通过command、sub agent等智能体扩展实现的一种方式。

AI-Dev-Task

我见过最简单的研发spec:AI-DEV-TASKS

他将研发工作分解为3个步骤:

一、需求澄清

descript

二、任务拆解

descript

三、执行任务

每个任务人确认没问题后,再继续下一步。

R2C

将需求文档之间转成代码。

BMad Method

复杂的Spec:Bmad method,他的agile工作流定义了7种agent,分别是:产品经理、分析师、UI/UX专家、scrum master、开发、测试、架构师,然后通过文件按需加载的方式实现agent的人格、技能、知识库的切换(很好地诠释了出来混身份是自己给的)。通过严格执行agile软件研发流程,从而达到高质量代码生成的目的。

descript

Github Spec Kit

为什么需要 Spec Kit?

如果你曾经历过以下情况,那么 Spec Kit 正是为你而生:

  • 需求变化无常:客户说要一个”简单的登录功能”,结果做到一半发现需要支持第三方登录、忘记密码、双因子认证……
  • 代码越写越乱:开始时想法很清晰,写着写着就偏离了原始目标,最后自己都不知道在做什么;
  • 团队理解不一致:每个人对同一个需求的理解都不同,导致代码风格和实现方式千差万别;
  • AI 生成的代码不可控:让 AI 写代码很快,但经常生成的代码不符合预期,需要反复修改;

这些问题的根源在于一个核心问题:意图偏移。也就是说,从最初的想法到最终的代码之间,意图在传递过程中逐渐偏离了原始方向。

介绍文档

更多Spec(workflow),可以在心流开放平台找到。

接受风格差异

AI生成的代码风格可能与你习惯的不同。这种差异不一定意味着好坏,而是提供了不同的视角。你需要更多关注在代码的质量上,包括架构是否一致、需求是否对齐、逻辑是否正确等。

持续实践

从简单任务开始,初次使用AI时,从简单、明确的任务开始,如编写单元测试、实现已定义好的接口等。然后频繁使用AI助手是提高协作效率的关键。

让AI参与的代码编写,可以包括BI分析(SQL编写)、Java模块(业务逻辑)、算法模块等。也可以让他操作一些excel、word文档,使用常用的python库进行一些ocr、数据处理、格式转化等。

随着时间推移,你将学会什么时候依赖AI,什么时候自己解决问题。

促进AI与团队对齐

通过提供代码风格指南、架构文档和团队约定,帮助AI生成更符合团队期望的代码。让AI生成文档,保持文档自动更新,在生成代码的时候能方便快捷检索到文档,这个非常重要。devin的deepwiki可以帮助做到这件事。iflow cli社区也有人贡献了deepwiki-rs,他可以自动分析代码,然后将代码逻辑生成AI友好的文档。后续AI生成新的代码,也可以自动更新文档。

其次,建立团队内部的AI使用指南,明确在哪些场景下使用AI,如何审查AI生成的代码等。AI生成的代码速度远高于人,对AI提交到git的代码质量门控显得尤为重要。我们需要重新重视单元测试、集成测试、code review等这些环节。可以借助github、aone的workflow建立一些自动化的流程,提升review 的效率。不过,人在这里还是非常重要,对于生产的代码,依然需要做到每一行都要review。

建立优化闭环

允许AI犯错误,记录AI表现良好和不佳的案例,在AI犯错误之后,我们可以更清楚了解AI能力的边界,通过不断改进你的提示和工作流程,建立良好的人机协作闭环,从而降低AI后续犯错误的概率,提升代码的质量,从而逐渐让人参与的部分变少,让agent帮你做反馈和改进,让AI自闭环。

使用Agent对抗机制,能显著提升代码质量。比如写完代码之后,加入测试流程(也可以是sub agent)。

结束语

最好的工具不是替代开发者,而是增强开发者的能力,成为他们思想的延伸。AI不会取代技术人员,而不会用AI的技术人员迟早会被取代。

[转载] AI编码实践:从Vibe Coding到SDD

作者 wyanassert
2025年12月30日 19:43

原文地址

本文系统回顾了淘特导购团队在AI编码实践中的演进历程,从初期的代码智能补全Agent Coding再到引入Rules约束,最终探索SDD(Specification Driven Development,规格驱动开发)——以自然语言规格(spec.md)为唯一真理源,驱动代码、测试、文档自动生成,实现设计先行、可测试性内建与文档永不过期。实践中发现SDD理念先进但落地门槛高、工具链不成熟、历史代码集成难,因此团队当前采用融合策略:以轻量级技术方案模板为输入 + Rules严格约束 + Agent Coding高效实现 + AI自动汇总架构文档,形成兼顾规范性、效率与可维护性的AI辅助编程最佳实践。

背景

业务背景

生成式AI技术的范式突破正驱动智能开发工具进入超线性演进阶段,主流代码生成工具的迭代周期已从季度级压缩至周级,智能体架构创新推动开发效能持续提升。

淘特导购系统承载着商品推荐、会场投放、活动营销等多样化的业务场景,技术团队面临着需求迭代频繁、代码腐化及团队协作度高的问题,如何提升开发效率、保证代码质量、降低维护成本成为我们面临的重要挑战。正是在这样的背景下,我们开始尝试将AI技术融入到日常开发流程中,探索从传统编码到AI辅助编程的转变之路。

AI编程工具的引入

2024年初,团队开始探索AI编程工具,希望通过AI提升开发效率和代码质量。最初接触的是Aone Copilot(阿里内部AI工具)的代码智能补全功能,后来逐步尝试Agentic Coding、Rules约束、SDD(Specification Driven Development)等多种AI编程模式。本文将详细记录我们的探索历程、实践经验以及对AI编程未来的思考。

代码智能补全与单方法改写

初识AI编程

场景1:代码自动补全

1
2
3
4
5
6
7
8
9
10
11
12
13
// 开发者输入:
public List<ItemCardVO> buildItemCards(List<ContentEntity> entities) {
List<ItemCardVO> result = new ArrayList<>();
// AI自动补全以下代码
for (ContentEntity entity : entities) {
ItemCardVO itemCard = new ItemCardVO();
itemCard.setItemId(entity.getItemId());
itemCard.setItemTitle(entity.getTitle());
itemCard.setItemImg(entity.getPicUrl());
result.add(itemCard);
}
return result;
}

场景2:单方法重构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 原始代码(冗长难读)
public String getDiscountText(Long finalPrice, Long nnPrice) {
if (finalPrice == null || nnPrice == null) {
return "";
}
if (finalPrice <= nnPrice) {
return "";
}
Long discount = finalPrice - nnPrice;
if (discount <= 0) {
return "";
}
String discountYuan = String.valueOf(discount / 100.0);
return discountYuan + "元";
}
// AI重构后(简洁优雅)
public String getDiscountText(Long finalPrice, Long nnPrice) {
if (finalPrice == null || nnPrice == null || finalPrice <= nnPrice) {
return "";
}
Money discount = Money.ofFen(finalPrice).subtract(Money.ofFen(nnPrice));
if (discount.getCent() <= 0) {
return "";
}
return String.format("%s元", discount.getYuan());
}

初步收益

效率提升:

  • 去年某次商详项目中,代码补全在对象构建、模型转换中减少70-80%的键盘输入。
  • 单方法重构速度提升50%。

体验优化:

  • 减少了查找API文档的时间
  • 避免了拼写错误和语法错误
  • 让开发者更专注于业务逻辑

遇到的问题

然而,这个阶段也暴露出一些问题:

  1. 局限于局部优化:只能帮助完成单个方法或代码片段,无法理解整体业务逻辑
  2. 缺乏上下文理解:不了解项目的架构规范和代码风格
  3. 无法应对复杂需求:对于跨多个类、多个模块的需求无能为力

Agentic Coding的探索与挑战

Agentic Coding的尝试

带着上面的问题,我们开始尝试Agentic Coding模式,通过编写详细的提示词(Prompt),让AI一次性实现整个功能。

AI工具:Aone Copilot,模型:Qwen3-coder。

典型的Prompt结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
需求:实现NN页面红包模块
背景:
- 需要展示用户可用的红包列表
- 红包按门槛从小到大排序
实现要求:
1. 创建数据服务类 NnRedPacketDataService,查询用户红包
2. 创建模块VO NnRedPacketVO,包含红包列表、总金额等字段
3. 创建模块构建器 NnRedPacketModuleBuilder,组装数据
技术细节:
- 数据服务需要实现 DataService<List<FundQueryDTO>> 接口
- 数据服务实现类需要依赖FpProvider,并执行红包查询,参数请参考已有实现
- 模块构建器需要继承 BaseModuleBuilder<NnRedPacketVO>
- 使用 @Component 注解标记为Spring Bean
- 遵循项目代码规范
请生成完整的代码。

AI生成的代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// 1. 数据服务
@Component
public class NnRedPacketDataService implements DataService<List<FundQueryDTO>> {
@Autowired
private FpProvider fpProvider;
@Override
public List<FundQueryDTO> execute(InvocationContext context, JSONObject req) {
// 从配置获取红包池ID
String poolIds = req.getString("nnRedPacketPoolIds");
List<Long> fundPoolIds = Arrays.stream(poolIds.split(","))
.map(Long::parseLong)
.collect(Collectors.toList());
// 查询用户红包
return fpProvider.queryUserFundBuyPoolId(
context,
fundPoolIds,
customRuleId,
securityCode
);
}
}
// 2. 模块VO
@Data
@Builder
public class NnRedPacketVO {
private List<RedPacketItem> redPacketList;
private String totalAmount;
private String expandText;
}
// 3. 模块构建器
@Component
public class NnRedPacketModuleBuilder extends BaseModuleBuilder<NnRedPacketVO> {
@Override
public String getName() {
return "nnRedPacket";
}
@Override
protected NnRedPacketVO doBuild(InvocationContext context) {
// 获取红包数据
List<FundQueryDTO> funds = ContextUtils
.<List<FundQueryDTO>>getBizResult(
NnRedPacketDataService.class.getSimpleName(),
context,
500
)
.orElse(Collections.emptyList());
if (CollectionUtils.isEmpty(funds)) {
return null;
}
// 构建VO
return NnRedPacketVO.builder()
.redPacketList(buildRedPacketList(funds))
.totalAmount(calculateTotalAmount(funds))
.build();
}
private List<RedPacketItem> buildRedPacketList(List<FundQueryDTO> funds) {
return funds.stream()
.map(this::toRedPacketItem)
.collect(Collectors.toList());
}
// ... 其他方法
}

显著的效率提升

Agentic Coding实现了开发效率的显著优化,通过提示词驱动的自动代码生成替代了传统手动编写,虽然Prompt设计需要额外时间,但综合效率提升效果明显。

快速暴露的问题

然而,在后续的需求迭代中,我们发现了严重的问题:

问题1:代码延续性差

现象: 同样的NN业务,第二次让AI实现时,生成的代码风格完全不同

1
2
3
4
5
6
7
8
9
10
11
// 第一次生成(简洁风格)
private String buildDiscountText(Money discount) {
return String.format("省%s元", discount.getYuan());
}
// 第二次生成(冗长风格)
private String buildDiscountText(Money discount) {
BigDecimal yuan = BigDecimal.valueOf(discount.getCent())
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
String yuanStr = yuan.stripTrailingZeros().toPlainString();
return "省" + yuanStr + "元";
}

影响: 同一个项目内,类似功能的实现方式五花八门,维护成本高

问题2:代码风格不一致

现象: AI不了解项目的代码规范,导致生成的代码风格和存量代码不一致。

问题3:团队协同性差

现象: 不同开发者写的Prompt差异大,生成的代码质量参差不齐

  • 新手写的Prompt过于简单,AI生成的代码质量差
  • 老手写的Prompt详细但冗长,难以复用
  • 缺乏统一的Prompt模板和最佳实践

原因分析

这些问题的根本原因在于:AI缺乏项目特定的上下文和约束

  • 没有项目规范: AI不知道项目的代码风格、架构模式、命名规范
  • 没有领域知识: AI不了解淘特导购业务的特定术语和设计模式
  • 没有历史经验: 每次都是”零基础”生成代码,无法从历史代码中学习

这让我们意识到,需要给AI建立”项目规范”和”领域知识”。

Rules约束 - 建立AI的”项目规范”

引入Rules文件

我们开始尝试用Rules文件来约束AI的行为,将项目规范、架构模式、领域知识固化下来。

Rules文件体系:

1
2
3
4
5
6
7
8
.aone_copilot/
├── rules/
│ ├── code-style.aonerule # 代码风格规范
│ ├── project-structure.aonerule # 项目结构规范
│ └── features.aonerule # 功能实现规范
└── tech/
├── xx秒杀-技术方案.md # 具体需求的技术方案
└── xx红包模块-技术方案.md

Rules文件内容示例

代码风格规范(code-style.aonerule)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 代码风格规范

## Java代码规范
- 类名使用大驼峰命名法(PascalCase)
- 方法名和变量名使用小驼峰命名法(camelCase)
- 常量使用全大写,单词间用下划线分隔(CONSTANT_CASE)

## 空值判断
- 集合判空统一使用:CollectionUtils.isEmpty() 或 isNotEmpty()
- 字符串判空统一使用:StringUtils.isBlank() 或 isNotBlank()
- 对象判空统一使用:Objects.isNull() 或 Objects.nonNull()

## 日志规范
- 使用 LogUtil 工具类记录日志
- 错误日志格式:LogUtil.error("类名, 方法名, 错误描述, 关键参数={}", param, exception)

## 注解使用
- Service类使用 @Component 注解
- 数据服务实现 DataService<T> 接口
- 模块构建器继承 BaseModuleBuilder<T>

项目结构规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 项目结构规范
## 包结构
com.alibaba.aladdin.app/
├── module/ # 模块构建器
│ ├── nn/ # NN业务模块
│ ├── seckill/ # 秒杀业务模块
│ └── common/ # 通用模块
├── domain/ # 领域对象
│ ├── module/ # 模块VO(继承ModuleObject)
│ └── [业务名]/ # 业务领域对象(BO、DTO)
├── dataservice/impl/ # 数据服务实现
└── provider/ # 外部服务提供者
## 命名规范
- 数据服务:[业务名]DataService(如 NnRedPacketDataService)
- 模块构建器:[业务名]ModuleBuilder(如 NnFeedsModuleBuilder)
- 模块VO:[业务名]VO(如 NnRedPacketVO)
- 业务BO:[业务名]BO(如 NnRoundFeatureBO)

功能实现规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 功能实现规范
## 数据服务层
- 必须实现 DataService<T> 接口
- 使用 @Component 注解
- execute方法的第一个参数是 InvocationContext
- execute方法的第二个参数是 JSONObject businessReq
示例:
```java
@Component
public class NnRedPacketDataService implements DataService<List<FundQueryDTO>> {
@Override
public List<FundQueryDTO> execute(InvocationContext context, JSONObject businessReq) {
// 实现逻辑
}
}

模块构建器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- 必须继承 BaseModuleBuilder
- 使用 @Component 注解
- 实现 getName()、doBuild()、bottomTransform() 三个方法
- 通过 ContextUtils.getBizResult() 获取数据服务结果
示例:

@Component
public class NnRedPacketModuleBuilder extends BaseModuleBuilder<NnRedPacketVO> {
@Override
public String getName() {
return "nnRedPacket";
}
@Override
protected NnRedPacketVO doBuild(InvocationContext context) {
List<FundQueryDTO> funds = ContextUtils
.<List<FundQueryDTO>>getBizResult(
NnRedPacketDataService.class.getSimpleName(),
context,
500
)
.orElse(Collections.emptyList());
// 构建逻辑
}
}

技术方案模板

除了Rules文件,我们还为每个需求创建技术方案文档,明确定义需要生成的代码:

技术方案示例(NN红包模块-技术方案.md):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
## 业务定义
NN红包模块用于展示用户在NN业务场景下可用的红包列表。
## 业务领域对象
无(复用 FundQueryDTO)
## 模块领域对象
| 对象含义 | 实现方案 | 属性及类型 |
|---------|---------|-----------|
| NN红包模块VO | 新增 | 1. redPacketList:List<RedPacketItem> - 红包列表<br>2. totalAmount:String - 总金额<br>3. expandText:String - 展开文案 |
## 数据服务层
| 数据服务定义 | 实现方案 | execute |
|------------|---------|---------|
| NN红包查询服务 | 新增 | 1. 从配置获取红包池ID列表<br>2. 调用FpProvider查询用户红包<br>3. 过滤可用红包(状态=2,未过期)<br>4. 返回红包列表 |
## 模块构建器
| 模块构建器定义 | 实现方案 | doBuild逻辑 |
|--------------|---------|-------------|
| NN红包模块构建器 | 新增 | 1. 获取红包数据<br>2. 过滤门槛>20元的红包<br>3. 按门槛从小到大排序<br>4. 构建VO |

显著改善的效果

引入Rules文件后,我们看到了明显的改善:

代码一致性:

  • 所有生成的代码都遵循统一的命名规范
  • 项目结构清晰,模块划分明确
  • 代码风格保持一致

开发效率:

  • 技术方案填写时间从2小时降低到20分钟
  • 代码实现时间从1天降低到2小时(需要人工收尾)

团队协作:

  • 技术方案成为团队共同语言
  • Code Review效率提升50%
  • 新人上手时间从1周降低到2天

依然存在的问题

虽然Rules带来了显著改善,但仍存在一些问题:

  1. 需求理解不够深入:AI仍然是基于技术方案”翻译”成代码,对业务语义理解有限
  2. 测试质量参差不齐:虽然能生成单测,但测试用例的通过率和覆盖度仍需人工把关
  3. 文档滞后:代码变更后,文档更新容易遗漏
  4. 依赖关系管理:对于复杂的模块依赖关系,AI处理不够优雅

这些问题让我们思考:能否找到一种方式,让AI能更加规范和延续的coding?

SDD探索 - 规格驱动开发

SDD的引入

近期,我们开始初步尝试SDD(Specification Driven Development,规格驱动开发),使用了Spec Kit工具链。

SDD的核心理念:

规格是唯一真理源(Single Source of Truth)

  • 所有的代码、测试、文档都从规格生成
  • 规格即文档,文档永不过期

设计先于实现

  • 先用自然语言描述”做什么”(规格)
  • 再让AI生成”怎么做”(代码)

可测试性内建

  • 规格中明确定义测试用例
  • 自动生成完整的单元测试

Speckit执行流程

环境准备

我们主要使用了两种工具:

  1. iflow + qwen3 coder plus + spec kit
  2. qwen + qwen3 coder plus + spec kit

文件体系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
├── .specify/
│ ├── memory/
│ │ └── constitution.md
│ ├── scripts/
│ └── templates/
├── specs/
│ └── 001-nn-redpacket-module/
│ ├── checklists/
│ │ └── requirements.md
│ ├── contracts/
│ │ └── api-contract.md
│ ├── data-model.md
│ ├── plan.md
│ ├── quickstart.md
│ ├── research.md
│ └── spec.md
└── req/
└── nn-redpacket.md

speckit.constitution—制定整个项目的原则

这一步会生成项目全局的宪章,constitution.md

以下是部分节选:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
## 核心原则

### I. 模块化服务架构
所有服务必须遵循模块化设计原则,具有明确的关注点分离和定义良好的接口。每个模块应具有单一职责并可独立部署。模块必须以松耦合和高内聚的方式设计,以增强可维护性和可扩展性,遵循最小依赖原则。
### II. 阿里巴巴开发标准
所有代码必须遵循阿里巴巴Java开发指南(基于阿里巴巴Java编码规范)。这包括命名约定、异常处理实践、日志标准、安全最佳实践和性能优化模式。代码必须遵守样式一致性要求,以保持代码库的统一性。
### III. 质量保证实践
全面测试是强制性的:对所有业务逻辑进行单元测试,对服务交互进行集成测试,对API兼容性进行合同测试。代码覆盖率必须保持在80%以上,特别关注关键业务路径。代码质量工具必须集成到CI/CD管道中以执行标准,遵循阿里巴巴开发规范以确保质量和可靠性。
### IV. 模块设计原则
遵循单一职责原则,每个模块都有一个明确的目的。模块必须以松耦合和高内聚的方式设计,遵循关注点分离原则。模块边界应与业务能力和领域上下文对齐。所有模块都遵循最小依赖原则,仅导入必要的依赖项以减少系统复杂性。
### V. 项目架构设计原则
本项目采用分层架构设计,通过模块化组织代码,支持淘特投放业务的各种场景需求。架构层次包括:
1. **接入层**:处理请求接入和协议转换
2. **解决方案层**:业务解决方案的统一入口
3. **子解决方案层**:细粒度的业务处理能力
4. **模块构建层**:按业务功能划分的模块构建器
5. **数据服务层**:负责各种业务数据的获取、处理和封装
6. **外部服务层**:负责调用外部服务并进行模型转换
7. **领域模型层**:定义核心业务对象和数据传输对象
8. **基础设施层**:包含基础组件和框架封装
9. **通用模块层**:公共组件和工具类
### VI. 依赖管理
遵循最小依赖原则:每个模块应只拥有其实际需要的依赖项。避免模块之间的循环依赖。使用依赖注入实现松耦合。定期审核和更新依赖项以最小化安全漏洞。这确保了可维护和高效的代码结构。
### VII. 代码风格一致性
在整个项目中保持一致的代码风格,使用标准化的格式化规则。所有代码在合并前必须通过静态分析检查。一致地遵循设计模式,并对与标准实践的任何偏差提供清晰的证明。这确保了统一的代码规范和样式,符合项目标准。
### VIII. Speckit中文本地化
所有speckit相关文件、文档和配置都应使用中文,以支持本地开发团队。`.specify/`和`specs/`目录中的文件和相关speckit构件必须使用中文,以便本地开发人员更好地理解和维护,同时应为可能服务国际市场面向用户的组件保留国际化支持。
## 安全和合规要求
所有代码必须符合阿里巴巴的安全标准,并在部署前进行强制性安全审查。必须为所有暴露的端点实现适当的身份验证和授权。敏感数据必须根据内部合规要求进行处理。必须扫描依赖项中的安全漏洞。
## 开发工作流程
1. 所有代码更改必须遵循标准的阿里巴巴开发工作流程:功能分支、代码审查、自动化测试和CI/CD管道验证。拉取请求必须通过所有测试并获得指定审阅者的批准后才能合并。除非明确批准进行具有迁移计划的破坏性更改,否则所有更改必须向后兼容。每次更改都必须遵循模块设计原则并保持代码风格一致性。
2. 所有操作不要创建新分支,而是在当前分支下进行
3. 代码生成必须遵循code-generation-prompt.aonerule文件
## 治理
本宪法凌驾于所有其他开发实践之上,必须在存储库中的所有工作中遵循。对本宪法的任何修改都需要正式文档、团队批准和迁移计划。所有PR和代码审查必须验证是否符合这些原则。

speckit.specify—编写规格说明

这一步会新建一个分支,创建spec.md、requirements.md等文件,这里反复修改了多次,主要还是后续的执行不理想的返工。

NN红包模块规格说明(spec.md):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

# NN红包模块规格说明
## 功能概述
NN红包模块用于在NN频道页面展示用户可用的红包列表,帮助用户了解可以使用的优惠。
## 功能需求
### FR-1: 红包数据获取
**描述:** 系统应该能够查询用户在当前NN业务场景下可用的红包
**前置条件:**
- 用户已登录
- 配置了红包池ID(fundPoolIds)
- 配置了规则id(customRuleId)
- 配置了securityCode
**输入:**
- userId:用户ID
- fundPoolIds:红包池ID列表
- customRuleId:自定义规则ID
- securityCode:安全码
**处理逻辑:**
1. 调用FpProvider.queryUserFundBuyPoolId()查询红包
2. 过滤条件:
- 红包状态(payStatus)= 2(可使用)
- 红包未过期(当前时间在startTime和endTime之间)
- 红包门槛 <= 配置的amountThreshold(默认20元)
**输出:**
- 返回符合条件的红包列表
**异常处理:**
- 如果FpProvider调用失败,返回空列表
- 如果用户未登录,返回空列表
...
...
...
**处理逻辑:**
1. 如果红包列表为空,不展示模块(返回null)
2. 构建NnRedPacketVO:
- redPacketList:转换每个红包为RedPacketItem
- totalAmount:计算所有红包金额总和
- expandText:从配置获取展开文案
**输出:** NnRedPacketVO
## 非功能需求
### NFR-1: 性能要求
- 红包查询超时时间:500ms
- 如果超时,返回空列表,不影响页面其他模块
### NFR-2: 可扩展性
- 支持配置不同的红包池ID
- 支持配置不同的门槛限制
## 测试用例
### TC-1: 正常流程 - 有可用红包
**前置条件:**
- 用户ID:123456
- 配置的红包池ID:[1001, 1002]
- FpProvider返回2个可用红包:
- 红包1:金额5元,门槛15元
- 红包2:金额10元,门槛25元
**执行步骤:**
1. 调用 doBuild(context)
**预期结果:**
- 返回的VO不为null
- redPacketList包含2个红包
- totalAmount = "15.00"(5 + 10)
- 红包按门槛排序(红包1在前)
### TC-2: 边界条件 - 红包门槛超限
**前置条件:**
- FpProvider返回1个红包,门槛25元
- 配置的amountThreshold = 20元(2000分)
**预期结果:**
- 返回null(因为门槛超限,红包被过滤)
...
...

speckit.plan—制定实施计划

这一步会生成data-model.md、api.md等几个文件,最重要的是plan.md

speckit.tasks—任务分解

生成task.md

speckit.implement—实现代码

数据服务实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Component
public class NnRedPacketDataService implements DataService<List<FundQueryDTO>> {
@Autowired
private FpProvider fpProvider;
@Override
public List<FundQueryDTO> execute(InvocationContext context, JSONObject businessReq) {
long userId = context.getUserId();
if (userId <= 0) {
return Collections.emptyList();
}
// 获取配置
String poolIdsStr = businessReq.getString("nnRedPacketPoolIds");
List<Long> fundPoolIds = parsePoolIds(poolIdsStr);
Long customRuleId = businessReq.getLong("customRuleId");
String securityCode = businessReq.getString("securityCode");
// 查询红包
List<FundQueryDTO> funds = fpProvider.queryUserFundBuyPoolId(
context, fundPoolIds, customRuleId, securityCode
);
if (CollectionUtils.isEmpty(funds)) {
return Collections.emptyList();
}
// 过滤可用红包(按规格FR-1的过滤条件)
Long amountThreshold = businessReq.getLong("amountThreshold", 2000L);
return funds.stream()
.filter(fund -> fund.getPayStatus() == 2) // 可使用状态
.filter(fund -> isNotExpired(fund)) // 未过期
.filter(fund -> fund.getAmountThreshold() <= amountThreshold) // 门槛限制
.collect(Collectors.toList());
}
private boolean isNotExpired(FundQueryDTO fund) {
long now = System.currentTimeMillis();
return fund.getStartTime() <= now && now <= fund.getEndTime();
}
// ... 其他方法
}

模块构建器实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

@Component
public class NnRedPacketModuleBuilder extends BaseModuleBuilder<NnRedPacketVO> {
@Override
public String getName() {
return "nnRedPacket";
}
@Override
protected NnRedPacketVO doBuild(InvocationContext context) {
// 获取红包数据(FR-1)
List<FundQueryDTO> funds = ContextUtils
.<List<FundQueryDTO>>getBizResult(
NnRedPacketDataService.class.getSimpleName(),
context,
500
)
.orElse(Collections.emptyList());
// 如果无可用红包,不展示模块(FR-3)
if (CollectionUtils.isEmpty(funds)) {
return null;
}
// 红包排序(FR-2)
List<FundQueryDTO> sortedFunds = sortFunds(funds, context);
// 构建VO(FR-3)
return NnRedPacketVO.builder()
.redPacketList(buildRedPacketList(sortedFunds))
.totalAmount(calculateTotalAmount(sortedFunds))
.expandText(getExpandText(context))
.build();
}
/**
* 红包排序:NN专属红包优先,其次按门槛从小到大
* 对应规格 FR-2
*/
private List<FundQueryDTO> sortFunds(List<FundQueryDTO> funds, InvocationContext context) {
JSONObject extraParams = getExtraParams(context);
String nnPoolIdsStr = extraParams.getString("nnRedPacketPoolIds");
Set<Long> nnPoolIds = parsePoolIds(nnPoolIdsStr).stream()
.collect(Collectors.toSet());
return funds.stream()
.sorted(Comparator
.comparing((FundQueryDTO fund) ->
nnPoolIds.contains(fund.getFromFundId()) ? 0 : 1)
.thenComparing(FundQueryDTO::getAmountThreshold))
.collect(Collectors.toList());
}
// ... 其他方法
}

完整的单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
@RunWith(MockitoJUnitRunner.class)
public class NnRedPacketModuleBuilderTest {
@Mock
private InvocationContext context;
@Mock
private NnRedPacketDataService dataService;
@InjectMocks
private NnRedPacketModuleBuilder builder;
/**
* 测试用例 TC-1: 正常流程 - 有可用红包
*/
@Test
public void testBuild_WithAvailableFunds_ShouldReturnVO() {
// 准备数据
List<FundQueryDTO> mockFunds = Arrays.asList(
createFund(1L, 500L, 1500L), // 5元,门槛15元
createFund(2L, 1000L, 2500L) // 10元,门槛25元
);
// Mock
when(dataService.execute(any(), any())).thenReturn(mockFunds);
mockContext(context, "nnRedPacketPoolIds", "1,2");
mockBizResult(context, NnRedPacketDataService.class.getSimpleName(), mockFunds);
// 执行
NnRedPacketVO result = builder.doBuild(context);
// 验证(按规格预期结果)
assertNotNull(result);
assertEquals(2, result.getRedPacketList().size());
assertEquals("15.00", result.getTotalAmount());
// 验证排序:门槛低的在前
assertEquals(1500L, result.getRedPacketList().get(0).getAmountThreshold());
}
/**
* 测试用例 TC-2: 边界条件 - 红包门槛超限
*/
@Test
public void testBuild_ThresholdExceeded_ShouldReturnNull() {
// 准备数据:门槛25元,超过配置的20元
List<FundQueryDTO> mockFunds = Arrays.asList(
createFund(1L, 500L, 2500L)
);
// Mock(配置amountThreshold=2000,即20元)
JSONObject businessReq = new JSONObject();
businessReq.put("amountThreshold", 2000L);
when(dataService.execute(any(), eq(businessReq))).thenReturn(Collections.emptyList());
mockBizResult(context, NnRedPacketDataService.class.getSimpleName(), Collections.emptyList());
// 执行
NnRedPacketVO result = builder.doBuild(context);
// 验证(按规格预期结果)
assertNull(result);
}
/**
* 测试用例 TC-3: 异常场景 - FpProvider返回null
*/
@Test
public void testBuild_ProviderReturnsNull_ShouldReturnNull() {
// Mock
when(dataService.execute(any(), any())).thenReturn(null);
mockBizResult(context, NnRedPacketDataService.class.getSimpleName(), null);
// 执行
NnRedPacketVO result = builder.doBuild(context);
// 验证(按规格预期结果)
assertNull(result);
}
/**
* 测试用例 TC-4: 异常场景 - 用户未登录
*/
@Test
public void testBuild_UserNotLogin_ShouldReturnNull() {
// Mock:userId = 0
when(context.getUserId()).thenReturn(0L);
when(dataService.execute(any(), any())).thenReturn(Collections.emptyList());
mockBizResult(context, NnRedPacketDataService.class.getSimpleName(), Collections.emptyList());
// 执行
NnRedPacketVO result = builder.doBuild(context);
// 验证(按规格预期结果)
assertNull(result);
}
// 辅助方法
private FundQueryDTO createFund(Long id, Long amount, Long threshold) {
FundQueryDTO fund = new FundQueryDTO();
fund.setFundId(id);
fund.setAvailableAmount(amount);
fund.setAmountThreshold(threshold);
fund.setPayStatus(2); // 可使用
fund.setStartTime(System.currentTimeMillis() - 3600000); // 1小时前开始
fund.setEndTime(System.currentTimeMillis() + 3600000); // 1小时后结束
return fund;
}
}

SDD带来的改进

一致性显著提升

代码层面:

  • 所有代码都严格遵循规格说明,消除了理解偏差
  • 不同开发者实现相同规格,代码风格完全一致
  • 代码变更时,必须先更新规格,保证文档与代码同步

业务层面:

  • 产品、开发、测试对需求的理解高度一致
  • 减少了需求理解偏差导致的返工

可测试性大幅提升

测试覆盖:

  • 自动生成的测试用例覆盖了所有正常和异常流程
  • 测试用例与规格说明一一对应,确保完整性
  • 边界条件和异常场景都有明确的测试用例

测试质量:

  • Mock方式规范统一,符合项目最佳实践
  • 断言准确全面,不会遗漏关键验证点
  • 测试代码可读性好,易于维护

可维护性显著改善

文档永不过期:

  • 规格说明就是最准确的文档
  • 任何变更都先更新规格,再同步代码
  • 新人通过阅读规格说明就能快速理解功能

变更影响分析:

  • 修改规格时,清晰知道影响哪些代码模块
  • 依赖关系在规格中明确定义
  • 重构时可以基于规格验证正确性

代码可读性:

  • 代码结构清晰,层次分明
  • 注释完整准确,与规格保持一致
  • 命名规范统一,易于理解

团队协作效率提升

  • 新人通过阅读规格说明快速上手
  • 跨团队协作时,规格成为统一语言
  • 历史需求回溯更容易,规格即完整记录

SDD的问题与挑战

虽然SDD带来了价值,但在实践中也遇到了一些明显的问题:

问题1:规格编写门槛高

现象: 编写高质量的规格说明需要较强的抽象能力和文档编写能力

  • 新手往往写不好规格,过于技术化或过于模糊
  • 规格模板虽然有,但如何填写仍需要经验
  • 不合格的规格对后面的代码实现影响

影响: 对于简单需求,写规格的时间甚至超过直接写代码

问题2:Spec Kit工具链不成熟

遇到的具体问题:

  1. 规格解析不准确
    • AI有时无法正确理解规格中的复杂逻辑
    • 需要用非常精确的语言描述,稍有歧义就可能理解错误
  2. 代码生成质量不稳定
    • 相同的规格,不同时间生成的代码质量差异大
    • 有时生成的代码过于冗长,有时又过于简化
  3. 增量更新困难
    • 规格修改后,很难做到只更新变化的部分
    • 往往需要重新生成整个文件,导致手工修改的部分丢失

问题3:与现有代码库集成困难

现象: 我们的代码库已经有大量历史代码,SDD更适合从零开始的新项目

  • 历史代码缺乏规格说明,无法纳入SDD体系
  • 新老代码风格混杂,维护成本反而增加
  • 团队一部分人用SDD,一部分人用传统方式,协作困难

问题4:学习成本高

数据:

  • 写出合格的第一份规格说明,平均需要3-5次迭代
  • 老员工接受度较低,认为”还不如直接写代码快”

SDD适用场景分析

经过3个月的实践,我们总结出SDD的适用场景:

适合使用SDD:

✅ 全新的项目或模块

✅ 核心业务逻辑,需要长期维护

✅ 复杂度高,需要详细设计的功能

✅ 多人协作的大型需求

✅ 对质量要求极高的场景

不适合使用SDD:

❌ 简单的工具函数或配置修改

❌ 快速验证的实验性功能

❌ 一次性的临时需求

❌ 对现有代码的小修改

当前最佳实践 -

Rules + Agentic Coding + AI文档汇总

融合各阶段优势

核心思路:

  1. 用Rules约束AI
  2. 用技术方案指导实现
  3. 用Agentic Coding快速迭代
  4. 用AI汇总文档保持同步

技术方案模板优化

我们优化了技术方案模板,更加轻量级:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# [需求名称]-技术方案
## 业务定义
[简要描述业务背景和目标,1-2句话]
## 业务领域对象
[如果需要新增/修改BO或DTO,在此说明]
## 模块领域对象
[需要新增/修改的VO对象]
| 对象含义 | 实现方案 | 属性及类型 |
|---------|---------|-----------|
| [对象名] | 新增/修改 | 1. 字段1:类型 - 说明<br>2. 字段2:类型 - 说明 |
## 数据服务层
[需要新增/修改的数据服务]
| 数据服务定义 | 实现方案 | execute逻辑 |
|------------|---------|-----------|
| [服务名] | 新增/复用 | 1. 步骤1<br>2. 步骤2 |
## 模块构建器
[需要新增/修改的模块构建器]
| 模块构建器定义 | 实现方案 | doBuild逻辑 |
|--------------|---------|-------------|
| [构建器名] | 新增/修改 | 1. 获取数据<br>2. 处理逻辑<br>3. 构建VO |

特点:

  1. 比SDD规格更轻量,编写时间从2小时降低到30分钟
  2. 比纯Agentic Coding更规范,有明确的结构约束
  3. 聚焦于”做什么”,而非”怎么做”(实现细节交给AI)

AI文档汇总机制

即:让AI自动维护”整体架构与业务逻辑文档”

文档汇总流程

1
完成需求开发 → 提交AI:"将本次代码逻辑汇总到汇总文档" → AI分析代码 → AI更新文档

Prompt示例:

1
2
3
4
5
6
7
8
9
我刚完成了NN红包模块的开发,请分析以下代码:
- NnRedPacketDataService.java
- NnRedPacketModuleBuilder.java
- NnRedPacketVO.java
然后将其业务逻辑汇总到"NN业务整体架构与逻辑文档.md"中,确保:
1. 描述模块的核心功能和业务价值
2. 说明数据流转过程
3. 列出关键的业务规则和判断逻辑
4. 保持与现有文档的风格一致

架构文档结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# NN业务整体架构与逻辑文档
## 一、业务概述
[业务背景、目标、核心价值]
## 二、整体架构
### 2.1 技术架构
[分层架构图、技术栈]
### 2.2 模块组成
[各个模块的功能和关系]
## 三、核心模块详解
### 3.1 NN Feeds模块
#### 3.1.1 功能说明
[模块的核心功能]
#### 3.1.2 数据流转
[数据从哪里来,经过哪些处理,最终输出什么]
#### 3.1.3 关键逻辑
[重要的业务规则、计算逻辑、判断条件]
#### 3.1.4 代码位置
[对应的类和方法]
### 3.2 NN红包模块
[类似结构]
### 3.3 NN吊钩层模块
[类似结构]
## 四、数据服务层
[各个数据服务的功能和依赖]
## 五、关键流程
[重要的业务流程时序图]
## 六、配置说明
[配置项说明]
## 七、扩展点
[未来可能的扩展方向]

AI汇总效果示例

AI生成的文档片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
### 3.2 NN红包模块
#### 3.2.1 功能说明
NN红包模块用于在NN频道页面展示用户可用的红包列表,帮助用户了解可以使用的优惠,提升转化率。
#### 3.2.2 数据流转
用户请求 → NnRedPacketDataService
→ 调用FpProvider查询用户红包
→ 过滤可用红包(状态=2,未过期,门槛<=20元)
→ 返回红包列表

NnRedPacketModuleBuilder
→ 获取红包数据
→ 红包排序(NN专属优先,门槛从小到大)
→ 构建NnRedPacketVO
→ 返回前端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#### 3.2.3 关键逻辑
**1. 红包过滤规则**
```java
// 只展示满足以下条件的红包:
// 1. 状态为可使用(payStatus = 2)
// 2. 未过期(当前时间在startTime和endTime之间)
// 3. 门槛 <= 配置的amountThreshold(默认20元)
if (fund.getPayStatus() != 2) return false;
if (fund.getStartTime() > now || now > fund.getEndTime()) return false;
if (fund.getAmountThreshold() > amountThreshold) return false;
**2. 红包排序规则**
```java
// 排序规则:
// 1. NN专属红包(配置的nnRedPacketPoolIds)优先
// 2. 其次按门槛从小到大排序
funds.stream()
.sorted(Comparator
.comparing(fund -> nnPoolIds.contains(fund.getFromFundId()) ? 0 : 1)
.thenComparing(FundQueryDTO::getAmountThreshold))
**3. 总金额计算**
```java
// 计算所有可用红包的金额总和
String totalAmount = funds.stream()
.map(FundQueryDTO::getAvailableAmount)
.reduce(0L, Long::sum)
.map(PriceUtils::getPriceFenToYuan)
.orElse("0.00");
#### 3.2.4 代码位置
- 数据服务:`NnRedPacketDataService`
- 位置:`dataservice/impl/nn/NnRedPacketDataService.java`
- 核心方法:`execute(InvocationContext, JSONObject)`
- 模块构建器:`NnRedPacketModuleBuilder`
- 位置:`module/nn/NnRedPacketModuleBuilder.java`
- 核心方法:`doBuild(InvocationContext)`、`sortFunds(List<FundQueryDTO>)`
- 模块VO:`NnRedPacketVO`
- 位置:`domain/module/nn/NnRedPacketVO.java`
#### 3.2.5 配置项
```json
{
"nnRedPacketPoolIds": "1001,1002,1003", // NN专属红包池ID
"amountThreshold": 2000, // 红包门槛上限(分)
"expandText": "展开查看更多" // 展开文案
}

思考总结

在淘特导购业务的AIcoding实践中,我们经历了从简单代码补全到Agentic Coding,再到基于规则和SDD的编程模式的演进过程。每个阶段都有其价值和局限性:

  1. 初期探索让我们认识到AI在编码辅助方面的潜力,但也暴露了缺乏规范指导的问题;
  2. Agentic Coding提升了功能实现的完整性,但可延续性和一致性仍有不足;
  3. 基于规则的模式有效解决了代码规范和架构一致性问题,成为当前的主要实践方式;
  4. SDD尝试虽然在理念上很有价值,但在实际应用中还需要进一步完善。

虽然在SDD编程方面遇到了一些挑战,但我们认为AI规范化编程是未来发展的方向。团队中的同学正在持续探索和优化:

  1. 完善工具链:改进Spec Kit等工具,提升自动化能力
  2. 优化流程整合:更好地将SDD模式与现有开发流程结合
  3. 降低学习成本:通过培训和实践案例帮助团队成员适应新模式
  4. 持续改进规则:根据实践经验不断完善规则定义

我们相信,通过持续的探索和实践,一定能找到更适合团队的AI辅助编程模式,进一步提升开发效率和代码质量。

Kuikly 开发框架笔记

作者 wyanassert
2025年12月30日 17:32

Kuikly 开发框架笔记

Kuikly(Kotlin UI Kit,发音同quickly),是使用Kotlin开发了声明式UI框架,映射到系统原生控件做渲染,最终用KMM(Kotlin Multiplatform Mobile)实现跨端。
Kuikly是一个开发语言高度同源的跨端框架,从业务代码、UI框架、布局层以及渲染层全部使用Kotlin语言(iOS渲染层是OC),这样不仅减少跨语言通信的性能成本,而且开发体验上更纯粹和高效。编译产物上,Android端采用原生的AAR方式,而iOS端通过KMM编译生成.framework,这样就不仅保证了原生开发体验,也保证了原生性能。如果希望实现动态化,Android端可以通过KMM编译成SO,iOS端可以编译成JS(KMM已经可以编译成Wasm,未来有稳定版本后就可以正式使用)。Kuikly具有优异的原生开发体验,相比于Hippy,更符合终端开发习惯。

跨端框架对比

对比维度 H5 Hippy Hippy + 预渲染/预加载 Hippy-SSR + 强缓存 Kuikly
性能表现 首屏 >1300ms 首屏在 800ms~1000ms 首屏 <300ms 非首次 ~350ms
首次 ~800ms
安卓原生 iOS接近原生
方案说明 传统的基于 WebView 的前端开发方案,拥有最广的通用性 Hippy 相对于 WebView 是一个更轻量的 UI 引擎,内存占用只有 20MB,能实现 Hippy 的主进程运行 在 Hippy 的基础上,针对核心页面加入预渲染/预加载能力,进一步提高启动性能 在 Hippy 的基础上引入服务端渲染 + 强缓存能力,能针对所有页面进一步解决非预渲染场景下的启动问题和版本覆盖问题 Hippy 固有的终端+JS 的跨端方案,对于 iOS 端能力受限,需要新的能力来突破前端的 JS 边界,而基于 KMM 的 Kuikly 则是直接建立在纯终端之上,能做到更好的能力扩展
存在问题 问题1:消耗资源多,启动慢(>500ms)
• WebView 内存占用超过 200MB
• 安卓 X5 需要 tool 进程启动,动态预加载 5 分钟内会自动释放,命中率低

问题2:缓存策略不可控
• 只能基于 HTTP 的缓存策略,无法通过编程的方式控制
问题1:版本无法实时更新
• Hippy 通过异步拉取模式进行更新,需要用户二次访问才能生效

问题2:JS 包大小影响启动性能
• Hippy 引擎启动快,但是需要动态载入业务 JS 包,JS 包越大加载启动越慢
问题1:预渲染命中率低
• 动态预渲染的整体命中率不到 10%
• 后端请求放大

问题2:终端资源占用
• 在预渲染模式下,除了加载 Hippy 引擎外还需要运行业务代码,整体内存占用超过 40MB
问题1:首次访问的加载问题
• 首次载入 JS 包时需要请求网络,同时由于没有本地缓存,白屏时间较长

问题2:可交互耗时仍有优化空间
• 服务端渲染能解决首屏问题,但可交互仍需要加载完整的 JS(>1s)

进一步思考:
• 版本覆盖问题
• 动态模式下性能问题
• 能力与接口丰富度
-
优化措施 WebView 启动慢:
• 预加载 tool 进程
• 点击/网络请求并行
• 预截图

缓存策略不可控:
• 升级 HTTP2(server push)
• 离线包提高静态资源缓存命中率
• 基于 PWA 通过编程的方式控制缓存策略
版本覆盖问题:
• 支持预下载能力
• 支持同步更新策略

JS 包大小问题:
• JS 分包策略
• 支持离线包能力
预渲染命中率低:
• 只针对特定入口启动
• 优化预渲染策略:红点+活跃用户

资源占用问题:
• 低端机器降级为预加载
• 长时间不启动自动释放
首次访问无缓存白屏:
• 内置骨架屏+动态数据
• 缓存数据预下发
• 终端强缓存能力

提升可交互耗时:
• 点击/网络请求并行
• JS 分包策略
• JS 内嵌直出能力
• JS 提前载入内存
-
安装包大小 RN7.5MB, Hippy 3.8MB 0.3MB

Kuikly 和 ComposeDSL 的对比

无标题思维导图
最终选择方向 2

Kuikly Compose最终架构方案

对比官方Compose 区别

特性 Kuikly 官方
平台支持 iOS, Android, 鸿蒙、H5、小程序 iOS, Android, PC, H5
动态更新 支持 不支持
渲染层 纯原生 Skia渲染
包体积 较小 较大

Kuikly 架构图

Kuikly 跨端渲染原理


  1. 将 Kotlin 代码编译成各个平台可执行产物
  2. 运行时调用各平台 Native 层渲染接口进行渲染
    1. RN 框架的流程 (三个虚拟树)
      1. 创建JS DOM 树 (平台无关)
      2. C++ 影子树 (平台无关)
      3. 原生渲染树
    2. 问题 - 跨语言序列化反序列化开销
    3. Kotlin 只维护一个树, 直接映射到原生渲染
      1. 在 Kotlin 层构建原型树
      2. 在 Kotlin完成测量和布局(影子树)
      3. 各平台支持统一的渲染接口, 如创建/删除/插入/设置属性/设置节点位置
      4. 转到平台各自原生渲染层,
  3. 原生渲染层, 渲染分为三种类型承接:
    1. View 通用属性
      1. Modifier.border 映射到 View.border
      2. .background 映射到 View.background
      3. .scale 映射到 View.transform
    2. 原子组件
      1. Text () 创建组件 TextView
      2. Image() 创建组件 ImageView
      3. LazyXXX() 创建组件 ScrollView
    3. Canvas 渲染
      1. Canvan { drawRect, drawCircle} 转发原生 CanvasView -> drawRect/ drawCircle

Kuikly DSL语法

  1. 声明式 api: 在原类拓展一个 init 的语法糖, 比如 TextView, 对应语法糖是 Text,
  2. 使用@DslMarker解决不能 Text 不应该嵌套的问题

Diff 性能

对比维度 类RN Flutter Compose SwiftUI
框架类型 跨平台框架 跨平台UI框架 Android声明式UI iOS声明式UI
Diff方案 运行时虚拟Dom Tree Diff 运行时Element Tree Diff 编译时+运行时Diff 编译时+运行时Diff
Diff性能 O(n) O(n) O(1-n) O(1-n)
优化策略 虚拟DOM树对比 Element树对比 编译时优化+运行时增量更新 编译时优化+运行时增量更新

调研结果:现有框架没有完全O(1)的解决方案

Kuikly 解决方案:


if -> vif
else -> velse
elseif -> velseif
when -> vbind
for -> vfor
开发的时候需要额外学习成本, 渲染时候能精确更新, 实现 O(1)的性能

怎么基于 Kotlin实现响应式?

  1. 基于 Kotlin 的属性委托能力 by observable() 将属性变成响应式属性
  2. 属性 getter/setter 触发时候, 触发依赖收集/订阅分发
  3. 只收集单向依赖, 破解死循环

比鸿蒙原生还快


鸿蒙性能优化关键点

  1. llvm 的 CPU Feature参数错误导致内联(inline)生效, 修正后性能提升 30%
  2. 鸿蒙软件模拟了线程私有参数, 导致频繁 throw 的时候性能低下, 提升 30%
  3. GC 优化

Qcon 上海 2025 Vibe Coding 在代码生成与协作中的实践与思考

作者 wyanassert
2025年12月26日 00:28

Vibe Coding 在代码生成与协作中的实践与思考 - 向邦宇

自我介绍

  • 多年从事研发者工具开发,包括内部 AI Coding 工具和 Web IDE 工具
  • 从 2023 年开始,从内部 Copilot 转型到 AI Agent 方向
  • 作为产品提供方,接触了大量内部用户,观察他们如何使用工具以及遇到的问题

演讲选题思考

  • Vibe Coding 概念出现几个月,但并非确定性的东西
  • 不同人对 Vibe Coding 理解不同,使用的工具也不同
  • 从两个视角分享:用户使用场景和问题、产品提供方的思考和解决方案

演讲结构

  1. 简单介绍业界和内部有哪些 Vibe Coding 工具在使用
  2. 用户在使用 Vibe Coding 工具过程中遇到的问题
  3. 作为 Vibe Coding 工具核心主导者的思考
  4. 国产模型适配过程中遇到的问题和解决方案

Vibe Coding 产品形态

当前工具分类的模糊性

  • 大家对 Vibe Coding 工具的理解和分类不够清晰
  • 每个工具都有人在用,但缺乏明确的定位

不同 Vibe Coding 工具的主要区别

1. Native IDE(原生集成开发环境)

  • 代表产品:Cursor、Cline、阿里 Qoder 等
  • 特点:以独立 IDE 形式存在
  • 优势:灵活性高,功能完整

2. IDE Plugin(IDE 插件)

  • 代表产品:内部 Aone Copilot 等
  • 基于现有 IDE(主要是 VS Code 或 JetBrains)的插件形式
  • 内部用户使用插件是比较主流的习惯
  • 灵活性可能没有 Native IDE 那么高

3. Web IDE

  • 入口在浏览器上
  • 整个执行在远端容器里,可能是沙箱环境
  • 优势:
    • 解决信任问题和云端执行的安全问题
    • 更适合协作:多个同学可以在同一个 Web IDE 里进行同步协作和分享
    • 跨平台支持

4. CLI 命令行工具

  • 代表产品:Copilot CLI
  • 最初没想到会受欢迎,但实际上非常受主流研发欢迎
  • 未来可能在被集成的方式(如 CI/CD)中执行一些自动化任务
  • 在这种场景下会有更高的可能性

内部 Vibe Coding 工具的使用实践

Aone Copilot(依托于 IDE 的Wibe Agent工具)

  • 内部协作多年的产品
  • 用户规模:数万用户,每周几千周活
  • 主要使用场景:
    • 代码生成
    • Bug 修复
    • 代码分析
  • 用户分布:后端场景渗透率较高,前端用户更倾向使用 Native IDE(如 Cursor 或 Qoder)

AI Agent(异步容器执行的 Agent 工具)

  • 以 Web 端发起的容器内运行的异步任务工具
  • 核心特点:用户通过自然语言发起任务
  • 在异步容器里拉起 Agent,Agent 自己调用工具(搜索工具、文件读写工具、Shell 工具等)
  • 用户角色更加多元:
    • 主要用户:后端开发
    • 其他用户:测试、前端、算法、产品、运营、设计、运维等
  • 任务类型丰富多元:
    • 代码分析
    • 代码改动
    • 单元测试
    • 代码生成
    • 文案方案调研等

工具尤其是 Agent 带来的效率提升

数据观察(从 4 月份开始的 Agent 模式)

代码提交量的显著提升

  • 蓝色线:高频用户(使用 Agent 模式)
  • 橙色线:其他用户
  • Agent 模式下,高频用户的每日代码提交行数有非常大的提升
  • 到 9 月份,高频用户每天提交 540-560 行代码,其他用户只有 400 多行
  • 至少从定量指标看,Agent 模式对提效肯定有帮助

用户分层现象

  • Top 10% 用户的代码提交量是其他人的两倍
  • 认为 Agent 对人的提效可能大于两倍,因为大量工作在协同、开会等非编码环节
  • Top 10% 用户的 Copilot 消耗占整体消耗的 80%

AI 新的应用场景

  • 单元测试由 AI 生成的提交代码占比越来越高
  • JDK 升级、NPM 包升级、SDK 升级等工作已经可以由 AI 完成
    • JDK 11 及以上版本升级场景,内部基本全部交给工具处理
  • 数据分析、数据整理工作部分交给 AI
  • 传统必须由人完成的任务现在由 Agent 完成:
    • 测试过程中的截图
    • 压测过程中的重复任务
  • 过去成本过高无法做的事情现在可以做:
    • 一次发布是否会引起其他相关系统故障
    • 每一行代码对其他系统的影响分析

用户使用 Vibe Coding 工具遇到的问题

用户情绪问题

AI 表现不足导致的崩溃

  • 后台日志中大量用户抱怨”AI 太笨了”等激动的话
  • 用户反复删除代码、修改代码的行为
  • 无论公司内部还是社区,都能看到用户因 Agent 能力不足而崩溃

GitHub 上的”八荣八耻”提示词

  • 用户分享给 Agent 的提示词规范
  • 例如:”以不能修改原始代码为荣”等

5.2 代码质量问题

我们看到的 Vibe Coding 的问题是多方面的

  1. 代码风格不一致
    • 生成的代码质量和风格差异较大
    • 在存量仓库里写代码时,可能以自己的风格编写,而非遵循项目规范
  2. 边界条件处理不完善
    • 对复杂业务逻辑的边界情况处理不够充分
  3. 性能缺陷
    • 生成的代码存在性能问题
  4. 安全漏洞
    • SQL 注入类漏洞严重
    • 斯坦福研究表明:AI 生成代码中注入类漏洞比例约 45%
    • 其他安全问题:
      • 接口注入
      • XSS 攻击
      • 逻辑错误
      • 边界条件处理错误
      • 异常控制
  • 数字越界

代码逻辑自洽问题

  • AI 在代码生成过程中会有非常多的”自洽”
  • 案例:数据去重函数及其对应的单元测试
    • 测试通过率 100%
    • 针对代码做了单测
    • 但如果让 AI 同时写单测和业务逻辑,无法保证质量
    • 会出现”自己和自己对话”的情况
  • 建议:至少有一项(单测或业务逻辑)是人去 review 的

调试和维护困难

调试时间增加

  • 使用工具后,调试时间增加 30%-50%
  1. 黑盒问题
    • Vibe Coding 更倾向于黑盒代码逻辑生成
    • 虽然最后会让人确认代码 diff 才能提交
    • 但生成过程是黑盒,不会有人认真看每一条
    • AI 生成代码像”黑魔法”,出问题时完全不知道如何下手
    • 技术债务越来越深
  2. 上下文理解局限
    • 存量任务的业务逻辑可能积累十几年
    • 有些代码为什么要这么写?有些代码是否能去掉?对 AI 来说都很困难
    • Vibe Coding 工具缺乏全局思维
    • 生成的代码模块化程度不够,代码耦合度很高
    • 解决方案:RepoWiki, DeepWiki 等方案
  3. 缺乏可追溯性
    • Vibe Coding 一次性生成大量代码
    • AI 无法知道:是新需求导致代码写错,还是一开始就写错了
      • 缺乏版本管理和版本概念
      • 一次生成代码出错后,不知道从哪个地方回滚
    • 现有方法:
      • 每次改完测试通过后提交一个 commit, 下次可以从这个 commit 回滚
      • 使用 Cursor 等回滚工具
    • 但仍然缺乏可追溯性,用户无法做版本管理,无法回到正确状态,只能重来

Vibe Coding 工具普遍不会使用常用的调试工具

  • AI 普遍不会使用人类常用的调试工具
  • 传统”古法编程”中,开发者大量使用 Debug、断点等工具
  • 浏览器上也可以做调试
  • 但让 Vibe Coding 工具使用这些调试工具去找堆栈、找问题非常困难
  • 工具能力缺失导致的问题:
    • AI 只能打大量的 console.log, 让用户执行完后,把 log 或控制台的报错、打印信息再粘贴给工具
    • 需要人介入
    • 不是高效的模式
  • 大模型的调试手段比较单一,传统调试方法无法被大模型用起来

Vibe Coding 工具本身存在的问题

1. 稳定性和成功率

  • 最大的问题
  • Vibe Coding 工具执行时间很长(30 秒到 5 分钟)
  • 不是每次都能成功
  • 失败原因:
    • 模型问题
    • 工具反馈不对
    • 某些工具出错
    • IDE 本身不稳定
  • 用户体验:用过一次发现不稳定,在时间紧、任务重时就不会再使用

2. 交互界面设计问题

  • 大量 Vibe Coding 工具产品频繁改版,功能丢失
  • 案例:Devin
    • 改版后用户找不到原来的功能
    • 工具里增加越来越多功能(剧本、MCP 市场、知识引入等)
    • 现在再看又没有了
  • 交互界面频繁改版

3. 沟通和交互障碍

  • 理解能力不足:AI 无法完全理解用户意图,需要反复确认
  • 不同场景下确认的必要性不同:
    • 复杂任务:需要确认(如 SpecCoding - 先建需求、生成设计稿、再让 AI 做)
    • 简单任务:不需要确认,需要 Agent 自由探索

4. 长链路任务执行能力不足

  • 无法维持长期上下文
  • Agent 大模型的 token 有上限
  • 上下文过长时,记忆和召回能力不足

5. 工程工作流程中断

  • 大量工具(IDE, CLI, Web Agent 等)各有擅长领域
  • 无法让用户在相同流程或上下文窗口里解决问题
  • 案例:在 IDE 里做一件事,需要切换CLI, 重新给 Agent介绍诉求和需求
  • 导致用户在不同工具间频繁切换

成本问题

成本问题导致各方不满意

1. Agent 的 Token 消耗巨大

  • 代码补全场景:
    • 调用频次高
    • 单次消耗约 4000 Tokens
  • Vibe Coding 任务:
    • 单次消耗百万级甚至千万级 Tokens
    • 原因:
      • 上下文更长
      • 交互轮次多(几十上百次)

2. Vibe Coding 加速带来的技术债务

  • 技术债务反而对 Agent 提出更高要求

3. 成本上升导致产品方频繁调整计费逻辑

  • 产品方(Cursor、Qoder 等)频繁切换计费逻辑
  • 没有任何一款产品敢保证包月或无限次使用
  • 成本压力导致产品设计不断调整:
    • 压缩上下文
    • 削减能力
  • 恶性循环:
    • 成本降低 → 成功率下降 → 用户多试几次 → 成本又上升
  • 产品方为了活下去压缩成本,但效果变差,用户要多试几次,成本又上去
  • 使用闭源模型(Claude、GPT-4、GPT-5)后成本难以下降

5. 缺乏规模效应

  • 大模型应用有规模效应,但不明显
  • 不存在”用户规模越大,成本就越低”的效应
  • Token 成本是固定的

产品自身也遇到的挑战

产品的演进导致模型成本越来越高

Token 消耗的演进

  1. 代码补全场景

    • 单个任务:约 4000 Tokens 输入
    • 输出:20-30 Tokens
  2. Chat 模式

    • 单个任务:约 1000+ Tokens 输入
    • 输出:约 4000+ Tokens
  3. 单个 Agent 模式(IDE/CLI)

    • 单个任务:约 10 万级 Tokens
  4. 具备独立容器的 Vibe Coding Agent

    • 能广泛使用各种工具
    • 实现各种内容、各种任务类型
    • 单个任务:百万级 Tokens
  5. 未来的架构(Cursor, TRAE 等):

    • 单个任务:可能上亿 Tokens

产品设计的两个同等重要目标

  1. 用户满意度
  2. 成本控制能够匹配用户规模

产品形态的问题

1. 产品界面区分度不够

  • 无论 Chat 产品还是 Vibe Coding 产品,都处于摸索阶段
  • 模型能力变化使产品不断变化
  • 所有产品都是一个对话框(ChatGPT、DeepSeek、AI 产品)
  • 用户难以区分不同产品的区别

2. 用户缺乏引导

  • 给用户一个对话框,但用户不知道应该输入什么
  • “Prompt Free”现象
  • 不同工具有不同使用场景,但用户往往一刀切
  • 用户印象中产品应该能做什么,但试用后发现达不到目标
  • 功能学习成本高,使用频次低
  • 留存率非常低(Devin 等 Vibe Coding 工具都存在这个问题)

3. 缺乏一站式功能闭环

  • 无法在一个产品里解决所有问题
  • 案例:
    • 一个 Vibe Coding Agent 能解决复杂产品问题
    • 但又能解决小白/初学者问题
    • 小白面临的问题不仅是代码能否写完,还有发布、部署、调试等
  • 发展过程中存在各种调整

安全风险问题

案例 1:Cursor 删除本地代码

  • Cursor 把用户本地代码删掉
  • 类似的小 case 还有一些

案例 2:Anthropic Claude 被劫持

  • 今年出现好几次
  • Claude 被劫持后,让 Vibe Coding 工具在用户网络里探测漏洞
  • 写代码把敏感信息暴露出来

内网使用的安全考虑

  • 不能完全相信 Vibe Coding 工具
  • 供应链攻击问题
  • 开源代码的风险:
    • 很多人在开源社区里种木马
    • 不注意可能拉取到的 SDK 或代码存在漏洞
  • Vibe Coding 工具对代码和电脑有基本控制权
  • 能够自由探索,找到系统漏洞并攻击

Agent 建设过程中一些经验分享

All In One 架构导致成本几句上升

最初的 All In One 架构问题

  • 建设 Vibe Agent 时采用的架构就是一个输入框
  • 外围:MCP 工具、knowledge、Playbook 一些剧本
  • 最外围:场景图(数据处理、后端开发、前端开发、代码浏览、风险管理等)

All In One 架构的问题

  1. 所有工具都放入沙箱
  2. Context 特别长,无法压缩成本
  3. 最开始一个任务调用 Claude 模型需要几百块钱成本,非常高
  4. 任务成功率低
  5. All-in-one 时,所有工具和 knowledge 放在一起:
    • 成本特别高
    • 占用特别长
    • 消耗大量资源
  6. 很难针对不同场景进行调优
    • 案例:与 Bolt 等产品对比,发现它们在前端场景有很好实现
    • 但自己的产品在前端场景做得不够让人满意

知识和数据建设

  1. 代码数据建设
    • 通过建设 DeepWiki、RepoWiki、Embedding 数据库
    • 增强对整体代码库的搜索、理解和搜索能力
  2. 研发行为数据
    • 构建、CI/CR、发布、监控等行为数据
    • 背靠整个集团内部平台(发布平台、代码平台等)
    • 建立代码数据和需求数据与这些行为的组合
  3. 文档知识库
    • 问题:文档知识库无法被Agent 直接用起来
    • 原因
      • 文档可能过时
      • 前后矛盾
      • 图文混杂
      • 存在错误信息
    • 直接把这些信息丢给 Agent 会产生误导
    • 解决方案
      • 不用传统 RAG 技术解决
      • 建立中间层
      • 面向 Agent 的数据处理协议
  4. 开发者知识沉淀
    • 很多知识不在文档里,也不在代码里,在开发者脑子里
    • 需要产品设计帮助用户沉淀这些知识
    • 不是靠任何东西生成,而是靠人来写

Agent 对上下文记忆处理的几个核心

记忆处理机制

  • 写入
  • 提取
  • 压缩
  • 隔离

  1. 任务管理和技能交互
  2. 文件操作
    • 读写编辑
    • 文件管理
  3. 命令行和执行监控
    • Agent 可以执行命令行
    • 有些命令是长耗时的
    • 如何监听命令结果
    • 超时后如何 kill 掉
  4. 浏览器自动化工具
    • 执行网页操作
    • 使用 Playwright 等方式点击页面, 帮助登录或解决交互问题
  5. 手机相关工具
  6. 多媒体工具
  7. 开发工具
    • 将用户写的代码部署、调试到指定地方
  8. 协作工具
    • 团队协作
    • 任务分享给其他人
    • 基于任务继续协作
  9. 高级功能
    • 并行执行优化
    • 网络搜索

成本控制方案

Token 消耗优化历程

  • 最开始:400-1000 万 Tokens/任务
  • 意识到这是最严重的问题
  • 通过各种设计和操作降低 Token 成本

国产模型适配实践

为什么要拥抱国产开源模型

国外闭源模型的风险

  1. 成本高
        - 复杂问题往往很长
        - 能让 Agent 在复杂任务下跑起来的模型非常贵

  2. 隐私问题:
        - 闭源模型存在合规风险

  3. 被限流和被降质:
        - 即使用同一个供应商的模型
        - 不同时候表现也不一样
        - 有时会出现格式不对、陷入循环等问题

  4. 国外模型的备案问题:
        - C 端用户使用可能存在备案问题

国产模型在短链和长链任务的问题

短链任务表现已经很好
长链任务还存在一些问题

国产模型存在的问题

  1. 死循环问题:
        - Agent 有很多选择和路径
        - 执行过程中可能陷入某种循环
        - 反复出不来
        - 案例:反复打开一个文件、反复执行某一项命令
  2. 格式遵循能力不足:
        - 常见问题:XML 标签格式不准确
        - 前后无法匹配
        - 导致无法被正确解析
        - 容易失败
  3. 指令遵循问题:
        - 在高达百万 Token 的上下文下
        - System Prompt 里给的规则
        - 模型如果没有被训练到,很难使用这些工具
        - 运行过程中会忘记某些指令
  4. 全局智能问题:
        - 观察发现模型存在全局任务理解能力缺陷
        - 容易陷入”一步一步看”的情况
        - Token 消耗大
        - 步骤时间长

解决方案

  1. 针对稳定性问题:
        - 主流模型的切换和重试
  2. 应对速度慢和 Infra 稳定性问题:
        - 当模型输出被截断时
        - 做一些有效输出或续写设计
  3. 健康检查和死循环检测:
        - 在 Agent 里做检测
        - 针对重复执行某个指令的死循环场景
        - 相同错误点的无限循环问题
        - 陷入明显错误逻辑时能够检查到
  4. 格式检查和修复:
        - 检测到不完整标签时
        - 通过堆栈方式自动补齐缺失的结束标签来修复

重试机制

主备切换

工具的解析与自动修复

成果

  • 在内部基本已经把国外模型全部去掉
  • 内部全部使用国产模型
  • 实时检测任务是否进入死循环
  • 进入死循环后进行干预:
    • 把后面任务执行截掉
    • 对任务总体做 summary 压缩
    • 让它继续往下走

模板化设计解决 Prompt Free 问题

Prompt Free 问题

普通用户/小白用户面临的问题

  1. 不知道产品能干什么
  2. 知道要干什么,但不知道如何提要求
  3. 不知道在产品里使用什么样的工具或知识
  4. 导致任务成功率很低
  5. Token 消耗也很大

模板化解决方案:

  • 某个垂直任务,有人通过很多探索做成功了(很满意)能否把它抽象成一套模板?
  • 针对不同垂直场景不断积累这些模板
  • 使成功率变高,Token 消耗变低
  • 面对对话框时给用户一些灵感

模板的本质

  • 一套工具的组合
  • 一个知识的组合

使用流程

  1. 用户看到对话框
  2. 先选一个模板
  3. 再执行任务

效果

  • 约 50% 的用户任务现在都用了模板
  • 使用模板后任务成功率提升

总结下:

  • 固化 Prompt
  • 固化工具
  • 固化知识
  • 形成模板后,用户生成任务时先选模板,再执行

架构上的更多创新

长上下文任务的问题

案例

  • 先做深度调研
    • 要先写一个网页
    • 再写一个 PPT
  • 单 Agent 的问题:
    • 上下文非常长
    • 需要频繁做 summary、压缩
    • 裁剪工具输出
    • 才能保证任务质量高
  • 没有子 Agent 之前的主任务需要频繁做所有琐事
    • 从上到下每个步骤:
      • 调网页
      • 打开网页
      • 把网页内容写下来
      • 做 summary
      • 写 PPT
      • 写网页
    • 项目越来越长, 任务执行完成率非常低, 效果也不好

Agents 拓扑解决方案

灵感来源

  • Manus 1.5, 提出 Agents 拓扑概念
  • Agent 本身也是一个工具

实现方式

  • 假设有一个 Deep Research 工具,做得很好
  • 可以自己做网页搜索、做 summary
  • 主 Agent 只要调用它就够了
  • 把这部分工具抽象出来,成为一个工具

演进路径

  • 过去:Function Call
  • 后来:LLM Call
  • 现在:用 Agent 来做
  • 把一个 Agent 当作一个工具去做子任务

Qcon 上海 2025 智能体时代的强化学习:AReaL框架与Agent最佳实践

作者 wyanassert
2025年12月24日 00:05

智能体时代的强化学习:AReaL框架与Agent最佳实践

以 RL 打造 Agent

两个核心”暴论”

  1. Agent是未来五年AGI时代最重要的事。
  2. 强化学习是构建Agent最关键的技术。

强化学习的历史发展与突破

强化学习的早期认知

大多数人对强化学习的认知来源于:

  • AlphaGo:DeepMind用强化学习训练围棋智能体,击败李世石和柯洁
  • OpenAI打Dota:2019年用强化学习击败两届世界冠军OG
  • 其他游戏AI:腾讯打王者荣耀、星际争霸等

当年的强化学习智能体主要都是打游戏的,与大模型驱动的AGI时代似乎没有太大关系。

强化学习与大模型的结合转折点

2020-2022年的关键变化

GPT-3 API的问题

  • 2020年OpenAI推出GPT-3 API时存在严重问题
  • 例子:输入”explain the moon landing to a six year old in a few sentences”
  • GPT-3会输出重复内容:”explain the serious gravity, explain the serious relative, explain blah blah blah”
  • 原因:大模型训练基于next token prediction,但用户给的是指令(instruction following problem)

注: “Next Token Prediction”(下一个 token 预测)是大语言模型(LLM)的核心机制。简单来说,它的意思是:给定一段文本的前面部分,模型预测接下来最可能出现的“token”是什么。

RLHF技术的突破

  • OpenAI花了两年时间解决这个问题
  • 2022年推出InstructGPT,采用RLHF(Reinforcement Learning from Human Feedback)技术
  • 方法:找人标注数据,判断哪些回答遵从指令,哪些不遵从
  • 训练奖励模型,然后用强化学习让模型探索获得最高分数的回答
  • 结果:同样的基座模型,有没有强化学习决定了好用还是不好用

注: RLHF(Reinforcement Learning from Human Feedback,基于人类反馈的强化学习)是一种用于对齐大语言模型(LLM)的技术。它的核心目标是:让模型的输出更符合人类的偏好、价值观和意图,而不仅仅是“语法正确”或“统计上常见”。

强化学习推动AGI产品发展的三个阶段

  • 第一阶段:2022年ChatGPT

    • 由RLHF技术引爆,让大家第一次感受到AGI能力
    • 强化学习捅破了窗户纸,让AGI能力真正可用
  • 第二阶段:2024年推理模型(Reasoning Model)

    • 也称为思考模型(Thinking Model)
    • 特点:给模型一个问题后,它会先想一会,输出大量thinking token
    • 例子:帮我算个24点,思考模型(比如 deepseek)会先在”草稿纸”上写10分钟(输出thinking token),然后给答案
    • 技术:也是强化学习驱动,模型自己探索如何思考, 思考什么,自己判断答案对不对, 也就产生了推理模型
    • 训练范式与RLHF类似,但判断标准可能不同
  • 第三阶段:2025年Agent模型

    • 基于Agent的强化学习技术
    • 代表产品:ChatGPT Deep Research 等

Agent产品的发展与特点

成功的Agent产品案例

  • ChatGPT Deep Research
    • 2024年第一个比较成功的Agent产品
    • 功能:给它一个topic,帮你做研究
    • 工作流程:
      • 花很多时间思考
      • 调用工具,在网上搜索很多topic
      • 可能运行20分钟到1小时
      • 最终给出非常详实、有大量引用和reference的报告
  • Manus /ChatGPT Agent / Kimi Agent Mode
    • 功能更丰富,可以帮你做PPT
    • 在Sandbox(沙盒)环境中工作:
      • 读取PDF文件
      • 在阅读器中打开PDF
      • 存储PDF文件
      • 编辑和创建文件
      • 在虚拟机中进行各种操作

Agent能力的演进

从Deep Research到Manus的发展体现了Agent能力的进步:

  • Deep Research:除了对话,可以调用搜索工具、浏览器工具,将信息放在Context Window中处理
  • Manus:更进一步,加上了Sandbox工程AI,相当于有了自己的电脑

AI的能力演进:

  1. 有了脑子(大模型)
  2. 有了草稿纸和笔(Context Window)
  3. 有了一台自己的电脑(Sandbox)

产品发展趋势分析

  • 用户交互的变化
    • ChatGPT时代:需要很长的prompt,详细描述要做什么
    • Agent时代:用户说的话越来越抽象,越来越少
  • AI能力的变化
    • ChatGPT:1秒钟给出文本输出
    • Thinking Model:1-2分钟思考后给出答案
    • Agent Model:1小时处理复杂任务,主动行动
    • 未来: 牛马 AI, AI一直在做事, 主动帮人安排
  • 从Reactive到Proactive的转变
    • 传统模式:用户告诉AI做什么(Reactive)
    • 未来趋势:AI主动准备,告诉用户要不要(Proactive)
    • 例子:OpenAI的ChatGPT Plus每天主动推送早报等内容

未来愿景

理想的AI助手具体技术化来讲:

  • 信息模糊处理:人很难把想做的事讲清楚
  • 个性化:每个人的需求不一样
  • 主动规划:主动安排和执行任务
  • 提前工作:AI不需要休息,可以一直工作

什么是好的 Agent 团队

  • 组织 AI 化
  • 技术栈完整
  • 持续高速0-1 创新, 高效迭代

为什么Agent需要RL(强化学习)

市面上Agent 有各种 framework, 这些框架主要通过拖拉拽的方式构建Agent工作流,但对于复杂的Agent问题存在局限性。

强化学习解决的三大核心问题

问题一:处理不确定性和冲突信息

  • 案例:阿里CTO是谁?

    • 阿里和蚂蚁有很多子公司,每个公司都有CTO
    • 搜索”蚂蚁CTO”会得到很多不同的结果
    • 需要AI去理解和判断才能做出正确回答
  • 案例:退票问题

    • 用户说”退票”,但上下文可能很不确定
    • 退什么票?需要AI主动提问澄清

问题二:长期记忆和个性化

  • 案例:美团小美推荐
    • 我说”要吃晚饭,要清淡点”
    • AI推荐白灼生菜等蔬菜
    • 但我从来不点蔬菜,喜欢吃肉
    • “清淡点”对我可能意味着”清淡点的肉”
    • 需要从很长的记录中挖掘个性化信息

问题三:海量工具和模型选择

  • 案例:Reddit上的模型组合使用
    • Claude写代码很聪明但Context Window短且贵
    • Gemini写代码不够聪明但Context Window长且便宜
    • 用户发现可以用Claude调用Gemini:让Gemini读代码,然后扔给Claude写
    • 相当于”聪明的人指挥体力无限的傻子干活”
    • 这种最佳实践应该由AI自己探索出来,而不是人工定义规则

强化学习的统一解决方案

强化学习可以用统一的框架解决这些复杂问题:

  • 让AI在环境中自己探索
  • 涌现出处理复杂任务的能力
  • 比规则和Workflow更灵活和强大

搜索智能体案例深度分析-看似简单的问题实际很复杂

问题案例:伦敦奥运会中国金牌数

表面上的简单

  • 问题:伦敦奥运会中国拿多少块金银铜牌?
  • 看起来很简单,百度搜索就能找到答案
  • 官网显示:中国队拿了38块金牌,是2012年历史第二高的成绩

实际的复杂性

  • 正确答案应该是39枚金牌
  • 原因:2012年伦敦奥运会女子田径竞走项目
  • 中国派出三位选手,当时拿了第3、4、5名
  • 后来第1、2名被查出禁药,被剥夺奖牌资格
  • 11年后(2023年),中国选手获得了补发的金牌
  • 所以现在问中国奥运会金牌数,答案应该是39枚

现有产品的表现
测试了多个产品:

  • DeeSeek:搜出38枚金牌
  • ChatGLM:38枚金牌
  • ChatGPT:搜到了39枚金牌的信息,说”有一些资料显示数字略有差异,39枚金牌”,但最后结论还是38枚金牌(因为大量信息都是38枚)
  • ChatGPT Agent Mode:会答对

传统方法vs强化学习方法

传统Multi-Agent System方法

需要构建复杂的多智能体系统:

  • 搜索Agent
  • 核查Agent
  • 调用知识的Agent
  • 检验Agent
  • 需要很长很复杂的流程

强化学习方法

极简设计

  • 一个模型
  • 两个工具:搜索工具 + 点击网页工具
  • 让模型在环境中循环探索

实际效果

  • 第5轮搜到39枚金牌的新闻
  • 开始疯狂核查
  • 经过60多轮迭代
  • 最终确定正确答案是39枚金牌
  • 还具有泛化能力,可以添加更强的工具
  • 32B模型可以在准确度上超越商用产品

强化学习的两大优势

  1. 简单: 简化Agent的workflow, 不需要复杂的多智能体系统设计
  2. 涌现: 让AI涌现出复杂的多步推理能力, 通过探索自动获得复杂能力

Agent RL 的核心难点

强化学习面临的三大挑战

要做好强化学习,必须解决三个问题:

  1. Infra和算法:强化学习算法运算速度很慢很慢
  2. 数据:训练数据的获取和质量, 强化学习的数据是很缺很缺德, 预训练数据可以在网上扒, 但强化学习的数据不太能直接网上扒
  3. 环境:Sandbox等执行环境的构建

如何全栈解决 Agent RL 的难点

Infra(基础设施)和算法优化

速度慢的根本原因

强化学习的三个流程

  1. 生成:让模型在环境中交互生成数据
  2. 评分:用奖励模型计算奖励
  3. 训练:放到训练集中训练

复杂性分析

  • 涵盖了三种完全不同的计算模块
  • 预训练只有训练,SFT只有训练,评测只有评测
  • 强化学习包含:训练、评测、在线生成、Sandbox等
  • 是一个算法编排了多种完全不同计算模式的复杂系统

算法与系统协同设计的重要性

为什么需要协同设计

  • 强化学习算法创新很容易碰到系统瓶颈
  • 四个系统模块(推理/训练/环境/算法整合)中任何一个打满都会成为瓶颈
  • 强化学习算法很容易打到系统瓶颈

团队组织建议

  • 做算法的同学需要了解Infra
  • 做Infra的同学需要了解算法
  • 最好能坐在一起工作, 这是加快创新节奏的重要方式

具体的性能瓶颈

搜索智能体的统计数据

  • 平均搜索时间:要调用 google 搜索引擎, 一个batch 5-10分钟
  • 长尾效应严重:特别难的prompt需要1-2小时
  • 问题:如果每个batch都要等最慢的那个,一天24小时只能更新12-24次
  • 导致大量CPU/GPU等待时间

AReaL的解决方案:异步架构

核心思想:推理不能等

  • 一部分卡不停地做推理,没有等待
  • 训练也没有等待,有数据就训练
  • 中间用随时更新参数的方式
  • 如果推理到一半需要更新参数,就停下来更新,然后用新参数继续rollout
  • 实现完全没有系统资源浪费

技术创新

  • 系统上做异步调整
  • 算法上做相应调整以适应异步更新
  • 在Agent场景上实现5倍加速,且没有效果损失

训练数据问题

数据稀缺的问题

  • 预训练可以直接从网上获取数据
  • 强化学习的训练数据不能直接从网上获取
  • 一般问题都跟简单, 用户提出的复杂问题很少,难以挖掘复杂问题的测试集

数据合成解决方案

Agenic合成数据方法

  1. 从网页上获取答案(搜索比较简单,从答案开始)
  2. 从答案构造问题
  3. 不断让问题变得更加复杂
  4. 评估问题,保证问题和答案匹配正确
  5. 难度检查:问题不能太难也不能太简单,需要适合强化学习自我提升的难度
  6. 构造出适合的训练数据

开源贡献

  • 数据、代码和脚本都已开源
  • 帮助社区训练更好的Agent产品

环境构建 - Aworld 项目

  • 主要是Sandbox等执行环境的构建
  • 未来会开源更多的Sandbox项目
  • 帮助大家训练更好的Agent产品

让更多人用 RL 训练更好的 Agent

AReaL团队发展历程与经验总结

团队发展时间线

  • 2020年:开始做开源学术项目,多智能体强化学习框架
  • 2022年:第一个大规模游戏场景可用的强化学习分布式训练框架
  • 2023年:当时最快的RLHF框架
  • 2024年:开始做AReaL,专注Agent AI

技术循环的有趣观察

回到原点的循环

  • 2025年的强化学习与当年打游戏很像
  • 有个大模型在”玩游戏”(Sandbox可以是浏览器或电脑)
  • 遇到的问题与打游戏类似:有黑盒环境,很慢,不能修改游戏规则
  • 五年后技术回到了当年的原点
  • 系统设计和算法技术都有循环

重要的经验教训

技术需要两个条件才能发挥价值

  1. 技术需要对的时间
    • 强化学习如果在2022年以前,大家很难感知到价值
    • 不是大家的错,而是技术没有在对的时间被感知
  2. 技术需要好的产品承载
    • 强化学习技术如果不是因为ChatGPT、RLHF、Agent model,大家可能也感知不到
    • 技术本身可能没有价值,需要好的产品去承载才能发挥更大价值

团队理念

  • 技术一定要产品化, 所有技术同学都应该尽可能把技术产品化
  • 希望创造能够实现AGI的Agent产品, 成为支持产品持续进化的平台

总结与展望

核心观点回顾

  1. Agent是AGI时代最重要的事情:从产品发展趋势和技术演进可以看出Agent的重要性
  2. 强化学习是Agent的最关键技术:能够统一解决Agent面临的复杂问题,让AI涌现出复杂能力

技术发展趋势

  • 从简单的对话模型到能够主动行动的Agent
  • 从Reactive到Proactive的转变
  • 从规则驱动到强化学习驱动的智能涌现
  • 算法与系统协同设计的重要性日益凸显

未来展望

  • Agent产品将越来越智能和主动
  • 强化学习技术将在Agent领域发挥更大作用
  • 需要更好的基础设施、数据和环境支持
  • 技术产品化是实现价值的关键路径

Qcon 上海 2025 商汤 从炫技到实用:AI 产品价值闭环

作者 wyanassert
2025年12月23日 15:53

商汤科技”从炫技到实用:AI 产品价值闭环”演讲大纲及内容整理

AI 企业落地现状分析

MIT 调研数据

  • 95% 的企业 AI 落地失败:MIT 调研显示,过去一年多超过 95% 的企业侧 AI 落地项目失败,只有 5% 的企业在 PNL(损益表)上看到了 AI 的价值
  • 技术与企业节奏错配:技术发展过快,企业在节奏和决心上存在错配
  • 自建效率低下:企业自建 AI 解决方案的成功效率是外部专业供应商的 1/3
  • 前台应用效果不佳:虽然期望 AI 在前台工作带来价值,但现在证明有效的主要是后台自动化
  • 员工与管理层利益冲突:CEO 希望 AI 降本增效,但员工担心失业,会自己采购 AI 工具而不使用企业内部的 AI 系统

企业 AI 探索历程

  • 早期阶段:全参数微调、预训练(算力要求高)
  • 中期发展:微调、强化学习
  • 当前状态:不再关注模型本身,转向各种 Agent(营销 Agent、客服 Agent、数据 Agent 等)

智能体(Agent)的定义与现状

Gartner 报告对智能体的严格定义

  • 智能体洗白现象:许多低代码产品、RPA 产品重新包装为智能体概念
  • 非智能体的产品
    • 模型本身不是智能体
    • RPA 不是智能体
    • 仅结合 RAG 的产品不是智能体
    • 简单的意图分发 chatbot 不是智能体

真正智能体的核心特征

  • 完整闭环:感知 → 思考 → 决策 → 行动 → 反思
    • 思考:面向任务主动选择规划路径
    • 反思:过程中发现问题及时修正
  • 企业客户不关心技术黑盒,只关心端到端的解决方案和确定性的高精度结果

C 端与 B 端的差异
Agent 看上去效果很好, 但是要抽卡, C 端声量高,但企业侧落地率低

  • B 端要求
    • 确定性、高精度场景
    • 不接受”抽卡”式的随机结果
    • 需要在高精度下解决企业问题

大模型解决的核心问题

  • 开放式场景:大模型主要解决开放式场景问题
  • 确定性场景不适用:规则明确、容错率低的场景不建议使用大模型, AI 无法生成100%正确的答案
  • 传统信息化的局限:如果场景非常确定,传统企业信息化建设已能满足需求, 不需要用大模型, 但AI 可以改善交互体验,但会带来精度下降和不确定性, 是不符合企业要求的, 看下来目前 AI 对企业侧还没有完全 ready

市场机遇与政策支持

政策红利

  • 人工智能+ 政策:类比 10 年前的”互联网+”政策,催生了 BAT 等头部企业
  • 具体目标:2027 年实现 70% 以上的终端智能和智能体应用
  • 市场空间:政策落地后将有配套实施政策,市场需求旺盛
  • 供给不足:供给侧还无法完全解决市场需求, 有巨大的空间
  • 蓝海机遇:怎么为企业和个人提供巨大的商业化价值

不同层级的价值需求

企业 AI 价值价值是什么?

  • 企业层面:管理价值,战略部署实施,标准程度低但企业价值高
  • 团队层面:协同价值,解决部门间沟通协同、部门墙等问题
  • 个人层面:降本增效,包容程度高但企业价值低

从下到上, AI 对企业的价值越高; 从上到下, 标准化程度越高

  • 效率瓶颈:企业效率瓶颈不在个人,而在部门间协同
  • 沟通策略:与不同层级人员沟通需要针对其关注点

价值实现的挑战

中国开源模型发展迅速,许多企业开始自己部署开源模型(如文心一言、千问等)

  • 采购额度低:上半年公开招投标的大模型一体机采购额仅 1400 万
  • 热度与实际落地的差距:虽然 AI 热度很高,但企业真正大额采购和使用的比例很低
  • 根本原因:企业需要的不是模型本身,而是场景化的价值和可量化的提升

商汤的 AI 原生产品策略

  • 能力工程:底层技术能力
  • 数据飞轮:数据处理和循环利用
  • 交互优化:用户体验提升
  • 工作流协作:企业流程整合

合作伙伴策略

  • 行业 Know-how:与垂直领域合作伙伴结合
  • 专业分工:商汤专注底层 AI 能力,合作伙伴提供行业专业知识
  • 创业趋势:越来越多行业专家选择 AI 创业,寻求专业 AI 公司合作

AI 面向个人使用工具的愿景

  • PC 时代:Office 套件,基于电脑能力实现专业模板和原子化能力
  • 互联网时代:云协同,垂直场景工具(如 figma)
  • AI 时代:跨平台、跨数据源,从过程交付到价值交付

AI 时代的特点

  • 数据处理能力:处理大量非结构化、结构化数据
  • 知识关联:强大的知识关联能力
  • 场景适配:复杂场景、多样场景的理解和适配
  • 人机协同:结果导向的人机协同工作模式

商汤数据分析智能体产品

产品整体图

  • 2024年1月:发布国内第一个数据分析智能体, (那时候还没有智能体这个概念)
  • 核心发现:大模型具有强大的工具调用能力
  • 技术能力
    • 调用本地电脑和沙盒
    • 代码编写能力
    • 数据可视化
    • 文件分析

给跟大模型离的不是那么近的用户群体做推广

  • 真实用户:老师、学生、医生、制造业管理者、大巴司机等
  • 用户特点:日常工作中与 AI 接触不多,但发现产品能实际解决问题
  • 产品迭代:从 1.0 对话框产品模式到 2.0 针对简单/复杂场景数据分析能力,计划年底推出 3.0, 仍需要平衡模型与用户体验

产品精度保证

技术路径选择

为什么不选择 ChatBI/Text2SQL:

  • SQL 局限性:SQL 主要用于数据库查询,不适合业务数据分析
  • 精度问题:SQL 语言数据量有限,模型精度只有 80% 多,企业无法接受
  • 数据库依赖:需要成熟数据库,但很多企业数据以表格、文档、图片形式存在, 即使头部公司的数据库建设也不完善

对于企业用户推广:

  • 客户验证:头部客户几百个真实用户实测
  • 用户角色:运营、商务等业务人员
  • 精度要求:95% 以上准确率,保证企业侧可用性

C 端到 B 端的转化路径

  • 增量价值:数据分析为员工提供增量价值,不会威胁工作
  • 实际案例:销售人员用于商机动态管理和查询
  • 采购动机:业务部门主动要求私有化部署或系统对接
  • 正向激励:帮助员工更好管理工作,对 KPI 和职业发展有正向作用

突破传统模式

  • 自下而上:业务部门主动找到产品方
  • 打破壁垒:解决自顶向下和自底向上的矛盾

技术精度突破

  • 百分百准确率:大模型做不到 100%, 但是在语义相对清晰的情况下,大模型调用工具可达到 100% 准确率
  • 适用场景
    • 持续计算
    • 数据匹配
    • 数理计算
    • 异常检测
  • 前提条件:用户语义相对清晰

企业问题解决

  • 系统连接:连接企业系统和本地数据
  • 行业知识结合:结合企业和行业 Know-how 进行深度分析
  • 数仓集成:与企业数据仓库结合

失败案例分析

金融公司财务部门推广失败:

  • 容忍度低:财务数字绝对不能出错, 场景容忍度非常低
  • 专业性高:财务人员操作极其专业,10 秒能解决的问题觉得 AI 太慢
  • 用户反馈差:导致后续无人使用

成功场景特征

  • 增量价值明显:对用户有明显的增量价值
  • 容忍度较高:场景容忍度比较高
  • 适用场景
    • 企业运营:趋势分析报告、供应链管理
    • 商务产品:不是非黑即白的工作环节
    • 数据分析能力不强的业务人员

传统 BI 的问题

  • 低代码困境
    • 专业开发人员觉得拖拉拽太复杂,宁可写代码
    • 业务人员觉得拖拉拽太复杂,宁可找人帮忙
  • 定位模糊:既要又要还要,导致上不下的产品定位

产品功能优化

二次编辑能力

  • 核心需求:AI 生成结果不是 100%,但最终交付必须是 100%
  • 支持格式:Excel、PPT、文本等可二次编辑文档
  • 表格编辑:针对格式、数字、颜色等的二次编辑功能

用户体验优化, 相比精度提升 1-2 个点,快速编辑功能用户感知更强, 显著提高用户黏性

任务规划模块(2.0 功能)

开发背景

  • 用户调研发现:60% 用户满意,40% 觉得不可用
  • 不可用原因
    • 用户不知道自己的需求
    • 分析数据不全
    • 无法给出更好的分析结果

解决方案

  • 意图完善:判断用户意图是否完整,意图不完整的话, 通过引导式方式帮助用户明确需求
    • 数据补充:通过数据调用、联网检索等获取更多专业数据
  • 任务拆解:帮助用户做分析思路大纲拆解,用户确认后给出最终结果

任务规划的产品理念

  • 像向领导汇报工作一样,先确认需求再执行
  • 解决一句话生成复杂结果但需求不清晰的问题
  • 避免等待很长时间后发现结果不符合预期

商业模式与合作策略

  • 商汤优势:算法、ToB 交付能力
  • 商汤劣势:垂直领域行业 Know-how
  • 策略选择:专注行业适应性强的通用场景

行业覆盖

  • 通用性强:数据分析流程相对通用,类似代码
  • 广泛应用:教育、医疗、金融、零售等各行各业
  • 合作模式:与合作伙伴结合行业 Know-how,商汤提供技术底层

AI vs 传统 BI 的差异

  • 定位差异:
    • AI 不是要代替 BI, AI 主要是做数据库清洗整合、跨系统融合, 在已有数据要素基础上,结合行业 Know-how 做深度分析
  • 推动方式差异:
    • 传统 BI:IT 部门牵头
    • AI 方案:业务部门发起,IT 部门配合落地部署

解决的问题:

  • 平台与业务部门协调:解决过去平台和业务部门关系不友好的问题
  • 双赢结果:帮助平台部门完成 KPI,帮助业务部门找到有用产品

客户案例分析

头部消费电子公司案例

  • 时间线:2023年7月接触,年底正式上线
  • 业务痛点
    • SMB 部门大量业务运营数据分散在各系统
    • 业务人员不擅长 Python 数据分析
    • IT 提单需要 2 周到 1 个月才能生成报告, 业务很难快速发展

解决方案与效果

  • 实施周期:1 个月系统设计,2 个月内完全上线 POC,月底付费
  • 人力投入:仅需 2 人,传统 BI 可能需要 10 人以上
  • 效果提升
    • 分析周期缩短 90% 以上
    • 超过 70% 业务人员明显受益

企业服务方法论

  • 头部企业服务流程
    1. 业务部门试用:优先找业务部门
    2. 需求收集:收集业务需求
    3. 内部部署:与 IT 人员共同构建平台
    4. 种子用户测试:内部招募种子用户
    5. 大批量上线:测试成功后大规模推广
  • 小企业服务模式
    • 轻量化推广:相对简化的推广流程 saas

时间优化, 从最早的 3 个月缩短到最快 2 个月, 但私有化还是很难

市场推广策略

客户沟通重点

  • 不谈技术:不讲模型、不讲 benchmark
  • 关注价值:重点讲案例、效率提升、收入增长

客户选择标准

  • 有资金实力:有预算支持
  • IT 实力:有一定的 IT 实施能力
  • 合作意愿:愿意共建和探索
    比如金山

市场推广节奏

  • 当前阶段:头部企业和C 端用户为主
  • 中腰部市场:预计 2026-2027 年才会完全起来
  • 策略重点:与行业最强企业合作,打造标杆案例后向下推广

总结与展望

  • 从炫技到实用:AI 产品必须解决实际问题,创造可量化价值
  • 场景选择关键:选择合适的应用场景比技术本身更重要
  • 价值闭环:从技术能力到用户价值到商业价值的完整闭环

Qcon 上海 2025 快手 Al ×大前端性能稳定性:快手亿级 DAU下的智能诊断实践

作者 wyanassert
2025年12月22日 20:39

AI × 大前端性能稳定性:快手亿级 DAU 下的智能诊断实践

近期AI赛道异常“内卷”,硅谷甚至出现了“996”乃至“007”的新闻。AI在编码(如Cursor、Anthropic)和解决复杂问题(如ACM竞赛夺冠、IMO金牌水平)上的表现,似乎已超越大部分程序员。

这引发了一个普遍的焦虑:AI coding + AI debug 是否将形成一个完美闭环,从而替代程序员?
然而,在快手这样拥有亿级日活(DAU)的复杂业务场景中,我们的实践表明,需要冷静看待这一议题。AI并非替代者,而是团队产出的放大器。今天的分享,将围绕快手在性能稳定性领域如何利用AI进行智能诊断与实践,揭示AI在真实工业场景中扮演的角色。

快⼿性能稳定性背景

发展历程

快手移动端稳定性建设经历了四个清晰的阶段:

  • L1 基础可观测(2019): 自研APM,替换Bugly,建立基础监控能力。
  • L2 工具平台化(2021): 专注于具体稳定性问题的治理,诞生了KOOM等开源工具。
  • L3 体系化运营(2024): 构建故障防御体系,推出Holmes(排障)、Ekko(应急处置)等系统。
  • L4 智能化探索(2025~至今): 引入AI,追求成本与效率的最优解。

每个阶段都基于上一阶段的成果进行迭代,这与移动互联网发展的节奏同步。

当下与未来的核心挑战

尽管硬件性能(如iPhone)已提升百倍,软件架构(如GMPC)演进多年,但大前端的性能稳定性问题远未解决。复杂性体现在多个维度:

  • 业务复杂: 用户操作、数据、物理环境千变万化,不可穷举。
  • 技术栈复杂: 原生、React Native、Flutter、H5、小程序等多栈并存,运行时机制、内存模型、线程模型差异巨大。
  • 系统机制复杂: 深入底层(ART、V8、内核),疑难杂症多。
  • 协作复杂: 跨团队快速迭代,质量保障难度高。

从算法复杂度视角看,我们解决问题的“算法”本质未变,但“输入”却因业务增长和技术栈扩张(如新增鸿蒙)而急剧增加,导致问题规模(年报警事件超150起,必解问题超2000个)庞大。

团队困境:资源错配与成长瓶颈

我们观察到团队中一个普遍的困境:

  • 专家工程师深陷于解决只有他们能处理的复杂Bug,时间被占满。
  • 普通工程师因经验不足无法解决这些Bug,难以快速成长。
  • 结果是,两者都无法有效成长,形成恶性循环,最终影响系统稳定性。

AI的机遇正在于此——它有望成为打破这一循环的放大器,将专家经验沉淀和复制,赋能整个团队。

AI x 性能稳定性介绍

AI x 稳定性:整体策略与架构设计

战略聚焦:从问题处置切入

稳定性体系覆盖研发生命周期多个环节(开发、测试、监控、排障、应急处置)。我们选择从 “问题处置” 切入,因为这里是消耗研发时间最多的“重灾区”。问题处置又可细分为:

  • 根因排障(慢性病治疗): 复杂的、偶发的、需要深度推理的问题。
  • 应急处置(急诊抢救): 突发的、需要快速止损的线上故障。
    这与大语言模型(LLM)在信息总结、逻辑推理、方案生成方面的能力高度契合。

实施架构:面向Agent的演进

我们判断,AI在工程领域的落地形态将是 “Agent(智能体)” 。因此,我们从一开始就以可扩展的Agent框架为基础进行架构设计。

我们的性能稳定性Agent架构分为四层:

  • Agent业务层: 承载具体场景的智能体,如根因修复Agent、故障应急Agent、指标巡检Agent。
  • Agent产品层: 定义与用户的交互形态,如生成Kim报告、自动提交MR修复、多轮对话、流式响应。
  • Agent框架层: 提供技术支撑。我们选择了灵活性强的AutoGen框架,支持Agent编排、链式规划、图编排,以及ReAct、CoT等推理策略,并能与Claude Code、Gemini CLI等协同。
  • Agent基建层:
    • AI基建: 灵活选型模型(轻量/深度/多模态),通过MCP(Model Context Protocol)将内部平台(如监控平台Keep)工具化,结合RAG进行知识增强。
    • 服务基建: 确保Agent系统本身可观测、可调试、可降级、成本可控。这是系统能否稳定运行的关键。

实践一:实践:AI 辅助根因排障

AI辅助根因排障——从“破案”到“自动修复”

从棘手案例说起


一个典型的NPE(空指针)崩溃,堆栈全是系统代码,无业务逻辑。它仅在特定活动场景下偶发,现场信息缺失,线下难以复现。直接将此堆栈扔给ChatGPT,它能解决吗? 实践表明,非常困难。

调研数据显示,96%的研发认为日常排障有痛点,其中69%认为现场信息太少,50%认为日志太多。行业数据也指出,开发者35-50%的时间花在调试验证上。这印证了我们的新范式:“Code is cheap, show me the (bug-free) fix.”

排障的本质与AI能力边界

排障本质上是逆向推理的认知活动,与侦探破案高度相似:

  1. 观察与数据收集(勘查现场)
  2. 提出假设(推测嫌疑人)
  3. 设计并执行实验(验证推测)
  4. 确认解决方案(定罪)

AI的能力在此链条上并非均匀:

  • 擅长区(激发与引导): 信息总结、模式匹配、基于专家经验规则进行初步分类。
  • 薄弱区(需人机协同): 模型能力有上限,对垂直、私域工具的使用需要学习。
  • 瓶颈区(需分治规避): 受限于推理深度、上下文长度,易产生幻觉。对于“越查越深”的Bug,需要人工提前拆解步骤。

核心工具:Holmes——动静结合的排障“现场还原”

我们自研了Holmes排障工具,核心思路是动静结合

  • 静态信息(Tombstone): 日志、崩溃堆栈、内存快照。相当于“死亡现场”。
  • 动态信息(Debugger): 调试器、性能分析器、动态追踪。相当于“一步一步死给你看”。

特别是Holmes UI视图,它能在崩溃时捕获:

  • ViewTree元信息: 布局、状态、文本、图片资源。
  • TouchEvent元信息: 触摸事件响应链和轨迹。
  • Activity/Fragment元信息: 生命周期状态。
    通过AI逆向推理,可以从这些信息中还原用户操作路径,极大辅助定位问题(例如,精确指出是点击了哪个按钮后发生的崩溃)。

实施路径:Agent编排与上下文工程

面对Holmes采集的海量、复杂信息,我们通过Agent编排来让AI消化:

  1. 故障概览Agent: 先汇总基本信息(版本、堆栈、基础代码上下文)。
  2. 基于规则分类: 根据专家经验规则,将问题分配给专项Agent(如UI崩溃Agent、空指针Agent)。
  3. 专项排障Agent: 例如UI崩溃Agent,它会调用MCP工具获取详细的UI日志和源码,进行分析,最终生成修复Diff或报告。


提升准确率的关键在于“上下文工程”,目标是达到“适定问题”状态:

  • 欠定问题(信息太少): 会导致输出模糊、幻觉。
  • 超定问题(噪声太多): 会稀释注意力,导致误导。
  • 适定问题(恰到好处): 为Agent提供单一职责、直接相关的上下文,才能获得确定性解。我们通过Few-shot示例和量化标准来逼近这一状态。

AI x 根因排障:效果展示

拓展:AI火焰图分析

性能分析的火焰图数据量巨大(十几秒可能产生60MB数据),分析门槛高、效率低、易遗漏。

我们的方案是:

  • 数据预处理: 对原始火焰图数据进行压缩、初筛、关键事件关联。
  • AI分析层: 让AI理解性能数据,自动分析卡顿、启动、帧率、功耗等多维度问题,直接定位到源码并给出优化建议,将分析过程从“小时级”缩短到“分钟级”。

实践二:AI 加速应急处置

AI加速故障应急处置——与时间赛跑

应急场景的挑战

iOS 26升级导致大量历史版本App崩溃为例。传统手段各有局限:

  • 商店更新: 覆盖率低(一周约50%),用户流失风险大。
  • 回滚: 对系统级变更无效。
  • 热修复: 涉及大量版本和历史代码,工作量大,且最关键的是不够“急”

应急处置的核心在于时效性,必须与故障扩散赛跑。

核心武器:Ekko——线上“时光倒流”

我们自研了Ekko安全气垫系统,其核心思想是:在崩溃发生后、应用闪退前,动态修改程序执行流,让其“跳回”安全状态继续执行,实现类似游戏“R技能”的时光倒流效果。

Ekko 崩溃阻断:覆盖所有崩溃类型

Ekko是 “售后方案” ,只在崩溃发生时触发,避免了无异常用户端的性能损耗,保证了安全性。

AI赋能应急处置流程

即使有了Ekko,配置和使用它依然复杂(需指定跳转地址、恢复上下文等),在紧急状态下人工操作易出错、易遗漏。

我们引入故障应急处置Agent,实现:

  1. 自动分析: Agent接收报警,自动分析故障维度(如操作系统、芯片型号、版本分布),快速界定影响范围。
  2. 辅助决策: 根据分析结果,建议是否启用以及如何配置Ekko兜底。
  3. 生成与发布: 自动生成兜底配置,并串联流水线完成白名单、审核、灰度、全量发布流程。


在“黑天鹅”事件中(如某次误操作导致千万级崩溃),AI冷静、全面的分析能力,能有效避免人在高压下的决策失误。

总结与展望:认知提升与人机协同

Agent开发的核心感悟

  • 思维切换(Thinking in LLM): 从图灵机的确定性思维,转向理解LLM的概率自回归本质。明确其能力边界(擅长/薄弱/瓶颈区),知道何时用模型、何时用程序、何时用工具。
  • 识别与释放瓶颈: 提示词工程无法突破模型认知上限,但能激发其表现。在模型推理深度有限的前提下,需基于专家知识提前做好任务拆解(分治)。
  • 评测体系至关重要: 建立科学的Agent能力评估体系,其重要性不亚于上下文工程。这关乎迭代效率和成本控制。

回顾初心:AI是放大器,而非替代者

回到最初的焦虑,Linus Torvalds的观点值得深思:“代码的审查和维护本身就充满挑战。” AI不会改变这一本质,而是帮助我们更好地应对它。

我们的结论是:

  • 人类弱化(生产力释放): AI将接管“体力型”排障和“死记硬背型”任务。
  • 人类强化(思考力升级): 工程师将更聚焦于辨别因果、验证结果、系统性思考、创造性实验以及深度的业务理解与战略决策

未来展望

在快手亿级DAU的复杂战场上,AI × 性能稳定性的探索刚刚启航。未来将是人机协同(Human in/on the Loop) 的深度结合。我们应积极拥抱AI,将其作为强大的杠杆,释放工程师的创造力,共同应对大前端领域越发复杂的稳定性挑战,奔赴星辰大海。

Qcon 上海 2025 小红书 AI Coding 实践:PRD 到代码直出的探索

作者 wyanassert
2025年12月22日 17:41

AI Coding 实践:PRD 到代码直出的探索

  • 分享分为四个环节:
    1. AI Coding 在客户端领域的发展阶段与现状
    2. 客户端AI Coding的关键解法
    3. 实际业务场景与需求分析
    4. 总结与展望

AI Coding 发展史和现状

AI模型发展速览

自2017年Google提出Transformer后,AI在各领域实现突破。
2023年起,大语言模型商业化加速,年增速达30倍以上。
AICoding 领域是发展最快的学科之一,因为反馈机制明确(“对就是对,错就是错”)。

AI Coding 发展的五个阶段(人机协作视角)

52ae54dc38a96fffaffd614f87545d5d

阶段 描述 人机角色 典型能力
L1 人类主导,Agent实时辅助 人主导,AI辅助 代码提示(如GitHub Copilot)
L2 人类布置任务,Agent生成代码 人布置单一任务 单一任务代码生成
L3 人类设定范围,Agent推进多环节流程 人设定范围,AI推进流程 生成方案 + 生成代码
L4 人类输入PRD,Agent端到端交付 人输入PRD,AI端到端交付 需求解析 + 架构设计 + 编码
L5 人定义目标,多Agent分工协作 人定义目标,多AI协作 多Agent模拟完整软件团队
  • L4阶段 前端领域已有产品(如“Lovable”),

“直出”型产品的现状与挑战

  • 文字稿提到
    • 前端已有“直出”产品(如Lovable、Bolt.new),可用自然语言直接生成可运行应用。
    • 客户端领域曾有一款叫 Builder.ai 的产品,但在2025年7月左右“爆雷”,据称背后有700多名工程师,被质疑是否真为AI驱动。公司估值从18亿跌至零,客户端直出仍存巨大挑战。

客户端AI Coding的独特困境

从技术栈视角看客户端

  • 技术栈碎片化
    • 前端:标准化高(HTML/CSS/JS)、框架集中(React/Vue)。
    • 客户端:平台碎片化(iOS/Android API版本差异)、框架分散(SwiftUI、UIKit、Jetpack Compose等)。
  • 构建与调试
    • 客户端编译耗时、真机调试必要,反馈循环慢。
  • 开发模式
    • 前端热重载实时反馈;客户端生命周期复杂,架构模式多样(MVVM、VIPER等)。

从AI 模型视角看客户端

  • 构建与调试复杂:编译时间长、真机调试必要,反馈循环缓慢。
  • 训练数据稀缺:高质量客户端代码多在企业内部未开源,公开数据规模小。
  • 代码模式多样:架构演进复杂(如Android从Activity到MVVM+Compose),上下文理解成本高。

结论

“前端开发像是在标准化、开放的乐高环境中工作;客户端则像是在碎片化、半封闭的复杂系统中进行精密工程。”


客户端AI Coding的关键解法


Mobile-SWE-bench

科学评测体系的建立:从SWE-bench到Mobile-SWE-bench

  • **SWE-bench**:由普林斯顿与芝加哥大学推出,基于真实GitHub Issue,要求AI生成PR来修复问题,以单元测试通过率为评测标准。

  • 局限性:侧重于Bug修复而非功能实现,项目多集中后端,缺少移动端特有考量(如UI还原、多模态输入)。

  • 移动端评测 Mobile-SWE-bench

    • 核心要素:高质量真实PRD、多模态输入(PRD+Figma)、详细测试用例、历史Commit基线、多维度评测方法。
    • 评测方法:人工评测、自动化测试、渲染树约束检查、视觉语言模型评估。

热门Coding Agents表现如何


把整个需求的测评级分成三类, 可以看到哪怕是业界比较火的一些模型放在测试集中表现也
一般, 30%已经算是很高了.
为什么这些 Code Agent 都表现不佳?

PRD的拆解与微调:将需求转化为结构化任务

PRD 是 “产品需求文档”(Product Requirements Document) 的缩写. 在传统的软件和产品开发流程中,PRD 是一个核心文档。它由产品经理(或业务分析师)撰写,详细描述了一个产品、功能或项目应该做什么、为谁而做以及要达到什么目标。

一个典型的 PRD 通常包含:

  1. 背景与目标: 为什么要做这个功能?要解决什么问题?业务目标是什么?
  2. 用户角色与画像: 为哪些用户设计?
  3. 功能需求: 详细的功能描述,包括用户场景、操作流程。
  4. 非功能需求: 性能、安全性、兼容性等要求。
  5. 成功指标: 如何衡量功能是否成功(如用户使用率、性能提升等)。
  6. 设计原型/线框图: 可视化地展示界面和交互。

这里探讨的是一种前沿的、由AI驱动的开发范式。在这个范式中,PRD 的角色发生了根本性的转变:

  1. 从“给人看”到“给AI看”:
    • 传统PRD是写给开发、测试、设计等团队成员看的,需要人类的理解和解读。
    • 在AI Coding实践中,PRD(或其结构化、AI友好的变体)是直接输入给AI智能体或大语言模型的“高级指令”。
  2. 成为AI的“蓝图”:
    • AI(例如GPT-4、Claude 3、DeepSeek等)会分析、理解PRD中的需求。
    • 基于对需求的理解,AI可以自动或半自动地执行后续开发任务,例如:
      • 生成技术设计: 设计系统架构、数据库Schema、API接口。
      • 直接生成代码: 产出前端、后端、测试代码的初稿。
      • 生成测试用例: 根据需求描述编写测试场景和脚本。
  3. 对PRD质量的要求更高:
    • 必须更加清晰、无歧义、结构化。 AI无法像人类一样通过模糊的上下文或沟通来“猜”出真实意图。
    • 可能需要使用更标准化、机器可读的语言来描述需求,或者结合结构化数据(如流程图、状态图、清晰的验收标准列表)。

核心上下文 - PRD

  • 核心上下文 - PRD:PRD是核心上下文,但当前PRD质量参差,AI难以聚焦。
    • 问题本质:PRD拆解是一个领域特定的命名实体识别(NER)任务,即从PRD中识别“UI控件实体”。
    • 控件实体分类:参考Apple HIG与Material Design,分为输入类、按钮类、浮层面板、导航栏、内容展示类等。
    • 微调方法:采用LoRA(低秩适配) 对模型进行轻量微调,显著提升控件识别的准确率与召回率。
    • 微调效果:带图评测的多模态模型F1分数达0.85,显著高于基线(0.57)。

UI高还原度出码:从设计稿到代码的准确转换

  • 低还原度原因
    • Figma设计稿不规范(绝对布局、标记不清)
    • 大模型存在幻觉(布局复杂时推理错误)
    • 还原度低,人工审核成本高
  • 还原度检测流程
  • 还原检测方案和挑战
  • 还原度检测方案对比
    • 静态代码分析:通过LLM推断约束关系,检测率**88.5%**,无需编译,易集成。
    • 运行时渲染树检测:需编译运行,检测率仅55.4%,链路集成难度大。
  • 优选静态方案:通过约束信息与样式Token比对,实现高效高精度检测。

UI组件召回:避免重复造轮子,提升代码采纳率

  • 组件召回闭环迭代
    • 问题:AI生成代码未使用企业内部组件,导致采纳率低。
    • 解法:基于代码上下文与开发意图,智能召回组件库中的最佳匹配组件。
    • 进化机制:通过UI自动化采集运行时属性与截图,自动构建训练数据集,实现组件的持续自学习与迭代。

典型业务场景与需求分析

一个实际的业务场景和需求分析, 用户登录页面,包含手机号输入框、密码框、登录按钮、忘记密码链接及成功/失败反馈。
流程:

  1. PRD
  2. 控件识别(手机号输入框、密码框、登录按钮、忘记密码链接)
  3. 逻辑聚合(登录成功Toast、失败弹窗)
  4. 结合企业组件库与设计规范生成代码

端到端提升:定制化Code Agent在Easy/Medium/Hard需求集上,比通用Agent(如GPT-5、Claude)提升约10%。


总结与展望

核心结论

客户端实现PRD到代码的完全直出目前尚不可能,但可通过“评测驱动子能力提升”路径逐步推进。
应关注四个关键课题:
1. 如何构建科学的端到端评测体系?
2. PRD该如何拆解、拆解到什么粒度?
3. 如何保证UI高还原度出码?
4. 如何实现组件的智能召回与闭环迭代?

未来方向

  • 生产级评测集:积累真实PRD、Figma、Commit、测试用例等数据。
  • 流动闭环的企业知识库:融入自动化流程,实现数据自收集与模型自进化。
  • 全周期覆盖:从编码扩展至测试、CR、Bug修复、发布全流程。
  • 跨平台垂类Agent融合:实现跨系统复杂任务的端到端闭环。

AICoding 核心价值

  • AI Coding不是完全替代开发者,而是作为“副驾驶”提升效率、规范流程。
  • 客户端AI落地的关键在于:高质量数据、领域适配、工程化闭环。
  • 长期来看,AI将重新定义软件生产流程,推动研发模式向智能化、自动化演进。

Qcon 上海 2025 支付宝 AI Agent编码助手实战:面向KMP原生跨端实现研发提效

作者 wyanassert
2025年12月22日 01:16

这边文章是 Qcon 上海站 2025 来自支付宝的KMP分享总结, 主题为”AI Agent编码助手实战:面向KMP原生跨端实现研发提效”
文章参考: 支付宝 MYKMP 原生跨平台解决方案
文章参考 : AI Agent 编码助手实战:面向 KMP 原生跨端实现研发提效

AI Agent编码助手实战:面向KMP原生跨端实现研发提效

背景介绍:支付宝KMP原生跨端架构

本次分享首先对相关核心技术术语进行说明:

术语名称 术语介绍
KMP(Kotlin Multiplatform) JetBrains 基于 Kotlin 推出的一套跨端框架,允许开发者使用 Kotlin 语言编写一次业务逻辑代码,然后将其编译成适用于多个平台的原生应用、Web 应用或服务端应用。
CMP(Compose Multiplatform) JetBrains 提供的一套基于 Compose 基础库的声明式 UI 跨端框架,支持在 Android、iOS、桌面和 Web 开发共享 UI。
支付宝 KMP 原生跨端 在 “Kotlin + Compose Multiplatform” 的基础上,为支付宝终端开发者提供一整套完善的跨端框架能力。
AntUI 组件库 基于 Compose 编写的支付宝 UI 组件库,包含丰富且风格统一的 UI 组件。
OHOS、Harmony OHOS 是鸿蒙项目的开源操作系统基底,而 HarmonyOS 是基于 OHOS 打造的商用智能终端操作系统。

KMP原生跨端的核心优势在于显著减少为不同平台重复开发的工作量,同时能保持各平台原生的最佳用户体验。

支付宝在基础KMP架构上进行了深度扩展,构建了增强型跨端框架,其分层架构如下:

  • MY CMP -> UI与体验层
    • 双渲染管线:除CMP默认的Skiko引擎外,自研了Canvas渲染引擎,以在内存、滑动流畅性等方面实现性能优化。
    • AntUI高阶组件库:提供丰富的企业级UI组件。
    • 自动化能力:集成自动化埋点(无需手动添加点击等事件上报)、UI重组耗时检测工具。
    • 运行时监控:对线上ANR(应用无响应)、掉帧、无限重组等问题进行监控。
    • 原生组件嵌入:支持在Android、iOS、鸿蒙平台嵌入原生渲染的View。
    • 上层框架:封装了导航、事件、应用生命周期等统一框架。
  • MY KMP -> Kotlin跨平台层扩展
    • 平台API导出:将各原生平台常用API导出为Kotlin接口供开发者调用。
    • Runtime优化:对平台运行时进行优化,降低内存占用并提升加载性能。
    • 自研LLVM技术:支持编译插桩等高级操作。
    • 编译器优化:通过前后端编译器优化,显著减小产物包体积。
    • 鸿蒙通信通道简化:去除了传统KMP鸿蒙开发中必需的C语言桥接层,实现了Kotlin与eTS(鸿蒙开发语言)的直接高效通信。
  • 跨端基座
    • C++基础库:将网络库等原生C++能力封装并透出Kotlin接口。
    • 原生平台能力增强:在鸿蒙平台深度集成其Pipeline、事件中心、渲染、资源加载等原生能力至KMP框架。
    • Tecla API:基于自研IDL(接口描述语言)提供的跨端、跨技术栈API调用机制。开发者只需调用Kotlin接口,即可在安卓、iOS、鸿蒙三端使用支付宝的中间件能力。
  • 工程体系集成:将KMP框架无缝融入支付宝现有的工程研发体系,提升开发效率。

目前,该KMP跨端架构已在支付宝多个核心业务场景(如“我的”、理财、直播、消息页,以及出行服务、健康管家等独立APP)中落地,覆盖安卓、iOS、鸿蒙三大平台,均实现了与原生开发对标的高性能体验。整体已支撑亿级PV,成为支付宝内重点发展的主流原生跨端技术栈。

KMP研发现状、痛点与AI工具调研

尽管KMP技术带来效率提升,但其研发全流程仍存在若干痛点:

  1. 起步阶段:开发者需从头学习Kotlin、Compose及KMP/CMP特有语法,存在较高的学习成本。
  2. 开发阶段:开发者不熟悉框架提供的跨端API(如AntUI、Tecla)是否能满足需求及具体调用方式。
  3. 测试阶段:主要依赖人工测试,效率低下,缺乏自动化与AI辅助手段。
  4. 上线运维阶段:三端(尤其是KMP特有)的崩溃堆栈反解与分析耗时较长,问题定位与优化成本高。

针对上述痛点,我们对现有AI编码工具进行了调研,结论是:目前缺乏一款能与客户端基础框架深度结合、支持KMP技术栈、并适配支付宝终端研发工程体系的专用编码助手。

具体对比如下:

  • 内部两款热门助手:能力丰富,但不支持KMP跨端开发辅助。
  • Cursor:支持KMP技术栈,但缺乏转码等深度能力,无法融入支付宝工程体系,且不了解CMP在鸿蒙平台的特定知识。
  • Cline:与Cursor存在类似问题,且其推理步骤复杂度较高。

因此,我们期望打造一款具备跨端特色的AI编程伙伴,以解决实际研发问题,提升效率。

KMP编码助手:方案与实践

构建了KMP的编码助手,其核心目标是运用AI技术为KMP开发带来“二次加速”。以下从方案构思到核心功能实现进行剖析。

整体可行性评估与架构

项目初期,我们从四个维度评估了可行性:

  1. 图生码/设计稿生码:通过让大模型学习AntUI组件,验证了其能直接输出对应界面代码的可行性。
  2. 算法支撑:具备在终端研发场景下产出领域自研算法模型的能力,以增强生码效果。
  3. 生产资料支撑:拥有完整的KMP(含鸿蒙)技术栈研发能力、四端AntUI组件库的开发和维护能力,以及可通过Tecla透出的丰富基础框架能力,能为大模型提供充足的学习素材。
  4. 插件结合方式:确定以Android Studio(KMP研发主要IDE)的IntelliJ IDEA插件形式进行集成验证。

整体架构分为三层:

  • 客户端层:作为Agent与用户的交互界面(IDE插件)。
  • Agent框架层(核心):进行了工作流编排、任务分解、知识图谱构建、UI转换等核心改造。
  • 基础服务层:支撑AI能力的Prompt工程、RAG检索、MCP协议调用及代码补全等服务。

界面开发提效:从设计稿/图片到代码

为帮助开发者快速上手Compose UI,我们提供了两种生码方案:

设计稿生码

  • 效果:可将Sketch设计稿中的图层高精度(还原度90%以上)转换为Compose UI代码,并在IDE中实时预览。
  • 实现链路
    • 启动链路:通过Node服务连接Sketch应用、IDE插件和Webview。

    • 设计稿转IR:将设计稿元素转换为中间表示(IR),包括类型、参数、样式及视图层级信息。

    • IR转Compose:依据规则将IR映射为Compose组件与修饰符。

    • 优化与输出:通过人工规则与模型二次优化,对生成的代码进行组件化、数据驱动等重构,输出高质量的生产级代码。

  • 挑战:处理了设计稿不规范、IR与Compose属性映射差异(如margin)、DIV类型具体化、图片资源转换、CSS风格属性适配等一系列复杂问题。
  • 解决:利用大模型进行二次优化,将界面布局进行组件化以及数据驱动的封装,比如一个平铺列表,最终优化成 ServiceItem 组件,对应传参 ServiceData,最终代码就可以直接用于生产。

再来整体对比下,从原始设计稿,到原始 Compose UI,再到模型二次优化的界面效果。这里能感受到模型二次优化后,基本上能够还原设计稿组件,但是代码更加直接可用。

  • 稿生码的优点:
    • 转换后还原精度高,
  • 缺点
    • 不支持基于支付宝 AntUI 组件库还原,
    • 设计稿不够规范影响还原效果。

我们自然而然的会想有更加简便,且支持高阶 UI 组件库的方案,就是图生码。

图生码

  • 背景:设计稿生码不支持AntUI组件,且受设计稿规范度影响。图生码旨在实现更简便且支持高阶组件的生码方案。
  • 方案演进
    • 方案一(图到代码直出):将高阶 UI 组价库的知识按照统一格式,输入给 MLLM 学习后,直接将图片转换成 Compose 代码。
      • 问题: 让大模型读图直接输出代码。效果欠佳,细节处理差,且技术栈绑定Compose,难以扩展。
    • 方案二(图→IR→代码):采用自研图生码算法。使用后训练的多模态大模型识别图片中的基础组件和AntUI组件,输出IR,再复用设计稿生码的转换规则生成代码。(此方案更优)
      • 图生码算法能力建设的三个阶段
        1. 数据构造, 构建自动化流程,通过大模型生成随机Compose代码→渲染截图→生成精确的图文数据对,解决了训练数据匮乏问题。

        2. 模型训练, 采用LoRA(低秩适应)等参数高效微调技术,对多模态大模型进行SFT(监督微调)和强化学习,使其获得精准的UI页面解析能力,能识别AntUI高阶组件。

        3. 后处理增强, 针对模型幻觉导致的位置、颜色、布局偏差,结合传统图像算法进行校准,提升输出IR的精确度。

    • 优势与挑战:方案二效果更精准,直接支持AntUI,且IR协议可扩展至其他原生技术栈。当前挑战在于进一步提升AntUI组件识别准确度,并构造更多特殊案例数据。

逻辑开发与运维提效:智能问答与诊断

为帮助开发者快速上手KMP逻辑开发与解决线上问题,我们构建了基于RAG和MCP的智能助手。

基于RAG的智能问答

背景

  • 内部文档质量参差不齐,内容多且繁杂,较难查找阅读
  • 阅读;由于文档质量不高,导致机器人答疑质量不高

开发者常咨询这三类问题:

  1. Kotlin 跨端能力库中是否包含某项能力?
  2. 这个 API 能力调用代码该怎么写?
  3. AntUI 组件库是否支持包含某个组件?

RAG 检索问答基本流程:

  • RAG流程优化
    • 源数据处理:面对复杂的JSON源数据(如含千条API记录),利用自建Agent将其转化为格式规整、模型可读的Markdown文档。
    • 检索效果提升:以FAQ(问答对)形式替代传统的文本切片,并借助大模型从文档中提炼生成近4000条FAQ知识,提高召回准确率。
    • 体系性问题回答:将知识图谱的实体关系作为检索语料,使模型能理解模块与接口的层级关系,回答体系性问题。
    • FAQ增强:让模型为同一答案生成多种问法,提升问题命中的灵活性。

具体问题诊断与解决

  • KMP构建/闪退排查:构建“构建失败Agent”和“闪退日志Agent”。其工作流为:先运行脚本提取日志关键信息,再通过RAG从知识库召回解决方案,最后由Agent组织答案反馈给开发者。
  • KMP应用框架快速接入:该框架用于抹平三端生命周期差异。我们提供模板代码自动生成工具,开发者可一键将框架集成到项目中,将原本需3人日的接入工作自动化。

KMP 模块在三端平台构建失败,无法定位原因

针对开发者不熟悉多端尤其是鸿蒙平台的痛点,我们通过定制Agent工作流解决问题:
KMP 模块在三端平台构建失败,无法定位原因
KMP 核心产物需要同时三端构建,一旦出现构建失败问题,传统排查方式效率比较低下,花费的时间从几分钟到一小时不等。

这里我们通过 Agent 工作流的方式,帮助开发者主动触发构建,利用 KMP 日志分析脚本,提取关键日志,再结合现有构建知识库进行召回,最终由模型整理组织答案。从而加快构建失败问题的排查速度。

安卓/ iOS /鸿蒙,三端闪退如何排查

开发者可以直接将闪退日志输入给 Agent ,Agent 会触发闪退分析的工作流,先用 KMP 堆栈反解工具提取关键内容并解析,再将解析结果返回给 Agent,由 Agent 结合当前的项目代码上下文,给出原因和解决方案。

基于MCP的工具集成

如何将众多工具(堆栈分析、模板生成、文件操作等)整合到大Agent中?我们采用了本地MCP(Model Context Protocol)路由机制。

  • MCP作用:一种标准协议,使工具能适配不同大模型。通过编写MCP协议描述工具功能,Agent可根据用户提示词自动路由并调用相应工具。
  • 示例:当用户输入“分析鸿蒙闪退堆栈”并提供日志时,Agent能自动匹配并调用“闪退堆栈分析工具”的MCP,执行分析并返回根因与建议。
  • 架构扩展:除本地MCP工具集外,未来规划提供远程MCP市场和Agent市场。

未来展望

KMP编码助手将持续优化与创新,重点方向包括:

  1. 生码能力增强:支持Figma设计稿生码;优化图生码IR协议;探索智能Compose UI视觉验收。
  2. 声明式UI动态化:结合模型对数据与UI组件的理解,通过自研KMP JS运行时与动态化技术,实现数据驱动的动态界面渲染。
  3. 技术架构扩展:以KMP技术栈为核心,逐步将AI辅助能力扩展至其他原生技术栈(如纯Android、iOS开发)。
  4. 生态建设:建设开放的Agent与MCP工具市场。

总结:AIAgent重塑软件开发生命周期

最后再来看一下AI Agent面向软件开发整个的生命周期,你可以发现 agent正在以一个非常非常快的速度改变我们的工作方式. 从构思到开发到落地, agent在每一个环节都会驱动我们来进行一些创新.
比如

  • 需求分析里面我们可以让AI来给出UI/UX设计建议
  • 开发与编码阶段, 可以让agent来帮助我们进行代码审查和质量保证
  • 测试阶段也很重要, 可以让agent智能测试以及报告
  • 在部署与发布上, agent可以帮助我们进行一个自动化的配置
  • 在维护与运营阶段, agent可以帮助我们分析用户的反馈以及线上的性能监控和优化

简而言之, AIAgent正在引领一场软件开发的全新的变革, 这将会深深地改变我们之后的一个工作方式, 那在这里呢也也祝愿大家能够在AI人工智能席卷而来的浪潮里面抓住机遇勇于创新, 说不定会有意想不到的惊喜和收获.

[转载] 基于dylib注入原理实现iOS热重载框架CocoaHotReload

作者 wyanassert
2025年12月3日 19:32

原文地址

背景

在iOS开发过程中,是否经常遇到这样的场景:简单修改几行代码,想要立刻看到效果,却需要重新增量编译,苦苦等待编译、链接、安装、运行这个漫长的过程;特别是在中大型项目(如手Q)中,增量编译耗时也要60s甚至更多,非常影响开发效率。

为了解决增量编译,业界上也有IPAPatchInjectionIII方案:

IPAPatch 是基于编译子工程,链接为动态库打包到宿主工程,需要改变代码依赖逻辑,使用局限性大且效果并不理想。

InjectionIII 也是基于dylib注入原理实现的iOS热重载能力,但是不支持真机以及dylib注入时仅简单处理函数替换,热重载的能力存在比较大的局限性,如存在对release版本静态库或动态库依赖的代码文件或存在被hook的类的实现代码文件进行热重载,要么无法支持,要么可能出现循环调用而导致应用出现Crash,且对Swift、C++代码的热重载支持也非常有限。如果基于InjectionIII的基础上进行开发,后续如果InjectionIII更新则不利于迭代。

上述两个方案均不能满足我们的需求,因此我们决定在基于dylib注入的原理基础上对热重载实现进行探索实现。

挑战

在复杂项目中实现热重载能力,我们面临的挑战也是巨大的。这里主要体现在dylib动态库的生成及注入后复杂场景的处理,在保证代码快速生效的同时也要保证业务逻辑的稳定性。

下图展示了iOS 源码文件的编译流程:     
 descript

  • 预编译(Precompile): 编译预处理,即替换宏、删除注释、展开头文件等,产生 .i 文件。
  • 编译(Compiling): 将之前的.i文件转换为汇编语言,产生 .s 文件。
  • 汇编(Assembly): 将汇编语言转换为机器码文件,产生 .o 文件。
  • 链接(Link): 将所以.o文件以及依赖的库链接后生成Mach-O类型的可执行文件。

可以看出,要生成dylib可执行文件,就需要获取源码文件,并进行预编译、编译及汇编操作后生成.o文件,再分析其依赖的库(.a.framework),最后链接后才可生成dylib。将dylib注入到运行的App中,并进行逻辑替换,即可使得代码生效,实现热重载能力。

所以我们就需要对源码文件变化的进行监听、查找对应的编译指令并使用xcode编译工具(clang swift)进行文件编译,接着分析编译后的.o文件依赖的库,并进行链接后,最终通过生成dylib

此外,生成dylib库后还需要有通信模块,将动态库发送到App端,进行dlopen注入到app中。

这里需要特别提下真机情况:通过dlopen注入时,会因为安全校验,导致dlopen失败,不过好消息是,在iOS13后,苹果在debug模式下开放了这个能力,使得热重载能力在真机上得到应用,而且在dlopen动态库后,还需要针对不同语言(ObjC、Swift、C)通过Runtime及其他方式进行代码逻辑的处理,才能保证代码及时生效。

针对复杂场景,如hook场景,还需要做保存原有调用栈,及避免死循环等逻辑的处理,保证逻辑正常运行及功能的稳定性,这又加大了热重载能力实现的难度。

设计与实现

项目架构

如前文分析,为了实现dylib的生成和注入以及两端之间的通信完成热重载能力,我们的思路是设计Mac appiOS framework来分别完成dylib的生成和注入,且两端的网络通信是通过Socket(TCP/USB)来完成。

主要的架构图如下:     
 descript

整个项目分为Mac appiOS framework两部分。

Mac app

主要职责是根据修改文件查找对应的编译指令并执行生成.o文件,接着链接.o文件及依赖的库生成dylib动态库。

主要的处理逻辑为标红部分:

ComplileCommandManager:
主要负责解压编译日志并通过正则表达式进行编译指令的提取&缓存,其中核心的正则表达式如下:

1
2
3
4
5
6
7
8
// 提取编译指令正则表达式
NSError *error;
// export LANG && -o 判断oc编译指令
// CompileSwift normal && -o判断swift编译指令
// CompileXIB && --compile 判断xib编译
// CompileStoryboard && --compilation-directory 判断sotryboard编译
// Siwft 编译指令太长会优化成文件映射 如 /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-frontend @/var/folders/xx/b4bgcx_1393fdwdnddgzkqmc0000gn/T/TemporaryDirectory.R2XBsY/arguments-639193350444245467.resp
NSRegularExpression *expression = [NSRegularExpression regularExpressionWithPattern:@"((export LANG)|(CompileSwift normal )|(CompileXIB )|(CompileStoryboard )).*?((swift-frontend @)|(-o )|(--compile )|(--compilation-directory )){1}.*?((\\.resp)|(\\.o)|(\\.xib)|(\\.storyboard)){1}" options:NSRegularExpressionDotMatchesLineSeparators error:&error];
  • FileDependentManager : 主要分析.h文件的依赖关系,当改动到.h文件时,需要分析所有依赖该.h的所有.m文件,并进行重编译,才能保证这个.h的改动能完全生效。分析依赖的逻辑是直接分析Xcode编译的中间产物Intermediates.noindex目录下的.d 文件,该文件中记录.m依赖的所有文件。

举个例子,ViewController.m 引用的头文件如下:

1
2
#import "ViewController.h"
#import "ObjCViewController.h"

对应生成的ViewController.d文件如下:

1
2
3
4
5
6
dependencies: \
/Users/username/Documents/Git/CocoaHotReload/CocoaHotReloadExample-CocoaPods/CocoaHotReloadExample-CocoaPods/ViewController.m \
/Users/username/Documents/Git/CocoaHotReload/CocoaHotReloadExample-CocoaPods/CocoaHotReloadExample-CocoaPods/ViewController.h \
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator15.0.sdk/usr/include/kcdata.modulemap \
// ... 这里忽略系统的modulemap文件
/Users/username/Documents/Git/CocoaHotReload/CocoaHotReloadExample-CocoaPods/CocoaHotReloadExample-CocoaPods/ObjCViewController.h
  • LibManager:主要职责是分析编译日志中,各个Target依赖的frameworklib,并查找私有符号库,并在.o文件linkdlyib时,进行依赖私有符号库查找一同link打包到dylib中。主要目的是为了解决dlopen dylib时,出现Symbol not found的问题,后面内容会详细介绍。

这里查找私有符号库的条件是使用nm工具进行判断的,判断条件如下:

1
2
3
4
5
6
// 判断私有符号库
// 带有private external
// C 或 C++ 符号为_开头
// OC类符号为 _OBJC_CLASS_$
// Swift [alt entry] _$ 或 [alt entry] _OBJC_CLASS_$ 开头
NSString *source = [NSString stringWithFormat:@"nm -nm %@ | grep '(__.*,__.*) private external \\(\\[alt entry\\] \\)\\?_'", libraryFilePath];

iOS Framework

主要职责是进行动态库注入资源包(xib、storyboard)更新。

其中,主要的逻辑处理是类替换和hook函数的处理。

  • 类替换:

    • ObjC 的替换主要是通过Runtime实现的,通过Runtime保证同一个类的所有的函数实现指针都指向最新注入的动态库地址,以保证所有镜像中的同一个类的实现都一致。
    • Swift 的函数替换就较为复杂,由于Swfit涉及到和ObjC混编,及继承ObjC类或者与ObjC相互调用等复杂场景。导致Swift类的派发方式出现了StaticV-TableWitness TableMessage四种派发方式,解决方案是根据这四种派发方式,分别进行函数指针替换以实现热重载能力。具体实现,后续内容会详细描述。
    • C 函数的替换,由于C函数是直接派发的方式,所以不能通过Runtime机制处理,最终是通过第三方开源库 Dobby实现C函数替换。
  • hook函数处理:

    • 由于热重载最主要的逻辑是函数替换,所以函数替换的处理逻辑要极其严谨,否则就会导致逻辑异常甚至Crash。比如当热重载一个存在被其他分类hook的时候,如果直接使用method_exchangeImplementations进行替换的话,就会破坏函数调用栈;例子如下:

      • 如Class A的原始函数OriginMethod,有两个Category
        分别hook了这个OriginMethod,其调用栈流程如下:
        1
        Call OriginMethod -> hook1 imp -> hook2 imp -> OriginMehod imp
      • 当热重载Calss A时,会产生新的New
        OriginMethod实现,使用method_exchangeImplementations进行替换, 导致最终调用栈如下:
        1
        Call OriginMethod -> New OriginMehtod imp
    • 以上情况会导致直接不调用hook 1 imp 和 hook 2

imp,影响正常的业务逻辑。正确的做法应该是找到OriginMehod
imp这个节点,进行替换,来维持之前的调用栈。

问题来了,如果找到正确的替换节点呢?

  1. 需要知道哪些函数被hook

    • 目前我们这里只考虑常规的hook方法(通过Category进行hook),所以优先判断函数是否属于Catrgory。

    • 可通过如下代码获取imp信息:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14

      /*
      * Structure filled in by dladdr().
      */
      typedef struct dl_info {
      const char *dli_fname; /* Pathname of shared object */
      void *dli_fbase; /* Base address of shared object */
      const char *dli_sname; /* Name of nearest symbol */
      void *dli_saddr; /* Address of nearest symbol */
      } Dl_info;

      Dl_info info;
      IMP imp = method_getImplementation(method);
      int result = dladdr(imp, &info);
    • 如TestClass和TestClass (TestCategory) 两个类对应的函数如下:

      1
      2
      3
      4
      5
      6
      7
      @interface TestClass : NSObject
      - (void)testFunction;
      @end
      // 分类
      @interface TestClass (TestCategory)
      - (void)tc_testFunction;
      @end
    • 获取到的Dl_info中的dli_sname分别如下:

      1
      2
      -[TestClass testFunction]
      -[TestClass(TestCategory) tc_testFunction]
    • 由上诉结果可以看出,Category函数是会包含分类名称的。由此来判断分类函数,至于判断是否被hook,只需判断method name 和 imp中的函数名,如果不一致则代表被hook了。

  2. 需要找到哪个函数指向的imp是当前要替换的函数的同名imp,即递归获取函数调用栈,找到method对应的imp name为当前热重载的method name一致的这个method进行指针替换。

    • 关键代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    // 查找原始实现所在的method
    + (Method)methodWithOrigImpForOriginClass:(Class)originClass
    originSel:(SEL)originSel
    baseClass:(Class)baseClass
    baseSel:(SEL)baseSel
    isClassMethod:(BOOL)isClassMethod
    {
    if (!originClass || !originSel) {
    return NULL;
    }

    NSString *baseClassName = NSStringFromClass(baseClass);
    NSString *baseSelName = NSStringFromSelector(baseSel);

    NSString *originClassName = NSStringFromClass(originClass);
    NSString *originSelName = NSStringFromSelector(originSel);

    Method originMethod = class_getInstanceMethod(originClass, originSel);
    IMP orginSelImp = method_getImplementation(originMethod);

    NSString *orginSelImpClassName = [self classNameForCategoryClassName:[self classNameForImp:orginSelImp]];
    NSString *orginSelImpMethodName = [self methodNameForImp:orginSelImp];

    // 这里只处理标准的hook即只在当前类的Category进行hook
    if (![originClassName isEqualToString:orginSelImpClassName] ||
    [originSelName isEqualToString:orginSelImpMethodName]) { // 没有被hook或者hook出现异常,返回base
    return class_getInstanceMethod(baseClass, baseSel);
    }
    if (([baseClassName isEqualToString:orginSelImpClassName] &&
    [baseSelName isEqualToString:orginSelImpMethodName])) { // 递归结束 // 返回需要baseClass类需要替换的函数
    return class_getInstanceMethod(baseClass, originSel);
    } else {
    Class class = NSClassFromString(orginSelImpClassName);
    if (isClassMethod) {
    class = object_getClass(class);
    }
    SEL sel = sel_registerName(orginSelImpMethodName.UTF8String);
    if (class && sel) { // 递归查找
    return [self methodWithOrigImpForOriginClass:class originSel:sel baseClass:baseClass baseSel:baseSel isClassMethod:isClassMethod];
    } else { // 失败 返回base
    return class_getInstanceMethod(baseClass, baseSel);
    }
    }
    }

工作原理

 descript

如上图所示,Mac app 和 iOS framework(CocoaHotReload.framework)之间是通过Socket(tcp/usb)进行发送command通信,连接成功后,Mac
app会监听文件变化和进行项目初始化(解压&解析编译日志、查找编译指令、签名、私有符号库),Xcode编辑源码并保存后,触发热重载指令,Mac app会查找修改文件的编译指令并执行,生成.o文件并分析.o文件依赖的私有符号库进行link后生成.dylib,并通过Socket发送到app;iOS framework 需内嵌在app中,当接收到.dylib时,通过Runtime&finshook进行类(ObjC&Swift)替换&C函数(动态库中)替换实现、资源(storyboardc&nib)更新及hook函数调用栈&死循环处理,实现dylib注入,完成一次热重载。

具体时序图如下:     
 descript

所遇挑战

这里主要介绍下项目实现过程中,比较难的挑战点:

dlopen error: Symbol not found

由于这个项目是通过dylib注入原理来实现的,所以dlopen是加载动态库的必经之路,但是在dlopen的时遇到了Symbol not found的问题,导致后续注入操作无法进行,直接导致热重载失效,这对于热重载能力是致命的问题。

具体问题如下图所示:     
 descript

该问题的背景如下:     
 descript

如上图所示:触发热重载的文件是QQWalletViewController.mm,该文件依赖了libPayCenterSDK.a文件中的TenpayPlugin类;为了减包,libPayCenterSDK.aRelease模式下Symbol Hidden by Default 设置为YES,该设置导致符号都被隐藏,dlopen时就出现了上方的错误。

最开始为了解决这个问题,尝试了以下两个方案:     
 descript

  • 方案一: 直接将这个Release模式改为NO,这样虽说能解决符号隐藏问题,但是带来了两个问题
    1. 需要业务更改工程设置
    2. 引来业务增包问题(特别是在手Q中对于包增量是很敏感的),所以增包问题是无法接受的。
  • 方案二: 在工程里面link库时,debug模式下linkdebug包,这样也可以解决问题。但是引来的其他问题是:业务都需要更改工程设置,且生成库的时候需要分别生成debug和release的库,对于业务使用热重载的成本还是不小。

以上两个方案,虽说解决了符号隐藏问题,但会带来业务增包问题和影响业务的工程设置。这对于用户使用来说并不友好。理想方案肯定既解决符号隐藏问题且对用户来说是无入侵、无感知且不要带来额外的问题。为了彻底解决问题,刨根问底,去探究下Symbol Hidden by Default这个设置对符号的影响。

这里通过下面的例子来分析下:     
 descript
根据苹果官方文档说明:Symbol Hidden by Default设置对应符号的影响等用于使用__attribute__((visibility("default|hidden")))修饰函数,来控制符号的可见性,所以通过__atrribute__修饰来看下符号影响。

新建一个SymbolTest工程,依赖libSymbolVisibilityTest.a静态库。

静态库中有两个函数分别通过__attribute__visibility("default")visibility("hidden")修饰。

生成静态库后,通过nm -nm libSymbolVisibility.a 查看符号信息,可以看到通过default修饰的符号为external,公开符号,可对外导出。

通过hidden修饰的,符号为private external,表示私有符号,不可导出。

再看下最终静态库打包到app后的符号信息,通过nm -nm symbolTest查看符号信息,可以明显看出default修饰的函数仍为external 可导出符号。

hidden修饰的函数,则变成non-external (was a private external)私有符号。

最后通过MachOView软件查看下SymbolTextExport Info对开开放的符号表信息可以看到只有external修饰的符号才会出现在这个表上面。

了解完Symbol Hidden by Default 设置对符号的影响后,来看下dlopen出现symbol not found的原因:     
 descript

如上图所示,动态库test.dylib,在没有link libSymblVisibilityTest.a库的情况下使用了这个库中的SymbolVisibilityHidden函数,当dlopen test.dylib时,会解析dylib中的符号,发现SymbolVisibilityHidden这个符号为 undenfine symbol时,会尝试调用resolveUndefined继续查找,继续调用findExportedSymbolAddress去符号导出表里面查找,如果能查找到就返回,查找不到及调用throwSymbolNotFound函数抛出”Symbol not found xxx”异常。之前尝试通过Symbol hidden by default设置为No可解决的原因就是设置完,符号会出现在符号导出表中,可被找到。但刚才分析了这个解决方案的不足之处,所以我们在这个调用链上再次寻找解决方案,发现在第二步undefine symbol是因为库没有一起打包到dylib,如果将库打包到dylib中,就不会出现undefine symbol符号问题,
因此,完美的解决方案出现了 通过link依赖的私有符号库即可解决 。现在有了解决方案以后,主要面临了以下两个实现问题:

  1. 如何查找哪些是私有符号库?

    • 首先,在解析编译日志的时候,需要解析出工程中所有target依赖的(farmework、lib),并且通过nm -nm 去分析这些库,只要库中出现了private external 符号及表示私有符号库,并记录下来。
  2. 如何判断哪些符号是私有符号且需要link哪个库?

    • 当生成.o文件后,可通过nm -nm 获取(undefined) external
      获取未定义的符号,但是这里有个问题就是undefined符号包含大量系统符号和私有符号。为了提升效率就需要需要先把系统符号过滤掉,要想过滤掉系统符号,就要先获取私有符号的集合,通过集合去过滤。获取app私有符号的集合可通过nm -nm去分析app可执行文件,通过non-external (was a private external)来记录下整个app中的所有私有符号,即可过滤掉系统符号,并通过这些私有符号在对应的哪些库中,就可以知道需要link哪些库。

最终的完整的实现方案如下: 
 descript

如上图所示,最终在项目初始化时,提前获取好项目的私有符号及私有符号库,触发热重载后,编译生成.o文件,获取.oundedined symbol符号,并且通过项目私有符号过滤掉系统符后,查找undeined symbol所在的库中,接着对依赖的库进行库瘦身再剔除库间重复的.o文件后link库生成dylib。这样就可以无入侵、无感知地完美解决symbol not found的问题。

Swift 函数热重载问题

最开始实现Swfit函数热重载的时候,并没有很透彻,导致出现各种热重载函数不生效的问题,这里看下影响Swift派发方式的因素,这里总结了下一些因素,不详细介绍,有兴趣的同学也可以了解相关的文章,如Swift 底层是怎么调度方法的

 descript
由上图可以看出,影响Swift派发方式有多种,且可以多个因素联合影响最终的派发方式,这里收敛后分析出最终派发的4种方式,如下: 
 
  descript

如上图所示:Swift的派发发送根据不同的数据类型和调用方式影响后,最终收归到4种派发方式,分别为Static(静态派发)V-Table(虚函数表)Witness
Table
以及Message(消息转发)。最终分别针对这4种派发方式进行热重载方案实现,如下图: 
   
 descript

  • Static: 与C函数一致都是属于静态派发函数,所以和C函数的热重载实现一致,均是通过Dobby进行inline hook来实现的热重载。

  • V-Table 和 Witness Table: 根据struct TargetClassMetadata的数据结构和内存布局去获取对应的表指针进行整个表的替换,实现热重载能力。

  • Message: 与ObjC的消息转发机制一致,即可以读取类结构中的MethodList函数列表,并通过Runtime 进行函数实现指针的替换,达到热重载的效果。

实现效果

在iOS开发中,通过CocoaHotReload热重载能力,能够节省增量编译时的链接、安装、运行App的时间,大大提升研发效率

Features

  • 支持模拟器&真机(iOS 13+)
  • 支持ObjC属性和方法多种操作(增加|删除|修改)
  • 支持第三方库设置Symbol hidden by Default为YES
  • 支持修改多种文件类型(.h|.m|.mm|.swift|.storyboard|.xib)
  • 支持新增的文件进行hot reload
  • 支持通过命令行工具生成dylib
  • 支持Swift(仅支持函数替换)
  • 支持Storyboard&xib
  • 支持Unit Tests
  • 支持C函数

效率提升效果

QQ场景

  descript

编译测试工具

 descript

总结与展望

本项目的初衷就是为了提升iOS开发的研发效率,以手Q开发为例,单文件从完成增量编译到应用完成安装、启动平均时间大约70s,使用CocoaHotReload无需启动应用,改动的代码完成带入注入后直接生效,改动生效的平均时间降低到6s左右。在公司内部的大型项目QQ、微信等都接入使用,并整体反馈也是不错的。

我们团队也在不断地探索更高效的研发方式,后续也在尝试实现远程热重载能力(使用远程构建机来完成动态库的生成,本机无需编译,仅拉取代码和安装包即可调试)。

如果你对这方面的技术也比较感兴趣,可以在文末留言,跟我们一起讨论。也欢迎大家给一些建议,非常感谢。

[转载] 给世界上色——滤镜底层原理

作者 wyanassert
2025年8月23日 15:37

滤镜最早应用在电视影视业,对剧和电影作品后期进行调色效果。如今拍照、修图都离不开滤镜效果。我们在微博、朋友圈、电视、影院里看到的照片视频,无一没有滤镜的效果,滤镜已经深入我们生活的方方面面。

这里浅略地揭秘一下当前图像处理中滤镜底层的原理。

在这里插入图片描述
在这里插入图片描述

RGB颜色


RGB色彩模式是工业界的一种颜色标准,是通过对红®、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是运用最广的颜色系统之一。
在这里插入图片描述
对于一张图片的每一个像素,都可以由唯一的颜色(R,G,B)表示,其中RGB的范围是0~255。0代表最暗,255代表最亮。
在这里插入图片描述

颜色查找表


滤镜的本质就是颜色的变换。我们可以把一个像素的颜色(R,G,B)看成是一个三维空间上的一个坐标点。颜色变换相当于是三维空间的 [一个坐标点][另一个坐标点] 的映射关系。也就是:

1
2
3
4
5
6
7
8
9
10
11
12
         (old R,old G,old B)  --->   (new R,new G,new B)

例如:

(23,77,134) ---> (122,34,255)

(189,65,138) ---> (5,65,21)

(0,0,2) ---> (33,0,1)

………………

在这里插入图片描述

即每一个(R,G,B)都有一个一一对应的目标颜色,那么一个完整的RGB颜色查找表总共有 256×256×256 = 2^24 条。

显然 2^24 这个数字有点太大了,存储它需要至少16Mb的空间,加载到内存也至少需要吃掉16Mb的内存
(问题1:这里可以先思考一下为什么是16Mb?后面会给出解释)

因此我们在实际查找表应用中一般使用 64×64×64 的查找表,RGB每个通道都将 [连续的4个坐标] 映射成 [1个目标坐标]

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(0,X,X)    --->   (34,X,X)

(1,X,X) ---> (34,X,X)

(2,X,X) ---> (34,X,X)

(3,X,X) ---> (34,X,X)


(4,X,X) ---> (128,X,X)

(5,X,X) ---> (128,X,X)

(6,X,X) ---> (128,X,X)

(7,X,X) ---> (128,X,X)

但显然这样会导致原始图片颜色精度的丢失(例如上面的0~3的亮度映射后都变成了无差别的34),那么就需要想办法降低这种精度丢失的问题
(问题2:这里可以先思考一下有哪些补偿精度的方法?后面会给出解答)

LUT图


有了颜色映射表,接下来需要考虑如何去表达这些映射关系了。有一个笨办法是用文本去存储这 64×64×64 这么多条 (old R,old G,old B) —> (new R,new G,new B)这样的映射关系。聪明的你肯定意识到了这个文本的大小会是一个天文数字。那么有没有更好的表达方法呢?优秀的前辈们发现可以用一个图片去表示这个映射关系,这就是LUT图:
在这里插入图片描述
这个LUT图的尺寸是 512×512 ,正好等于 64×64×64(查找表的数量),也就是刚好每一个像素可以表示一条 (old R,old G,old B) —> (new R,new G,new B)这样的映射关系!

我们可以用 [图片的像素坐标] 表示(old R,old G,old B),用这个坐标所在的 [像素的颜色] 表示(new R,new G,new B),这样就实现了映射表的一一对应关系。

我们可以先从比较简单的开始看,为什么用这个坐标所在的 [像素的颜色] 表示(new R,new G,new B)?因为图片本身每个像素就是由(R,G,B)组成,并且都是0~255,刚好可以完美表示一个(new R,new G,new B)。

再来看看比较复杂一丁点的,如何用 [图片的像素坐标] 表示(old R,old G,old B)?因为一个图片的坐标是二维坐标(x,y),要如何表示一个三维的(old R,old G,old B)呢?

可以看到这个图片每一行有8个小正方形,每一列也有8个小正方形,总共有64个小正方形。每个小正方形都是一张 64×64的图片。

我们先从一个小正方形开始看。其实每一个小正方形都代表了完整的(old R,old G) —> (new R,new G)的映射。其中像素Target坐标(x,y)代表(old R,old G),像素Target的颜色的RG值就代表(new R,new G)。

例如下面这张图片:

1.假设(old R,old G) = (100,150),那么Target的坐标就是(100 / 4,150 / 4)= (25,37)

2.如果这个图片里坐标是(25,37)的Target的像素值是(213, 88),那么(new R,new G)=(213, 88)

2.即完成了(old R,old G) = (100,150) —> (new R,new G)=(213, 88) 的映射关系!
在这里插入图片描述
至此已经完成了R通道和G通道的映射,那么如何确定B通道的映射关系呢?前面说到一个完整LUT图有8×8=64个小正方形,这64个小正方形就是用来确定B通道的映射关系的。

我们把这64个小正方形按从左到右从上到下排列,编号0~63,我们就可以把(old B)映射到其中的某个小正方形格子。

拿下面这个示例图比较能说明过程:

假如(old B) = (50),那么最终的颜色落在第(50 / 4)=(12)个格子上。

但注意,这里的(12)并不是(new B),(12)仍然只是图片的 [坐标],因此它代表的其实还是(old B),仅仅 / 4 了而已。

我们回到上面一步的步骤,确定了在是哪个小正方形,就可以在这个小正方形里根据(old R,old G)确定最终的Target。那么(new B)就等于 像素Target颜色的B值!
在这里插入图片描述

聪明的你也许已经意识到了,在这64个小正方形里,每个小正方形相同(x,y)坐标所对应Target像素颜色的(R,G)都是一样的,仅仅只有B不一样。这也就是为什么B颜色最终是根据计算 [在哪个小正方形里] 来确定的。

我们再来完整走一遍LUT图颜色映射的全过程。

1.假如一个像素点原始的颜色是 (old R,old G,old B)=(38,201,88)

2.根据(old B)确定在哪个小正方形:88 / 4 = 22

3.在第22个小正方形里,根据(old R,old G)确定最终Target的坐标:(38 / 4,201 / 4)=(9,50)

4.假如第22个小正方形中,(9,50)所对应的Target像素的颜色是(50,3,126)

5.那么最终的颜色(new R,new G,new B)=(50,3,126),至此完成一个像素点颜色的映射。

6.遍历原始图片的每一个像素,都走一遍1~5的过程,完成对整张图片的滤镜效果。

这里先解答一下上面 [问题1] 和 [问题2] 的解答:

问题1:为什么是16Mb?

因为映射关系都用LUT图表示,每个像素代表一条映射,那么64×64×64 = 2^18,一张 2^18 个像素的无损图片(一般是.png)大小至少是256Kb,而 256×256×256 = 2^24 个像素的无损图片大小至少是16Mb。

问题2:有哪些补偿精度的方法?

注意到上面精度的丢失是因为像素颜色从 256 –> 64,我们上面在做除法的时候丢失了小数点,例如(38 / 4,201 / 4)=(9,50),但其实应该是(38 / 4,201 / 4)=(9.5,50.25),在实际运用的时候我们并不会抛弃小数点。

在计算的时候,如果计算得到的坐标不是位于一个像素的正中心,那么在取色时,会对相邻的几个像素进行加权平均,更靠近的像素权重越大。直观地说就是,理谁越靠近,那么谁就对最终的颜色有更重要的影响。

例如下面这个图,在最终确定颜色时,会考虑相邻的4个像素点的颜色。这就是双线性插值法,除此之外也有单线性插值法,有兴趣的朋友欢迎交流。
在这里插入图片描述
双线性插值法示意图:
在这里插入图片描述

如何制作一个LUT图?


1.打开Photoshop,打开一个你想调色的图片

在这里插入图片描述

2.通过各种调节,达到你所期待的颜色

在这里插入图片描述

3.载入一张原始LUT图

在这里插入图片描述
什么是原始LUT图:就是经过这个LUT颜色变化之后,还是原来的颜色,也就是 [什么颜色都不变]

它的映射关系:

1
2
3
4
5
6
7
8
9
10
(0,0,0) ---> (0,0,0)

(0,0,1) ---> (0,0,1)

………………

(254,255,255) ---> (254,255,255)

(255,255,255) ---> (255,255,255)

4.对这张LUT图也应用上对刚才图片的调色效果

在这里插入图片描述
至此一张LUT滤镜图就做好了:
在这里插入图片描述

怎么理解这个过程?

我的理解是,我们对图片进行 [调色的一系列操作],再 [作用在原始LUT图上],就相当于让这张原始LUT图记录下了 [这一系列操作],记下来之后就可以拿去对任意的图片进行滤镜效果了。

最后附一个效果图

请添加图片描述

[转载] 美颜的奥秘——磨皮底层原理

作者 wyanassert
2025年8月23日 15:14

原文地址

据不完全统计,全世界每隔3秒就有一个人上传自己的自拍照,甚至不少人在P图上所花的时间都超过了化妆时间。

从十多年前“美图秀秀”的横空出世,再到近年来的实时美颜。到今天,美颜功能已经嵌入到各类手机系统当中,帮助大家实现完美自拍。有玩笑说,中国的P图术、韩国的整容术和日本的化妆术瓜三分天下。此秘术自诞生以来教众不断,但受用者,可瞬间变成天仙下凡,号称“传说中的3大东方神秘力量”。由此可见,随着朋友圈、微博等自拍社交越来越盛行,拍个美美的照片已经是人们的刚需了。

其实磨皮算法最底层的本质就是一种降噪算法,也可以说是模糊算法。即通过对脸部的模糊,把各种面部瑕疵模糊掉,以达到磨皮的效果。

本文很简单地介绍几种很基础的模糊算法以及磨皮后的边缘和细节还原。
在这里插入图片描述

一、磨皮核心——模糊算法


模糊算法也可以说是降噪算法,把清晰可见的东西变得模糊。磨皮的原理就是把脸部变“模糊”,把各种瑕疵、皱纹、痘印等变模糊,使得整个脸看起来很光滑平整。模糊算法就是这样,可以隐去很多细节,也可以说是可以用更少的图像信息量去表达一幅图,因此许多细节就在模糊的过程中被抹去。

如何使一张图片变模糊呢,我们不妨从微观看起。

我们来看一张3*3的图:

在这里插入图片描述

假设上面的数字都代表当前位置的像素值。

假如正中央的像素”9”代表一个瑕疵点,那么我们如何把这个”9”模糊掉呢?

1.中值滤波

对核心及周围的像素值排序,取中间值作为新的像素值。
在这里插入图片描述

2.均值滤波

将核心及周围的像素求和取平均值,作为新的像素值。
在这里插入图片描述

3.高斯滤波

在均值滤波的基础上,对每个像素加上一个权重。这个权重是一个高斯函数。概况地说,距离中心点越近,权重越大;距离中心点越远,权重越小。

一维高斯函数可以这样表示:
在这里插入图片描述
下图分别是一维高斯图像和二维高斯图像:
在这里插入图片描述
把二维高斯函数放到我们上面的3*3的区域中,并做归一化,就得到了权重:
在这里插入图片描述
那么最终的颜色这样计算:
在这里插入图片描述

4.双边滤波

在高斯滤波的基础上,再加上一个[像素差异]的权重:与中心颜色相差越大,权重越低;与中心颜色相差越小,权重越高。

这么做是为了能够在模糊的时候,较好地保护图像的边缘细节信息。这也是磨皮常用的模糊算法,因为磨皮就是需要保留人脸的一些纹路边缘细节,使得磨皮效果看起来更加自然。

可以这么理解:

高斯核是[空间域]上的权重:距离中心的空间距离越远,权重越小。

双边滤波多了一个[值域]上的权重:距离中心的像素差别越大,权重越小。

以下两个图片可以更好理解双边滤波:
在这里插入图片描述
在这里插入图片描述
还是拿上面3*3的区域应该这样算:
在这里插入图片描述
这里的值域核仅为了表达方便,实际应用中也需要做类似归一化的操作

我们可以看一下这几种模糊算法的效果:

原图:
在这里插入图片描述
中值滤波:
在这里插入图片描述
均值滤波:
在这里插入图片描述
高斯滤波:
在这里插入图片描述
双边滤波:
在这里插入图片描述

二、锐化——边缘和细节还原


锐化可以分成2步:第一步,提取边缘;第二步:边缘还原到原图上。第二步其实就是简单的把边缘图叠加到原图上,因此这里重点介绍边缘提取算法。

在这里插入图片描述

1.USM锐化

USM锐化是最常见的锐化,其主要利用了模糊图,原理如下:
在这里插入图片描述
上文说过的模糊算法其实就是把大部分细节抹去,用原图减去模糊图,就得到了这幅图像的边缘和细节了。得到细节之后,叠加回原图,就实现了锐化的效果。

如果想要更大的锐化怎么办呢?那就使用更模糊的图,以得到更大的差值:
在这里插入图片描述

2.拉普拉斯Laplace锐化

拉普拉斯锐化方式是通过对像素进行卷积遍历,以得到边缘。
在这里插入图片描述
以4领域卷积核为例:

如果当前像素与上下左右4个像素完全相同,那么计算得的结果为0,即代表当前像素并不是边缘;

反之如果计算结果不为0,说明当前像素与上下左右像素值存在差异,那么这个像素在一定程度上是边缘的一部分。

拉普拉斯锐化效果如下:
在这里插入图片描述

3.索贝尔sobel锐化

sobel锐化也是使用对像素的卷积遍历,不同的是它区分横纵卷积核。
在这里插入图片描述
以横向卷积核为例:

如果左边一列和右边一列像素完全相同,那么计算得的结果为0,即代表当前像素并不是边缘;

如果左边一列和右边一列像素值有所差别,那么计算结果不为0,代表当前像素正处在边缘部分。

sobel边缘提取效果如下:
在这里插入图片描述

效果图:
请添加图片描述

Kuikly 开发笔记

作者 wyanassert
2025年7月15日 17:40

参考链接

Kuikly - 组件

Pager

Pager为Kuikly页面的入口类,类似iOS UI中的VC, Pager也有类似VC的生命周期:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 已创建
override fun created() {
super.created()

}

// 页面在屏幕上可见
override fun pageDidAppear() {
super.pageDidAppear()

}

// 页面在屏幕上消失
override fun pageDidDisappear() {
super.pageDidDisappear()

}

// 页面即将消失
override fun pageWillDestroy() {
super.pageWillDestroy()

}

一个常见的Page

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 设置路由跳转名
@Page("my_ctstom_name")
// 定义一个自己的类, 继承于 BasePager
internal class MyCustomPage : BasePager() {
// 伴生对象, Kotlin 中,类没有静态成员的概念,但通过 companion object,你可以模拟静态成员的行为
companion object {
// 定义一个静态常量 TAG
private const val TAG = "MyCustomPageTag"
}

// 定义 currentName 是个可观察的属性, 设置给 UI 组件后, 值变化会直接更新 UI 组件的内容, 类似 OC 的 RAC
var currentName by observable("")

// 构造 self.view
override fun build(): ViewBuilder {
// 使用 this 会导致循环引用, 所以一般在这里定义一个 ctx
val ctx = this
return {
// 布局属性
attr {
backgroundColor(Color.WHITE)
}
// 子 View
View {

}
}
}
override fun createPlugins(): Map<String, () -> PluginModule> {
val plugins = HashMap<String, () -> PluginModule>().apply {
putAll(super.createPlugins()) // 基类的
hashMapOf(
MediaPlugin.PLUGIN_NAME to { MediaPlugin() } // 再增加一个
).let { putAll(it) }
}
return plugins + BizPluginBuilderList.getSomePlugin() // 这里也能加
}
}

VM

Pager 继承 BasePageStatePager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Page("my_ctstom_name")

internal class MyCustomPage : BasePageStatePager() {

companion object {
private const val TAG = "MyCustomPage"
}

val viewModel: MyCustomModel by lazy { getViewModel(MyCustomModel::class) }


override fun created() {
super.created()
KLog.i(TAG, "created")
viewModel.acquirePageData() // 初始化获取数据
}

override fun buildWhenSuccess(): ViewBuilder {
val ctx = this
KLog.i(TAG, "buildWhenSuccess")
return {
// 布局属性
attr {
flexDirectionColumn()
}
View {
attr {
size(pagerData.pageViewWidth, pagerData.pageViewHeight)
backgroundColor(Color.BLUE)
}
}
}
}

override fun onErrorStateClick() { }

override fun onEmptyStateClick() { }

override fun onNoNetStateClick() { }

override fun bizType(): List<String>? = null

override fun getPageStateViewModel(): BasePageStateViewModel<out BasePageStatePager> = viewModel

override fun createPlugins(): Map<String, () -> PluginModule> {
return super.createPlugins() + BizPluginBuilderList.getBubble() // 业务需要的 在这里加
}
}

VM 继承 BasePageStateViewModel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
internal class MyCustomModel : BasePageStateViewModel<MyCustomPage>() {
companion object {
private const val TAG = "MyCustomModel"
}
fun acquirePageData() {
KLog.i(TAG, "acquirePageData")
setTimeout(200) {
KLog.i(TAG, "markSuccess")
markSuccess()
}
}
}

// 在 ViewModelFactory.kt 增加一行, **千万注意, 这里是 Model 对 Model**
fun createViewModel(modelClass: KClass<out BaseViewModel<out BasePager>>): BaseViewModel<out BasePager>? {
return when (modelClass) {
// ...
MyCustomPageModel::class -> MyCustomPageModel()
// ...
}
}

数据刷新

by observable

变量被设置为 by observable 后, UI 绑定这个变量会直接变化

1
2
3
4
5
6
7
var isOpen by observable(false)

Switch {
attr {
isOn(ctx.isOpen)
}
}

vbind

组件使用vbind包裹,当vbind内属性发生改变时,整个组件会重新绘制

1
2
3
4
5
6
7
8
9
10
11
vbind({ ctx.displaySomething }) {
if ( xx ) {
View {

}
} else if ( xx ) {
View {

}
}
}

vfor

对于数量可变的列表数据,使用vfor包裹来实现cell,列表变化时会重新绘制

1
2
3
4
5
6
7
vfor({ ctx.dataList }) { itemData ->
PersonInfoCustomizeCell {
attr {
item = itemData
}
}
}

vif

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
vif({ xxx }) {
View {

}
}
velseif({ }) {
View {

}
}
velse {
View {

}
}

ComposeView

1
2
3
4
5
class CustomView : ComposeView<>() {
}
internal fun ViewContainer<*, *>.Custom(init: CustomView.() -> Unit) {
addChild(CustomView(), init)
}

外部设置值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class CustomView : ComposeView<CustomViewAttr>() {

override fun body(): ViewBuilder {
val ctx = this
val bottomArrowX = ctx.attr.bottomArrowX
// ...
}

override fun createAttr(): CustomViewAttr {
return CustomViewAttr()
}
}

internal class CustomViewAttr : ComposeAttr() {
var bottomArrowX = 0f
// ...
}

// 其他类调用
class OtherClassView : ComposeView() {

override fun body(): ViewBuilder {
val ctx = this

View {
Custom {
attr {
bottomArrowX = showX
}
}
}
}
}

事件处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class CustomView : ComposeView<CustomViewEvent>() {
override fun body(): ViewBuilder {
val ctx = this

View {
event {
click {
KLog.i(TAG, "onSendClick")
ctx.event.onSendClick?.invoke(ctx.attr.customViewParam)
}
}
}
}
}

class CustomViewEvent : ComposeEvent() {
var onSendClick: ((CustomViewParam) -> Unit)? = null
}
// 其他类调用
class OtherClassView : ComposeView() {

override fun body(): ViewBuilder {
val ctx = this

View {
Custom {
event {
onSendClick = { customViewParam ->
// 事件处理
}
}
}
}
}
}

布局

布局属性

Flex 布局

flexDirection 主轴

  • flexDirectionColumn(默认): 主轴方向为竖直方向,子孩子从上往下布局
  • flexDirectionRow: 主轴方向为水平方向,子孩子从左往右布局
  • flexDirectionColumnReverse: 主轴方向为竖直方向,子孩子从下往上布局
  • flexDirectionRowReverse: 主轴方向为水平方向,子孩子从右往左布局

justifyContent 主轴分布模式

  • justifyContentFlexStart(默认): 主轴开始的位置进行对齐
  • justifyContentFlexEnd: 主轴结束的位置进行对齐
  • justifyContentCenter: 主轴的中间位置进行对齐
  • justifyContentSpaceBetween: 主轴两端对齐,子孩子之间的间隔都相等
  • justifyContentSpaceAround: 每个项目两侧的间隔相等。所以,子孩子之间的间隔比项目与边框的间隔大一倍
  • justifyContentSpaceEvenly: Flex Item之间的间距相等,包括与边缘位置的距离

alignItems 交叉轴

  1. alignItemsFlexStart: 交叉轴的起点对齐
  2. alignItemsFlexEnd: 交叉轴的终点位置对齐
  3. alignItemsCenter: 交叉轴的中点位置对齐
  4. alignItemsStretch(默认): 如果 Flex 容器的孩子没有指定大小(高度或者宽度,取决于交叉轴是水平还是竖直)的话,将占满 Flex 容器
  • 当 flexDirection 为 flexDirectionRow 时,交叉轴的方向为竖直方向,此时 alignItems 各个属性的效果为
    企业微信截图_6d150c72-644a-42cd-9a28-8b92a9a082df
  • 当 flexDirection 为 flexDirectionColumn 时,交叉轴的方向为水平方向,此时 alignItems 的各个属性效果为:
    企业微信截图_ce12e3a8-ebf2-4c89-a397-638292dbc2c5

Flex Item 布局属性

上面讲述的 Flex Container 属性,是针对 Flex Container 下的所有孩子生效。Flex Item 也可自己设置布局属性,覆盖 Flex Container 的属性。

alignSelf

alignSelf属性是控制Flex Item自身在 Flex 容器的交叉轴上的对齐方式,会覆盖 Flex 容器指定的 alignItems 属性,可选值为:

  1. alignSelfFlexStart: Flex Item 自身在 Flex 容器的交叉轴的起点对齐
  2. alignSelfCenter: Flex Item 自身在 Flex 容器的交叉轴中点对齐
  3. alignSelfFlexEnd: Flex Item 自身在 Flex 容器的交叉轴终点对齐
  4. alignItemsStretch: Flex Item 自身在交叉轴方向上铺满Flex 容器
  • alignSelf 与 alignItems 差不多,具体的对齐方向与 flexDirection 的值有关
  • 当 flexDirection 的值为 flexDirectionColumn 时,Flex Item 设置 alignSelf 的效果如下:

flex

Flex Item 在主轴上,占据 Flex 容器的剩余可用空间的比例。 可用空间是指 Flex 容器除去已经被占用的空间,剩下的空间大小, 比如: 顶部是一个 titlebar,剩下的空间分配给列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
View {
attr {
size(screenWidth, screenHeight)
flexDirectionColumn() // 主轴方向为竖直方向,孩子从上往下排列
// alignItems默认值为 alignItemsStretch, 因此在交叉轴,即水平方向上
// 其孩子的宽度与父亲一样大
}

View { // title bar
attr {
height(56f)
// 宽度为父亲的宽度, 因为父亲的alignItems 默认为 alignItemsStretch,
// 在交叉轴上的大小会占满父亲
}
}

List {
attr {
flex(1f) // 表示占满父容器主轴上的可用空间,即占满父亲可用的高度(screenHeight - 56f)
}
}
}

绝对布局

与 superView 一样大

1
2
3
4
5
View {
attr {
absolutePositionAllZero()
}
}

保持间距

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
View {
attr {
size(screenWidth, screenHeight)
}

Image {
attr {
positionAbsolute()
// 表示宽高与父容器的宽高一样大
top(0f)
bottom(0f)
right(0f)
left(0f)
}
}
}

常用UI组件

设置背景色/圆角

1
2
3
4
attr {
backgroundColor(ctx.buildThemedColor("skin_floor_color"))
borderRadius(10f)
}

文本

1
2
3
4
5
6
7
8
9
10
Text {
attr {
fontSize(36f.pxw)
fontWeight500()
marginTop(40f.pxw)
marginBottom(10f.pxw)
text(itemData.title)
color(ctx.buildThemedColor("skin_text_main_color"))
}
}

按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Button {
attr {
size(80f, 40f)
borderRadius(20f)
marginLeft(2f)
marginRight(15f)
backgroundLinearGradient(
Direction.TO_BOTTOM,
ColorStop(Color(0xAA23D3FD), 0f),
ColorStop(Color(0xAAAD37FE), 1f)
)

titleAttr {
text(JUMP_TEXT)
fontSize(17f)
color(Color.WHITE)
}
}
click(Clickable.nill()) {

}
}

加载图片

1
2
3
4
5
6
7
Image {
attr {
src("https://musicx.y.qq.com/kuikly/assets/ic_svip_quality_guide_check.png")
size(DESIGN_DESC_ICON_SIZE.pxh(), DESIGN_DESC_ICON_SIZE.pxh())
marginRight(DESIGN_DESC_ICON_MARGIN_RIGHT.pxh())
}
}
1
2
3
4
5
6
7
8
9
10
const val MORE_ICON_PATH = "bubble/common/bubble_function_more.png"

Image {
attr {
size(songIconWH, songIconWH)
// TODO:wyan 后面改成内置图, 打包验收
// src(getPager().getResPath(MORE_ICON_PATH))
src("https://y.qq.com/music/photo_new/T011M000004FTa8N17qFpb.png")
}
}

列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
List {
attr {
positionAbsolute()
}
vfor({ ctx.dataList }) { itemData ->
View {
attr {
Size(pagerWidth, 40F)
flexDirectionRow()
}

Image {
attr {
size(30F, 30F)
src(itemData.avatarUrl)
}
}

Text {
attr {
fontSize(36f.pxw)
fontWeight500()
marginTop(40f.pxw)
marginBottom(10f.pxw)
text(itemData.title)
color(ctx.buildThemedColor("skin_text_main_color"))
}
}
}
}
}

列表 cell 高度设置(在 cell 设置)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
List {
ref {
// it 是一个隐式名称(implicit name)的参数,用于表示单个参数的 lambda 表达式中的参数。当你在 lambda 表达式中只有一个参数时,可以使用 it 来代替显式声明参数名称
ctx.listViewRef = it
}
attr {
absolutePositionAllZero()
}
vfor({ ctx.dataList }) { itemData ->
PersonInfoCustomizeCell {
attr {
item = itemData
}
}
}
}

// cell 单独设置
override fun body(): ViewBuilder {
val ctx = this
return {
attr {
height(120F)
}
View {
attr {
backgroundColor(Color.WHITE)
borderRadius(10F)
positionAbsolute()
top(0F)
left(20F)
right(20F)
bottom(10F)
flexDirectionRow()
}
}
}
}

列表调用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import com.tencent.kuikly.core.views.ListView

internal class xxxPager : BasePager() {
var listViewRef : ViewRef<ListView<*, *>>? = null


override fun build(): ViewBuilder {
val ctx = this
return {
List {
ref {
ctx.listViewRef = it
}
attr {
absolutePositionAllZero()
}
}
}
}
override fun viewDidLayout() {
super.viewDidLayout()

this.listViewRef?.view?.setContentInset(top = 500F)
this.listViewRef?.view?.setContentOffset(0F, -500F) // 设置 inset 后 自动产生了一个 offsetY 偏移
}
}

subView 回调

1
2
3
4
5
6
7
8
// subView 先获取到 pager
var pager = getPager()
// 判定 pager 类型,
if (pager is SomePager)
{
// 调用被标记为 observable 的对象, 达到调用的目的
pager.isShowAutoTranslateWarningDialog = true
}

弹窗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 是否需要展示自动翻译弹窗
var isShowAutoTranslateWarningDialog by observable(false)

return {
View {
// 自动翻译弹窗
ctx.buildAutoTanslateWarningDialog().invoke(this);
}
}

private fun buildAutoTanslateWarningDialog(): ViewBuilder {
val ctx = this
return {
SendMsgWarningDialog {
attr {
showDialog = ctx.isShowAutoTranslateWarningDialog
title = StringConst.AUTOTRANSLATE_DIALOG_TITLE
content = StringConst.AUTOTRANSLATE_DIALOG_CONTENT
}
event {
onButtonSureClick = {
ctx.isShowAutoTranslateWarningDialog = false
KLog.i(TAG, "[clickedTranslateBtnForAlert] click sure ")
}
onButtonCancelClick = {
// 取消
ctx.isShowAutoTranslateWarningDialog = false
KLog.i(TAG, "[clickedTranslateBtnForAlert] click cancel ")
}
}
}
}
}


// 最后 在需要的时候
this.isShowAutoTranslateWarningDialog = true

ViewBuilder

直接返回一个方法体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
override fun build(): ViewBuilder {
val ctx = this
return {
// 复用分割线, invoke 是 kotlin 的语法
ctx.cutOffLineViews().invoke(this)
}
}
// 定义一个可复用的标签
private fun cutOffLineViews(): ViewBuilder {
return {
View {
attr {
backgroundColor(Color(0xffEBEBEB))
height(1f)
margin(40f)
}
}
}
}

跟肤

直接跟肤的字段

1
color(ctx.buildThemedColor("skin_text_main_color"))

不能直接用的 这样处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
var cardSubTitleColor by observable(Color.BLACK.withAlpha(0.5f))

override fun created() {
super.created()
getThemeColor()
(getPager() as BasePager).let { page ->
page.nativeEvent?.on(EVENT_THEME_STATE_CHANGE)
page.registerNativeEventListener(this)
}
}

override fun viewDestroyed() {
super.viewDestroyed()
(getPager() as BasePager).let { page ->
page.unregisterNativeEventListener(this)
page.nativeEvent?.off(EVENT_THEME_STATE_CHANGE)
}
}

private fun getThemeColor() {
KLog.i(TAG, "[getThemeColor] start")
val scheme = QMSchemeUtil.buildScheme(
module = "theme",
method = "getThemeColors",
params = JSONObject()
)
Bridge.getPlugin<UIPlugin>(UIPlugin.PLUGIN_NAME)?.apply {
openNative(OpenUrlInfo.createSchemeInfo(scheme)) { res ->
KLog.i(TAG, "[getThemeColor] getThemeColors, result= $res")
val skinMainTextColorStr = res?.optJSONObject("data")?.optString("skin_text_main_color")
KLog.i(TAG, "[getThemeColor] getThemeColors, skinMainTextColorStr= $skinMainTextColorStr")
if (skinMainTextColorStr != null) {
val color = ColorTransformer.fromRGBAStr(skinMainTextColorStr).buildColor()
val pushBtnBgColor = color.withAlpha(0.5f)
cardSubTitleColor = pushBtnBgColor
}
}
}
}

override fun onNativeEvent(event: String, data: JSONObject?) {
super.onNativeEvent(event, data)
when (event) {
EVENT_THEME_STATE_CHANGE -> {
KLog.i(TAG, "theme change")
getThemeColor()
}

else -> {}
}
}

图片染色(tintColor), 需要用 vbind 包一下, 不然不会跟着变化(by observable 失效了)

1
2
3
4
5
6
7
8
9
10
11
vbind({ ctx.viewModel.cardIconColor }) {
Image {
attr {
absolutePositionAllZero()
tintColor(ctx.viewModel.cardSubTitleColor)
src(getPager().getResPath(DEFAULT_ALBUM_PATH))
tintColor(ctx.viewModel.cardIconColor)
resizeCover()
}
}
}

KuiklyView size 变化通知 Native

  1. Kuikly 侧页面调用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    View {
    attr {}
    event {
    layoutFrameDidChange {
    // 通知 native层 业务宽高变化,
    // 会发送 "content_size_changed" "content_size_did_changed" 两个通知
    ctx.sendContentSizeChangeEvent(it)
    }
    }
  2. 胶水层 (统一处理, 业务不用处理) 监听 content_size_did_changed 通知
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    // 胶水层 KKKuiklyView.m
    // 业务宽高变化,需要业务侧自行实现,用于动态撑开KKKuiklyView
    -(void)onReceiveContentSizeChanged:(NSNotification *)notification
    {
    NSString *thisUUID = _routerInfo.extraInfo.uuid;
    NSDictionary *info = notification.userInfo; // Kuikly侧传递的参数
    if (info[@"container_uuid"] && [info[@"container_uuid"] isKindOfClass:[NSString class]]) {
    NSString *eventContainerUUID = [info objectForKey:@"container_uuid"];
    if (thisUUID && eventContainerUUID && ![thisUUID isEqualToString:eventContainerUUID]) {
    // 事件的containerUUID对不上,说明不是相同页面的事件,直接忽略
    return;
    }
    }
    NSString *pageName = @"";
    if (info[@"pageName"] && [info[@"pageName"] isKindOfClass:[NSString class]]) {
    pageName = [info objectForKey:@"pageName"];
    }
    CGFloat contentWidth = -1;
    if (info[@"width"] && [info[@"width"] isKindOfClass:[NSNumber class]]) {
    contentWidth = [info[@"width"] floatValue];
    }
    CGFloat contentHeight = -1;
    if (info[@"height"] && [info[@"height"] isKindOfClass:[NSNumber class]]) {
    contentHeight = [info[@"height"] floatValue];
    }
    KuiklyLog(TAG, @"[onReceiveContentSizeChanged] pageName: %@, width: %f, height: %f", pageName, contentWidth, contentHeight);
    CGRect newFrame = self.frame;
    newFrame.size.height = contentHeight;
    newFrame.size.width = contentWidth;
    // 因为KKKuiklyView重写了setFrame方法,这里要调用父类的 setFrame,避免触发KuiklyRendeView的布局变化
    [super setFrame:newFrame];

    CGRect newKuiklyFrame = self.kuiklyDelegator.renderView.frame;
    newKuiklyFrame.size.height = contentHeight;
    newKuiklyFrame.size.width = contentWidth;
    // 更新kuikly render view的frame
    [self.kuiklyDelegator.renderView setFrame:newKuiklyFrame];
    }
  3. 业务 监听 content_size_did_changed 通知
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 业务宽高变化,需要业务侧自行实现,用于动态撑开KKKuiklyView
    -(void)onReceiveContentSizeChanged:(NSNotification *)notification
    {
    NSDictionary *info = notification.userInfo; // Kuikly侧传递的参数
    NSString *notiUUID = [JsonHelper getStringFromDictionary:info forKey:@"container_uuid" isBase64Format:NO withDefault:@""];
    NSString *curUUID = kuiklyContentView.routerInfo.extraInfo.uuid;
    PersonalInfoLog(@"Kuikly view size 更新 %@, %@, %d, height: %.1f", notiUUID, curUUID, kuiklyContentView == nil, kuiklyContentView.height);
    if (curUUID.length > 0 && [curUUID isEqualToString:AVOID_NIL_STRING(notiUUID)]) {
    if (kuiklyContentView) {
    // ,,,
    }
    }

逻辑

日志

1
2
3
4
5
KLog.i(
TAG,
"[onMessageBodyLongPress] msgFrame=$msgFrame, msgBodyFrame=$msgBodyFrame, clickX=$clickX, clickY=$clickY, msgBodyLeft=$msgBodyLeft, messageModel=$messageModel"
)
KLog.i(TAG, "created artist_enc_uin: fromOne ${fromOne} fromTwo ${fromTwo}")

缓存

1
bubblePlugin?.read(key = KEY_HAVE_DISPLAYED_GUIDE_VIEW + userId + myOwnEncryptUin)

判空(null 执行 toInt() 会崩溃)

1
!clickCountBefore.isNullOrEmpty()

类型转换

1
2
3
4
// toLong() 在遇到 "abc" 这样的字符串 会崩溃, 要用 toLongOrNull, toInt() 也有类似问题
var clickTs = clickTsBefore?.toLongOrNull() ?: 0L

val tsStr = clickTs.toString()

延时处理

1
2
3
setTimeout(500) {
// xxx
}

获取时间戳

1
var ts = DateTime.currentTimestamp(); // 毫秒

枚举

1
2
3
4
5
6
internal enum class MediaType {
PHOTO,
CAMERA,
VIDEO,
VOICE
}

字符串兜底值

1
2
3
4
var fromOne = pagerData.getParamString("artist_enc_uin") ?: ""
var fromTwo = pagerData.getExtrasParamString("artist_enc_uin") ?: ""
// 兼容web侧跳转
var from = fromOne.ifEmpty { fromTwo }

通知

1
2
3
4
5
6
7
8
9
10
11
12
// 监听
notifyModule.addNotify("xx") {
// let 在非空的情况下对一个对象执行一个代码块(block), 作用是对非空对象进行操作,避免了空值检查的需要
it?.getString("xx")?.let { returnName ->
// xx
}
}

// 发通知
notifyModule.postNotify("xx", JSONObject().apply {
put("xx", "value")
})

页面跳转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 跳转封装
fun openPage(page: String, jsonObject: JSONObject? = null, mode: Int = TABBAR_DISPLAY_HIDE_ALL, statusLight : Boolean = true, extras: JSONObject? = null) {
Bridge.getPlugin<UIPlugin>(UIPlugin.PLUGIN_NAME)?.apply {
openKuikly(KuiklyRouterInfo().apply {
router = KuiklyRouterInfo.ROUTER_OPEN_QMPAGE
pageName = page
params = jsonObject
// 状态栏模式
container = Container().apply {
tabbarMode = mode
this.statusLight = statusLight
}
pExtras = extras
})
}
}

// 调用
openPage("my_ctstom_name", JSONObject().apply {
put("uin", uin)
put("XX", xx)
})

调用 Native 的 scheme

1
2
3
4
5
6
7
8
//  使用 plugin 记得在 pager 的 createPlugins 加一下, 
uiPlugin?.openNative(
module = "ui",
method = "xxxMethod",
params = JSONObject().apply {
put("color", 1)
}
)

数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 数组 
listOf("*.jar", "*.aar")
// 可变数组
val topImages = mutableListOf<DressDialogTopImage>()
topImages.add(topImage)

// 遍历
val array = intArrayOf(1, 2, 3, 4, 5)

for (element in array) {
println(element)
}

// 使用 for 循环和索引
val array = intArrayOf(1, 2, 3, 4, 5)

for (index in array.indices) {
println("Index: $index, Value: ${array[index]}")
}

哈希表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 哈希表
mapOf("h" to h, "m" to m, "s" to s)
// 可变哈希表
val data = mutableMapOf()
data[""] = ""

// 遍历
val map = mapOf("a" to 1, "b" to 2, "c" to 3)
map.forEach { (key, value) ->
println("Key: $key, Value: $value")
}
// 遍历键/值
val map = mapOf("a" to 1, "b" to 2, "c" to 3)

for (key in map.keys) {
println("Key: $key")
}

通知

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
internal class xxxPager : BasePager() {
private lateinit var eventCallbackRef: CallbackRef

override fun created() {
super.created()
eventCallbackRef = acquireModule<NotifyModule>(NotifyModule.MODULE_NAME).addNotify("xxx") {
// ...
}
}

override fun pageWillDestroy() {
super.pageWillDestroy()
acquireModule<NotifyModule>(NotifyModule.MODULE_NAME).removeNotify("xxx", eventCallbackRef)

}
}

事件通知 event on/off

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// View 版示例
internal class xxxView: ComposeView(),
BasePager.NativeEventListener {

override fun created() {
super.created()
KLog.i(TAG, "created")

// 监听进入后台
(getPager() as BasePager).let { page ->
page.nativeEvent?.on(EVENT_ENTER_BACKGROUND)
page.registerNativeEventListener(this)
}
}

override fun viewDestroyed() {
super.viewDestroyed()
KLog.i(TAG, "viewDestroyed")

(getPager() as BasePager).let { page ->
page.unregisterNativeEventListener(this)
page.nativeEvent?.off(EVENT_ENTER_BACKGROUND)
}
}

override fun onNativeEvent(event: String, data: JSONObject?) {
super.onNativeEvent(event, data)
if (EVENT_ENTER_BACKGROUND == event) {
KLog.i(TAG, "enter bg")

}
}
}

如果是 BasePager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
internal class xxxPager : BasePager() {
override fun created() {
super.created()
nativeEvent?.initCallback(this)
nativeEvent?.on(EVENT_PLAY_STATE_CHANGED)
}
override fun viewDestroyed() {
super.viewDestroyed()
nativeEvent?.off(EVENT_PLAY_STATE_CHANGED)
}

override fun onNativeEvent(event: String, data: JSONObject?) {
super.onNativeEvent(event, data)
val innerData = data?.optJSONObject("data")
innerData?.let {
if (event == EVENT_PLAY_STATE_CHANGED) {

}
}
}
}

Scheme 调用

普通调用 scheme

1
(getPager() as? BasePager)?.uiPlugin?.openScheme(OpenUrlInfo(moreScheme))

调用已有 scheme (media.selector 格式)

比如 media.DelObserverPlayerState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
internal class DelObserverPlayerState(
params: JSONObject
) : MediaScheme(DEL_OBSERVER_PLAYER_STATE, params) {
internal class Builder : QMScheme.Builder() {
override fun build(): QMScheme {
return DelObserverPlayerState(params)
}

fun initParams(
userID: String
): Builder {
params.put("userId", userID)
return this
}
}
}

调用:

1
2
3
4
5
val delObserverScheme = DelObserverPlayerState.Builder().initParams(userId).build().scheme()

GetModuleHelper.uiPlugin()?.openNative(delObserverScheme, callback = { data ->

})

上面的 openNative 可以换成openNativeKeepAlive, 达到多次回回调目的(iOS 如下支持, Android 不支持, 两端保持一致 可以用 event on)
iOS 需要做如下修改才能多次回调

1
2
3
4
5
6
7
// MediaJSBridgePlugin.m
- (void)handle_media_delObserverPlayerState:(NSDictionary *)params callback:(NSString *)callback
{
// 保存回调
QQJSWebViewCallback cb = [self.webViewController popCallBack:callback];
// 需要的时候自行调用
}

Kuikly 注册调用 (支持多次调用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
internal class BubblePlugin : CompatPluginModule() {
override fun pluginName(): String {
return "xxx_plugin"
}

fun callSchemexxx(param1: String, callback: (objReturn: Boolean, objReturn1: String) -> Unit) {
// keepCallbackAlive 保活, 不然只能回调一次
toNative(true, pluginMethod("xxxselector"), JSONObject().apply {
put(PARAM_1, param1)
}.toString(), { data ->
data?.let {
val objReturn = it.optString("objReturn")
val objReturn1 = it.optString("objReturn1")
callback.invoke(objReturn == "1", objReturn1)
}
}, false)
}
}

Native 实现:

1
2
3
4
5
6
7
REGISTER_BRIDGE_PLUGIN(@"xxx_plugin", @"xxxselector");

- (void)xxxselector:(NSDictionary *)params callback:(KuiklyRenderCallback)callback
{

}

❌
❌