系列 《生产化与运营:让 AI 应用可靠、可控、可持续地跑》 第 3 / 3 篇 进阶
系列总览 — 为什么这样组织、读它需要什么、读完能做什么
学习地图 — AI 应用工程师学习地图 第 6 / 7 个系列
- 模型网关——所有调用经过的一层
- 成本工程——从账单到每任务成本
- 延迟工程——从 TTFT 到 agent 的多步
上一篇:成本工程——从账单到每任务成本
事故先说:一个 RAG 问答的 p95 延迟 9 秒,用户在 4 秒时就关了页面。团队换了更快的模型,p95 降到 7 秒——没解决。分解后发现:检索 1.2 秒(rerank 对 50 个候选逐个打分)、prefill 2.5 秒(每次带 60K 的上下文且缓存不命中——前缀里有时间戳)、排队 1 秒(峰值时段)、输出 3 秒(400 token 的回答且不流式)。四段各有各的药,换模型只碰了一段。
这一篇讲延迟的分解、每一段的手段、感知延迟、p95 预算的分配,以及 agent 多步任务的延迟怎么压。
本篇要回答的核心问题是:
延迟怎么分解到每一段,每段的手段是什么?1 感知延迟与绝对延迟怎么分开优化,p95 预算怎么分?2 agent 的多步延迟怎么压,延迟与成本、质量怎么一起看?3
一、总览
1. 分解
%%{init: {"flowchart": {"wrappingWidth": 360}}}%%
%% 图:p95 = 9 秒的四段分解与各段的药
flowchart TB
Q["请求"] --> A["应用侧准备:检索 1.2 s(rerank 50 个候选)"]
A --> N["网络 + 排队:1 s(峰值时段)"]
N --> P["prefill / TTFT:2.5 s(60K 上下文,前缀有时间戳 → 缓存未命中)"]
P --> D["输出:3 s(400 token,不流式)"]
D --> U["用户看到:p95 = 9 s,但 4 s 时已经关了页面"]
A -. "rerank 候选 50 → 20、两路召回并行" .-> A
P -. "去掉时间戳让缓存命中" .-> P
D -. "流式:首字 1 s 就出现" .-> D
style U fill:#fde8e8,stroke:#c0392b
从左到右是一次请求的先后,格子宽度不代表耗时。TTFT 这一格拆成网络、排队、prefill 三段。《模型作为组件(04):成本与延迟的账——一次调用花多少钱、慢在哪一段》里 TTFT 的三段是排队、prefill、思考,这里多算了到供应商的网络;用推理模型时,prefill 后面还有一段思考。
| 段 | 决定因素 | 典型量级 | 手段 |
|---|---|---|---|
| 网关 | 多一跳、区域 | 10–100 ms | 边缘 / 就近部署、连接复用 |
| 应用侧准备 | 检索(embedding + 召回 + rerank)、上下文组装、工具预查 | 100 ms–2 s | 并行、预取、rerank 候选数、缓存检索结果 |
| 网络到供应商 | 区域、连接 | 20–200 ms | 区域路由、HTTP/2 复用 |
| 排队 | 供应商侧负载、优先级层级 | 0–几秒(峰值) | 避峰、优先级层级、fallback 区域 |
| prefill | 输入 token 数;缓存命中跳过大部分 | 每 10K token 几百毫秒(未命中) | prompt caching、瘦身、卸载 |
| 输出 | token 数 × 每 token 时间(模型越大越慢) | 20–80 ms / token | 限长、结构化、更小模型、流式 |
| 后处理 | 解析、校验、渲染 | 10–200 ms | 增量解析、边到边渲染 |
2. 本文的章节安排
第二章逐段的手段;第三章感知延迟与流式;第四章 p95 预算的分配与四层超时;第五章 agent 的多步延迟;第六章三者一起看;第七章实践建议。
二、逐段的手段
1. 应用侧准备
并行:检索、用户画像加载、工具预查互不依赖的并行发;两路召回并行(《检索与知识接入(04):索引、混合检索与 rerank》)。rerank 的候选数:从 50 降到 20 常省几百毫秒且精度几乎不变。检索缓存:同一会话内重复的检索结果缓存。预取:多轮对话里在用户打字时就按当前上下文预检索(命中率不高时是浪费——量)。上下文组装不该慢——慢通常是同步读多个存储,改并行或预加载。
2. 网络与排队
区域路由:供应商在多区域有端点时选就近的;网关部署在靠近用户或靠近供应商的位置(第一篇)。避峰:非实时任务走 Batch / Flex(《模型作为组件(04):成本与延迟的账——一次调用花多少钱、慢在哪一段》、第二篇)让实时请求少排队;峰值时段的 fallback 到另一区域或供应商(《模型作为组件(06):客户端工程——重试、超时、幂等、限流与流式解析》的方向性)。优先级层级:部分供应商有优先处理层级(加价换低排队)——对延迟敏感路径值得。
3. prefill
缓存命中是最大杠杆:命中时 prefill 跳过前缀,TTFT 从秒级到几百毫秒(《Prompt 与上下文工程(05):prompt caching 与上下文的排列》);事故里的时间戳在前缀让命中率为零。瘦身:《Prompt 与上下文工程(02):prompt 设计——稳定的模式与不稳定的措辞》的 prompt 瘦身、《Prompt 与上下文工程(04):上下文预算与压缩——给每一部分定配额,超了怎么办》的卸载与清理——agent 的每步都带几十 K 时 prefill 是主因。新版本预热:上线前用一批请求把新前缀写进缓存(第一轮全写 1.25× 的成本换用户不遇到冷启动)。
%% 图:一次对话式 RAG 请求的七段耗时(毫秒,p95)修前与修后——修前:检索 1,200(rerank 50 个候选)、排队 1,000(峰值)、prefill 2,500(60K 上下文、时间戳毁掉缓存)、输出 3,000(不流式);修后:rerank 候选 20 并两路并行 400、避峰 300、去掉时间戳缓存命中 300、流式首字 1 秒后出现;TTFT 从 4.8 秒到 1 秒
%%{init: {"xyChart": {"width": 760, "height": 340, "plotReservedSpacePercent": 60}, "themeVariables": {"xyChart": {"plotColorPalette": "#c0392b, #5b8fd6, #4d9a5c"}}}}%%
xychart-beta
title "七段 p95 耗时(毫秒):修前(红)vs 修后(蓝)"
x-axis ["网关", "应用准备 / 检索", "网络 + 排队", "prefill", "输出(到首字)", "输出(总)", "后处理"]
y-axis "毫秒" 0 --> 3200
bar [50, 1200, 1000, 2500, 3000, 3000, 200]
bar [50, 400, 300, 300, 30, 3000, 50]
输出总时长两边一样(300 token × 30 ms 谁也省不掉),但修后用户在 1 秒就开始读;修前要等 9 秒才看到第一个字——多数人 4 秒就关页面了。
4. 输出
限长:max_tokens 与 prompt 里的长度约束;结构化输出比自由文本短(《Prompt 与上下文工程(03):结构化输出——约束解码、schema 设计与失败修复》);不要让模型”先解释思路”(推理模型的思考已经在内部——《Prompt 与上下文工程(02):prompt 设计——稳定的模式与不稳定的措辞》)。更小的模型:每 token 时间与模型大小相关,级联让简单请求走快模型(第二篇)。流式:不减少总时长,但让用户在第一个 token 就看到东西——第三章。
5. 后处理
流式下的增量解析(部分 JSON 解析器——《Prompt 与上下文工程(03):结构化输出——约束解码、schema 设计与失败修复》)让结构化输出也能边到边渲染;校验在流结束时做;渲染不要等全部。
三、感知延迟与流式
1. 感知 ≠ 绝对
用户感受的是”多久有反应”与”多久能开始用”,不是”多久全部结束”。一个 6 秒的流式回答(0.8 秒开始出字)比一个 4 秒的非流式回答感觉更快。所以指标要分两个:TTFT(或 UI 上的首次有意义反馈)与总时长;前者决定”卡不卡”,后者决定”等多久”。
2. 流式的三层
- token 流:模型输出逐 token 推到前端(《模型作为组件(02):API 契约(一)——消息、工具调用、结构化输出与流式,四家 API 的共同骨架》的流式事件)。
- 步骤流:agent 的每一步(调了什么工具、拿到什么)推到前端(《工具、Agent 与 harness(03):agent 运行时——会话、持久化与 durable execution》的事件流)——用户看到”正在搜索…找到 3 个文件…正在修改…”比等 30 秒看结果好得多。
- 进度与预估:长任务给进度(完成 3 / 7 步)与预估——L7 讲呈现。
3. 流式的成本
服务端要维持连接、前端要处理增量、结构化输出要增量解析、错误可能在流中间发生(《模型作为组件(06):客户端工程——重试、超时、幂等、限流与流式解析》的六个坑);但对任何面向用户的对话与 agent,流式是默认——不流式的场景只有后台批处理与非交互的 API。
4. 骨架与占位
首字前的 0.5–1 秒也要有反馈:骨架屏、”正在思考”的状态、已知信息先渲染(用户的问题回显、检索到的来源先列出来——来源在回答之前就有)。这些是 L7 的内容,但它们是延迟工程的一部分:把等待变成可见的进展。
%% 图:同一个 6 秒的回答,用户看到的三种时间线——非流式:6 秒白屏后整段出现,用户在第 4 秒已经怀疑卡死;token 流:0.8 秒出第一个字,之后持续有字出来;步骤流(agent):0.3 秒「正在搜索」、1.5 秒「找到 3 个文件」、3 秒「正在修改」、6 秒结果;总时长相同,感知完全不同
flowchart TB
subgraph A["非流式"]
direction LR
A0["0 s:白屏"] --> A4["4 s:还是白屏(用户怀疑卡死)"] --> A6["6 s:整段出现"]
end
subgraph B["token 流"]
direction LR
B0["0 s:骨架屏 · 来源先列出"] --> B1["0.8 s:第一个字"] --> B3["1–6 s:持续出字,用户边读"] --> B6["6 s:结束"]
end
subgraph C["步骤流(agent)"]
direction LR
C0["0.3 s:正在搜索…"] --> C1["1.5 s:找到 3 个文件"] --> C3["3 s:正在修改 src/x.py"] --> C6["6 s:完成 + diff"]
end
A ~~~ B ~~~ C
classDef bad fill:#fdecea,stroke:#c0392b,color:#222
classDef ok fill:#eefaf0,stroke:#4d9a5c,color:#222
classDef n fill:#fff7e0,stroke:#c98a00,color:#222
class A0,A4 bad
class A6 n
class B0,B1,B3,B6,C0,C1,C3,C6 ok
四、p95 预算与四层超时
1. 分预算
先定用户能接受的总时长(对话 3 秒内开始出字、10 秒内结束;后台任务分钟级),再把 p95 预算分到每段:
| 段 | p95 预算(对话式 RAG 例) |
|---|---|
| 网关 | 50 ms |
| 检索(两路并行 + rerank 20) | 400 ms |
| 网络 + 排队 | 300 ms |
| prefill(缓存命中) | 300 ms |
| TTFT 合计 | ~1 s |
| 输出(流式,300 token × 30 ms) | 9 s(用户在 1 s 已开始读) |
| 后处理 | 50 ms |
每段有预算就有告警:某段的 p95 超预算是那一段出了问题(rerank 候选数被改大、缓存命中率掉、供应商排队)。
2. 四层超时
《模型作为组件(06):客户端工程——重试、超时、幂等、限流与流式解析》:连接、首字、字间、总时长各设超时,超时后的动作(重试、fallback、降级、部分结果)。超时值从预算来:首字超时 = TTFT 预算的 2–3 倍,字间超时防流卡死,总时长超时 = 用户容忍的上限。
%% 图:p95 预算怎么分到每段、每段的告警与超时怎么从预算推出来——总时长目标 10 秒、首字 1 秒;分到网关 50 ms、检索 400、排队 300、prefill 300;每段的 p95 超预算就告警到那一段(rerank 候选被改大 → 检索段;命中率掉 → prefill 段);首字超时 = TTFT 预算 × 2–3 = 3 秒,字间超时 15 秒,总超时 = 用户容忍上限 30 秒
flowchart TB
G["用户目标:1 秒开始出字 · 10 秒结束"] --> B["分预算(p95)"]
B --> S1["网关 50 ms"] & S2["检索 400 ms<br/>两路并行 + rerank 20"] & S3["网络 + 排队 300 ms"] & S4["prefill 300 ms<br/>(缓存命中)"]
S1 & S2 & S3 & S4 --> T["TTFT ≈ 1 s"]
T --> O["输出 300 token × 30 ms = 9 s(流式,用户已在读)"]
S2 -.->|"超预算 → 告警:rerank 候选被改大?"| AL["按段告警"]
S4 -.->|"超预算 → 告警:命中率掉了?"| AL
T -.->|"× 2–3"| TO1["首字超时 3 s"]
O -.-> TO2["字间超时 15 s(防流卡死)· 总超时 30 s(用户容忍上限)"]
classDef g fill:#eef6ff,stroke:#5b8fd6,color:#222
classDef s fill:#fff7e0,stroke:#c98a00,color:#222
classDef t fill:#eefaf0,stroke:#4d9a5c,color:#222
classDef a fill:#fdecea,stroke:#c0392b,color:#222
class G,B g
class S1,S2,S3,S4,O s
class T t
class AL,TO1,TO2 a
3. 延迟的分布
与成本同理(第二篇):看 p50 / p95 / p99,长尾(缓存未命中、排队、fallback 路径)单独画像;发布的门禁里”延迟不退化”看 p95(《评测、可观测与可追溯(04):回归与门禁——CI、供应商静默升级、A/B 与在线评测》)。
五、agent 的多步延迟
1. 步数是主因
一个 20 步的 agent 任务:每步 TTFT 1 秒 + 输出 2 秒 + 工具 1 秒 = 80 秒。压延迟的杠杆按大小:
| 杠杆 | 做法 | 效果 | 出处 |
|---|---|---|---|
| 减步数 |
|
最大 | 《工具、Agent 与 harness(02):工具调用与 MCP——协议、tool search 与程序化工具调用》、第一篇 |
| 并行 | 一步内多个独立工具调用并行;独立子任务交给并行子 agent | 墙钟大降,token 不降 | 《工具、Agent 与 harness(04):长任务的上下文管理与子 agent》、《工具、Agent 与 harness(07):多 agent——编排、handoff 与 A2A》 |
| 级联 | 简单任务走短路径(一次调用),复杂的才进循环 | 多数任务变快 | 《检索与知识接入(05):从流水线到 agentic retrieval》、《模型作为组件(05):选型——榜单的失真、自己的评测集与供应商的弃用周期》 |
| 每步更快 | 缓存命中(agent 的前缀稳定——《Prompt 与上下文工程(05):prompt caching 与上下文的排列》)、卸载让每步 prefill 小、effort 按步选 | 每步降 30–70% | L2、《模型作为组件(03):API 契约(二)——推理模型:thinking、effort 与跨轮的推理状态》 |
| 后台异步 | 长任务不让用户等,交付可审阅的中间物 | 感知延迟归零 | 《工具、Agent 与 harness(08):memory 与 human-in-the-loop》 |
| 步骤流 | 每步推事件 | 感知 | 本篇第三章 |
%% 图:一个 20 步 agent 任务 80 秒的墙钟怎么压——每步 TTFT 1 s + 输出 2 s + 工具 1 s = 4 s × 20 = 80 s;减步数(PTC 把 6 步数据处理变 1 步、更好的工具描述少试错 3 步)→ 12 步 48 s;并行(一步内 3 个独立工具并发、两个子任务并行)→ 32 s;每步更快(缓存命中 TTFT 1 → 0.3 s、卸载让 prefill 小、简单步 effort low)→ 20 s;杠杆按大小排:减步数 > 并行 > 每步更快
%%{init: {"xyChart": {"width": 760, "height": 340, "plotReservedSpacePercent": 60}, "themeVariables": {"xyChart": {"plotColorPalette": "#c0392b, #5b8fd6, #4d9a5c"}}}}%%
xychart-beta
title "20 步 agent 任务的墙钟(秒)随杠杆叠加"
x-axis ["原始:20 步 × 4 s", "+ 减步数 → 12 步", "+ 并行", "+ 每步更快", "+ 后台异步"]
y-axis "秒" 0 --> 90
bar [80, 48, 32, 20, 0]
最后一根柱子是 0 不是因为任务变快了,是用户不再等——交付一个可审阅的 PR,感知延迟归零。
2. 工具的延迟
工具是 agent 每步的一部分:慢的工具(编译、大查询、外部 API)要有超时与异步形态(提交后轮询——MCP Tasks 扩展,《工具、Agent 与 harness(02):工具调用与 MCP——协议、tool search 与程序化工具调用》);agent 在等慢工具时可以并行做别的(模型决定,或运行时调度)。
3. 压缩的延迟
压缩是一次额外的模型调用(《Prompt 与上下文工程(04):上下文预算与压缩——给每一部分定配额,超了怎么办》),在长任务中间出现一次几秒的停顿;在思考链边界做、用小模型做摘要、预留时间——或用会话记忆式的便宜压缩(《工具、Agent 与 harness(04):长任务的上下文管理与子 agent》)。
六、三者一起看
1. 取舍
| 手段 | 延迟 | 成本 | 质量 |
|---|---|---|---|
| 更小的模型 | ↓ | ↓ | ↓ |
| 缓存命中 | ↓ | ↓ | — |
| 瘦身 / 卸载 | ↓ | ↓ | 看评测 |
| 流式 | 感知 ↓ | — | — |
| 并行 | ↓ | — | — |
| 优先级层级 | ↓ | ↑ | — |
| effort 降档 | ↓ | ↓ | ↓(关键步不能降) |
| Batch | ↑↑ | ↓ | — |
| 级联 | 多数 ↓ | ↓ | 分流器决定 |
缓存、瘦身、并行、流式是”免费的”(三者不冲突);模型与 effort 的选择是三者的取舍——在评测集上量质量、在账本上量成本、在 trace 上量延迟,三个数字一起报(《评测、可观测与可追溯(03):评什么——单步、RAG、agent 与多轮的指标矩阵》的原则)。
2. 面板
TTFT p50 / p95、总时长 p50 / p95、按段的 p95、缓存命中率、排队时间(供应商侧)、fallback 比例、agent 的步数分布——与成本、质量、安全并列(总纲的四指标)。
七、实践建议
- 给主要路径画分解图:从 trace 里取每段的 p50 / p95,找最大的一段。
- 先做免费的:缓存排列(去掉前缀里的动态内容)、rerank 候选数、并行检索、流式、限长。
- 分 p95 预算到每段,每段告警;四层超时从预算来。
- 新版本预热缓存,避免用户遇到冷启动。
- agent 先减步数(PTC、工具描述、工作流化),再并行,再后台异步;步骤流推前端。
- 三个数字一起报:每次改动看延迟、成本、质量的变化。
八、本文小结
- 分解七段:网关、应用侧准备(检索、组装)、网络、排队、prefill、输出、后处理;每段有自己的手段,换模型只碰输出段。
- 手段:并行与预取、rerank 候选数、区域路由、避峰与优先级层级、缓存命中(最大杠杆,前缀纪律)、瘦身与卸载、新版本预热、限长与结构化、更小模型、增量解析。
- 感知 ≠ 绝对:TTFT 与总时长分开;流式三层(token、步骤、进度)是面向用户的默认;首字前用骨架与已知信息把等待变成进展。
- p95 预算分到每段并告警;四层超时从预算来;看分布与长尾。
- agent:减步数(PTC、描述、工作流)> 并行 > 级联 > 每步更快(缓存、卸载、effort)> 后台异步;工具要超时与异步形态;压缩在边界用小模型。
- 延迟、成本、质量一起看:缓存、瘦身、并行、流式免费;模型与 effort 是取舍;三个数字一起报。
九、自测
-
事故里的 RAG 问答 p95 9 秒,按分解各段的药是什么?预期能到多少?
-
为什么说缓存命中是 prefill 段最大的杠杆?命中率从 0 到 80% 对 TTFT 意味着什么?
答案
命中时供应商跳过前缀的 prefill 计算,60K 上下文的几秒 prefill 变成几百毫秒(只算未缓存的尾部);80% 的请求 TTFT 降一个量级,p95 大幅下降(剩下 20% 未命中是长尾)。前提是 《Prompt 与上下文工程(05):prompt caching 与上下文的排列》的排列纪律与新版本预热。详见第二章。
-
一个 25 步的 coding agent 任务要 2 分钟。按第五章的杠杆顺序给出三个最有效的动作。
答案
(1)减步数:看轨迹里的试错(工具描述不清导致的重试、重复搜索),改描述;多步数据处理用 PTC 合并;(2)并行:独立的文件读取与测试运行并行,独立子任务交给并行子 agent;(3)每步更快:前缀稳定让缓存命中、卸载大工具返回让每步 prefill 小、非关键步 effort 降档。加步骤流让用户看到进展,或改为后台异步交付 PR。详见第五章。
-
团队把
max_tokens从 1,000 砍到 200 让输出段变快,用户投诉回答被截断。哪里错了?答案
限长要配合 prompt 里的长度约束(让模型在 200 内说完)与结构化输出(短而完整),而不是硬截断;结构化输出下截断还会让 JSON 坏掉(《Prompt 与上下文工程(03):结构化输出——约束解码、schema 设计与失败修复》)。正确:prompt 约束长度 + 结构化 +
max_tokens留余量 + 流式让长回答也早开始出字;stop_reason为长度时要处理。详见第二章。 -
优先级层级加价 30% 换排队时间从 1 秒到 0.1 秒。什么路径值得、什么不值得?
-
七段:网关(10–100 ms,边缘 / 就近、连接复用);应用侧准备(检索 embedding + 召回 + rerank、上下文组装、工具预查,100 ms–2 s:并行、预取、rerank 候选 50 → 20、检索缓存、组装并行加载);网络(区域路由、HTTP/2 复用);排队(峰值,避峰走 Batch / Flex、优先级层级、区域 / 供应商 fallback);prefill(每 10K token 未命中几百毫秒:缓存命中是最大杠杆——前缀纪律、去掉时间戳,瘦身与卸载,新版本预热);输出(20–80 ms / token:限长配 prompt 约束、结构化、更小模型或级联、流式);后处理(增量解析、边到边渲染)。换模型只碰输出段。详见第一章、第二章。 ↩
-
感知是”多久有反应、多久能开始用”,绝对是总时长;指标分 TTFT(卡不卡)与总时长(等多久),6 秒流式 0.8 秒出字比 4 秒非流式感觉快。流式三层:token 流、步骤流(agent 每步推事件)、进度与预估;面向用户的对话与 agent 默认流式,代价是连接、增量解析、流中错误处理;首字前用骨架、状态、已知信息(问题回显、来源先列)把等待变成进展。p95 预算:先定用户容忍的总时长,分到每段(对话式 RAG 例:网关 50 ms、检索 400、网络排队 300、prefill 命中 300 → TTFT 约 1 s;输出流式 300 token 约 9 s 但 1 s 开始读),每段超预算告警定位;四层超时(连接、首字、字间、总)从预算来,首字超时 = TTFT 预算 2–3 倍;看 p50 / p95 / p99 与长尾画像,门禁看 p95。详见第三章、第四章。 ↩
-
步数是主因(20 步 × 4 s = 80 s)。杠杆按大小:减步数(PTC 多步变一轮、更好的工具描述减试错、工作流替代不必要循环)> 并行(一步内独立工具并行、独立子任务并行子 agent——墙钟降 token 不降)> 级联(简单任务短路径)> 每步更快(前缀稳定缓存命中、卸载让 prefill 小、effort 按步选)> 后台异步(交付中间物,感知归零)+ 步骤流;慢工具要超时与异步形态(MCP Tasks),压缩在思考链边界用小模型。三者一起看:缓存、瘦身、并行、流式对延迟与成本都好且不伤质量(免费);更小模型与 effort 降档三者都降;优先级层级延迟降成本升;Batch 延迟升成本降;级联看分流器——每次改动在 trace 量延迟、账本量成本、评测集量质量,三个数字一起报;面板含 TTFT / 总时长 p50 / p95、按段 p95、命中率、排队、fallback 比、步数分布。详见第五章、第六章。 ↩
系列 《生产化与运营:让 AI 应用可靠、可控、可持续地跑》 第 3 / 3 篇 进阶
系列总览 — 为什么这样组织、读它需要什么、读完能做什么
学习地图 — AI 应用工程师学习地图 第 6 / 7 个系列
- 模型网关——所有调用经过的一层
- 成本工程——从账单到每任务成本
- 延迟工程——从 TTFT 到 agent 的多步
上一篇:成本工程——从账单到每任务成本
去 Series 挑下一个系列
本文由 arganzheng 创作,采用 CC BY 4.0 许可协议。在保留原文作者、署名以及完整原文链接(https://arganzheng.life/latency-engineering-from-ttft-to-multi-step-agents.html)的前提下,欢迎各种形式的转载、翻译或商业引用。
COMMENTS
评论存放在 GitHub Discussions, 用 GitHub 账号登录即可发表,支持 Markdown。 想针对正文某句话说?选中那段文字,点浮出的「评论」即可划线评论;觉得哪里写错了,发表时勾上「同时提交 Issue」。 有人回复你时 GitHub 会按你的通知设置发邮件,不用守在这里。