我不再给长文档切块了——Gemini 2.5 Pro百万token上下文让我重写了整个问答系统

30秒速览

  • - 别再用RAG折磨自己了,Gemini 1.5 Pro能把整本手册塞进去,跨页逻辑再也不会被切碎了,缓存还能把延迟压到3秒内。
  • - Vertex AI上的实现简单到离谱,核心代码就几行,但权限控制和文档过滤得自己用GCS前缀+安全组焊死,不然合规部门第一个找上门。
  • - 成本比想象中低,每月不到一千刀,而且文档更新只需要上传PDF,业务方自己就能搞定,我终于不用半夜修分块流水线了。

我们团队差点被RAG搞疯的那段日子

去年秋天,我接手了一个看似普通的项目:给一家制造型企业搭一个内部政策问答系统。他们的合规文档、操作手册、安全规范加起来差不多500多页,PDF、Word、Excel混在一起,还带大量流程图截图。老板说,员工每天要花20分钟翻文档,咱们用AI解决一下。我当时想,这不就是标准的RAG场景嘛,分块、向量化、检索、增强生成,一条龙下来肯定没问题。

我用了三周时间搭了第一版。选型上是OpenAI的embedding加本地Qdrant向量库,分块大小512 token,重叠128,检索用混合搜索,生成端接GPT-4。测试的时候,问一句“机台紧急停机后的复位流程是什么?”系统返回了三段内容,拼起来看着像模像样。可业务部门一用就傻了。有位产线主管问“夜班巡检时发现氮气罐压力低于200bar,同时冷却水流量报警,优先处理哪个?”系统把氮气罐和冷却水各自的相关段落找出来了,但拼凑出来的答案完全搞错了优先级顺序,差点让人按错误流程操作。问题的根子在于,跨章节的逻辑链条被我的分块切得七零八落,那些“若同时发生,则优先执行第X章第Y条”的强引用,在向量检索里根本捞不起来,因为语义上这些约束不像“压力低”那么突出。

接下来的两个月我就在调分块参数、尝试不同的重叠策略、引入多跳检索、甚至给某些关键条文手工打标签。每当我以为找到了一组“黄金参数”,总会有新的案例冒出来打脸。有次我问系统“请列出所有涉及‘作业现场至少两名人员在场’要求的条款”,RAG返回了4条,但我手动翻文档找到了7条,漏掉的3条因为表达方式不同(比如“须有监护人员”、“严禁单人操作”),embedding相似度不够高。我还在检索后加了个reranker模块,用交叉编码器过滤一遍,准确率上去了一些,但整个流水线已经膨胀到四个微服务,延迟从500ms涨到1.8秒,开发成本光我一个人的时间就烧了两个月。更让我头疼的是,每次文档更新,哪怕只改一张流程图里的注释,都要重新跑一遍解析、分块、嵌入的流水线,那套用LangChain搭的编排脚本总有各种奇怪的编码异常,维护起来像踩地雷。

真正让我动摇的是有一次业务方问:“如果我们把整本手册当成背景知识,模型能不能自己找出答案?”我试着把500页文档的文本全部抽出来,大概只有80万token,但当时主流的上下文窗口最大也就128K,根本塞不进去。直到今年初我瞥见Gemini 2.5 Pro的公告,说上下文窗口能到100万token,还能直接处理PDF,我当时心里咯噔一下:这玩意是不是能把我从RAG的地狱里拉出来?

这100万token不是免费的午餐:Gemini 2.5 Pro上下文机制里的坑与蜜

说实话,第一次读到“100万token上下文窗口”时,我第一反应是怀疑。过去被各种“超长上下文”宣传轰炸过太多次,什么“128K”“200K”,实际用下来要么注意力明显衰减,要么推理时间长得没法用。但Gemini 2.5 Pro的架构设计上有个根本的不同——它用了混合注意力机制,把长距离依赖的建模效率和计算开销平衡得更实用。我翻了些Google的技术报告,虽然细节没完全公开,但大致是局部注意力和全局稀疏注意力的组合,这样模型在处理超长序列时不会像标准Transformer那样复杂度平方级爆炸,而是近似线性增长。所以它能真正把100万token内的每一段文字都“看到”,而不是像某些模型那样只能关注开头和结尾,中间的内容变成盲区。

