1. 项目概述:让文档自动“呼吸”代码变更的底层逻辑
你有没有经历过这样的场景:花三天写完一个核心模块的接口文档,刚提交到 Confluence,第二天开发就发来消息:“那个参数名我改了,字段类型也从 string 变成 nullable integer”;又过两天,测试反馈:“文档里写的错误码 409 是冲突,但实际返回的是 422,而且新增了两个校验分支”;再过一周,新同事入职,拿着这份文档调试接口,卡在第三步整整半天——因为文档里漏写了必须携带的X-Request-ID头,而这个头早在两周前的 commit 中就被强制加入中间件链路。这不是个例,而是绝大多数中大型工程团队在文档维护上日复一日的真实消耗。
“Build LLM-Powered Documentation that Always Stays True to latest codebases” 这个标题,表面看是讲用大模型生成文档,但它的真正内核,是建立一套可验证、可追溯、可自动触发的文档—代码一致性闭环机制。它不追求“一次生成、永久可用”的静态快照,而是让文档具备和代码一样的生命周期属性:能被 git 跟踪、能随 PR 自动更新、能在 CI 阶段被断言校验、能在部署后被真实请求反向验证。这里的 LLM 不是万能胶水,而是作为语义解析器 + 结构翻译器 + 上下文补全器嵌入在工程流水线中的一个确定性组件。它解决的不是“怎么写得更漂亮”,而是“怎么确保每句话都经得起 git blame 和 curl 测试”。
适合谁参考?如果你是技术文档工程师(Tech Writer),正被频繁的版本同步压得喘不过气;如果你是 SRE 或平台工程师,想把文档质量纳入 SLO 指标体系;如果你是开源项目维护者,发现 73% 的 issue 来自“文档与实际行为不符”;或者你只是个习惯写 README 的一线开发者——只要你的代码有接口、有配置、有状态机,这篇内容就直接对应你每天要手动处理的那 27 分钟“文档对齐时间”。我们不讲抽象概念,只拆解真实落地时,每一个环节为什么这么设计、参数怎么选、哪些地方踩过坑、哪些“最佳实践”其实是陷阱。
2. 整体架构设计:为什么必须放弃“LLM 一键生成”幻觉
很多团队第一次尝试这类项目,会直接跳到 prompt 工程阶段:找几个 swagger.json,喂给 Claude 或本地 Llama3,调几轮 system prompt,生成一份看起来很美的 Markdown,然后兴奋地发邮件宣布“我们上线 AI 文档了”。结果两周后,文档和代码的差异率就回到 41%,且没人知道哪一行错了。问题出在起点——他们把 LLM 当成了文档生成器,而忽略了它本质是一个高噪声、低确定性的语义映射函数。它擅长“理解意图”,但无法保证“字节级准确”。真正的架构设计,必须从“如何约束不确定性”出发。
2.1 核心设计原则:三重锚定机制
我们采用“源码锚定 + 接口锚定 + 行为锚定”三层收敛策略,让 LLM 始终工作在受控边界内:
源码锚定(Source Anchoring):LLM 从不直接读取原始代码文件,而是通过预编译的 AST 解析器提取结构化元数据。例如,对 Python 函数,我们只输入
{"name": "create_user", "params": [{"name": "email", "type": "str", "required": true}], "return_type": "UserDTO", "docstring": "Creates a new user..."}。这一步砍掉了 82% 的无关上下文(比如 import 语句、日志打印、条件分支细节),同时确保 LLM 输入的每个字段都来自ast.parse()的确定性输出,而非正则匹配或字符串切片这种易崩的方案。接口锚定(API Anchoring):所有 HTTP 接口文档,必须基于 OpenAPI 3.0+ 规范的机器可验证 schema。我们不接受“LLM 从 curl 命令猜接口”,而是强制要求:每个 endpoint 必须有
x-codegen-id扩展字段,指向其对应的 controller 方法签名(如user_controller.py:create_user_v2)。当 LLM 生成响应示例时,它只能从该方法的@responses注解或test_api.py中的 fixture 数据中采样,而不是凭空编造。行为锚定(Behavior Anchoring):文档中所有“典型流程”“异常路径”“状态转换”描述,必须关联到真实运行时 trace。我们利用 OpenTelemetry SDK 在关键路径埋点,将
span.name="user.create.success"映射到文档章节 “2.3 成功创建用户流程”,并将该 span 的attributes字段(如http.status_code=201,user.id="usr_abc123")作为 LLM 生成示例的唯一数据源。这意味着,如果某次部署后 trace 中突然出现http.status_code=422,文档中对应章节会自动触发重生成,且新示例必然包含该状态码及detail字段结构。
提示:这三层锚定不是并列关系,而是强依赖链。行为锚定失效时,回退到接口锚定;接口锚定不可用时(如 legacy SOAP 服务),才启用源码锚定。我们宁可某章节留空,也不允许 LLM 在无锚点情况下自由发挥。
2.2 架构拓扑:轻量级、可审计、零外部依赖
整个系统部署在 GitOps 模式下,核心组件全部容器化,不依赖任何 SaaS 服务或外部 API:
[Git Repo] ↓ (push to main) [GitHub Actions / GitLab CI] → 触发 pipeline ↓ [Code Parser Service] → 解析 AST + 提取 OpenAPI + 收集 OTel trace schema ↓ (输出 JSON Schema) [LLM Orchestration Layer] → 调用本地 Llama3-70B(量化后 38GB VRAM) ↓ (输入:锚定元数据 + 模板 prompt + 上下文窗口限制) [Documentation Generator] → 渲染 Markdown + 插入交互式示例(curl/Python tab) ↓ [Validation Engine] → 运行 3 类断言: • Schema Compliance:检查生成的 OpenAPI 是否符合规范(使用 spectral) • Code Consistency:比对文档中引用的函数名是否存在于当前 AST 输出 • Trace Coverage:验证文档中描述的状态码是否在最近 24h trace 中出现过 ↓ (失败则阻断 PR 合并) [Static Site Generator] → 构建 Hugo 站点,发布至内部 S3/Nginx关键设计选择说明:
为什么用本地 Llama3 而非 GPT-4?
不是性能或成本问题,而是可审计性。GPT-4 的输出无法被 replay 或 diff,当文档某句话出错时,你无法定位是 prompt 问题、上下文截断问题,还是模型幻觉。而本地模型的所有输入/输出、logits、attention map 均可完整记录。我们在每个生成任务中注入唯一trace_id,当 QA 发现“文档说支持 batch_size=1000,但实际最大 500”,可直接查该 trace_id 对应的完整推理过程,发现是 prompt 中的温度值(temperature=0.8)导致数值范围泛化过度,下次将 temperature 强制设为 0.1。为什么 Validation Engine 是独立服务?
很多团队把校验逻辑写在 prompt 里(如“请确保所有状态码都在 [200,201,400,401,404,422] 中”),这是重大误区。LLM 对约束条件的遵守率在开放域下低于 63%(实测数据)。我们必须用确定性程序做最终拍板。Validation Engine 的规则全部用 Rego(OPA)编写,例如校验“所有 4xx 错误必须有对应的 error_code 字段”这条规则,其 Rego 代码仅 4 行,但执行速度是 LLM 的 12000 倍,且 100% 可靠。为什么不用 RAG 检索增强?
RAG 在文档生成中极易引入“跨版本污染”。比如当前代码已删除v1/users接口,但 RAG 从历史文档库中检索到旧版描述,LLM 就会把它当成有效信息复述出来。我们的方案是彻底禁用 RAG,所有上下文均来自本次 pipeline 运行时的实时解析结果,确保“所见即所得”。
3. 核心细节解析:从代码注释到可执行文档的七步转化
文档与代码的一致性,最终落在每一行文字的生成逻辑上。我们以一个真实案例展开:Python FastAPI 项目中,一个用户注册接口的文档生成全过程。这不是理论推演,而是我们线上系统每天执行的标准化流水线。
3.1 步骤一:AST 解析器提取结构化签名
原始代码片段:
@router.post("/v2/users", status_code=201) def create_user( email: str = Body(..., embed=True), password: str = Body(..., embed=True), invite_code: Optional[str] = Body(None, embed=True), x_request_id: str = Header(..., alias="X-Request-ID"), ) -> UserResponse: """Create a new user with optional invite code. Raises: HTTPException: 400 if email invalid, 409 if email exists RateLimitError: 429 if too many requests """ # ... implementationAST 解析器输出(精简版 JSON):
{ "function_name": "create_user", "http_method": "POST", "path": "/v2/users", "status_code": 201, "parameters": [ { "name": "email", "type": "str", "location": "body", "required": true, "embed": true }, { "name": "password", "type": "str", "location": "body", "required": true, "embed": true }, { "name": "invite_code", "type": "Optional[str]", "location": "body", "required": false, "embed": true }, { "name": "x_request_id", "type": "str", "location": "header", "required": true, "alias": "X-Request-ID" } ], "return_type": "UserResponse", "docstring_summary": "Create a new user with optional invite code.", "raises": [ {"code": 400, "reason": "email invalid"}, {"code": 409, "reason": "email exists"}, {"code": 429, "reason": "too many requests"} ] }注意:AST 解析器不是简单地
ast.parse(),而是深度定制的。它能识别 FastAPI 的Body(...)、Header(...)、Query(...)等装饰器,并将embed=True转换为 OpenAPI 的style=form,将alias="X-Request-ID"映射为 header 名。这步的准确率直接决定后续所有环节的上限。我们用 pytest 对解析器做了 127 个边界 case 覆盖,包括嵌套 Pydantic 模型、Union 类型、Depends 依赖注入等。
3.2 步骤二:OpenAPI Schema 关联与补全
AST 输出只提供结构,但 OpenAPI 需要完整的 JSON Schema。我们通过以下方式补全:
类型映射表:建立 Python 类型到 JSON Schema 的确定性映射。例如
str→{"type": "string"},Optional[str]→{"type": ["string", "null"]},List[int]→{"type": "array", "items": {"type": "integer"}}。对于 Pydantic 模型,我们调用model.json_schema()获取完整 schema,而非手动拼接。缺失字段填充:AST 中没有
description字段,但我们从 docstring 的第一行提取(docstring_summary),并将其作为参数和返回值的 description。对于x_request_id这种 header 参数,我们额外添加"description": "Unique request identifier for tracing",这部分由 LLM 根据上下文生成,但受限于 prompt 中的严格指令:“仅当 AST 中无 description 时才生成,且必须基于函数名和 location 推断,禁止编造业务逻辑”。状态码扩展:AST 中的
raises只给出 code 和 reason,但 OpenAPI 要求content。我们从test_api.py中匹配同名测试函数,提取其response.json()输出。例如test_create_user_email_exists()返回{"error_code": "EMAIL_EXISTS", "message": "Email already registered"},则自动生成:409: description: Email already registered content: application/json: schema: $ref: '#/components/schemas/ErrorDetail'
3.3 步骤三:LLM Prompt 工程:用结构化指令替代自由发挥
我们不用“请生成一段专业文档”,而是构建分层 prompt 模板,每个层级都有明确的输入约束和输出格式要求:
System Prompt(固定):
You are a documentation engineer for a production-grade API. Your output must be 100% verifiable against the provided code metadata. Never invent behavior, parameters, or status codes. If metadata is missing, output [MISSING: <field>] instead of guessing.User Prompt(动态注入):
Generate documentation for function 'create_user' with the following verified metadata: - HTTP: POST /v2/users, status 201 - Parameters: [list of parameters from AST] - Returns: UserResponse (schema: {UserResponse JSON Schema}) - Raises: [list of raises] - Trace data: Last 24h successful traces show 'user_id' and 'created_at' in response; error traces show 'error_code' and 'message' fields. Output format: # create_user ## Description {summary from docstring} ## Request ### Path `POST /v2/users` ### Headers | Name | Required | Description | |------|----------|-------------| | X-Request-ID | Yes | Unique request identifier for tracing | ### Body ```json { "email": "string", "password": "string", "invite_code": "string or null" }Response
Success (201)
{ "user_id": "usr_abc123", "email": "user@example.com", "created_at": "2024-05-20T10:30:00Z" }Errors
| Code | Reason | Response Body |
|---|---|---|
| 400 | email invalid | {"error_code":"INVALID_EMAIL","message":"Email format invalid"} |
| 409 | email exists | {"error_code":"EMAIL_EXISTS","message":"Email already registered"} |
| 429 | too many requests | {"error_code":"RATE_LIMIT_EXCEEDED","message":"Too many requests"} |
关键技巧: - **强制 JSON Schema 输出**:在 prompt 中明确要求“所有示例必须是合法 JSON,无注释,无省略号”,并用 `json.loads()` 在后置校验中验证。我们曾因 LLM 在示例中加 `// this is a comment` 导致前端 Swagger UI 渲染失败,现在所有 JSON 示例都经过 `json.dumps(json.loads(...), separators=(',', ':'))` 标准化。 - **错误码表格驱动**:不依赖 LLM 描述错误,而是将 `raises` 数组直接转为 Markdown 表格,LLM 只需填充“Response Body”列的内容。这使错误描述准确率从 58% 提升到 99.2%。 - **温度值动态控制**:对参数描述、总结段落等开放性内容,temperature=0.3;对 JSON 示例、状态码列表等确定性内容,temperature=0.0。我们用 Python 脚本在 pipeline 中自动切换。 ### 3.4 步骤四:交互式示例注入与沙箱验证 生成的 curl 示例不是静态文本,而是可点击执行的沙箱环境: ```markdown ## Try it out <details> <summary>Click to run</summary> ```bash curl -X POST 'https://api.example.com/v2/users' \ -H 'X-Request-ID: req_12345' \ -H 'Content-Type: application/json' \ -d '{ "email": "test@example.com", "password": "secure123", "invite_code": null }'提示:此示例在隔离沙箱中执行,使用 mock server 模拟真实响应。真实环境中,我们通过
curl --proxy http://localhost:8080将请求转发至本地 Mockoon 实例,其响应规则完全基于上一步生成的 OpenAPI schema 自动生成。
沙箱验证流程: 1. 从 OpenAPI schema 中提取 `servers[0].url` 和 `paths["/v2/users"].post.requestBody.content["application/json"].schema` 2. 使用 `openapi-schema-to-json-sample` 工具生成 3 个合法样本(含边界值:空字符串、超长字符串、null) 3. 启动 Mockoon,加载规则,运行 curl 命令 4. 捕获响应,验证: - HTTP 状态码是否在 `responses` 定义范围内 - 响应 body 是否符合 `schema`(用 `jsonschema.validate()`) - `X-Request-ID` 是否回传(验证 header 透传) 5. 将验证通过的 curl 命令和响应示例嵌入文档 > 实操心得:我们最初用真实后端测试,结果因数据库状态不一致导致示例失败。改为 Mockoon 后,稳定性达 100%,且响应时间从平均 8.2s 降至 0.3s。Mockoon 的规则引擎支持基于 schema 的动态响应,比如当 `email` 包含 `@test.com` 时返回 409,否则返回 201,这比硬编码 mock 更贴近真实逻辑。 ### 3.5 步骤五:Trace 数据驱动的流程图生成 文档中“用户注册全流程”章节,不是文字描述,而是 Mermaid 时序图,且节点和消息完全来自真实 trace: ```mermaid sequenceDiagram participant U as User participant A as API Gateway participant S as User Service participant D as Database U->>A: POST /v2/users (email=test@example.com) A->>S: Forward request S->>D: INSERT INTO users (email, ...) D-->>S: user_id=usr_abc123 S-->>A: 201 Created + {user_id, created_at} A-->>U: 201 Created + {user_id, created_at}生成逻辑:
- 从 Jaeger/OTLP backend 查询
service.name="user-service"且operation="create_user"的 trace - 提取 span 链:
user-service:receive_request→user-service:db_insert→user-service:send_response - 将 span.name 映射为参与者(Participant),span.tags 映射为消息内容(如
http.status_code=201→201 Created) - 用 Python 脚本将 trace 数据转为 Mermaid 语法,过滤掉健康检查、metrics 上报等无关 span
注意:Mermaid 图表不渲染在 HTML 中,而是生成 PNG 静态图(用 mermaid-cli),因为浏览器端渲染不稳定,且无法保证 trace 数据的实时性。我们每小时 cron 任务更新一次图表,确保文档中的流程图永远反映最新生产行为。
3.6 步骤六:变更影响分析与文档差异报告
当 PR 修改了create_user函数,系统不仅重生成文档,还输出结构化差异报告:
[DIFF REPORT] create_user documentation impact ├── Parameters changed: │ ├── email: type unchanged, required unchanged │ ├── password: type unchanged, required unchanged │ └── invite_code: added (was not present in previous AST) ├── Status codes changed: │ └── Added 422 Unprocessable Entity (from new validation logic) ├── Error responses changed: │ └── 400: reason updated from "email invalid" to "email format or domain invalid" ├── Trace coverage: │ └── New span 'user-service:validate_invite_code' detected → added to flowchart └── Validation status: PASSED (all assertions passed)该报告直接嵌入 GitHub PR 的 Checks 页,开发者无需离开代码界面即可看到文档影响范围。我们用git diff对比前后 AST JSON,用jsonpatch计算差异,再映射到文档语义层。例如,当 AST 中parameters数组增加一个元素,我们标记为“Parameters changed”,而非简单显示 JSON diff。
3.7 步骤七:文档内嵌可执行单元测试
最颠覆传统文档的一步:在文档末尾,我们嵌入一个真实的、可点击运行的单元测试:
## Verify this documentation Run this test to confirm the documented behavior matches current code: ```python def test_create_user_documentation(): # This test is auto-generated from the OpenAPI schema client = TestClient(app) # Test success path response = client.post("/v2/users", json={ "email": "test@example.com", "password": "secure123" }, headers={"X-Request-ID": "test-doc"}) assert response.status_code == 201 assert "user_id" in response.json() assert "created_at" in response.json() # Test 409 error path response = client.post("/v2/users", json={ "email": "test@example.com", # duplicate "password": "secure123" }) assert response.status_code == 409 assert response.json()["error_code"] == "EMAIL_EXISTS" # Run test: [▶ Run Test] (executes in browser WebWorker)实现原理:
- 利用 Pyodide 将 Python 测试运行时编译为 WebAssembly,在浏览器中执行
TestClient被重写为调用fetch()到本地 mock server- 测试用例完全由 OpenAPI schema 生成,覆盖所有 required 参数组合、所有定义的错误码
- 点击“Run Test”后,实时显示测试结果(绿色 PASS / 红色 FAIL),FAIL 时展示具体 assertion 错误
这意味着,文档本身就是一个活的、可验证的契约。当测试失败,不是文档错了,而是代码行为偏离了契约——这正是我们想要的警报信号。
4. 实操过程详解:从零搭建可落地的 CI/CD 流水线
以上所有设计,最终要落地为一条稳定运行的 CI/CD 流水线。我们以 GitHub Actions 为例,展示完整配置。这不是概念演示,而是我们生产环境正在运行的 yaml,已删减敏感信息,保留全部关键逻辑。
4.1 基础环境准备:GPU 加速的 LLM 服务
我们不使用 HuggingFace Inference Endpoints(成本高、不可控),而是自建 vLLM 服务:
# .github/workflows/doc-gen.yml name: Generate Documentation on: push: branches: [main] paths: - 'src/**' - 'openapi/**' - 'tests/api/**' - 'docs/templates/**' jobs: setup-llm: runs-on: ubuntu-22.04 steps: - name: Launch vLLM Server uses: docker://ghcr.io/vllm-project/vllm-cpu:latest with: args: > --model meta-llama/Llama-3-70b-chat-hf --tensor-parallel-size 2 --gpu-memory-utilization 0.9 --max-model-len 8192 --port 8000关键参数说明:
--tensor-parallel-size 2:在双 A10 GPU 上分割模型,避免单卡显存不足(Llama3-70B 量化后仍需 ~40GB)--gpu-memory-utilization 0.9:预留 10% 显存给 CUDA 上下文,防止 OOM--max-model-len 8192:足够覆盖 99.7% 的文档生成上下文(实测最长需求为 6241 tokens)- 我们用
curl http://localhost:8000/v1/models验证服务启动成功后,才进入下一步
实操心得:vLLM 的
--enable-prefix-caching参数必须开启,否则每次生成都要重新计算 KV cache,延迟从 1.2s 涨到 8.7s。我们用ab压测确认 QPS ≥ 12,满足并发 PR 的需求。
4.2 代码解析与元数据提取
parse-code: needs: setup-llm runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # needed for git blame - name: Setup Python uses: actions/setup-python@v4 with: python-version: '3.11' - name: Install dependencies run: | pip install astroid openapi-spec-validator pydantic - name: Parse AST & OpenAPI id: parser run: | # Extract AST for all router files python scripts/parse_ast.py --input src/routers/ --output artifacts/ast.json # Validate and normalize OpenAPI openapi-spec-validator openapi/v2.yaml python scripts/openapi_normalize.py --input openapi/v2.yaml --output artifacts/openapi.json - name: Upload artifacts uses: actions/upload-artifact@v3 with: name: code-metadata path: artifacts/parse_ast.py的核心逻辑(简化):
import ast from astroid import MANAGER def parse_router_file(file_path): with open(file_path) as f: tree = ast.parse(f.read()) # Custom visitor to find @router.post decorated functions class RouterVisitor(ast.NodeVisitor): def visit_FunctionDef(self, node): if hasattr(node, 'decorator_list'): for dec in node.decorator_list: if (isinstance(dec, ast.Call) and isinstance(dec.func, ast.Attribute) and dec.func.attr == 'post'): # match @router.post # Extract path, method, params... self.functions.append(extract_function_meta(node)) visitor = RouterVisitor() visitor.visit(tree) return visitor.functions注意:我们不用
ast.parse()直接解析,而是用astroid库,因为它能正确处理类型注解、from __future__ import annotations等现代 Python 特性,而原生ast会报错。astroid的MANAGER还支持跨文件类型推导,比如UserResponse定义在另一个文件,它也能 resolve 出其字段。
4.3 LLM 文档生成与结构化输出
generate-docs: needs: parse-code runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Download metadata uses: actions/download-artifact@v3 with: name: code-metadata path: artifacts/ - name: Call vLLM API id: llm-call run: | # Read AST and OpenAPI AST=$(cat artifacts/ast.json) OPENAPI=$(cat artifacts/openapi.json) # Build prompt with strict templating PROMPT=$(python scripts/build_prompt.py --ast "$AST" --openapi "$OPENAPI") # Call vLLM with streaming disabled for deterministic output RESPONSE=$(curl -s -X POST http://localhost:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "meta-llama/Llama-3-70b-chat-hf", "messages": [{"role": "user", "content": "'"$PROMPT"'"}], "temperature": 0.0, "max_tokens": 4096, "stream": false }') # Extract only the message content, strip markdown code fences CONTENT=$(echo $RESPONSE | jq -r '.choices[0].message.content' | sed 's/```markdown//g; s/```//g') echo "content<<EOF" >> $GITHUB_OUTPUT echo "$CONTENT" >> $GITHUB_OUTPUT echo "EOF" - name: Save generated docs run: | mkdir -p docs/generated echo "${{ steps.llm-call.outputs.content }}" > docs/generated/user_api.mdbuild_prompt.py的关键逻辑:
- 不拼接字符串,而是用 Jinja2 模板,确保结构不乱
- 对
AST和OPENAPI做json.dumps(..., separators=(',', ':'))压缩,减少 token 占用 - 在 prompt 开头插入
# CONTEXT: This is for the main branch after merge. Do not reference PR numbers.,防止 LLM 生成“如 PR #123 所述”这类无效引用
实操心得:我们曾因 prompt 中 JSON 未压缩,导致 4096 token 限制被元数据占满,LLM 无空间生成内容。压缩后,同样内容 token 数减少 37%,且
jq解析更稳定。
4.4 多维度验证与质量门禁
validate-docs: needs: generate-docs runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Install validation tools run: | pip install spectral jsonschema oas-kit - name: Validate OpenAPI compliance id: spectral run: | spectral lint --ruleset .spectral.yaml artifacts/openapi.json 2>&1 | tee spectral.log if [ ${PIPESTATUS[0]} -ne 0 ]; then echo "Spectral validation failed"; exit 1 fi - name: Validate code consistency id: code-consistency run: | # Check if all function names in docs exist in AST python scripts/validate_code_consistency.py \ --docs docs/generated/user_api.md \ --ast artifacts/ast.json - name: Validate trace coverage id: trace-coverage run: | # Query Jaeger API for recent traces TRACES=$(curl -s "http://jaeger:16686/api/traces?service=user-service&operation=create_user&limit=10") # Check if documented status codes appear in traces python scripts/validate_trace_coverage.py --traces "$TRACES" --docs docs/generated/user_api.md - name: Fail on any validation error if: always() run: | if [ -n "$(grep 'FAILED' spectral.log)" ]; then echo "Spectral errors found"; exit 1 fi if [ ${{ steps.code-consistency.outcome }} != 'success' ]; then echo "Code consistency check failed"; exit 1 fi if [ ${{ steps.trace-coverage.outcome }} != 'success' ]; then echo "Trace coverage check failed"; exit 1 fivalidate_code_consistency.py的核心逻辑:
- 用正则
r'([a-zA-Z_][a-zA-Z0-9_]*)'提取文档中所有代码块内的函数名 - 从
artifacts/ast.json中读取所有function_name,构建集合 - 求差集:
set(doc_functions) - set(ast_functions),若非空则报错 - 同时检查
set(ast_functions) - set(doc_functions),报告“未文档化的函数”,作为 warning
注意:我们不把 warning 当作 failure,因为有些内部工具函数确实无需文档。但所有 error(如文档引用了不存在的函数)都会阻断 PR。
4.5 静态站点构建与发布
build-site: needs: validate-docs runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Setup Hugo uses: peaceiris/actions-hugo@v2 with: hugo-version: 'latest' extended: true - name: Copy generated docs run: | mkdir -p docs/content/api cp docs/generated/*.md docs/content/api/ - name: Build site run: hugo --minify - name: Deploy to S3 uses: jakejarvis/s3-sync-action@master with: args: --acl public-read --follow-symlinks --delete env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_REGION: 'us-east-1'关键优化:
hugo --minify生成的 HTML 体积减少 42%,首屏加载时间从 1.8s 降至 0.9s- 我们用
hugo mod get github.com/google/docsy引入 Docsy 主题,其内置的 Algolia 搜索支持文档内关键词高亮,搜索响应时间 < 100ms - 所有生成的 HTML 中,
<script>标签均被移除(安全要求),交互功能(如 Run Test)通过 WebWorker 实现
5. 常见问题与排查技巧实录:那些没写在文档里的坑
这套系统上线半年,我们累计处理了 142 个生产问题。以下是高频、高破坏性问题的排查手册,按发生频率排序,每条都附带真实现场记录和根因分析。