Bookshelf API v2 参考文档解析:wigolo 提取基准中 API 参考类 golden 样本的规范化 Markdown 形态
2026/9/18 1:20:27 网站建设 项目流程

Bookshelf API v2 参考文档解析:wigolo 提取基准中 API 参考类 golden 样本的规范化 Markdown 形态

【免费下载链接】wigoloThe go-to web for your AI coding agent — local-first search, fetch, crawl & research over MCP. No API keys, no cloud, $0/query. Public beta.项目地址: https://gitcode.com/GitHub_Trending/wi/wigolo

本篇指南以 api-001.md(一份完整规范的 REST API 参考文档样例)为核心样本,深入讲解它在 wigolo 项目内容提取基准测试体系中的角色:它是一份golden 期望输出,用于量化评估 HTML→Markdown 提取器在"API 参考文档"这一页面类别上的质量。读完本文,你将理解 golden 文件的格式约定、提取基准的评测指标与底层管线实现,并能在仓库中新增或维护同类评估样本。

golden 文件的定位:它服务于什么

benchmarks/extraction/fixtures/golden/api-001.md不是一份普通的文档,而是 wigolo 提取(extraction)基准测试的金标样本。基准测试的整体思路是:给定一份真实网页的 HTML 夹具,让内容提取器把它转换成 Markdown,再将转换结果与人工整理好的"期望输出"(即 golden 文件)逐项比对,用数值化指标衡量提取质量。

在 manifest.json 中,api-001条目定义了该样本的元信息:

{ "id": "api-001", "url": "https://api.example.com/docs/v2", "category": "docs", "htmlFixturePath": "html/api-001.html", "goldenPath": "golden/api-001.md", "expectedExtractor": "defuddle", "tags": ["api", "rest", "endpoints"] }

从字段可以看出:该样本对应一个 API 文档页面(category: "docs"tags标注为 API/REST/端点),其 HTML 夹具位于benchmarks/extraction/fixtures/html/(由htmlFixturePath指向),而goldenPath指向本文件——这份api-001.mdexpectedExtractordefuddle,意味着基准运行时希望主提取器判定该页面应由 defuddle 提取器负责,这一字段在 runner.ts 中会与实际使用的提取器比对,得到extractorMatch指标。

因此,golden 文件本质上是"人工标定的正确答案",它同时约束了两件事:内容要提取到什么程度(哪些段落、表格、代码块必须保留),以及格式应呈现为什么样子(标题层级、表格、代码围栏等 Markdown 语法特征)。

样本内容全解:Bookshelf API v2 参考文档

golden 样本的正文是一份虚构的图书管理服务 "Bookshelf API" 的 v2 接口参考文档。它覆盖了生产级 REST API 文档的典型组成要素:认证、限流、统一响应结构、资源端点、错误码表、分页、Webhook 与变更日志。以下逐节展开。

Base URL 与版本前缀

https://api.bookshelf.example.com/v2

所有端点均基于该基础地址,v2路径前缀用于版本隔离。文档明确约定所有端点返回 JSON,且需要通过 Bearer token 认证——这是多数现代 REST API 的通用基线约定。

认证方式

在请求头中携带 API 密钥:

Authorization: Bearer sk_live_abc123def456

未携带有效 token 的请求统一收到401 Unauthorized响应。注意这里用的是sk_live_前缀的密钥,属于服务端密钥(live key)命名惯例,与测试密钥sk_test_区分。

限流策略

API 按密钥维度实施速率限制,不同套餐的配额不同:

PlanRequests/minBurst
Free6010
Pro60050
Enterprise6000200

Burst(突发配额)表示在短时间内允许超出每分钟配额的请求数,用于容忍短时尖峰。每次响应都会携带限流头,客户端可以据此感知剩余额度:

X-RateLimit-Limit: 600 X-RateLimit-Remaining: 594 X-RateLimit-Reset: 1713187200

X-RateLimit-Reset是 Unix 时间戳,表示配额重置时刻;配合X-RateLimit-Remaining,客户端可实现优雅退避(backoff)策略,避免触发429 rate_limited

统一响应封装(envelope)

