AI Coding 工程化进阶
术语边界: 本章把 Context Engineering 定义为 “选择, 组织, 更新和裁剪模型完成任务所需的最小充分上下文”. 这是本文使用的工程定义, 不是唯一行业标准. 本章聚焦上下文, 工具协议, 隐私, 供应链, 审查与回退; harness 配置见 65, 反馈循环见 67.
适用范围: 本章用于 context, tool calling, MCP 与治理工程实践; 本章工作定义不构成行业标准, 也不代表协议或产品的当前能力. 核验日期与来源见文末.
学习目标
能为 Android 小任务打包可追溯上下文, 审查 AI 生成测试, 并记录一次模型 / 工具执行的可回放证据. 本文 schema 是示意, MCP 和具体 tool calling 的字段, 能力与认证方式须以接入时官方规范和客户端文档为准.
一, 从 Prompt 到 Context
Prompt 主要描述本轮目标和输出; Context Engineering 还管理真实代码, 架构规则, 依赖版本, 历史决策, 失败证据和工具结果. 好上下文不是越多越好, 而是:
- 与当前决策直接相关.
- 有来源, 时间和版本.
- 能区分事实, 推断和用户偏好.
- 过期后可失效或重新检索.
- 不包含任务不需要的秘密和个人数据.
对 Android 任务, context pack 通常包括目标模块, 相邻实现, Gradle 版本矩阵, min/target SDK, UI/架构约定, 可运行命令和设备条件.
Android context pack 范例
task: Fix duplicate refresh after process recreation
facts:
repository_commit: <commit>
module: feature/orders
sdk: { min: <actual-minSdk>, target: <actual-targetSdk> }
architecture: UI observes Room; network writes through repository
sources:
- path: AGENTS.md
- path: feature/orders/OrderViewModel.kt
- path: feature/orders/OrderRepository.kt
- path: feature/orders/OrderViewModelTest.kt
constraints:
write_scope: [feature/orders/**]
non_goals: [dependency upgrade, database schema change]
required_checks: [":feature:orders:testDebugUnitTest"]
unknowns: [whether device recreation test environment is available]
facts 必须有文件 / commit 来源; unknowns 不能由模型补成事实. 敏感日志, 访问令牌, 完整生产数据不进入 pack.
二, Agentic Coding 的能力边界
Agentic coding 指模型通过工具读取, 搜索, 编辑, 运行命令并根据反馈迭代. 自主性越高, 越需要:
- 明确可写范围和非目标.
- 破坏性动作和高风险领域的人工审批.
- 超时, 重试上限, 成本预算和终止条件.
- 可重放的工具结果与审计日志.
- 独立测试或 reviewer, 不能由同一输出自证正确.
三, MCP 与 Tool Calling 不是同一概念
- Model Context Protocol (MCP) 是开放协议, 用于客户端与 server 之间发现和交换能力. Server 可暴露 tools, resources 和 prompts; 支持哪些能力取决于协议版本和具体实现.
- Tool calling/function calling 是模型或 API 产品让模型选择并参数化调用工具的能力, schema, 执行方式和安全语义由具体产品定义.
- MCP 可以成为 tool calling 的能力来源之一, 但两者不能合称为同一个 “标准工具接口”.
接入 MCP/server 或任何工具系统时必须处理:
- 用户同意: 让用户知道将读取或发送什么数据, 会执行什么动作.
- 最小权限: 限制 server, tool, 资源范围, 文件路径, 网络和凭据.
- Server trust: 校验来源, 维护者, 传输, 更新渠道和供应链风险.
- 输入验证: 参数 schema, 路径, URL, 命令和返回内容都视为不可信输入.
- 审计: 记录协议/客户端/server 版本, 调用参数摘要, 结果和批准事件.
- 版本协商: 不能假设客户端与 server 永远支持相同协议版本和可选能力.
- Prompt injection 防护: 工具返回, issue, 文档和网页内容不能自动升级为高优先级指令.
Tool schema 的最小审查点
下面是示意 JSON Schema, 不代表 MCP 或任一 API 的必需字段. 它展示了路径应被约束在已批准根目录, 而不是将任意 shell 文本直接交给模型执行:
{
"name": "read_source",
"description": "Read a UTF-8 source file under an approved repository root",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"minLength": 19,
"maxLength": 240,
"pattern": "^feature/orders/(?:[A-Za-z0-9_-]+/)*[A-Za-z0-9_-]+\\.kt$"
},
"start_line": { "type": "integer", "minimum": 1 },
"end_line": { "type": "integer", "minimum": 1, "maximum": 2000 },
"max_bytes": { "type": "integer", "minimum": 1, "maximum": 262144 }
},
"required": ["path"],
"additionalProperties": false
}
}
该模式由允许的路径段组成: 目录段只能是 [A-Za-z0-9_-]+, 文件名也只能由该集合组成后接 .kt, 因此根目录后的首段, 任意中间段及末段均不可能是完整的 . 或 ... 正例: feature/orders/OrderViewModel.kt, feature/orders/ui/Order_List.kt; 反例: feature/orders/../Secrets.kt, feature/orders/ui/../../Secrets.kt, feature/orders/./OrderViewModel.kt. additionalProperties: false 防止未声明参数混入; max_bytes 是调用方可要求的上限, 服务端仍须以实际文件大小强制限制. 普通 JSON Schema 无法以可移植的方式表达 end_line >= start_line 这类跨字段比较, 因此 runner 必须在解析后显式拒绝反向范围. 运行时仍必须做 canonical-path 校验, 符号链接防护, 大小限制, 审计与权限判断; schema 匹配不等于安全授权.
四, 测试生成与代码审查
AI 可生成测试骨架, mock 数据和边界清单, 但有效闭环应是:
需求不变量
-> 人工确认预期
-> 测试先在错误实现上失败
-> 最小实现
-> 运行相关测试和静态检查
-> 独立审查断言,diff 和剩余风险
重点防止只验证 mock 调用, 用实现细节复制生产逻辑, 删除失败断言, 或为了过测试改变业务语义.
测试生成前后审查对照
| 阶段 | 审查问题 | 通过证据 |
|---|---|---|
| 生成前 | 用户不变量是什么? 旧实现为何应失败? | 用例名称, 状态转移, 反例. |
| 生成后 | 测试是否只断言 mock 调用? 是否与生产代码复制同一分支? | 将旧实现运行为红; 断言可观察状态 / 副作用. |
| 修复后 | 覆盖旋转, 取消, 异常或进程恢复了吗? | 对应 fake/dispatcher/恢复测试及环境说明. |
| Review | 测试是否因实现而被放宽? | 独立 reviewer 的 diff 和断言审查记录. |
弱测试草稿与人工修订版
下列 Kotlin 对照是示意: 需求不变量为 “ 重建后恢复的已有订单不额外刷新; 一次失败后重试从 Error 恢复为 Content“.草稿只验证 mock 调用, 错误实现即使重建时额外刷新或把状态永久留在 Loading 也可能通过; 修订版分别验证恢复和失败重试, 能拦截这两类错误实现.
// AI 弱测试草稿:实现细节断言,不能证明 UI 可恢复.
@Test fun refresh_calls_repository_once() = runTest {
val repository = mockk<OrderRepository>()
coEvery { repository.refresh() } returns Unit
val viewModel = OrderViewModel(repository)
viewModel.refresh()
coVerify(exactly = 1) { repository.refresh() }
}
// 人工修订版:重建时错误调用 refresh() 会使调用计数断言失败.
@Test fun recreated_view_model_with_restored_orders_does_not_refresh_again() = runTest {
val restoredOrders = listOf(order)
val repository = FakeOrderRepository()
val recreated = OrderViewModel(
repository = repository,
savedState = OrderSavedState(orders = restoredOrders)
)
advanceUntilIdle()
assertEquals(OrderUiState.Content(restoredOrders), recreated.uiState.value)
assertEquals(0, repository.refreshAttempts)
}
// 人工修订版:错误实现若在失败后永久保留 Error 或 Loading,会在状态断言失败.
@Test fun retry_after_failure_transitions_from_error_to_content() = runTest {
val repository = FakeOrderRepository(
results = listOf(Result.failure(IOException()), Result.success(listOf(order)))
)
val viewModel = OrderViewModel(repository)
viewModel.refresh()
assertEquals(OrderUiState.Error, viewModel.uiState.value)
viewModel.retry()
assertEquals(OrderUiState.Content(listOf(order)), viewModel.uiState.value)
assertEquals(2, repository.refreshAttempts)
}
五, 模型与工具版本治理
每次重要结果至少记录:
- 模型名称 / 版本或快照, 推理配置和最大预算.
- 客户端, agent, MCP server 和关键插件版本.
- 仓库 commit, 依赖锁文件和运行环境.
- 输入数据来源, 权限范围和人工批准.
- 命令, 结果, diff, 回退点和剩余风险.
模型或工具升级应像依赖升级一样经过代表性 eval 和分阶段 rollout. 不要用 “同一品牌模型” 推断行为不变.
可回放记录范例
run_id: 2026-08-07-search-recovery-01
repository: <commit>; dirty_worktree: false
model/client/tool-server: <exact identifiers and versions>
context_manifest: <paths plus content hashes>
permission_policy: <version/hash>; approvals: <none or approver>
commands: <literal command, environment, exit status>
diff: <commit or patch hash>; evaluation: <case ids and results>
residual_risk: <unrun device path, known limitation>
这记录的是治理要求, 不应伪装为本章已经执行过的输出.
六, 隐私, 安全与供应链
| 风险 | 控制 |
|---|---|
| token, 客户数据或日志外泄 | 数据分类, 脱敏, 最小上下文, 保留策略和区域 / 合同复核 |
| 生成不安全代码 | SAST/secret scan, 安全 review, 高风险模块人工 owner |
| 虚构或投毒依赖 | 允许列表, lockfile, SBOM, 签名 / 来源和漏洞扫描 |
| 工具或 MCP server 被替换 | 固定版本, 校验发布渠道, 最小权限和隔离运行 |
| 许可证 / 来源不明 | 代码来源与许可证审查, 不能把生成等同于无版权风险 |
| 自动发布或改密钥 | 环境隔离, 短期凭据, 双人审批和可撤销操作 |
隐私和商店政策具有地区与日期属性, 必须由安全/法务/发布 owner 按实际部署复核.
七, 回退与降级
可靠 AI Coding 流程必须允许:
- 回退 agent 生成的 commit 或配置变更.
- 在模型 / API 不可用时切换人工流程.
- 关闭有问题的工具/server/自动审批.
- 保留上一个已通过 eval 的模型和 harness 版本.
- 对连续失败, 不确定安全影响或超预算任务主动停止.
八, 面试怎么答
面试官问「MCP 和 tool calling 有什么区别」→ MCP 是客户端与 server 交换 tools/resources/prompts 的开放协议, tool calling 是具体模型/API 发起结构化调用的产品能力, 二者可结合但不是同一标准; 接入时按协议版本与客户端文档核验, 不凭记忆断言能力. 面试官问「AI 生成测试怎么审查」→ 先确认业务不变量, 要求测试在旧实现上先失败 (红), 再检查断言是否验证可观察状态而非 mock 被调用, 防止测试复制生产分支或被实现放宽; 审查记录留 diff 与断言证据. 面试官问「模型/工具版本怎么治理」→ 每次重要结果记录模型/客户端/server 版本, commit, 命令结果与批准事件; 升级像依赖升级一样先过代表性 eval 再分阶段 rollout, 保留上一个已通过 eval 的版本作回退, 用可回放记录支撑结论.
高频面试题
Q1: MCP 和 tool calling 的区别?
MCP 是客户端与 server 交换 tools/resources/prompts 等能力的开放协议; tool calling 是具体模型/API 发起结构化工具调用的产品能力. 它们可以结合, 但不是同一个标准.
Q2: Context Engineering 为什么不是塞入整个仓库?
无关或过期内容会增加成本和错误锚定, 秘密还会扩大泄露面. 应按任务检索最小充分且可追溯的上下文.
Q3: 如何上线 agent 工具升级?
固定 eval 集和基线, 记录版本, 比较正确率/越权率/成本, 小流量试用, 保留回退版本, 高风险能力重新审批.
练习与预期证据
为一个 ViewModel bug 创建 context pack, 受限 read tool schema 和测试审查表. 预期证据: 每个事实有来源, 每条未知项显式保留, 旧实现失败的测试记录, 以及模型/工具/命令/diff/风险的版本记录.
版本与参考资料
- 最后核验: 2026-08-07.
- Model Context Protocol 官方站点
- Model Context Protocol 官方仓库
- OpenAI Codex 官方文档
- MCP, 模型 API 和 coding agent 均快速演化; 协议版本, 产品能力和数据条款应在接入时重新核验.