news 2026/5/19 10:52:36

基于 LangChain 的毕业设计实战:从零构建可扩展的智能问答系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于 LangChain 的毕业设计实战:从零构建可扩展的智能问答系统


背景痛点:Demo 级项目的“三宗罪”

去年指导毕设答辩,最常被问到的一句话是:“如果 PDF 换成 10 万篇,你的系统还能跑吗?”
大多数同学的答案都是沉默。归结下来,问题集中在三点:

  1. 提示词写死在代码里,换一道题就要重新部署;
  2. 对话历史放在内存列表,重启即清零;
  3. 异常全靠try/except包一层,网络抖动直接 500。

这样的“玩具”在答辩现场一演示就翻车,更别提后续扩展。
我的毕设目标很明确:做一套能上线、能扩容、能溯源的智能问答系统,而 LangChain 提供了最接近“工业级”的脚手架。

技术选型:为什么不是原生 OpenAI SDK 或 LlamaIndex

维度原生 SDKLlamaIndexLangChain
链式抽象自己拼消息检索链封装更高阶的 LCEL
多模型切换手动改 base_url支持,但文档散一行model="gpt-3.5-turbo"
记忆组件自己维护列表有,但偏检索ConversationBufferWindowMemory即插即用
社区生态官方示例少偏学术工业案例多,Issue 回复快

一句话总结:LlamaIndex 像“研究版”,LangChain 像“工程版”
毕设时间有限,选 LangChain 能把“写论文”的时间省出来“写代码”。

核心实现:模块化 RAG 流程

系统架构图先奉上,方便对照代码阅读:

下面所有代码均放在src/目录,遵循 Clean Code 原则:

  • 一个类只做一件事
  • 函数长度 ≤ 30 行
  • 依赖注入,方便单测

1. 文档加载器:统一接口,支持 PDF / Markdown / Web

# src/loader.py from pathlib import Path from langchain.document_loaders import PyPDFLoader, UnstructuredMarkdownLoader class UniversalLoader: """根据后缀自动选 loader,返回 List[Document]""" def __init__(self, file_path: str): self.path = Path(file_path) def load(self): suffix = self.path.suffix.lower() if suffix == ".pdf": return PyPDFLoader(str(self.path)).load() if suffix in {".md", ".markdown"}: return UnstructuredMarkdownLoader(str(self.path)).load() raise ValueError(f"unsupported suffix: {suffix}")

2. 文本切分:按“章节”语义保留标题

# src/splitter.py from langchain.text_splitter import RecursiveCharacterTextSplitter class SemanticSplitter: def __init__(self, chunk_size=800, chunk_overlap=100): self.splitter = RecursiveCharacterTextSplitter( separators=["\n\n", "\n", "。", ". "], chunk_size=chunk_size, chunk_overlap=chunk_overlap, length_function=len, ) def split(self, docs): return self.splitter.split_documents(docs)

3. 向量存储:Qdrant + 本地磁盘双保险

# src/store.py from qdr import QdrantClient from langchain.vectorstores.qdrit import QdrantVectorStore from langchain.embeddings import OpenAIEmbeddings class VectorStoreFactory: @staticmethod def from_docs(docs, collection: str): client = QdrantClient(path="./qdrant_data") # 本地持久化 store = QdrantVectorStore( client=client, collection_name=collection, embedding=OpenAIEmbeddings(), ) store.add_documents(docs) return store

4. RAG 链:带溯源、带记忆、可并发

# src/chain.py from langchain.chains import ConversationalRetrievalChain from langchain.memory import ConversationBufferWindowMemory from langchain.chat_models import ChatOpenAI def build_chain(vectorstore): memory = ConversationBufferWindowMemory( memory_key="chat_history", return_messages=True, k=6, # 只保留最近 6 轮,防 token 爆炸 ) llm = ChatOpenAI(model="gpt-3.5-turbo-16k", temperature=0) chain = ConversationalRetrievalChain.from_llm( llm=llm, retriever=vectorstore.as_retriever(search_kwargs={"k": 5}), memory=memory, return_source_documents=True, # 溯源关键 verbose=True, ) return chain

5. 并发封装:FastAPI + 异步队列

# src/api.py from fastapi import FastAPI from chain import build_chain import asyncio app = FastAPI() chain = build_chain(...) # 省略初始化 @app.post("/ask") async def ask(question: str): # 使用 run_in_executor 把同步链跑在线程池,防止阻塞主事件循环 loop = asyncio.get_event_loop() ans = await loop.run_in_executor(None, chain, {"question": question}) return {"answer": ans["answer"], "sources": [s.metadata for s in ans["source_documents"]]}