所有成功响应遵循统一信封结构,data承载业务数据,meta携带请求追踪信息:

{ "data": {}, "meta": { "request_id": "req_7f3a9b2c", "timestamp": "2026-04-15T10:30:00Z" } }

错误响应使用同一信封,但用error字段替换data,并包含机器可读的code、人类可读的message以及 HTTPstatus

{ "error": { "code": "not_found", "message": "Book with ID 999 does not exist.", "status": 404 }, "meta": { "request_id": "req_8e4b0c3d", "timestamp": "2026-04-15T10:30:01Z" } }

统一的request_id让调用方可以把一次失败请求与后端日志、追踪系统关联起来,是生产 API 的重要可观测性设计。

图书(Books)端点

列出图书:GET /books

返回分页图书列表,支持以下查询参数:

ParameterTypeDefaultDescription
pageinteger1Page number
limitinteger20Items per page (max 100)
sortstringtitleSort field:title,year,rating
orderstringascSort order:ascordesc
genrestringFilter by genre slug
qstringFull-text search across title/author

一个按流派过滤并限制条数的实际请求:

curl -H "Authorization: Bearer sk_live_abc123def456" \ "https://api.bookshelf.example.com/v2/books?genre=sci-fi&limit=2"

响应中的meta.pagination携带分页元数据(pagelimittotaltotal_pages),业务数组位于data

{ "data": [ { "id": 42, "title": "Neuromancer", "author": { "id": 7, "name": "William Gibson" }, "year": 1984, "genre": "sci-fi", "isbn": "978-0-441-56956-4", "rating": 4.2, "pages": 271 }, { "id": 108, "title": "Snow Crash", "author": { "id": 15, "name": "Neal Stephenson" }, "year": 1992, "genre": "sci-fi", "isbn": "978-0-553-38095-8", "rating": 4.0, "pages": 468 } ], "meta": { "request_id": "req_a1b2c3d4", "timestamp": "2026-04-15T10:32:00Z", "pagination": { "page": 1, "limit": 2, "total": 87, "total_pages": 44 } } }
获取单本图书:GET /books/:id

按数字 ID 返回图书详情,字段比列表更完整,包含descriptiontagscreated_atupdated_at

{ "data": { "id": 42, "title": "Neuromancer", "author": { "id": 7, "name": "William Gibson", "bio": "American-Canadian speculative fiction writer." }, "year": 1984, "genre": "sci-fi", "isbn": "978-0-441-56956-4", "rating": 4.2, "pages": 271, "description": "The sky above the port was the color of television, tuned to a dead channel.", "tags": ["cyberpunk", "ai", "hacking"], "created_at": "2025-01-10T08:00:00Z", "updated_at": "2026-03-22T14:15:00Z" } }
创建图书:POST /books

需要write权限 scope。请求体通过author_id关联作者:

{ "title": "The Dispossessed", "author_id": 22, "year": 1974, "genre": "sci-fi", "isbn": "978-0-06-051275-2", "pages": 387, "description": "A brilliant physicist decides to take action.", "tags": ["utopia", "anarchism", "physics"] }

成功返回201 Created及完整对象(此时ratingnull,待后续评分):

{ "data": { "id": 253, "title": "The Dispossessed", "author": { "id": 22, "name": "Ursula K. Le Guin" }, "year": 1974, "genre": "sci-fi", "isbn": "978-0-06-051275-2", "rating": null, "pages": 387, "created_at": "2026-04-15T10:35:00Z", "updated_at": "2026-04-15T10:35:00Z" } }
更新图书:PATCH /books/:id

部分更新语义——请求体只包含要修改的字段,例如更新评分与标签:

{ "rating": 4.5, "tags": ["utopia", "anarchism", "physics", "award-winner"] }

成功返回200 OK与更新后的完整图书对象。PATCHPUT的差异正在于此:前者支持字段级局部更新,后者要求提交完整资源。

删除图书:DELETE /books/:id

永久删除图书记录,成功返回204 No Content(无响应体)。

作者(Authors)端点

GET /authors支持pagelimit(默认 20,最大 100)与按姓名搜索的q参数;GET /authors/:id返回作者信息及其图书摘要:

