尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

基于RAG的LLM写作工作流:素材检索与初稿生成实践

基于RAG的LLM写作工作流:素材检索与初稿生成实践 如果你过去一年也在坚持写技术博客或者至少要维护一份团队技术文档你大概率会遇到同一个问题真正耗时的不是“写”而是把散落在代码、聊天记录、笔记软件里的信息重新拼起来形成一段别人能看懂的上下文。我在反复经历这个过程之后对 LLM 写作这件事有了一个和最初认知完全不同的判断LLM 写作的核心价值不在于“让 AI 替你写”而在于让“素材检索、上下文整理、初稿生成、人工修订”这个链条变成一条可复用、可自动化的工作流。它改变的不是你对文字的控制力而是你整理信息的方式。这篇文章会用一套完整的实践路径讲清楚我是怎么理解和使用这条工作流的。内容包括 LLM、RAG、向量数据库、嵌入模型、Agent 编排等关键概念以及一套可以实际跑起来的最小实现。无论你是 CSDN 技术作者、团队文档维护者还是刚接触 LLM 的新手读完都能从中找到可以落地的部分。1. 这篇文章真正要解决的问题很多接触 LLM 的开发者第一次尝试用 AI 写文章做法往往是这样把主题丢给 ChatGPT 或类似的对话大模型让它生成一篇 3000 字文章然后复制粘贴。这种做法的问题在于生成的文字很流畅但信息密度极低。模型不了解你过去的实践不知道你踩过哪些坑也不认识你的项目背景。它只是在用通用的语言模式生成一篇“看起来合理”的文章。你不敢直接发布最后还是得重新写一遍。这里真正要解决的问题是技术写作中的“上下文断层”。一篇高质量技术博客通常由三类材料构成你通过实验得到的结论和数据。你在代码、日志、配置中遇到的问题与解决办法。你长期积累的项目背景和个人经验。这三类材料大多不是结构化的它们散落在笔记软件、命令行记录、代码仓库甚至聊天记录里。传统写作流程中你必须手动把这些素材捞出来再按逻辑串联成文。这个过程极其消耗精力而且往往在你最想写作的时候素材找不齐状态就断了。LLM 写作工作流的思路是先用向量检索把你积累的笔记和资料找出来再把这些资料作为上下文交给大语言模型生成初稿。模型不认识你但检索系统认识你的笔记二者结合之后输出质量会明显高于“空对空”生成。所以这篇文章要解决的核心问题可以概括成一句话如何用工程化的方式把“个人素材”和“大模型生成能力”连接起来形成一套完整的技术写作辅助系统。如果你正准备开始接触 RAG或者想搭建一个个人知识库助理这篇文章同样适用。技术写作只是这套组合拳的一个常见场景。2. 核心概念LLM、RAG、向量库与编排框架在进入代码之前先厘清几个概念。因为 LLM 写作工作流里的每个模块本质上都是在解决某一类信息处理问题。2.1 大语言模型LLMLLM 就是大语言模型。它的能力本质是“根据已有上下文预测下一个 token”。这里的 token 可以粗略理解为词或子词。它知道大量的通用知识也擅长模仿各种文体但它不知道你的私有资料除非你把这些资料放进输入文本里。它也不是数据库不应该被用来“凭记忆回答”你项目中的具体事实。在写作工作流中LLM 负责三个任务提炼要点、组织段落、生成初稿。这三个任务对模型的要求并不高真正影响质量的是上下文中是否包含充分的素材。2.2 上下文窗口上下文窗口指的是模型一次能接收的最大 token 数。你可以把它理解为模型的“工作台面”。台面越大能摆出来的资料越多。但技术笔记和日志往往很长无法全部塞进窗口所以需要检索模块先从资料堆里挑出最相关的部分。这也是为什么很多知识库工具都采用“检索 生成”的组合而不会简单地把所有内容都丢给模型。2.3 检索增强生成RAGRAG 的全称是 Retrieval-Augmented Generation检索增强生成。它的流程可以拆成三步把个人文档切分成片段并转成向量存入向量数据库。用户提出问题时先将问题转为向量再从向量库中检索最相关的片段。把检索到的片段和用户问题一起拼进 prompt交给 LLM 生成回答。RAG 解决的核心问题是让模型“在回答前先查资料”。技术写作中资料就是你的历史笔记和实验记录。引入 RAG 之后模型的输出不再依赖它训练时记住的内容而是基于你提供的素材。2.4 向量化与向量数据库要让计算机在大堆文档中做“语义相似度检索”不能直接比较文本需要先把文本转成向量。嵌入模型Embedding Model做的事就是把文本映射到一个高维向量空间。语义相近的文本向量距离也更近。向量数据库负责存储这些向量并提供高效的相似度检索。常见的开源方案包括 Chroma、FAISS、Qdrant、Milvus。个人写作场景中Chroma 或 FAISS 足够用不需要引入重型的分布式数据库。2.5 编排框架当你的工作流不再只是“一个 prompt 调一次模型”而是需要串联检索、重写、分章节生成、人工审核等多步操作时就需要编排框架。编排框架可以是简单的 Python 函数链也可以是完整的 Agent 框架。这里引入一个关键概念Agent。Agent 可以理解为一个能自主调用工具的 LLM 程序。它不只会“生成文本”还能根据任务决定调用检索、访问数据库、运行命令。在写作场景中一个 Agent 可能先根据主题搜索笔记再根据笔记列提纲再逐节生成内容最后调用某个格式检查工具。另一个相关的协议是 MCP即 Model Context Protocol。它像是一种“模型工具接口标准”让 LLM Application 能够以一种统一的协议连接外部工具和数据源。如果你需要把内部知识库、数据库、甚至绘图工具都接入同一个 LLM AgentMCP 是一个值得关注的趋势。2.6 传统写作流程与 LLM 工作流对比环节传统手写流程LLM 写作工作流素材收集手动搜索笔记和聊天记录向量检索自动召回上下文整理人工理解并拼接材料系统拼装 prompt 上下文初稿生成从零开始组织语言模型基于素材生成初稿修订边写边改反复重写人工聚焦结构和事实校验可复用性每篇文章重复劳动同一知识库持续复用真实项目里这套工作流并不神秘也不需要一开始就上 Agent 和 MCP。先用最小成本把“向量检索 LLM 生成”跑通再逐步扩展是更稳妥的路线。3. 环境准备与前置条件下面进入实操部分。我会用 Python 实现一套最小可用的 LLM 写作工作流。为了避免把文章变成“指定版本说明书”这里的版本信息只做参考重点演示通用思路。3.1 运行环境我目前的示例基于 Python 3.10。你需要确认本机已经安装了 Python 和 pip。如果你的系统同时存在 Python 2 和 Python 3建议用python3和pip3命令区分。还需要准备以下几种环境之一作为模型服务在线大模型 API支持 OpenAI 兼容接口。本地部署的模型服务例如 Ollama、llama.cpp 等。企业内部统一模型网关。无论使用哪种关键是拿到一个可以调用的接口地址和密钥。如果你想先跑通流程推荐使用本地模型或测试环境的 API成本低速度也足够。3.2 安装依赖需要安装的 Python 包有三个类别大模型调用、向量数据库、文本处理。下面命令可以一次性安装基础依赖pip install openai chromadbopenai官方 Python 库不仅支持 OpenAI 的服务也兼容大部分提供 OpenAI 风格接口的模型服务因此你可以通过修改base_url来连接本地模型或其他服务。chromadb是轻量向量数据库适合个人知识库场景。如果后续使用 FAISS再单独安装pip install faiss-cpu3.3 模型与密钥配置不要直接把 API 密钥写进代码文件。这是很多新手容易忽略的安全问题。推荐使用环境变量或独立配置文件并加入.gitignore。在命令行临时设置环境变量export LLM_API_KEYyour-api-key export LLM_BASE_URLhttps://your-model-service.example.com/v1如果使用 Windows PowerShell$env:LLM_API_KEYyour-api-key $env:LLM_BASE_URLhttps://your-model-service.example.com/v1如果你不知道模型服务地址本地运行 Ollama 后默认地址一般是http://localhost:11434/v1模型名以你拉取的模型为准。这些细节建议以你所用工具的官方文档为准。4. 第一步搭建一个能调用的 LLM 文本处理模块这个模块是整个工作流的地基。它只负责一件事把一段文本和你的指令一起发给模型拿到生成的文本。后面所有的摘要、改写、初稿生成都基于这个函数。新建一个文件llm_text.py# 文件路径llm_text.py import os from openai import OpenAI client OpenAI( api_keyos.environ.get(LLM_API_KEY), base_urlos.environ.get(LLM_BASE_URL), ) def chat(system_prompt: str, user_prompt: str, temperature: float 0.3) - str: 通用 LLM 调用函数。 :param system_prompt: 系统提示词用来设定角色和规则。 :param user_prompt: 用户输入内容包含任务描述和素材。 :param temperature: 采样温度越低越稳定。 :return: 模型生成的文本。 response client.chat.completions.create( modelos.environ.get(LLM_MODEL, your-model-name), messages[ {role: system, content: system_prompt}, {role: user, content: user_prompt}, ], temperaturetemperature, ) return response.choices[0].message.content这里的关键逻辑很简单client在模块加载时创建复用连接避免重复初始化。system_prompt控制模型角色和输出风格。user_prompt是每次任务的核心输入。temperature设为 0.3是为了让技术文书类输出更稳定减少发散。现在用一个真实的技术笔记片段测试这段代码。假设你在一个运维项目中记录了这样一段笔记今天把服务从单实例扩容到三实例应用负载确实下降了。但是 Redis 缓存命中率从原来的 95% 掉到了 80% 左右。排查后发现热点 key 分散到了不同实例的本地缓存中导致每个实例命中率都变低。后来把缓存热点 key 统一放回 Redis命中率才恢复。调用这个函数# 文件路径test_llm_text.py from llm_text import chat note 今天把服务从单实例扩容到三实例应用负载确实下降了。 但是 Redis 缓存命中率从原来的 95% 掉到了 80% 左右。 排查后发现热点 key 分散到了不同实例的本地缓存中 导致每个实例命中率都变低。后来把缓存热点 key 统一放回 Redis命中率才恢复。 system_prompt 你是一名技术写作助手擅长把零散的技术笔记整理成逻辑清晰的中文文字。 result chat(system_prompt, f请根据下面的笔记整理一段可供博客引用的技术总结\n\n{note}) print(result)这一步跑通之后你已经拥有了最核心的生成能力。但只靠这个模块模型仍然不知道你过去写过的其他笔记。下一步就是建知识库。5. 第二步把个人笔记变成可检索的知识库要让 LLM 生成的内容带有“你的上下文”必须先把散落的笔记向量化并存储起来。这里使用 Chroma 的本地持久化模式。新建一个文件knowledge_base.py# 文件路径knowledge_base.py import chromadb CHROMA_PATH ./writing_kb COLLECTION_NAME tech_notes # 默认使用 Chroma 内置的 embedding 函数 # 如果要替换为模型 API 的 embedding可以在此传入自定义 embedding function。 client chromadb.PersistentClient(pathCHROMA_PATH) collection client.get_or_create_collection( nameCOLLECTION_NAME, metadata{hnsw:space: cosine}, ) def add_note(note_id: str, text: str, metadata: dict None): 向知识库添加一条笔记。 :param note_id: 笔记的唯一 ID。 :param text: 笔记正文。 :param metadata: 附加元信息例如标签、日期、来源。 collection.add( ids[note_id], documents[text], metadatasmetadata or {}, ) def search_notes(query: str, top_k: int 3): 根据查询内容从知识库中检索最相关的笔记。 results collection.query( query_texts[query], n_resultstop_k, ) return results这段代码有几点值得说明PersistentClient会把数据持久化到本地目录./writing_kb重启后数据还在。collection 类似数据库中的表hnsw:space: cosine表示用余弦距离衡量向量相似度。add_note是写入接口search_notes是检索接口。写入一条笔记# 文件路径test_kb.py from knowledge_base import add_note, search_notes add_note( note_idredis-cache-hit-001, text服务从单实例扩容到多实例后本地缓存导致热点 key 分散Redis 命中率下降。 解决思路热点 key 统一放回 Redis或使用分布式缓存。, metadata{tag: redis, date: 2025-01-10}, ) results search_notes(多实例部署后缓存命中率下降怎么办, top_k2) print(results[documents][0])运行后你会在终端看到检索到的笔记片段。如果检索结果和你的问题明显相关说明知识库已经可用。这里插入一个常见注意点分块大小会影响检索效果。笔记如果太长检索到的片段可能是一大段无用信息如果太短又会缺失上下文。个人写作场景中按“语义小节”切分是一个比较稳妥的策略。可以先用整篇笔记入库后续检索效果不佳再调整分块策略。6. 第三步组装完整的 RAG 写作助手现在把 LLM 模块和知识库模块组合起来形成一个完整的 RAG 工作流。新建文件writing_agent.py# 文件路径writing_agent.py from llm_text import chat from knowledge_base import search_notes SYSTEM_PROMPT 你是一名技术博客写作助手。你的任务基于用户提供的参考资料撰写技术文章初稿。 要求 1. 标题要具体避免“从零开始”“深入浅出”这类空泛表达。 2. 正文包含背景、操作步骤、验证方式、注意事项四个部分。 3. 尽量保留参考资料中的技术细节不要泛化。 4. 如果参考资料不足以支撑某个结论直接写明“该部分需要补充资料”不要编造。 def build_context(query: str, top_k: int 3) - str: 检索知识库拼装上下文。 results search_notes(query, top_ktop_k) documents results[documents][0] return \n---\n.join(documents) def write_draft(topic: str, top_k: int 3) - str: 根据主题生成技术文章初稿。 context build_context(topic, top_ktop_k) user_prompt f请根据以下主题写一篇技术文章初稿。 写作主题{topic} 参考资料 {context} return chat(SYSTEM_PROMPT, user_prompt, temperature0.3) if __name__ __main__: topic 多实例部署后 Redis 缓存命中率下降 draft write_draft(topic) print(draft)整个流程的关键点在于build_context先把相关笔记从向量库中捞出来write_draft再把笔记拼进 prompt。模型没有直接“回答”而是在给定的素材范围内组织语言。运行命令python writing_agent.py预期输出是一篇结构完整的中文文章初稿。它不一定每个细节都准确但至少包含了背景、步骤和问题分析比直接让模型“凭记忆写”要靠谱得多。如果你希望生成更长的文章可以把write_draft拆成两个步骤先让 LLM 列出大纲再对每个章节分别执行一次“检索 生成”。这样每次生成的内容更聚焦上下文窗口的压力也更小。7. 运行结果与效果验证技术文章不能写“运行即可”就结束需要明确验证标准。这里给出三个维度的验证方法。7.1 验证生成结果的信息覆盖度一篇合格的技术初稿至少要覆盖你笔记中的核心结论。在测试时你可以在 prompt 中加入一个系统指令SYSTEM_PROMPT 你是一名技术博客写作助手。生成结束后必须列出“本次参考了哪些技术要点”。这样你可以快速检查模型是否真的使用了检索到的笔记而不是自己在编。7.2 验证检索质量如果 LLM 输出内容和你的笔记完全不相关问题很大可能不在生成而在检索。你可以在search_notes后单独打印results[documents]观察召回结果是否合理。7.3 验证幻觉最简单的幻觉验证方法是让模型在输出中标注信息来源段落引用的原文关键句。在技术写作中我通常要求模型使用“根据参考资料……”这类句式避免把推测写成既定结论。下面是一个判断项目是否为“验证成功”的检查列表检查项通过标准检索结果相关返回的笔记片段与主题语义相关输出覆盖关键点初稿包含背景、步骤、验证、注意点不胡编细节关键结论在参考资料中有对应表述格式可复用输出结构稳定能直接进入人工修订环节调用成本可接受单次运行耗时和 token 消耗在可接受范围如果失败第一步不是改 prompt而是查看检索结果。RAG 项目里大部分问题都出在“没检索到该检索的内容”模型只是忠实地下游生成。8. 进阶方案编排框架、MCP 与 Agent最小方案跑通之后你会发现一个局限上面的代码是“硬编码流程”。一旦你想加入更多环节比如自动抓取网页、调用绘图工具、读取数据库就需要不断改代码。这种场景下编排框架和 Agent 概念就派上用场了。8.1 为什么需要编排框架编排框架要解决的是多步骤任务的组织问题。在写作场景中典型的多步骤任务可能是根据主题抓取相关网页内容。检索个人笔记库。整理大纲。逐节生成内容。调用格式检查工具。输出最终 Markdown。如果用最原始的 Python 写几段流程也能完成但很难维护。每个环节都要处理异常、重试、上下文传递代码很快会变得复杂。编排框架做的事情是把这些环节抽象成“节点”并提供状态管理和节点间通信机制。你可以把“检索笔记”和“抓取网页”都注册成工具然后让 Agent 根据任务动态调用。8.2 MCP 与 AgentMCPModel Context Protocol是一种连接模型和外部工具的标准协议。你可以把它理解成“USB 接口”之于电脑设备厂商只需要按标准接口实现就能被不同系统识别。在 LLM 写作工作流里MCP 的价值在于让不同来源的工具、数据库和企业系统都能以统一协议接入 Agent。你不需要为每个工具单独写一套适配代码而是通过 MCP 配置接入。近期中文技术社区中Spring AI 相关讨论也经常出现。Spring AI 是 Java 生态中相对成熟的 AI 应用框架它把模型调用、向量存储、Agent 编排纳入统一的编程模型。如果你本身是 Java 技术栈可以考虑用它替代 Python 手写方案。不过MCP 和 Spring AI 的这些能力仍处于快速演进期API 变化频繁。我建议先用最小的 Python 示例理解 RAG 原理再根据实际项目需要选择框架不要一上来就引入重依赖。8.3 一个更贴近 Agent 的流程示意真实的 Agent 流程可以用下面的伪代码表达# 文件路径agent_flow_demo.py def agent_writer(topic: str): notes retrieve_notes(topic) # 1. 检索笔记 web_data fetch_web_pages(topic) # 2. 抓取网页可选 outline generate_outline(topic, notes, web_data) # 3. 生成大纲 draft for section in outline.sections: section_context retrieve_section_material(topic, section.title) draft generate_section(section.title, section_context) draft \n\n return post_process(draft) # 4. 格式检查和修订 result agent_writer(多实例部署后的缓存一致性) print(result)这段代码不是直接可运行的完整实现但它展示了从“线性 RAG”到“Agent 编排”的思维差异系统需要在不同阶段动态决定检索什么、生成什么、校验什么。9. LLM 精度问题fp16、fp32、bf16 对写作任务的影响很多读者会在社区里看到关于 LLM 精度问题的讨论比如 fp16、fp32、bf16 的区别。这里用写作场景做一个简单分析帮助你在选型时不被测试指标带偏。9.1 三种常见精度格式格式占用内存表示范围典型用途fp324 字节大训练早期稳定计算fp162 字节较小容易溢出部分推理场景bf162 字节大精度低大规模训练和推理fp16 的问题在于表示范围小可能出现溢出。bf16 牺牲了尾数精度但保留了和 fp32 相同的大动态范围所以在很多现代大模型训练和推理中被广泛使用。9.2 对写作任务的实际影响写作任务本质上是对文本分布的采样对数值精度并不敏感。模型不需要小数点后很多位的精确计算它需要的是在语义空间中稳定地走出一条合理路径。因此在个人写作工作流中你完全可以优先使用量化版本或 bf16 版本模型获得更低的显存占用和更快的推理速度而不必执着于 fp16 或 fp32 精度。需要注意的是如果你的场景涉及数值计算、工具调用 JSON 输出、或者模型需要精确地对齐结构化数据精度和量化对结果的影响会明显增大。写作场景下影响输出质量的首要因素往往是提示词、检索资料和上下文长度而不是“最后几位精度”。如果你打算在本地部署模型建议参考模型发布方的推荐精度和量化方案以官方文档为准不盲目追求高精度。10. 常见问题与排查思路在搭建 LLM 写作工作流时最容易遇到的几个问题如下问题现象可能原因排查方式解决方案检索结果不相关笔记分块过大或过小打印检索到的文档片段调整分块策略或换更合适的 embedding 模型模型输出出现编造内容参考资料不足或 prompt 未约束在 prompt 中要求“来源不完全则明确说明”补充知识库笔记降低 temperature日志中提示模型不存在模型名配置错误查看模型服务列表确认实际模型 ID填入环境变量首次运行 chromadb 时下载模型失败网络受限或默认模型不可用查看报错日志换用 API 嵌入模型或配置代理方式访问调用 API 超时上下文过长或服务不稳定查看服务端监控和错误日志缩短 prompt 长度增加重试机制向量库数据一直增加越来越慢collection 中数据冗余检查 collection.count()删除过期数据或按日期建多个 collection生成内容风格不像你缺少个人风格样本在知识库中存入自己过去的文章增加“参考我的历史文章风格”示例在这些问题中最值得强调的还是第一条检索质量是 RAG 的生死线。模型生成能力再强如果检索系统没有把正确的资料放进上下文产出效果也必然打折。遇到输出异常时先从链路前端开始排查而不是一味调整生成 prompt。11. 最佳实践与工程建议经过一段时间使用我总结出几条比较稳定的工程建议。它们适合个人写作场景也适合小团队的知识管理。11.1 分块策略要“按语义切而不是按行数切”技术笔记中一段完整的调试记录往往包含背景、操作、结论。按固定字符数量切块容易把结论切丢。更推荐的做法是整理笔记时用空行或标题划分语义块。入库时按语义块插入。为每个块添加元数据例如项目名、日期、标签。11.2 知识库的“写入质量”比“检索算法”更重要很多人在优化 RAG 时第一反应是换更复杂的检索算法。但个人知识库场景下第一步应该做的是清洗数据。如果你存入的笔记本身有空话、结论不清任何检索算法都无能为力。建议在写入前加一个简单的质量检查流程def is_useful_note(text: str) - bool: 简单过滤空泛笔记。 if len(text) 50: return False if 待补充 in text or TODO in text: return False return True11.3 使用环境变量管理密钥和模型配置不要在代码库中提交真实密钥。生产或长期使用场景建议使用.env文件加配置文件进行管理并把敏感文件加入.gitignore。示例.env文件LLM_API_KEYyour-api-key LLM_BASE_URLhttps://your-model-service.example.com/v1 LLM_MODELyour-model-name11.4 人工修订是不可或缺的环节LLM 生成的初稿只适合作为“第二稿素材”不能直接发布。至少要检查三个点技术结论是否正确尤其是数据和命令。参考资料引用是否真实。文章结构是否符合你的博客习惯。为了让人工修订更高效可以在生成 prompt 中要求模型输出“初步校验问题清单”让模型先把不确定的地方列出来。11.5 控制上下文长度避免 token 浪费写作场景不需要把所有笔记都塞进上下文中。通过top_k控制召回数量用max_tokens限制生成长度可以显著降低成本。如果你的服务支持缓存历史对话注意区分“可复用上下文”和“一次性任务上下文”。11.6 给生成结果做版本管理当你反复调 prompt 时建议记录每个版本的 prompt 和输出结果。最简单的做法是按日期保存 prompt 模板# 文件路径prompt_templates/20250110_writer_system.txt 你是一名技术博客写作助手。你的任务基于用户提供的参考资料撰写技术文章初稿。 ...这样当某次输出效果特别好时你能回溯到当时的完整配置。12. 总结与后续学习方向LLM 写作工作流的核心不是“让模型替你创造”而是“把你已有的素材和模型的表达能力连接起来”。文章开头提到的那句话值得再强调一次它改变的是信息整理方式不是文字控制力。从实际操作看你不需要一次性搭建完整的 Agent 系统。先把最小 RAG 跑通至少应该完成一个能稳定调用的 LLM 模块。一个能存储个人笔记的向量知识库。一个能把检索结果拼进 prompt 的写作助手。一套确定的验证标准用于判断输出是否可用。后续值得深入的方向包括更合理的分块策略、针对不同文章类型的提示词模板、多路召回、MCP 工具接入以及把工作流打包成内部团队服务。最后提醒一句在把内部技术资料提交给任何外部模型服务前先确认数据合规边界和最小权限原则。敏感代码、客户信息、内部架构图不要在没有授权的情况下发送给外部 API。安全边界永远是第一位。如果你准备开始实践可以先从自己的旧笔记中挑 10 条有代表性的素材按步骤跑通最小工作流。这套系统不会替你完成全部写作但它能帮你把“找素材、搭框架、写初稿”这个最耗精力的阶段压缩到几分钟之内。
返回列表