
之前在做 AI 知识库问答时我经常遇到一个尴尬场景明明只是一本工具书想让它根据项目需求回答某个具体问题结果把整本书的章节内容都塞进提示词上下文窗口快撑爆回答质量却不升反降。后来接触到 book-to-skill 这个思路核心就一句话不要把整本书喂给模型而是从书里提炼出“按需技能”让模型在需要时才加载对应部分。这个方向对处理长文档、降低上下文开销很有帮助本文就围绕它展开。1. 上下文爆掉的痛为什么不能把整本书直接塞给 AI1.1 上下文窗口到底卡在哪里现在的 LLM 基本都有“上下文窗口”限制例如常见的 8K、32K、128K部分模型虽然宣称支持更大窗口但窗口越大Token 成本、推理延迟和注意力分散的问题也会越明显。你可以把上下文窗口理解成模型一次对话的“工作台”工作台越大能放下的资料越多但太满之后模型反而找不到重点区域。尤其当一本书有几十万字时直接拼接进 Prompt会引发几个问题超出窗口长度系统直接报错或截断。即便没有超限中间部分的资料容易被“注意力稀释”模型更偏向开头和结尾。大量无关章节占据 Token导致单次请求成本上升。资料更新后整段 Prompt 需要重新维护非常笨重。1.2 整本书直接作为 Prompt 的典型失败场景假设我们现在要做一个小工具基于一本书例如《Python 数据处理》回答读者的提问。如果直接这么做用户问题如何在 Pandas 中做数据透视表 系统输入 整本书的原文内容约 40 万字全部放入 Prompt。最后的结果往往是请求体积巨大很多模型直接拒绝。或者模型在大量文本中“迷路”答非所问。消耗 Token 多调试一次的成本很高。这里的关键问题不是模型能力不够而是我们使用了错误的“知识承载方式”。把书本原文当成上下文是一种粗暴但低效的做法。1.3 book-to-skill 是怎么解决这个问题的book-to-skill 的做法是把长资料转换为“按需技能”。所谓“按需技能”可以理解成一张张小卡片每张卡片只描述“在什么场景下如何完成某个操作”。比如一本 40 万字的《Python 数据处理》可以拆成技能卡片 1数据读取与清洗。技能卡片 2Pandas 数据透视表。技能卡片 3Matplotlib 图表绘制。当用户问“如何在 Pandas 中做数据透视表”时系统只加载“技能卡片 2”而不必加载整本书原文。这样上下文占用大幅减少有效内容密度反而更高。标题中提到“一本书省 51 倍上下文”可以理解为如果原书内容约 50 万字而提炼出的技能卡片总量只有原书内容的几十分之一那么按需加载时上下文占用自然就下降到原来的几十分之一。实际倍率取决于书的类型、卡片设计的粒度以及检索命中率不需要拘泥于具体数字重点是数量级上的差距。book-to-skill 的核心理念可以总结成下面这个对比方案上下文占用有效信息密度更新成本整本书喂给模型极高低大量无关内容高每次都要重新维护book-to-skill 按需加载低高只放相关技能低只更新对应卡片理解了这层逻辑后面的实现思路就顺了。2. 环境准备与项目形态2.1 本文使用的环境说明先说明一下以下示例以实现思路为主不会依赖某个特定模型或特定 API。大家在本地运行的时候需要根据实际环境调整版本。本文演示环境大致如下操作系统Windows / macOS / Linux 均可。编程语言Python 3.9 或更高版本。文档处理库PyMuPDF用于读取 PDF 文本。嵌入模型与向量数据库可选用 OpenAI 兼容接口、Ollama 本地模型、Chroma 或 FAISS 等。LLM 接口支持 Chat Completions 格式的任意模型本地或远程皆可。这里不指定具体版本号因为该领域工具更新很快。你只需要确认以下几点Python 环境能正常安装 pip 包。能调用你选择的 LLM API。如果使用本地模型注意内存和显存占用。2.2 一个最小可落地的项目结构book-to-skill 本身并不是一个固定的软件它更像一种“长文档知识处理范式”。我们可以把它实现为命令行工具、API 服务甚至一个 MCP Server这取决于使用场景。为了让流程更容易理解我在后面会演示一个最小可运行原型目录建议如下book-to-skill-demo/ ├── requirements.txt ├── extract_text.py # 文档切块与文本提取 ├── generate_cards.py # 从文本块生成技能卡片 ├── build_index.py # 构建向量索引 ├── query.py # 按需检索技能卡片并回答 └── data/ ├── book.pdf # 原始书籍 PDF ├── chunks.json # 切块结果 ├── cards.json # 技能卡片结果 └── index/ # 向量索引目录实际项目中你完全可以把这些步骤合并成一个流水线任务也可以拆成多个微服务。重点是理解这个处理链路。3. 核心原理从整本书到技能卡片的四步流程3.1 第一步拆解文档按结构切块直接把整本书丢给 LLM 生成技能卡片同样会遇到上下文爆掉的问题。所以我们要先按章节、标题或页码范围切块。切块时需要注意按章节切分最自然但要处理目录、页眉页脚等干扰信息。按固定长度切分简单但容易把完整逻辑截断。比较好的方式是按 Markdown 标题或 PDF 书签切分然后对超长块做二次拆分。下面是一个基于 PyMuPDF 的文本提取示例它按 PDF 页面读取文本然后按“每 N 个字符”合并成块# extract_text.py import fitz # PyMuPDF def extract_chunks(pdf_path, chunk_size3000): doc fitz.open(pdf_path) chunks [] current_chunk current_page 0 for page_index in range(len(doc)): page doc.load_page(page_index) text page.get_text(text) current_page page_index 1 if not text.strip(): continue current_chunk text if len(current_chunk) chunk_size: chunks.append({ page: current_page, content: current_chunk }) current_chunk if current_chunk.strip(): chunks.append({ page: current_page, content: current_chunk }) return chunks if __name__ __main__: chunks extract_chunks(data/book.pdf) with open(data/chunks.json, w, encodingutf-8) as f: import json json.dump(chunks, f, ensure_asciiFalse, indent2) print(f生成 {len(chunks)} 个文本块)这个脚本非常简单但它代表了一个重要理念先把长文本切成“可以交给模型处理的粒度”而不是直接整本投喂。3.2 第二步让 LLM 从文本块中提取技能卡片有了文本块之后下一步是让 LLM 从每个文本块中提取结构化技能卡片。技能卡片需要包含以下核心字段技能名称这个技能叫什么。适用场景用户可能在什么情况下需要它。核心步骤完成该技能的关键操作流程。示例代码 / 示例用法可供模型直接参考的内容。注意事项边界条件、易错点。通过 Prompt 明确要求模型输出 JSON 格式后续解析更方便。一个最小实现的思路如下# generate_cards.py import json import openai client openai.OpenAI(api_keyYOUR_API_KEY) SYSTEM_PROMPT 你是一个知识提炼助手。请根据给定的书籍文本块提取其中可复用的技能卡片。 输出 JSON 数组每个元素包含 - name: 技能名称 - scene: 适用场景 - steps: 操作步骤数组 - code: 示例代码如果存在 - attention: 注意事项 只输出 JSON不要输出多余解释。 def generate_cards(chunk): resp client.chat.completions.create( modelgpt-4o-mini, # 按你的实际模型调整 messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: chunk[content]} ], temperature0.2, response_format{type: json_object}, ) content resp.choices[0].message.content return json.loads(content)这里需要注意几点不是每个文本块都会产出技能卡片有些块只是引言、目录或过渡内容。卡片粒度不宜太细也不宜太粗。太细会让卡片数量爆炸太粗又起不到按需加载的作用。生成后可以人工抽查修正错误内容。如果担心 API 成本也可以先用规则方法提取标题和代码片段再让 LLM 做二次整理成本会更低。3.3 第三步存储技能卡片并建立索引技能卡片生成后不能让用户每次提问时都遍历所有卡片那样效率太低。更好的方式是把卡片向量化后存入向量数据库用语义检索快速定位最相关的几张卡片。常见的向量数据库有 Chroma、FAISS、Milvus、Qdrant 等。个人项目用 Chroma 或 FAISS 比较轻量公司项目可以用 Milvus。这里以 Chroma 为例说明如何把技能卡片写入本地向量库# build_index.py import chromadb from chromadb.utils import embedding_functions client chromadb.PersistentClient(pathdata/index) collection client.get_or_create_collection( nameskill_cards, embedding_functionembedding_functions.DefaultEmbeddingFunction() ) def add_cards(cards): for i, card in enumerate(cards): collection.add( ids[fcard_{i}], documents[card[name] 。 card[scene]], metadatas[card] )这里有一个值得思考的点我们用来做检索的内容是“技能名称 适用场景”而不是整张卡片。这样做的好处是检索时上下文更短命中精准度也更高。真正需要完整卡片时再根据 metadata 返回。3.4 第四步按需加载技能卡片回答用户问题到了真正的问答环节我们不再携带整本书而是根据用户问题先检索最相关的 2 到 5 张技能卡片然后把卡片内容和用户问题一起放入 Prompt。伪代码如下# query.py def answer(question): similar_cards search_cards(question, top_k3) context build_prompt(similar_cards) prompt f 基于下面的技能卡片回答用户问题。 技能卡片 {context} 用户问题 {question} return llm(prompt)这样做的效果是什么上下文只包含“用户问题 相关技能卡片”。单次请求 Token 消耗大幅降低。模型不需要在海量原文中找答案回答准确率提升。整体流程可以用下面的流程图概括书籍 PDF ↓ 拆解成文本块 ↓ LLM 抽取技能卡片 ↓ 向量化并建立索引 ↓ 用户提问 → 检索相关卡片 → 组装上下文 → 生成回答这也是“book-to-skill”这个名称的由来把一本被动等待阅读的书转化成一组主动响应的技能。4. 实战演示一个最小可用的 book-to-skill 原型4.1 创建项目结构打开终端创建目录并安装依赖mkdir book-to-skill-demo cd book-to-skill-demo pip install pymupdf openai chromadb如果需要读取 docx 或其他格式可以额外安装 python-docx本文以 PDF 为例。4.2 编写技能卡片生成 Prompt为了让生成结果更稳定建议把 Prompt 单独拆出来维护。我实际测试时发现使用结构化的 JSON 输出格式比自由文本好解析得多。# card_prompt.py CARD_EXTRACT_PROMPT 你正在处理一本技术书籍的某个片段。你的任务是从中提取“可复用技能”。 技能卡片需要满足以下条件 1. 必须是一个完整的、可独立执行的操作知识。 2. 需要给出适用场景便于后续检索。 3. 包含核心步骤或示例代码。 4. 语言简洁避免含糊表达。 输出格式如下 { cards: [ { name: 技能名称, scene: 适用场景描述, steps: [步骤1, 步骤2], code: 示例代码, attention: 注意事项 } ] } 书籍片段 {chunk} 这个 Prompt 的作用是约束模型输出结构。如果你的模型不支持response_format强制 JSON也可以在后处理时用正则解析。4.3 编写完整处理链路脚本接下来把这些步骤串联起来。为方便演示我把切块、生成卡片、建索引、查询都放在一个流水线脚本中。# pipeline.py import json import openai import chromadb import fitz def extract_chunks(pdf_path, chunk_chars3000): doc fitz.open(pdf_path) chunks [] buffer page_no 1 for page in doc: text page.get_text(text).strip() if not text: page_no 1 continue buffer text while len(buffer) chunk_chars: chunks.append({page: page_no, content: buffer[:chunk_chars]}) buffer buffer[chunk_chars:] page_no 1 if buffer.strip(): chunks.append({page: page_no, content: buffer}) return chunks def generate_cards_with_llm(chunk, client): prompt CARD_EXTRACT_PROMPT.format(chunkchunk[content]) resp client.chat.completions.create( modelgpt-4o-mini, messages[ {role: system, content: 你是一个知识提炼助手只输出 JSON。}, {role: user, content: prompt} ], temperature0.2, ) text resp.choices[0].message.content try: data json.loads(text) return data.get(cards, []) except json.JSONDecodeError: print(f第 {chunk[page]} 页附近解析失败) return [] def build_index(cards): client chromadb.PersistentClient(pathdata/index) collection client.get_or_create_collection(nameskill_cards) for i, card in enumerate(cards): collection.add( ids[fcard_{i}], documents[card[name] 。 card[scene]], metadatas[{name: card[name], content: json.dumps(card)}] ) return collection def deal_with_question(question, collection): results collection.query(query_texts[question], n_results3) cards [] for meta in results[metadatas][0]: cards.append(json.loads(meta[content])) context_parts [] for i, c in enumerate(cards): context_parts.append(f[技能 {i1}] {c[name]}\n{c[steps]}) context \n\n.join(context_parts) return context if __name__ __main__: openai_client openai.OpenAI() chunks extract_chunks(data/book.pdf) all_cards [] for chunk in chunks: cards generate_cards_with_llm(chunk, openai_client) all_cards.extend(cards) with open(data/cards.json, w, encodingutf-8) as f: json.dump(all_cards, f, ensure_asciiFalse, indent2) collection build_index(all_cards) print(f成功生成 {len(all_cards)} 张技能卡片)这个脚本是核心链路的最小演示。实际项目中你还需要加入断点续跑避免生成到一半失败后重新开始。错误重试处理 API 限流和超时。成本控制对每个文本块调用模型前先预估 Token。4.4 运行与验证假设你有一本《Python 数据处理》PDF运行python pipeline.py预期输出成功生成 42 张技能卡片然后写一个简单的问答脚本# query.py import json import openai import chromadb client openai.OpenAI() chroma_client chromadb.PersistentClient(pathdata/index) collection chroma_client.get_collection(nameskill_cards) def answer(question): context deal_with_question(question, collection) prompt f基于以下技能卡片回答问题\n\n{context}\n\n问题{question} resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: prompt}] ) return resp.choices[0].message.content print(answer(如何在 Pandas 中做数据透视表))如果技能卡片提取得足够好回答会直接引用卡片里的操作步骤而不用再去翻阅书籍原文。4.5 结果说明与重要提醒从实验结果看这种方式的收益主要体现在几个方面上下文体积小单张卡片通常只有几百字。回答聚焦模型不会在无关章节里找答案。知识更新方便只需要重新生成特定章节对应的卡片。但这里有几点必须说明上面代码里用到的gpt-4o-mini是按我本地环境写的你实际使用时要改成自己能访问的模型。Chroma 的默认 embedding 函数在不同版本间有差异建议查看你安装版本的文档。如果书籍内容涉及敏感数据请务必在本地环境处理不要随意上传到第三方 API。5. 常见问题与排查思路5.1 技能卡片质量不高常见表现是卡片描述太泛或者步骤缺细节。可能原因文本切块太大模型没抓住重点。Prompt 中没有强调“可复用”和“场景化”。某些章节本身只是理论介绍不包含具体技能。解决思路减小切块长度例如从 3000 字符降到 1500 字符。在 Prompt 中增加示例卡片让模型模仿。生成后人工审核把不满足要求的卡片删除。5.2 检索结果不相关用户提问后检索到的卡片和问题关系不大。可能原因向量化时只用了标题和场景这些字段信息量不足。嵌入模型和问题领域的匹配度不够。卡片数量少导致语义区分度不够。解决思路检索文本改为“技能名称 适用场景 核心步骤摘要”。更换嵌入模型比如本地用 bge-m3或者使用更契合领域的中文向量模型。增加候选卡片数量比如从 top_k3 调到 top_k5。下面用表格整理几类高频问题问题现象常见原因解决思路生成卡片时 API 频繁超时文本块过长或并发过高减小切块增加重试机制卡片 JSON 解析失败模型输出带多余文字开启 response_format或在 Prompt 中严格约束回答仍然过长加载了过多技能卡片调低 top_k同时精简卡片内容上下文还是很大卡片本身太长把卡片压缩为“步骤 代码 注意点”摘要检索结果不稳定向量索引参数不合适调整距离度量方式或更换嵌入模型5.3 上下文超限问题如果你在调试时看到了“上下文大小仍超出限制”之类的提示可以按以下顺序排查计算当前请求中所有文本的 Token 总和。确认技能卡片是否过多或过长。查看是否还有历史对话被一并携带。如果是类似 Claude Code 这类工具尝试使用压缩上下文命令或开启自动摘要功能。检查是否有 MCP 服务器返回了超大数据。这些原则不止适用于 book-to-skill也适用于所有长上下文场景。6. 最佳实践与工程建议6.1 技能卡片的命名与粒度设计技能卡片是这个方案的核心资产卡片设计得好不好直接影响最终效果。我的建议是命名采用“动词 对象”的结构例如“创建 Pandas 透视表”“清洗缺失值”。一个卡片只描述一个可独立完成的操作不要把多个技能塞在一起。每个卡片必须包含“适用场景”字段这个字段是检索的主要依据。代码示例保持最小可运行不要贴大段无关代码。如果把卡片当作代码来管理那么“每个函数只做一件事”的原则同样适用。6.2 上下文经济不要把所有卡片都塞进系统提示词很多初学者做完卡片之后又习惯性地把所有卡片全部放到系统提示词里理由是“这样模型什么都会”。这种做法实际上违背了 book-to-skill 的初衷。正确的做法是“按需检索、动态组装”用户提问后先做语义检索。挑选最相关的 2 到 5 张卡片。用这些卡片组装本次请求的上下文。为什么是 2 到 5 张因为太少可能信息不全太多又会引入噪音。实际项目可以通过评测集调优。这种思路与“上下文工程”中强调的“减少无关信息、提高有效密度”是完全一致的。你可以把它理解为上下文不是越大越好而是越精准越好。6.3 安全与合规边界使用 book-to-skill 处理书籍资料时有几个安全红线值得注意如果图书有版权不要把整本书随意上传到第三方 API 做提取建议在本地模型上完成处理。如果文本包含个人隐私、企业机密绝对不要直接发送到外部服务。生成的技能卡片如果会被他人使用需要经过人工审核避免错误知识被传播。对模型生成的代码示例尤其是数据库操作和系统命令要提示风险。给一个更稳妥的落地方式把“文本切块 卡片生成”放在本地完成只把“用户提问 相关卡片”发送给外部模型。这样既控制了上下文也减少了数据暴露面。6.4 从单本书扩展到整个知识库当你有几十本书、几百份文档时book-to-skill 的思路可以进一步扩展成企业知识库每本书生成一组技能卡片。卡片统一存入向量数据库。卡片按来源、版本、领域打标签。回答问题时先过滤来源再检索卡片。这种做法的好处是知识库不是一堆文档的堆砌而是一组可调用、可追溯、易更新的“技能单元”。7. 总结与动手实践建议book-to-skill 的价值不在于“压缩上下文”这一个指标而在于它改变了我们组织知识的方式把静态的长文本变成动态的、按需响应的技能单元。这个思路可以用在 AI 编程助手、企业内部知识库、个人读书笔记等场景中。如果你现在正被“上下文不够用”“长文档问答效果差”困扰可以按照本文的流程实践一遍找一本你已经拥有版权的 PDF 书籍。用 PyMuPDF 提取文本块。让模型提取技能卡片。把卡片写入向量库。写一个问答脚本验证效果。不用追求一步到位先跑通最小流程再逐步优化卡片质量和检索策略。最终你会发现书还是那本书但模型不再需要把整本书背下来才能回答问题。