{ "data": { "id": 7, "name": "William Gibson", "bio": "American-Canadian speculative fiction writer and essayist.", "born": 1948, "website": "https://williamgibsonbooks.com", "book_count": 12, "books": [ { "id": 42, "title": "Neuromancer", "year": 1984 }, { "id": 43, "title": "Count Zero", "year": 1986 }, { "id": 44, "title": "Mona Lisa Overdrive", "year": 1988 } ] } }

注意books数组中的条目是轻量摘要(只含id/title/year),与图书详情端点的完整字段形成对照——这是 API 文档中常见的"列表瘦身"模式。

阅读清单(Reading Lists)端点

创建清单:

POST /lists
{ "name": "Summer 2026", "description": "Books to read this summer", "visibility": "public", "book_ids": [42, 108, 253] }

向清单添加图书:POST /lists/:list_id/books,请求体为{"book_id": 77},返回200 OK与更新后的清单。从清单移除图书:DELETE /lists/:list_id/books/:book_id,返回204 No Contentvisibility字段暗示清单具备公开/私有两种可见性,属于权限模型的典型设计。

错误码表

CodeStatusDescription
bad_request400Malformed request body or params
unauthorized401Missing or invalid API key
forbidden403Insufficient scope for this action
not_found404Resource does not exist
conflict409Duplicate ISBN or resource conflict
rate_limited429Too many requests
internal_error500Unexpected server error

unauthorizedforbidden的区分(认证失败 vs 权限不足)呼应了前文"需要writescope"的授权模型;conflict专门覆盖 ISBN 重复这类唯一性冲突。

分页:游标模式

除页码外,所有列表端点还支持游标式分页:

GET /books?cursor=eyJpZCI6NDJ9&limit=20

游标是一个不透明字符串(示例中为 base64 编码的记录定位信息),比页码更稳定——避免新增/删除数据导致页码漂移。当还有更多结果时,meta.pagination会返回has_morenext_cursor

{ "meta": { "pagination": { "limit": 20, "has_more": true, "next_cursor": "eyJpZCI6NjJ9" } } }

Webhook 订阅

客户端可注册 Webhook 以接收资源变更事件:

POST /webhooks
{ "url": "https://yourapp.example.com/hooks/bookshelf", "events": ["book.created", "book.updated", "book.deleted"], "secret": "whsec_your_signing_secret" }

每次投递都会携带X-Signature头用于验签(配合secret做 HMAC 校验),这是防止伪造回调的标准做法。事件名采用<resource>.<action>的命名约定,便于按资源前缀订阅。

变更日志(Changelog)

  • v2.3(2026-04-01) — 新增游标分页
  • v2.2(2026-02-15) — 新增 Webhook 支持
  • v2.1(2025-11-01) — 新增阅读清单端点
  • v2.0(2025-08-01) — v2 首发,引入新认证模型

延伸阅读区块

样本文档以"Further Reading"结尾,列出认证指南、Webhook 验签、SDK 参考与状态页等后续阅读入口(样例中为指向文档站内部的示例链接)。这一区块与正文一样被保留在 golden 中——它说明完整提取不应丢弃文档页尾的导航性内容。

提取基准如何用这份 golden 评估质量

golden 的价值最终在基准运行时兑现。runner.ts 是提取基准的执行入口(通过npm run bench:extraction调用,见 package.json),其核心流程为:

  1. 加载清单loadManifest读取 manifest.json,并可用--filtercategoryidtags筛选样本(filterManifestEntries);
  2. 装载夹具loadFixtureHtml读入html/api-001.htmlloadGoldenMarkdown读入本 golden 文件;
  3. 执行提取runSingleBenchmark调用extractContent(html, url)(入口为 pipeline.ts),记录耗时与所选提取器,并校验extractor === expectedExtractor
  4. 计算指标computeMetrics对比提取结果与 golden,产出 Precision、Recall、F1、ROUGE-L 以及标题数/链接数是否一致(metrics.ts);
  5. 汇总报告computeSummarygenerateMarkdownReport按整体、按类别(category)、按提取器(extractor)三层输出统计,并写入extraction-benchmark.jsonextraction-benchmark.md(report.ts)。