我在Vertex AI上开了个实验,直接传了一本490页的PDF(合规手册扫描版),用gemini-2.5-pro-001模型,大概占用了87万token。第一轮测试时,我提了一个跨章节的综合问题:“如果仓库发生化学品泄漏且有人受伤,完整的应急流程包括哪些步骤?请严格按手册章节顺序给出。”模型返回了一整段流畅的答案,顺序完全对,连“先救人再控制泄漏”这种隐含的优先级都抓住了。我反复对比了手册,没有幻觉、没有跳跃。最让我吃惊的是,它能引用第37页一张表格里的数据,而那张表格是扫描图片嵌入在PDF里的——就是说模型对多模态内容也有不错的理解,不用我额外做OCR。

不过很快我就撞上了现实的墙。第一次调用花了将近12秒才返回第一个token,总生成时间18秒。对一个同步请求的问答系统来说,这延迟足够让用户关掉页面。我一开始以为是PDF解析慢,后来发现真正耗时的是预填充阶段:模型需要把87万token的KV-cache一次性算出来。Vertex AI提供了流式响应,所以可以在生成第一个字符前就开始显示,但核心延迟仍然在。后来我摸索出一条折中路线:对于常用的大文档,可以利用Vertex AI的上下文缓存(context caching)功能,把文档内容预先缓存起来,这样后续请求不用每次都重新计算KV-cache,首次调用可能还是慢,但接下来的调用能在3秒内响应。代码很简单,就多传一个cached_content参数:

from vertexai.generative_models import GenerativeModel, Part
from vertexai.preview import caching

model = GenerativeModel("gemini-2.5-pro-001")
pdf_part = Part.from_uri("gs://my-bucket/handbook.pdf", mime_type="application/pdf")

# 创建缓存
cached = caching.CachedContent.create(
    model_name="gemini-2.5-pro-001",
    contents=[pdf_part],
    display_name="handbook_cache"
)

# 后续请求用缓存
response = model.generate_content(
    ["根据手册,电气检修的断电流程是什么?"],
    cached_content=cached
)

缓存机制把重复的长文档预填充时间几乎省掉了,但要注意缓存有有效期,通常一小时左右无活动就会失效,需要根据实际访问模式设计刷新策略。另外缓存按存储token计费,成本远低于反复推理。

还有一点容易踩坑的是,100万token窗口虽然大,但不是让你无脑往里塞。我试过同时塞三本手册加上所有历史对话,结果模型在某些细粒度问题上开始出现注意力分散,遗漏一些中间文档的具体约束。所以实际生产里我还是做了简单的上下文预算管理:单次查询只保留核心文档,把无关的部分摘掉,或者根据用户问题动态截取相关章节。听起来这又有点像检索,但其实粒度粗得多,我可以按整章或者整个子文档来选,不做切块,也就不会破坏内在逻辑。这比RAG那些512 token的小碎片靠谱太多了。

我把整个知识库塞进一个prompt,系统反而简单了十倍

既然一个文档能塞进去,我索性把整个知识库重新设计了一版。过去那套RAG系统有文档解析服务、分块服务、向量数据库、检索器、重排器、提示构建器,六个组件。新系统核心就一个Python文件,不到300行代码。架构变成了:前端请求过来,带上用户身份和问题;后端去Google Cloud Storage捞对应用户组可见的文档列表,把所有授权文档拼成一个大文本或者多个PDF Part,直接一次性喂给Gemini 2.5 Pro,加上系统提示“你是一个严格依据给定文档回答的企业政策问答助手,禁止引用外部知识”。

代码骨架长这样:

import vertexai
from vertexai.generative_models import GenerativeModel, Part
from google.cloud import storage

vertexai.init(project="your-project", location="us-central1")
model = GenerativeModel("gemini-2.5-pro-001")

def answer_query(user_question, user_groups):
    docs = fetch_authorized_documents(user_groups)  # 从GCS获取PDF
    parts = []
    for doc_path in docs:
        parts.append(Part.from_uri(doc_path, mime_type="application/pdf"))
    
    prompt = """
    你是一个内部政策问答助手,只根据提供的文档内容回答。
    如果文档中没有相关信息,请明确说“未在文档中找到”。
    """
    response = model.generate_content([prompt] + parts + [user_question])
    return response.text

对比原来那个需要维护六个微服务的烂摊子,这简直像从蒸汽机跳到了电动车。开发时间从两个月变成一周,大部分时间花在权限模型和文档预处理上。维护成本更是直线下降:文档更新只需要把新版本PDF上传到GCS覆盖旧文件,缓存会自动失效并重建,不用再跑分块流水线,不用处理PDF解析崩溃,不用半夜被向量库的存储告警吵醒。

