# RAG + LangGraph 实战:4 个工程踩坑,让 AI 从"能用"到"能上线"

从一个"不可能的需求"说起

去年底,团队接到一个需求:用 AI 自动生成建筑设计领域的专业文档。

不是那种几百字的摘要,而是动辄 50 页、必须引用行业标准、每个章节都有合规要求的正式文档。

第一反应是:这事儿靠 ChatGPT 套个壳能搞定吗?

试了一下,答案是不能。单次对话生成的内容质量不稳定,长文档的上下文窗口直接爆掉,更别提还要精准检索特定行业标准了。

于是我们决定自己造------OpenSpec,一个基于 LangGraph 多智能体架构的 AI 长文档生成平台。项目已开源,这篇文章聊聊我们在工程落地过程中踩过的真实的坑。

整体架构:Nginx 分流,双引擎并行

很多 AI 应用的架构是"前端 → 后端 → AI 服务"的串联模式。我们一开始也是这么设计的,但很快发现了问题:SSE 流式输出经过 Java 后端转发后,延迟明显增大,而且 Spring Boot 处理长连接并不是它的强项。

最终我们采用了 Nginx 分流的双引擎架构:

bash 复制代码
                        ┌─ /api/*  ──→ Spring Boot(业务处理)
用户 → Vue3 前端 → Nginx ─┤
                        └─ /agent/* ──→ FastAPI AI 引擎(文档生成)
                                         ├── LangGraph(工作流编排)
                                         ├── RAGFlow(知识库检索)
                                         ├── DashScope/Qwen(LLM)
                                         └── Langfuse(可观测性)

两条链路各司其职:

  • Spring Boot 负责文档管理、用户管理、项目信息等业务 CRUD,数据存 PostgreSQL
  • FastAPI Agent 负责所有 AI 相关的工作------工作流编排、RAG 检索、LLM 调用、流式输出
  • Nginx 做反向代理和路由分发,前端根据接口路径自动走不同的后端

这样做的好处是:AI 生成的 SSE 流直接从 FastAPI 推到前端,中间没有额外的转发层,延迟最低;同时业务逻辑和 AI 逻辑完全解耦,两边可以独立迭代部署。

四个 Docker 容器:Web(Nginx + Vue)、Backend(Spring Boot)、Agent(FastAPI)、PostgreSQL。docker-compose up 一键启动。

为什么选 LangGraph

2026 年多智能体编排框架不少,LangGraph、CrewAI、AutoGen 各有定位。我们选 LangGraph 的核心原因:

确定性边和护栏。专业文档生成不能"随机发挥",每一步都需要可控、可追踪、可回溯。LangGraph 的有状态图(Stateful Graph)天然支持这一点------节点之间的流转是确定性的条件分支,不是 Agent 自由发挥。

持久化检查点。50 页文档不是一次生成的,是按章节逐个生成。每个章节生成完,状态会写入 PostgreSQL 作为 checkpoint。如果中途出错,可以从断点恢复,不用从头来。

原生流式支持 。LangGraph 的 astream_events 提供 token 级别的流式输出,配合 SSE 推送到前端,用户能实时看到生成过程。

多智能体工作流:写、查、审分离

这是整个系统最核心的设计。我们用 LangGraph 编排了一个多节点状态图:

javascript 复制代码
Router(意图路由)
  ├── 文档生成链路:
  │   Researcher(知识库检索 + 上下文收集)
  │       ↓
  │   Generator(章节内容生成)
  │       ↓
  │   Auditor(质量审核)→ 不合格 → 回到 Researcher 重新检索
  │                     → 合格 → 输出最终内容
  │
  └── General Agent(通用对话)

为什么不用单 Agent?

单 Agent 生成专业文档有一个致命问题:它会"自说自话"。生成的内容看起来通顺,但可能引用了不存在的标准条款,或者遗漏了关键的合规要求。

拆成三个角色后,职责清晰:

  • Researcher:只负责从知识库检索相关标准和案例,不做内容生成
  • Generator:基于 Researcher 提供的上下文生成章节内容
  • Auditor:审核生成内容是否符合要求,不合格则打回重来

关键的循环控制参数:

python 复制代码
MAX_RESEARCH_LOOPS = 3       # Researcher 最多循环 3 次
MAX_AUDIT_LOOPS = 2          # Auditor 最多审核 2 次
MAX_AUDITOR_TOOL_CALLS = 5   # Auditor 单次工具调用上限

这些是防止无限循环烧 Token 的护栏。实测下来,大部分章节 1-2 轮就能通过审核。

上下文窗口管理:长文档场景下如何避免 Token 超限

生成 50 页文档,如果把所有已生成章节都塞进上下文,很容易超出模型的最大输入长度。长文档场景下,上下文窗口管理是绕不开的工程问题。

我们的方案是动态 Token 预算------每次 LLM 调用前,先算清楚"模型还剩多少空间给检索上下文":

python 复制代码
def calculate_available_tokens(question, template, project_info):
    counter = get_token_counter()

    # 先算固定开销:问题 + 模板 + 项目信息 + Prompt + 响应预留
    fixed_tokens = (
        counter.count_tokens(question) +
        counter.count_tokens(template) +
        counter.count_tokens(project_info) +
        1000 +  # Prompt 模板开销
        2000    # 响应预留
    )

    # 剩余空间才是可用的检索上下文窗口
    available = counter.get_token_limit() - fixed_tokens
    return max(available, 500)

几个关键策略:

  • ToolMessage 裁剪:上下文最多保留 30 条 ToolMessage,超出的按时间顺序丢弃
  • 中文 Token 估算优化:Qwen 模型的 tokenizer 对中文的切分和 tiktoken 有差异,我们做了专门的系数校准(中文字符 ×1.2,英文单词 ×1.3)
  • Langfuse 成本追踪:每次 LLM 调用都记录 input/output token 数和对应成本,方便按项目、按章节分析费用

实测效果:上下文长度压缩约 70%,长文档生成全程不会触发 Token 超限。

踩坑 2:RAG 检索质量------召回率不稳定怎么办

直接用 RAG 检索行业标准,召回率忽高忽低。有时候明明知识库里有相关内容,就是检索不出来。

我们的方案分三层:

第一层:知识库分类。案例库和标准库分开存储、分开检索,避免不同类型的文档互相干扰。

python 复制代码
DEFAULT_CASE_KB_IDS = [...]    # 案例库------过往项目的文档范例
DEFAULT_STAND_KB_IDS = [...]   # 标准库------行业规范、国标等

第二层:相似度阈值 + 最小内容阈值

python 复制代码
# 相似度低于 0.55 的结果直接过滤
chunks = rag_object.retrieve(
    question=query,
    dataset_ids=kb_ids,
    similarity_threshold=0.55
)

# 检索内容总量低于 1000 字符时,触发二次检索(换个角度重新查)
MIN_CONTENT_THRESHOLD = 1000

第三层:个人模板知识库。用户可以上传自己的文档模板,系统会优先匹配用户模板的结构和风格,生成的文档更贴合用户的实际需求。

踩坑 3:Prompt 管理------硬编码是条死路

早期 Prompt 硬编码在 Python 代码里,每次调整都要改代码、重新构建 Docker 镜像、重新部署。

做过 AI 应用的都知道,Prompt 调优是个高频操作,一天改十几次很正常。这个流程直接把迭代效率拖死了。

现在全部迁移到 Langfuse,用它做 Prompt 的版本管理和运行时加载:

python 复制代码
class PromptManager:
    """Langfuse Prompt 管理器(单例模式)"""

    def get_prompt(self, prompt_name, label=None):
        target_label = label or self.default_label  # 默认 "latest"
        return self.langfuse.get_prompt(prompt_name, label=target_label)

# 运行时动态加载,支持变量编译
prompt = prompt_manager.get_prompt("construction_agent_system")
full_prompt = prompt.compile(
    context=context,
    question=question,
    template=template
)

改完 Prompt 在 Langfuse 后台保存,线上立即生效,不用重新部署。每个版本自动保存历史,出了问题随时回滚。

更重要的是 Langfuse 提供了完整的调用链路追踪------每次 LLM 调用关联到具体的 Prompt 版本、输入输出、Token 消耗,排查生成质量问题时能精确定位到是哪个 Prompt 版本导致的。

流式输出:让用户看到生成过程

长文档按章节生成,单个章节的生成时间从几秒到几十秒不等。实时反馈生成进度,对用户体验至关重要。

我们用 SSE(Server-Sent Events)做流式推送。前端直连 FastAPI Agent 服务(通过 Nginx 的 /agent/ 路由),LangGraph 的 astream_events 把每个 token 实时推到前端,逐字渲染。

除了 token 流,我们还推送了工作流进度事件------用户能看到当前处于哪个阶段("正在检索资料"、"正在生成内容"、"正在审核"),而不是只看到文字在跳。这个细节对用户体验的提升比想象中大。

技术栈一览

层级 技术
前端 Vue 3 + TypeScript + Vite + Element Plus
业务后端 Spring Boot 3 + Java 17 + MyBatis Plus
AI 引擎 FastAPI + LangGraph + LangChain + DashScope (Qwen)
知识库 RAGFlow
可观测性 Langfuse(Prompt 管理 + 调用追踪 + 成本分析)
存储 PostgreSQL(业务数据 + LangGraph checkpoint)
部署 Docker Compose,4 个容器,Nginx 做路由分发

写在最后

做 AI 长文档生成这大半年,最大的感受是:核心难点不在模型能力,而在工程架构

选什么模型、用什么框架,这些决策一两天就能定下来。但 Token 怎么省、检索质量怎么稳定、Prompt 怎么高效迭代、流式输出怎么做到丝滑------这些工程问题,每一个都需要反复试错和打磨。

没有银弹,都是一个个方案堆出来的。

项目已开源:github.com/zhuzhaoyun/... ,欢迎 Star 和 PR。如果你也在做类似的 AI 文档生成系统,欢迎交流。

相关推荐
qyresearch_2 小时前
移动感应健身:全球市场扩张下的中国机遇与破局之道
大数据·人工智能·区块链
啊阿狸不会拉杆2 小时前
《机器学习导论》第 16 章-贝叶斯估计
人工智能·python·算法·机器学习·ai·参数估计·贝叶斯估计
范桂飓2 小时前
Claude Code 高级特性和应用实践
人工智能
格林威2 小时前
Baumer相机橡胶O型圈直径测量:用于密封件入库检验的 6 个关键技术,附 OpenCV+Halcon 实战代码!
人工智能·opencv·计算机视觉·视觉检测·工业相机·智能相机·堡盟相机
格林威2 小时前
Baumer相机印刷标签二维码可读性评估:优化打码工艺的 7 个实用技巧,附 OpenCV+Halcon 实战代码!
人工智能·opencv·计算机视觉·视觉检测·工业相机·智能相机·堡盟相机
Fairy要carry2 小时前
面试-SFT
人工智能
Alice_whj2 小时前
AI云原生笔记
人工智能·笔记·云原生
Lyan-X2 小时前
鲁鹏教授《计算机视觉与深度学习》课程笔记与思考 ——13. 生成模型 VAE:从无监督学习到显式密度估计的建模与实现
人工智能·笔记·深度学习·计算机视觉
AI_Auto2 小时前
智能制造-MES与AI结合的核心价值与逻辑
大数据·人工智能·制造