1. 从“内容能被搜到”说起:GEO 工程化到底在解决什么问题
做内容的人这两年应该都有一个明显感受:以前写完一篇文章,发到平台上,多少能靠平台推荐拿点自然流量;现在推荐越来越卷,流量越来越贵,反而“搜索”这条老路又被重新重视起来了。但今天的搜索已经不是当年那个“堆关键词就能排上去”的搜索了——用户可能在搜索引擎里搜,也可能在 AI 助手里直接问,还可能在企业内部的知识库里检索。内容能不能被这些入口“看见”,已经变成一套需要工程化设计的活儿。
这就是 GEO(Generative Engine Optimization,生成式引擎优化)被反复提起的背景。它和传统 SEO 最大的区别在于:SEO 面对的是“爬虫 + 排序算法”,你优化的是页面权重和外链;GEO 面对的是“检索 + 生成”,你优化的是内容能不能被检索链路召回、能不能被大模型正确理解和引用。换句话说,SEO 是让页面排上去,GEO 是让内容被“读懂并说出来”。
而 RAG(Retrieval-Augmented Generation,检索增强生成)就是这条链路里最核心的工程载体。它把内容切块、向量化、存进知识库,用户提问时先检索相关片段,再交给大模型组织答案。问题在于,很多人把 RAG 当成一个“搭起来就能用”的玩具,结果上线之后发现:该被召回的内容召不回,召回了答非所问,答对了又引用错来源。这些问题的根子,往往不在模型,而在内容可见性架构没设计好。
这篇东西我想聊的就是这件事:在 RAG 链路下,怎么把 GEO 从“玄学优化”做成“工程化实践”。我会围绕内容可见性架构设计、Schema 结构化、平台实现对比这几条主线展开,把踩过的坑、验证过的参数、能直接抄的配置都摊开讲。适合正在做企业知识库、内容中台、AI 搜索产品的同学,也适合内容运营想搞明白“为什么我的内容 AI 看不见”的人。不管你是刚接触 RAG 的新手,还是已经上线过一版想优化的老手,应该都能从里面找到能直接用的东西。
2. 内容可见性架构的整体设计思路
2.1 为什么“可见性”要当成架构问题而不是运营问题
很多人第一次做 GEO,思路是运营式的:多写关键词、多发平台、多铺外链。这套打法在传统搜索时代有效,但在 RAG 链路下基本失效。原因很简单:RAG 的召回不是靠关键词匹配,而是靠语义向量相似度。你堆了一堆关键词,向量空间里可能反而稀释了核心语义,导致真正相关的查询召不回你的内容。
我举个实际例子。之前帮一个做企业服务的朋友看他们的知识库,他们把所有产品文档都加了一段“XX系统、XX平台、XX解决方案”的关键词堆砌。结果用户问“你们系统支持哪些部署方式”,召回的全是那些关键词堆砌段落,真正的部署说明反而排在后面。后来把关键词堆砌删掉,改成结构化的“部署方式”小节,召回准确率立刻上来了。
所以内容可见性必须当成架构问题来解。它涉及三个层面:内容怎么切、元数据怎么标、检索怎么配。这三层任何一层没设计好,后面模型再强也救不回来。架构设计的核心目标就一个:让“对的内容”在“对的查询”下,以“对的优先级”被召回。
2.2 RAG 链路下内容可见性的三层结构
我把这套架构拆成三层来看,这样设计的时候不容易漏。
第一层是内容层,也就是原始素材本身。这一层要解决的是“内容颗粒度”问题。一篇 5000 字的文章直接扔进向量库,检索出来的是一大坨,模型拿到之后要么截断要么抓不住重点。所以内容层要做的是合理切块(chunking),并且保证每个块语义自洽。
第二层是结构层,也就是 Schema 和元数据。这一层是很多人忽略的,但恰恰是 GEO 工程化的关键。Schema 决定了你的内容在检索时能带多少“筛选维度”,比如来源、时间、类型、权限、业务域。没有 Schema,检索就只能靠纯语义,精度上不去。
第三层是检索层,包括向量检索、关键词检索、混合检索、重排序。这一层要解决的是“怎么把候选集排好序”。纯向量检索在长尾查询上表现不稳定,混合检索加 rerank 是目前比较稳的方案。
这三层是递进关系:内容层决定召回上限,结构层决定筛选精度,检索层决定最终排序。任何一层偷懒,整体效果都会打折。
2.3 方案选型:为什么是 RAG + Schema 而不是纯微调或纯关键词
这里要解释一个关键取舍。做内容可见性,常见有三条路:纯关键词检索、纯向量 RAG、模型微调。
纯关键词检索的问题是语义泛化差。用户问“怎么退款”,你的文档写的是“申请售后返还流程”,关键词对不上就召不回。纯向量 RAG 的问题是精确匹配弱,用户搜一个具体订单号或者产品型号,向量可能召回一堆语义相近但型号不对的内容。模型微调的问题是成本高、更新慢,内容一变就得重新训,不适合内容频繁迭代的场景。
所以目前比较务实的方案是RAG + Schema 结构化。RAG 负责语义召回,Schema 负责精确筛选和元数据过滤,两者互补。Schema 在这里的作用有点像数据库的索引:它不直接参与语义计算,但它能在检索前把候选集缩小到正确的范围,让向量检索在更小的空间里工作,精度和速度都更好。
这个选型的另一个好处是可解释。纯向量检索召回错了,你很难说清为什么;加上 Schema 之后,你可以看到是哪个字段过滤掉了、哪个字段没匹配上,排查起来有抓手。
3. 核心细节解析:Schema 设计与内容切块实操
3.1 Schema 到底该怎么设计:从业务查询反推字段
Schema 设计最容易犯的错是“照着数据库表抄”。很多人直接把 MySQL 的 database schema table 搬过来,字段一大堆,结果检索时根本用不上。正确的做法是从业务查询反推:先列出用户最常问的 20 个问题,看这些问题需要哪些筛选维度,再定 Schema 字段。
我一般会分三类字段来设计:
- 身份类字段:来源、作者、业务域、内容类型。这类字段用于权限过滤和范围限定。
- 时间类字段:创建时间、更新时间、生效时间。RAG 场景下时间很重要,用户问“最新政策”时,没有时间字段就只能靠语义猜。
- 语义辅助类字段:标签、实体名、产品型号。这类字段用于精确匹配补充。
举个实际 Schema 的例子,用 JSON Schema 描述大概是这样:
{ "doc_id": "string", "source": "string", "biz_domain": "string", "content_type": "enum[faq, doc, policy, manual]", "created_at": "datetime", "updated_at": "datetime", "tags": "array[string]", "entities": "array[string]", "chunk_text": "string", "chunk_index": "integer" }这里要特别说一句,Schema 不是越全越好。字段太多会导致元数据存储膨胀,检索时过滤条件组合爆炸。我的经验是核心字段控制在 8 到 12 个,其余的都塞进 tags 或 entities 数组里。
3.2 内容切块:为什么固定长度切块是个坑
切块(chunking)是 RAG 里最容易被低估的环节。很多 rag 教程上来就是chunk_size=500, overlap=50,然后就不管了。实测下来,固定长度切块在结构化内容上表现很差,因为它会把一个完整的语义单元从中间切断。
我踩过的一个典型坑:一份产品配置文档,每个配置项是一个小节。固定 500 字切块,正好把一个配置项的说明切成两半,前半段在 chunk 3,后半段在 chunk 4。用户问这个配置项怎么设,检索召回 chunk 3,模型只看到一半说明,答出来的东西缺了关键参数。
后来我改成语义切块:优先按标题层级切,H2 切大块,H3 切小块,段落作为最小单元。如果单个段落超过 800 字,再按句子边界二次切分。这样每个 chunk 都是语义自洽的。实测召回准确率比固定切块高了大概 20 个百分点。
具体参数上,我的经验值是这样的:
| 内容类型 | 切块策略 | 目标 chunk 大小 | overlap |
|---|---|---|---|
| FAQ | 一问一答为一块 | 100-300 字 | 0 |
| 产品文档 | 按 H3 小节切 | 300-600 字 | 50 |
| 政策法规 | 按条款切 | 200-500 字 | 30 |
| 长篇文章 | 按段落 + 句子边界 | 400-800 字 | 80 |
overlap 的作用是防止边界信息丢失,但不要设太大,否则检索结果里会有大量重复内容,浪费上下文窗口。
3.3 元数据注入:让每个 chunk 都“自带说明书”
切完块之后,每个 chunk 不能光有文本,还要带上元数据。这一步很多人偷懒,结果检索时没法过滤。我的做法是给每个 chunk 注入一段“上下文前缀”,把关键元信息拼在文本前面。
比如一个产品文档的 chunk,注入后大概是这样:
[产品: XX系统] [模块: 部署配置] [版本: v3.2] [更新: 2024-06] 支持三种部署方式:单机部署、集群部署、容器化部署。单机部署适合测试环境...这段前缀在向量化时会一起参与计算,相当于给 chunk 增加了语义锚点。用户问“XX系统 v3.2 怎么部署”,前缀里的信息能显著提升召回率。实测这个小技巧能让召回准确率再提升 10% 到 15%。
注意:前缀不要写太长,控制在 50 字以内。太长会稀释正文语义,反而降低效果。
3.4 结构化知识库和 RAG 知识库的边界
热词里有个问题问得很好:kg 知识库、rag 知识库和结构知识库怎么区分,各自用在哪。我的理解是这样:
- 结构化知识库(比如 MySQL、图数据库):适合精确查询,比如“订单号 12345 的状态”。它强在准确,弱在语义泛化。
- RAG 知识库(向量库):适合语义查询,比如“怎么处理退款”。它强在泛化,弱在精确。
- KG 知识库(知识图谱):适合关系推理,比如“A 产品的配件有哪些兼容 B 产品”。它强在关系,弱在覆盖广度。
实际工程里,这三者往往是组合使用的。我的做法是用 RAG 做第一层召回,用结构化字段做过滤,用 KG 做关系补全。比如用户问“XX系统支持哪些数据库”,RAG 召回相关文档,Schema 过滤出“数据库兼容性”类型,KG 补全具体版本对应关系。单靠任何一个都做不好。
4. 实操过程:从零搭一条可用的 GEO 检索链路
4.1 环境准备与工具选型
这一节讲具体怎么落地。工具选型上,我不追求最新最炫,追求的是稳定和可维护。下面是我目前比较常用的一套组合:
- 向量库:Milvus 或 Qdrant。Milvus 生态成熟,Qdrant 部署轻量。小规模用 Qdrant 就够。
- Embedding 模型:中文场景用 bge-large-zh 或 m3e-base。bge 系列在中文检索上表现稳定。
- Rerank 模型:bge-reranker-base。召回之后加一层重排,精度提升明显。
- 编排框架:LangChain 或 LlamaIndex。LangChain 灵活,LlamaIndex 开箱即用。我一般用 LangChain,因为要自定义 Schema 过滤逻辑。
- 本地部署:如果数据敏感,可以用 Ollama 跑本地模型,配合本地向量库,整套离线。
这里要提醒一句,Embedding 模型和 Rerank 模型最好用同一系列的,比如都用 bge,因为它们的语义空间更一致,配合效果更好。混用不同系列的模型,有时候反而会互相拖累。
4.2 完整链路搭建步骤
下面是我实际搭一条链路的步骤,按顺序来。
第一步:内容预处理。把原始内容(HTML、PDF、Markdown)统一转成纯文本,保留标题层级。这一步用unstructured或markdown库都行。关键是保留结构信息,别一上来就拍平成纯文本。
第二步:语义切块。按前面说的策略切块,每个 chunk 记录chunk_index和所属标题路径。标题路径很重要,它能在检索时提供上下文。
第三步:元数据注入。给每个 chunk 拼上上下文前缀,同时把 Schema 字段填好。这一步建议写个脚本批量处理,别手工搞。
第四步:向量化入库。用 Embedding 模型把 chunk 转成向量,连同元数据一起写入向量库。写入时注意把 Schema 字段建成 payload 索引,方便后续过滤。
第五步:检索链路配置。检索分两步:先做混合检索(向量 + 关键词),再做 rerank。混合检索的权重我一般设向量 0.7、关键词 0.3,具体看内容类型。FAQ 类可以调高关键词权重,长文档类调高向量权重。
第六步:Schema 过滤。在检索前根据用户查询解析出过滤条件,比如业务域、时间范围、内容类型。这一步能大幅缩小候选集。
第七步:结果组装。把 rerank 后的 top-k 结果按 chunk_index 排序,拼成上下文交给大模型。注意控制总长度,别超过模型上下文窗口。
4.3 关键参数计算与选择过程
参数这块我展开说几个关键的。
chunk_size 怎么定?我的算法是:先看内容平均段落长度,chunk_size 设为平均段落长度的 1.5 到 2 倍。比如平均段落 300 字,chunk_size 设 500 到 600。这样既能保证语义完整,又不会太大。
top_k 怎么定?召回阶段 top_k 设大一点,比如 20 到 30,给 rerank 留足候选。rerank 之后取 top 3 到 5 交给模型。实测 top_k 太小会漏召回,太大 rerank 压力大且容易引入噪声。
相似度阈值怎么定?这个没有固定值,要看你的 Embedding 模型。我的做法是拿一批标注好的查询-文档对,跑一遍看相似度分布,取能覆盖 90% 正样本的阈值。一般余弦相似度在 0.6 到 0.75 之间。
rerank 阈值怎么定?rerank 分数一般比向量相似度更可靠。我通常取 rerank 分数 top 3,如果第 3 名分数低于 0.3,就只取前 2 名甚至前 1 名。宁可少给,别给错。
4.4 平台实现对比:自建 vs 云服务 vs 开源框架
这块我做个对比表,方便选型。
| 维度 | 自建(LangChain + 向量库) | 云服务(托管 RAG) | 开源框架(LlamaIndex 等) |
|---|---|---|---|
| 可控性 | 高,全链路可改 | 低,受平台限制 | 中,框架内可改 |
| 成本 | 中,主要是运维 | 高,按量计费 | 低,主要是开发 |
| 上手速度 | 慢,要搭环境 | 快,开箱即用 | 中,有模板 |
| 数据安全 | 高,数据自持 | 低,数据上云 | 高,可本地部署 |
| 适合场景 | 数据敏感、需求定制 | 快速验证、小团队 | 中等规模、要平衡 |
我的建议是:验证阶段用云服务快速跑通,确认有价值之后迁到自建或开源框架。别一上来就自建,容易在环境上耗掉太多时间。
5. 常见问题与排查技巧实录
5.1 召回不准的排查思路
召回不准是最常见的问题,排查要按链路顺序来。
先看切块。把召回的 chunk 打出来看,是不是语义不完整。如果是,调整切块策略。再看元数据。检查 Schema 字段有没有填对,过滤条件是不是把正确内容过滤掉了。然后看Embedding。拿几个典型查询,手动算一下和正确文档的相似度,如果相似度很低,可能是模型不适合你的领域,考虑换模型或加领域微调。最后看rerank。如果召回里有正确内容但排序靠后,是 rerank 的问题,检查 rerank 模型和阈值。
我整理了一个速查表:
| 现象 | 可能原因 | 排查方法 | 解决 |
|---|---|---|---|
| 完全召不回 | 切块切断语义 | 打印 chunk 看完整性 | 改语义切块 |
| 召回了但答非所问 | 元数据缺失 | 检查 Schema 字段 | 补元数据 |
| 正确内容排后面 | rerank 弱 | 看 rerank 分数 | 换 rerank 模型 |
| 召回大量重复 | overlap 太大 | 看 chunk 重叠率 | 调小 overlap |
| 长尾查询差 | 纯向量泛化不足 | 加关键词检索 | 改混合检索 |
5.2 大模型请求失败的常见原因
热词里有个报错很典型:llm request failed: provider rejected the request schema or tool payload。这个报错一般是请求体不符合模型 API 的 Schema 要求。常见原因有几个:一是字段类型不对,比如该传数组传了字符串;二是必填字段缺失;三是 payload 太大超过限制。
排查方法很简单:把请求体打出来,对照 API 文档逐字段检查。我遇到过最常见的是messages字段格式不对,或者tools定义里parameters不是合法的 JSON Schema。用 zod schema 做校验能提前发现这类问题,建议在请求前加一层校验。
5.3 RAG 知识库能不能存图片
这个问题问的人很多。答案是能,但方式有讲究。纯向量库一般存文本向量,图片要先用多模态模型转成文本描述或图文向量。我的做法是:图片单独存对象存储,向量库里存图片的文本描述和 URL。检索时召回描述,需要展示图片时再按 URL 取。这样既省向量库空间,又能支持图文混合检索。
如果要做图文联合检索,可以用 CLIP 这类多模态模型,把图片和文本映射到同一向量空间。但这条路成本高,一般场景用文本描述就够了。
5.4 实操避坑心得
最后分享几个我踩过的坑。
第一个坑是过度依赖向量检索。刚开始做的时候觉得向量万能,结果精确查询一塌糊涂。后来加了关键词检索和 Schema 过滤才稳。记住,向量检索是泛化工具,不是精确工具。
第二个坑是忽略内容更新。内容更新了但向量库没更新,检索出来的还是旧内容。我的做法是给每个 chunk 加updated_at,内容变更时触发重新向量化。增量更新比全量重建省太多资源。
第三个坑是rerank 模型和 Embedding 模型不匹配。有次混用了不同系列的模型,召回精度反而下降。后来统一用 bge 系列,效果立刻回来。模型选型上,一致性比单点性能更重要。
第四个坑是上下文拼太长。为了给模型更多信息,把 top 10 全拼进去,结果模型被噪声干扰,答非所问。后来改成 top 3 加 rerank 阈值过滤,答案质量反而更好。给模型的信息要精不要多。
这套东西我前后调了大半年,从最开始召回率不到 50%,到现在稳定在 85% 以上。核心体会就一句:GEO 工程化不是调模型,是调内容和结构。模型是现成的,内容和 Schema 才是你的护城河。后续如果要做多模态或者跨语言检索,这套架构也能扩展,无非是在内容层加多模态处理,在 Schema 层加语言字段。先把文本这条链路跑通,其他的都是增量。