指标的具体含义

api-001为例,评测时提取结果与 golden 都会被送入 tokenizer.ts 做归一化:去掉 Markdown 标记(标题#、列表符号、围栏代码块、加粗/行内代码、链接目标),把空白折叠为单空格并转小写,再按非字母数字边界切成 token 集合:

  • Precision:提取结果中属于 golden 的 token 比例(有没有提取"多余"的东西);
  • Recall:golden 中被提取结果覆盖的 token 比例(有没有"漏掉"正文内容);
  • F1:两者的调和平均,是衡量提取保真度的综合指标;
  • ROUGE-L:基于最长公共子序列(LCS)的相似度,对 token 顺序敏感,能捕捉"内容齐全但顺序混乱"的质量问题;
  • Heading / Link 匹配:直接比对标题数量与链接数量是否与 golden 一致(metrics.ts),用于捕获"整段丢失"或"提取了无关导航链接"等结构性问题。

对于像api-001这样表格与代码块密集的 API 文档,countHeadingscountLinks尤其重要:页面导航、页脚链接若被错误保留,会直接拉低 Link 匹配率。

底层提取管线如何还原这类 Markdown 形态

golden 文件的格式特征——ATX 风格标题(#)、围栏代码块(```)、GFM 表格(| --- |分隔行)、行内代码——与提取管线的 HTML→Markdown 转换器输出是严格对齐的。

转换核心位于 markdown.ts:buildTurndown基于 TurndownService 构建,显式配置了headingStyle: 'atx'(标题用#而非 setext 下划线)与codeBlockStyle: 'fenced'(代码块用围栏而非缩进)。其中两个自定义规则直接解释了 golden 中表格和代码块的呈现:

  • table 规则<table>被整体转换为 GFM 表格,首行作为表头并生成| --- |分隔行,单元格文本中的换行被折叠为空格(markdown.ts)。这也正是 golden 中大量参数表、错误码表、限流表能整齐呈现的原因;
  • codeBlockLang 规则<pre><code>的 class 会经过detectCodeLanguage(来自 lang-hints.ts)推断语言名,围栏长度会根据代码内容中最长的反引号连续段自动加长,避免嵌套反引号破坏代码块(markdown.ts)。

在管线层面,pipeline.ts 的applyPostProcessing会对原始转换结果做一系列后处理:resolveRelativeUrls把相对链接/图片解析为基于页面 URL 的绝对地址;stripBoilerplateMarkdown剔除导航、页脚等样板内容;filterDecorativeImages按 URL 特征与 alt 文本过滤装饰性图片;sanitizeExtractedMarkdown做最终清洗。这些步骤共同决定了"最终进入指标比对的内容边界",而 golden 正是对这一边界的人工标定——例如 Bookshelf 文档中的Authorization头、curl 命令、JSON 响应体都属于正文必须保留,而装饰图标、埋点像素则不应出现。

如何新增与维护同类 golden 样本

如果你想为项目补充一个 API 参考类页面的评估样本,流程如下:

  1. benchmarks/extraction/fixtures/html/下放置原始网页 HTML 夹具;
  2. benchmarks/extraction/fixtures/golden/下编写对应的期望 Markdown,格式约定与本文所示一致:ATX 标题、围栏代码块、GFM 表格、行内代码,只保留页面正文内容;
  3. 在 manifest.json 的entries数组中新增条目,填写id(唯一)、urlcategory(docs/article/github 等)、htmlFixturePathgoldenPathexpectedExtractortags
  4. 运行npm run bench:extraction全量评测,或通过 filter 只跑新增样本,观察报告中的 F1、ROUGE-L 与 Heading/Link 匹配率。

编写 golden 时需注意:它应反映"理想提取结果"而非"当前实现的结果",否则指标会失去监督意义;同时应与 HTML 夹具保持一一对应,任何一方的改动都要同步评审,避免黄金标准失真。

【免费下载链接】wigoloThe go-to web for your AI coding agent — local-first search, fetch, crawl & research over MCP. No API keys, no cloud, $0/query. Public beta.项目地址: https://gitcode.com/GitHub_Trending/wi/wigolo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询