至此,“加载-切分-存储-检索-生成”全链路打通,代码总量不到 300 行,却满足:

  • 换模型只需改配置
  • 换数据只需扔文件到./data
  • 重启服务历史不丢

性能与安全:让答辩老师挑不出刺

  1. 冷启动延迟

    • chain对象在应用启动时初始化,做成单例;
    • 向量库预加载到内存,Qdrant 的memmap模式可省 40% 时间。
  2. Token 消耗控制

    • 记忆窗口k=6+chunk_size=800,实测 90% 问答 < 4k token;
    • 采用gpt-3.5-turbo-16k而非gpt-4,成本降 90%,效果可接受。
  3. 用户输入过滤

    • 先过一遍re黑名单,屏蔽脚本注入;
    • 再用 OpenAI Moderation API 做二次校验,违规直接 403。

生产环境避坑指南

  1. 异步调用陷阱
    LangChain 早期版本很多链没加arun,直接await chain.acall会阻塞。
    解决:用run_in_executor或升级到0.1+的 LCEL 语法,官方已补全异步支持。

  2. 缓存策略

    • 向量库层:Qdrant 自带on_disk索引,重启秒级加载;
    • 大模型层:对高频 FAQ 配置RedisCache,key 用question的向量哈希,TTL 1 h,命中率 35%,节省 20% 预算。
  3. 日志追踪
    采用structlog+trace_id透传,每一行日志都带user_idquestion_hash,方便回滚审计;
    再接入 Grafana Loki,答辩现场可直接展示“本月问答 1.2 万次,异常率 0.3%”,瞬间拉高印象分。

效果评估与扩展思考

指标数值
平均响应1.8 s
检索准确率(Top-1)0.91
生成事实性(人工抽 100 条)0.87
并发 50 请求 95th 延迟4.2 s

如果想再进一步:

  • 多模态:把课件的配图用CLIP做向量,一并扔进 Qdrant,支持“图+文”混合问答;
  • 微调:收集历年答辩问题,用 LoRA 训一个 7B 小模型,替代gpt-3.5,成本再降 70%;
  • 插件化:把loadersplitter做成PlugIn接口,毕设结束后可无缝迁移到实习项目。

结尾:把“能跑”变成“能扛”

毕设不是写 Demo,而是写一份“能扛 1 万用户”的简历。
本文的代码仓库已开源(见文末链接),你可以直接fork后换上自己的数据集,再按“性能与安全”章节调优。
下一步不妨思考:如果给系统加上语音输入,或把检索结果画成知识图谱,你的答辩 PPT 会不会更酷?
动手吧,让 LangChain 成为你毕业设计里的“工程加分项”,而不是“玩具减分项”。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/16 22:43:41

JSON解析的艺术:从基础到进阶

在计算机编程中,处理JSON数据是非常常见的一项任务。最近,我在处理一个JSON解析的项目时,遇到了一个有趣的挑战:如何正确地将一个JSON字符串解析成一个指定类型的对象?本文将通过一个实际案例,深入探讨JSON解析的过程和技巧。 问题背景 假设我们有一个包含交易订单信息…

作者头像 李华
网站建设 2026/4/25 20:17:39

微信小程序智能客服接入实战:从零搭建到性能优化

微信小程序智能客服接入实战&#xff1a;从零搭建到性能优化 摘要&#xff1a;本文针对微信小程序开发者面临的客服系统接入复杂、响应延迟高等痛点&#xff0c;详细介绍如何通过云开发智能对话引擎快速搭建高性能客服系统。你将掌握Webocket长连接优化、多轮对话状态管理、以及…

作者头像 李华
网站建设 2026/5/12 5:59:45

16GB显存就能跑!Z-Image-Turbo消费级显卡实测分享

16GB显存就能跑&#xff01;Z-Image-Turbo消费级显卡实测分享 你有没有过这样的体验&#xff1a;在AI绘图工具里输入一段提示词&#xff0c;按下“生成”&#xff0c;然后盯着进度条数秒——等它出来&#xff0c;灵感早凉了半截&#xff1f;更别提批量做图时&#xff0c;每张都…

作者头像 李华
网站建设 2026/5/13 12:37:57

告别繁琐配置!MGeo镜像让地址对齐一键启动

告别繁琐配置&#xff01;MGeo镜像让地址对齐一键启动 1. 为什么地址匹配总在“调参—报错—重试”里打转&#xff1f; 你有没有遇到过这样的场景&#xff1a; 物流系统要自动合并同一收货地址的不同写法&#xff08;“杭州市西湖区文三路398号” vs “杭州文三路398号”&am…

作者头像 李华