准确率方面的提升更是肉眼可见。我们用50个历史用户问题做了对比测试,这些问题里大概有三分之一涉及跨章节引用或隐含优先级。在原来的RAG系统(加reranker)上,准确率大概72%;换到Gemini 2.5 Pro全文档模式后,涨到了93%,那些多跳逻辑基本没再出过错。唯一的几次错误出现在文档本身自相矛盾的地方——比如2021版和2023版操作规程对同一机型的参数写得不一致,模型直接指出了矛盾,而不是强行给个答案,这种诚实反而让业务方很认可。

延迟方面,得益于缓存,典型查询的端到端延迟稳定在2.8到3.5秒,比原来RAG管道1.8秒慢了近一倍,但业务部门反馈说“等3秒出一个靠谱答案,比等2秒出一个需要自己再查的答案强多了”。我后来还加了后台预加载,系统启动时就把常用文档的缓存建好,上班高峰期的首次查询也能压在3秒内。

至于成本,这可能是开发者最关心的。我们粗略算了一笔账:旧RAG系统用GPT-4生成,加上embedding和自建向量库的运维费用,每月大约1200美元;新方案全用Vertex AI上的Gemini 2.5 Pro,输入token价格是每百万$1.25,输出是$5,每月查询量大概8000次,每次平均输入90万token(含缓存,缓存按每百万$0.3125计),输出300 token,总账单大概900美元每月。省下的不光是钱,更是我那两个月调参的心血。

生产环境中,权限管控比模型能力更让人睡不着

模型能一次性吞下整个知识库固然爽,但一个要命的问题随之而来:公司可不是所有人都能看所有文档。薪酬制度、反舞弊条款、未公开的产品路线图,这些文档的分级访问控制如果没做好,后果可比一个错误答案严重得多。在RAG那套架构里,我好歹可以在检索阶段根据用户身份过滤掉无权查看的文档块,到了长上下文模式,你不能简单地把所有文档塞进去然后让模型“自觉”筛选,模型没那自律。

我的解决方案是在应用层做实打实的权限门禁,分两步。第一步是入口控制,用Google Cloud IAP或Istio的授权策略保证只有通过身份验证的用户才能到达问答接口。第二步是文档级过滤,在answer_query函数里,根据用户所属的安全组动态构建可见文档列表。我们把用户组映射到GCS里不同的文档目录,比如/hr/payroll.pdf只对HR组开放,/ops/全部文档对所有人开放。代码里直接根据用户组拼出GCS前缀,取回对象列表后再组装Part数组。

光这样还不够,我还加了两道安全护栏。一道是Vertex AI自带的安全过滤器,可以设置有害内容类别(骚扰、危险内容等)的阈值,我设得比较保守:

from vertexai.generative_models import SafetySetting

safety_config = [
    SafetySetting(category=SafetySetting.HarmCategory.HARM_CATEGORY_HATE_SPEECH, threshold=SafetySetting.HarmBlockThreshold.BLOCK_ONLY_HIGH),
    SafetySetting(category=SafetySetting.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, threshold=SafetySetting.HarmBlockThreshold.BLOCK_ONLY_HIGH),
]
response = model.generate_content(..., safety_settings=safety_config)

这样如果有人试图诱导模型输出危险操作步骤,它会被直接拦截。另一道护栏是我在提示里硬植入的规则:“禁止回答任何与薪酬、股权、未公开财务数据相关的问题,如果用户问题涉及上述内容,请回复‘此问题超出你可查询的范围’。”模型非常听话,即使某些PDF里混入了不该有的内容片段,它也会遵守指令拒绝输出。

我还搞了一个简单的审计日志,所有问答对都存到BigQuery,带上用户ID、时间戳、所访问的文档列表、安全过滤器状态。合规部门看了之后很满意,说“这下有人问敏感问题至少能追溯”。其实我自己也受益,有次半夜收到告警说某员工连续问了11个关于裁员补偿的问题,我一看日志,系统全都安全拒绝了,对方最后自己关掉页面去翻纸质手册了。

长上下文窗口彻底改变了我对文档问答系统的设计思路,但也要求更强的控制面。你不能光信模型自己的判断,必须用工程手段把权限和安全焊死。现在这套系统跑了三个多月,零安全事故,零权限泄露,准确率一直稳定。最让我欣慰的是,文档更新后业务团队再也没找过我,因为上传新PDF到GCS就行,他们自己都能操作。我终于可以不用半夜改分块参数,安心睡觉了。

本文由 AI 辅助生成,经人工审核后发布。内容由 林默 基于实战经验指导完成。

觉得有用?

林默

全栈开发者,写了8年代码,从jQuery时代一路写到AI Copilot。目前专注AI编程工具链的深度使用和评测,相信好的工具能让开发者事半功倍。喜欢用实际项目验证技术方案,不写没踩过坑的教程。