Python开发LangChain与Ollama定制RAG检索增强生成、Agent智能体进行私有知识库问答
作为长期从事机器学习与人工智能算法应用开发的一线从业者,同时也是一名高校教师,我经常被学生和企业同行问到同一个问题:大模型的知识不是实时的、领域知识缺乏、还会一本正经地胡说八道,这些问题怎么解决?答案就藏在检索增强生成技术(RAG)里。
成为新会员获取本项目完整代码、数据和AI智能体
作为长期从事机器学习与人工智能算法应用开发的一线从业者,同时也是一名高校教师,我经常被学生和企业同行问到同一个问题:大模型的知识不是实时的、领域知识缺乏、还会一本正经地胡说八道,这些问题怎么解决?答案就藏在检索增强生成技术(RAG)里。过去几年,我们团队在多个咨询项目中反复使用LangChain框架为客户搭建私有知识库问答系统,从密钥配置、提示词设计,到文档加载、向量检索,再到Agent智能体的编排,积累了完整的一线经验。本文正是改编自其中一个咨询项目的技术沉淀,我们将这套经验整理成一份学生也能看懂的教程文档。本文将我们的RAG检索增强生成建模经验沉淀为一个对话式AI智能体,把整个技术链路拆解为十个循序渐进的模块,每个模块都给出可直接运行的核心代码与运行结果。文章首先介绍OpenAI官方SDK与Ollama本地部署的基础用法,随后讲解提示工程、零样本与少样本学习,以及JSON、CSV两种数据交换格式;在此基础上引入LangChain框架,系统梳理文档加载器、文本分割器、嵌入向量与向量数据库的用法,并给出完整的RAG问答实现;最后介绍Agent智能体的概念、ReAct框架与中间件机制,并展示一个RAG项目的完整工程结构。读者既可以把本文当作从零入门到工程落地的路线图,也可以直接复用其中的代码骨架来完成课程设计与毕业设计。
本项目完整代码、数据和AI智能体
摘要
本文围绕基于LangChain框架与Ollama本地模型的私有知识库问答系统开发展开,回答了五个核心问题:如何通过环境变量安全配置模型API密钥并在本地部署蒸馏模型?如何设计提示词与少样本示例提升模型输出质量?如何用JSON、CSV组织模型的输入输出数据?如何借助文档加载器、文本分割器与向量数据库构建RAG检索增强生成问答闭环?如何基于ReAct框架与中间件搭建可自主规划、调用工具的Agent智能体?文中给出每一步的核心代码与运行结果。
Abstract: This article focuses on developing a private knowledge-base question-answering system with the LangChain framework and local Ollama models. It answers five core questions: how to securely configure API keys via environment variables and deploy distilled models locally; how to design prompts and few-shot examples to improve output quality; how to organize model I/O with JSON and CSV; how to build a RAG (Retrieval-Augmented Generation) Q&A loop with document loaders, text splitters, and vector databases; and how to build agents with autonomous planning and tool invocation based on the ReAct framework and middleware. Core code and runtime results are provided for each step.
阅读原文进群获取本文完整代码、数据、AI智能体及更多最新AI见解和行业洞察,可与900+行业人士交流成长;还提供人工答疑,拆解核心原理、代码逻辑与业务适配思路;遇代码运行问题,更能享24小时调试支持。
全文的技术脉络可以用下面的流程图概括:
全文技术脉络 密钥配置与本地部署(环境变量、Ollama蒸馏模型) │ ├── 基础调用:客户端对象→调用模型→处理结果 │ ├── 提示工程:零样本、少样本与金融文本分类 │ ├── 数据格式:JSON对象、JSON数组与CSV读写 │ ├── LangChain框架:三类模型、消息类型与嵌入向量 │ ├── 模板与链:PromptTemplate、Chain与输出解析器 │ ├── 会话记忆:内存历史→JSON文件持久化 │ ├── 文档处理:文档加载器与递归字符文本分割 │ ├── 向量数据库:内存存储→Chroma持久化 │ ├── RAG问答:检索→注入提示词→生成答案 │ └── Agent智能体:ReAct框架与中间件
本文项目文件的目录结构截图如下:

基础环境:OpenAI官方SDK与Ollama本地部署
认识OpenAI官方SDK
openAI库是OpenAI官方推出的Python SDK,核心作用是让开发者能简单、高效地调用OpenAI等云端模型的各类API(如GPT聊天、DALL·E绘图、语音转文字等),无需手动处理HTTP请求、身份验证等底层细节。由于其发布较早且比较易用,现如今许多模型服务商(如阿里云百炼平台)均兼容OpenAI SDK的调用。
可以在01测试APIKEY的文件中,更换model来访问不同的大模型。
用环境变量隐藏API密钥
如果将APIKEY明文显示在代码中,存在很大的安全隐患。我们可以通过环境变量来隐藏明文APIKEY,在代码里不体现APIKEY,而是把它记录在操作系统里的环境变量中,当我们运行代码的时候,它会自动从操作系统的环境变量中读取到APIKEY。其中,OPENAI_API_KEY用于openai库;DASHSCOPE_API_KEY用于langchain库(后续学习)。
把密钥放进环境变量,就像把家门钥匙交给物业统一保管(行业术语:凭据管理)——代码里只保留取用方式,而不是钥匙本身。
用Ollama在本地部署大模型
Ollama 是一款旨在简化大型语言模型在本地部署和运行过程的开源软件,它提供了一个轻量级、易于扩展的框架,开发者可以导入和定制自己的模型,也支持很多流行的模型,无需关注复杂的底层实现细节,可以将其视为阿里云百炼平台的本地版本——在自己电脑上部署和运行大模型,由本地电脑的硬件提供算力来支撑模型运行。
Ollama的部署非常简单,主要步骤如下:
①访问Ollama官网(https://ollama.com),下载并安装。
②安装完成后,在命令行(终端)中使用以下命令:ollama run <模型名称>。即可启动对应模型,并直接在命令行中进行交互。这样就能在本地快速部署并运行大语言模型,无需依赖云端服务。
注意,Ollama里面运行的模型都是蒸馏模型,而不是标准的大模型,因为标准的大模型对硬件要求很高。4b、8b、30b代表的是参数的量,电脑的显存越大,在ollama里可运行的蒸馏模型越强。
显存与可运行蒸馏模型的对照如下:
| 显存大小 | 可运行的蒸馏模型规模 |
|---|---|
| 4G | 8b以内 |
| 8G | 14b以内 |
本地运行的大模型和云上模型的性能差别还是很大的。
OpenAI库的三步基础用法
①获取客户端对象

其中,api_key是模型服务商提供的 API KEY 密钥,若已经添加了环境变量,则这一行代码可以删去;base_url是模型服务商的 API 接入地址,主要基于此参数来切换不同的模型服务商(如 OpenAI、阿里云、腾讯云等),通义千问3MAX的API接入地址为:https://dashscope.aliyuncs.com/compatible-mode/v1
②调用模型

其中client.chat.completions.create这个函数会创建ChatCompletion对象,传入的主要参数有2个:model:选择所用模型,如代码的qwen3-max;messages:提供给模型的消息。
messages的类型是字典list,可以包含多个字典消息,每个字典消息包含2个key:role角色和content内容。角色包括system、assistant、user。
| 角色 | 作用 |
|---|---|
| system | 设定助手的整体行为、角色和规则,为对话提供上下文框架(如指定助手身份、回答风格、核心要求),是全局的背景设定,影响后续所有交互 |
| assistant | 代表AI助手的回答,可以在代码中人为设定 |
| user | 代表用户,发送问题、指令或需求 |
③处理结果
response变量是一个ChatCompletion对象,其包含的信息如下:

其中,可以通过print(response.choices[0].message.content)来打印出response变量中的message中的content内容,即打印出大模型回答出的答案。
总结代码如图:

阅读原文进群获取完整内容及更多AI见解、行业洞察,与900+行业人士交流成长。
最受欢迎的见解
- Python员工数据人力流失预测:ADASYN采样CatBoost算法、LASSO特征选择与动态不平衡处理及多模型对比研究
- R分布式滞后非线性模型DLNM分析某城市空气污染与健康数据:多维度可视化优化滞后效应解读
- Python古代文物成分分析与鉴别研究:灰色关联度、岭回归、K-means聚类、决策树分析
- Python TensorFlow OpenCV的卷积神经网络CNN人脸识别系统构建与应用实践
- Python用Transformer、SARIMAX、RNN、LSTM、Prophet时间序列预测对比分析用电量、零售销售、公共安全、交通事故数据
- MATLAB贝叶斯超参数优化LSTM预测设备寿命应用——以航空发动机退化数据为例
- Python谷歌商店Google Play APP评分预测:LASSO、多元线性回归、岭回归模型对比研究
- Python+AI提示词糖尿病预测模型融合构建:伯努利朴素贝叶斯、逻辑回归、决策树、随机森林、支持向量机SVM应用
与AI智能体的多轮对话:基础模型调用
第一轮对话:基础模型调用
“我有一批企业内部的客服工单数据,想接入通义千问大模型做一个问答助手。请帮我用Python的OpenAI官方SDK写一个最小可用示例:创建客户端对象时从环境变量读取密钥、指定DashScope兼容接口地址,然后用qwen3-max模型完成一轮对话,messages里分别设置system角色设定助手身份和user角色提问,最后打印模型回复内容。”
# 导入官方SDK
from openai import OpenAI
# 创建客户端:密钥从环境变量读取,接入地址指向兼容接口
client_obj = OpenAI(
api_key=os.environ.get("OPENAI_API_KEY"), # 已配置环境变量,此处可省略
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
# 组装消息列表:设定角色并提问
chat_msgs = [
{"role": "system", "content": "你是一名严谨的智能客服助手"},
{"role": "user", "content": "订单退款需要几天到账?"}
]
# 调用模型并解析返回对象
resp = client_obj.chat.completions.create(
model="qwen3-max",
messages=chat_msgs
)
print(resp.choices[0].message.content)
开启流式输出模式
可以设定结果输出为stream模式(流式输出),获得更好的使用体验。

开启流式输出主要就2步:①在client.chat.completions.create()调用模型的时候设定参数:stream=True;②for循环response对象,并在循环内输出内容。
总结代码如图:

第二轮对话:流式输出与历史消息
“上一版代码要等整个回复生成完才一次性打印,体验不好。请改成流式输出(stream=True),用for循环逐段打印;另外我想把上一轮问答追加进messages再发第二轮提问,让模型记住上下文,请给出完整代码。”
from openai import OpenAI
client_obj = OpenAI(
api_key=os.environ.get("OPENAI_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
chat_msgs = [
{"role": "system", "content": "你是一名严谨的智能客服助手"},
{"role": "user", "content": "订单退款需要几天到账?"}
]
# 流式调用:逐段接收并打印
stream_resp = client_obj.chat.completions.create(
model="qwen3-max",
messages=chat_msgs,
stream=True
)
for chunk in stream_resp:
print(chunk.choices[0].delta.content, end="")
......(此处省略了逐段拼接与异常处理的次要代码)
携带历史信息调用模型
调用模型传入的参数messages,其要求是list对象,即表明其支持非常多的消息在内。我们可以基于此,将历史消息填入,让模型知晓对话的上下文,更好的回答。

注,这种历史消息是一次性的,即历史信息保存在了内存中,代码运行完之后,这些历史消息也就丢失了,如果是生产系统的话,这样肯定是不行的,可以把消息保存到文件或数据库等持久化的工具中,需要的话就去提取。
提示工程与少样本学习
什么是提示工程
Prompt Engineering,提示工程,也称为 In-Context Prompting,是指在不更新模型权重的情况下如何与大模型交互以引导其行为以获得所需结果的方法。在人工智能领域,Prompt就是用户给大型语言模型发出的指令。例如,”讲个笑话”、”用Python编个贪吃蛇游戏”、”写封情书”等。虽然看似简单,但实际上,Prompt的设计对于模型的结果影响很大,因此如何设计prompt,进而与模型更好的交互,是研究人员必不可少的技能。
写好提示词就像给新来的实习生交代任务(行业术语:提示工程)——要求写得越具体、给足参考样例,交付质量越高。写好提问有以下技巧:
①描述更加详细
②让模型充当某个角色
③使用分隔符标明输入的不同部分,中括号、XML标签、三引号等分隔符可以帮助划分要区别对待的文本,也可以帮助模型更好的理解文本内容。
④指定任务的步骤
⑤提供例子
⑥使用参考文本,即知识库用法。基于文本文档,辅助大模型问答,降低模型幻觉(一本正经的胡说八道),让大模型使用我们提供的信息来组成答案。
大模型本身是一种很简单的结构,即:用户输入,模型输出。用更详细、更清晰、有逻辑、有参考的提问,获得期望中的回答效果。不管是RAG还是Agent智能体亦或是其它围绕模型的各类复杂的开发工作,本质上都可以简单总结为在提示词上下功夫。
零样本学习
Zero-shot Learning 零样本学习,指在训练阶段不存在与测试阶段完全相同的类别,但是模型可以使用训练过的知识来推广到测试集中的新类别上。
举例1:已知类别特征:马(四脚兽)、虎(有条纹)、熊猫(黑白色)。未知类别(斑马)的描述:是四脚兽、有黑白色的条纹。模型任务:利用已知特征(”四脚兽”、”有条纹”、”黑白色”)进行推理组合,从而识别出从未在训练数据中出现的”斑马”。
举例2:情感判断:请判断用户评论中的情感倾向,输出正面或负面。”这款代餐鸡胸肉饱腹感很强,吃起来也不柴,很推荐!”
少样本学习
Few-shot Learning少样本学习。当模型在学习了一定类别的大量数据后,对于新的类别,只需要少量的样本就能快速学习。对应的还有 one-shot learning (单样本学习),即样本少到唯一的情况,是Few-shot learning的一种特例。
举例2:请抽取产品名称和核心卖点2个字段,格式为JSON。
提供2个示例:
示例1输入:MacBookPro高效节能,性能强大,适合牛马工作使用
示例1输出:{“产品名称”: “MacBookPro”, “产品卖点”: “高效节能,性能强大”}
示例2输入:联想笔记本拥有RTX4060独立显卡,畅玩游戏,丝滑流畅
示例2输出:{“产品名称”: “联想笔记本”, “产品卖点”: “畅玩游戏,丝滑流畅”}
待处理内容:华为MatepadPro,高清大屏,长效续航,你的好帮手。
提示词优化案例:金融文本分类
下面以一个金融文本分类案例说明提示词优化的实际应用。该案例资料来自某平台客户反馈文本(行业术语:标注语料),由项目组完成类别标注。



数据交换格式:JSON与CSV
JSON简介
JSON数据格式JavaScript Object Notation,是带有格式的字符串,主要用于数据交换,即程序和程序之间的信息互传,是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。Json数据格式和普通text文本和csv文本的对比如下:

| 对比项 | JSON | CSV |
|---|---|---|
| 全称 | JavaScript Object Notation | Comma-Separated Values |
| 结构 | 键值对(对象)与数组 | 逗号分隔的表格行 |
| 打开方式 | 记事本、代码 | 可直接用Excel打开 |
Json主要有两种结构:json对象、json数组。Json对象就相当于python里面的字典,Json数组就相当于python里面包含多个字典的列表。Json在Python中,就是字典和列表套字典的字符串表现形式。
CSV简介
CSV: Comma-Separated Values, 逗号分隔值,是一种简单、通用的文本文件格式,用于存储表格数据,可以直接使用Excel打开。csv文件的后缀是.csv,文件里的逗号在用excel打开的时候自动转换为不同的单元格。
用记事本打开的csv文件案例如图:

用excel打开的csv文件案例如图:

操作csv文件有两种方式:
方式一:按照普通文件方法去操作,案例如图:

方式二:使用csv文件专用库来操作,案例如图:

其中DictWriter和DictReader都是一个class类,通过这两个类就可以对csv文件进行读或写操作,而且写操作的时候,writerow写入的类型是字典类型,读操作时,可以使用对字典的操作来访问数据,即31行代码中row的类型是字典。
JSON对象与JSON数组
json对象:{“key”:value,”key”:value,”key”:value}。其中,key必须是字符串。value可以是:数字、字符串、列表、Json对象、Json数组等任何类型。注,json中的布尔值是true,false全都是小写。
Json对象举例:

json数组:[{},{},{}]。即json数组就是一堆json对象的组合体。
Json数组举例:

Python中使用JSON
Python中使用Json主要用来完成:A、将Python字典、列表转换为Json字符串;B、读取Json字符串,转换为Python字典或列表。主要使用Python内置的json库来使用json。
json.dumps(字典或列表,ensure_ascii=False):这行代码的作用是将字典或列表转换为Json字符串;ensure_ascii=False的作用是确保中文能正常显示。返回值:Json字符串。
json.loads(json字符串):这行代码的作用是将Json字符串转换为Python字典或列表。返回值:Python字典或 Python列表。
演示代码如图:


LangChain框架与RAG检索增强生成
LangChain简介
LangChain创建于2022年10月,它是围绕LLMs(大语言模型)建立的一个框架,它自身并不开发LLMs,它为各种LLMs实现通用的接口,把LLMs相关的组件”链接”在一起,简化LLMs应用的开发难度,方便开发者快速地开发复杂的LLMs应用。LangChain是一个开发LLM相关业务功能的集大成者,是一个Python的第三方库,提供了各种功能的API。
langchain的功能:A、提示词优化的相关功能API;B、调用各类模型的功能API;C、会话记忆的相关功能API;D、各类文档管理分析的功能API;E、构建Agent智能体的相关功能API;F、各类功能链式执行的能力。LangChain是学习RAG开发的主力框架。
使用langchain,一般需要安装以下包:
pip install langchain langchain-community langchain-ollama dashscope chromadb
基础大模型的四大痛点与RAG
通用的基础大模型存在一些问题:A、LLM的知识不是实时的,模型训练好后不具备自动更新知识的能力,会导致部分信息滞后;B、LLM领域知识是缺乏的,大模型的知识来源于训练数据,这些数据主要来自公开的互联网和开源数据集,无法覆盖特定领域或高度专业化的内部知识;C、幻觉问题,LLM有时会在回答中生成看似合理但实际上是错误的信息;D、数据安全性。
RAG就是用来解决以上问题的,全称是检索增强生成技术Retrieval-Augmented Generation,利用检索外部文档提升生成结果质量,它为大模型提供了从特定数据源检索到的信息,以此来修正和补充生成的答案。可以总结为一个公式:RAG = 检索技术 + LLM提示。
RAG技术的具体优势:A、解决知识时效性问题:大模型的训练数据有截止时间,RAG可以接入最新文档(如公司财报、政策文件),让模型输出”与时俱进”。B、降低模型幻觉:模型的回答基于检索到的事实性资料,而非纯靠自身记忆,大幅减少编造信息的概率。C、无需重新训练模型:相比微调(Fine-tuning),RAG只需更新知识库,成本更低、效率更高。
RAG就像开卷考试(行业术语:检索增强生成)——学生拿到试卷后先翻书找答案依据,再动笔作答,答案自然比闭卷凭记忆更可靠。
> 答辩高频提问:RAG和微调有什么区别?标准回答:微调需要更新模型权重、训练成本高;RAG只需更新知识库,成本低、效率高,且回答可追溯到检索到的原文,更适合知识更新频繁的场景。
RAG的工作有两部分:离线模块、实时模块。
A、在离线模块,有一个程序24小时不断地从各种各样的地方获取各种各样的信息,然后对这些信息进行分割,切分成标准长度的文本块chunk,然后进行嵌入向量化embedding转成数字向量,然后存储在数字向量数据库vector database中。
B、在实时模块,用户对大模型提出问题之后,不会直接把用户提出的问题给大模型,而是先将问题转化为向量,然后根据此向量在数字向量数据库中进行相似度匹配,检索与此问题相关的文本块,将资料与用户提出的问题结合到一起形成提示词prompt,然后输入大模型,进行回答。这样会使得回答更精确正确。RAG可以认为是更高级的prompt优化。
文本向量化与余弦相似度
将文本转为向量,主要使用文本嵌入模型(如 text-embedding-v1,每段文本固定得到1536个特征),通过深度学习等技术,从文本提取语义特征并映射为固定长度的数字序列。向量嵌入的过程,我们一般选用合适的文本嵌入模型来完成。
在向量匹配的过程中,如何识别2段文本是否表述相似的含义,主要可以通过如余弦相似度等算法来完成。举例:
A:”如何快速学打篮球” → [0.2, 0.5, 0.8]
B:”打篮球怎么学得快” → [0.18, 0.52, 0.79]
C:”运动后吃什么好呢” → [0.9, 0.1, 0.2]
通过余弦相似度算法可以计算得到:
| 文本对比 | 余弦相似度 |
|---|---|
| A和B | 0.999789 |
| A和C | 0.361446 |
由此可通过精确的数学计算,去匹配2段文本是否描述同一个意思,提高语义匹配的效率和精度。
余弦相似度算法:向量的数字序列,共同决定了向量在高维空间中的方向和长度。而余弦相似度主要就是撇除长度的影响,得到方向的夹角,只关心向量的夹角大小而不关心长度。夹角越小越相似,即方向相同。

其实,余弦相似度其实就是cos,值越接近1,两向量的方向越接近,则认为两个文本的相似度越高。
LangChain支持的三类模型
现在市面上的模型多如牛毛,LangChain模型组件提供了与各种模型的集成,并为所有模型提供一个精简的统一接口。LangChain目前支持三种类型的模型:LLMs(大语言模型)、Chat Models(聊天模型)、Embeddings Models(嵌入模型)。
| 模型类型 | 定位 | 输入输出 | 主要场景 |
|---|---|---|---|
| LLMs | 技术范畴统称,Transformer架构 | 文本进、文本出 | 文本生成 |
| Chat Models | 应用范畴细分 | 消息序列进、消息出 | 对话交互 |
| Embeddings Models | 文本嵌入 | 文本进、向量出 | 文本向量化、检索 |
A、LLMs:是技术范畴的统称,指基于大参数量、海量文本训练的Transformer架构模型,核心能力是理解和生成自然语言,主要服务于文本生成场景。
B、聊天模型:是应用范畴的细分,是专为对话场景优化的LLMs,核心能力是模拟人类对话的轮次交互,主要服务于聊天场景。
C、文本嵌入模型:文本嵌入模型接收文本作为输入,得到文本的向量。LangChain支持的三类模型,它们的使用场景不同,输入和输出不同,开发者需要根据项目需要选择相应。我们所用的阿里云通义千问系列主要来自于:Langchain_community包。
LLMs使用场景最多,常用大模型的下载库:
https://huggingface.co/models
https://modelscope.cn/models
用LangChain访问大语言模型
使用LangChain访问LLMs语言大模型代码如图:

也可以访问ollama中下载的本地大语言模型,代码如图:

如果需要流式输出结果,需要将模型的invoke方法改为stream方法即可。invoke方法:一次性返回完整结果;stream方法:逐段返回结果,流式输出。这两个方法是新版LangChain(1.0版本后)中基于Runnable接口的通用核心方法。绝大多数组件(如提示词模板、链、向量检索、工具调用等)都支持这两个方法,这也是LangChain设计的核心统一范式。
LangChain的流式输出代码如图:

聊天消息类型
聊天大模型中聊天消息包含下面几种类型,使用时需要按照约定传入合适的值:
A、AIMessage:就是 AI 输出的消息,可以是针对问题的回答。(对应 OpenAI 库中的 assistant角色)
B、HumanMessage:人类消息就是用户信息,由人给出的信息发送给 LLMs 的提示信息,比如”实现一个快速排序方法”。(对应 OpenAI 库中的 user角色)
C、SystemMessage:可以用于指定模型具体所处的环境和背景,如角色扮演等。你可以在这里给出具体的指示,比如”作为一个代码专家”,或者”返回 json 格式”。(对应 OpenAI 库中的 system角色)。
用LangChain访问聊天大模型代码如下:

其中,有一个简写形式,把messages写成:

这种简写形式不仅方便,而且还能支持内部填充{变量}占位,可在运行时填充具体值。
用嵌入模型完成文本向量化
使用Embeddings Models嵌入模型可以将文本转成向量。将文本字符串作为输入,它返回一个浮点数的列表(向量)。在NLP中,Embedding的作用就是将数据进行文本向量化。
通过访问阿里云千问云端模型实现文本转向量,代码如图:

通过访问Ollama本地模型实现文本转向量,代码如图:

提示词模板与链式调用
PromptTemplate提示词模板
提示词优化在模型应用中非常重要,LangChain提供了PromptTemplate类,用来协助优化提示词。PromptTemplate表示提示词模板,可以构建一个自定义的基础提示词模板,支持变量的注入,最终生成所需的提示词。
使用PromptTemplate类访问通义千问的代码如下:

注意,以上代码使用的是zeroshot思想的提示词模版,还可以使用fewshot。
还有另一种写法,代码如下:

其中,chain = prompt_template | model 的意思是先把提示词放到model里面,此时还没有传入lastname和gender参数。
可以手动拼接字符串来实现PromptTemplate,但是使用后者的好处有:A、使用Template模板构建提示词,在大型工程中更容易做标准化模板。B、Template模板类,支持LangChain框架的链式调用(Runnable接口):如PromptTemplate、FewShotPromptTemplate、ChatPromptTemplate。
PromptTemplate:通用提示词模板,支持动态注入信息。FewShotPromptTemplate:支持基于模板注入任意数量的示例信息。ChatPromptTemplate:支持注入任意数量的历史会话信息。
FewShotPromptTemplate少样本模板
使用fewshot思想的提示词模版代码如图:

其中,examples表示示例数据,list,内套字典;example_prompt表示示例数据的提示词模板;prefix表示组装提示词,示例数据前内容;suffix表示组装提示词,示例数据后内容;input_variables表示列表,注入的变量列表。
举例代码如图:


format与invoke的区别
在前面使用通用提示词的代码和使用FewShot提示词模版的代码中,我们使用了format和invoke来把数据注入到参数中,format和invoke的区别在于:
| 对比项 | format | invoke |
|---|---|---|
| 本质 | 纯字符串替换,解析占位符生成提示词 | Runnable 接口的标准方法,也能解析占位符生成提示词 |
| 返回值 | 字符串 | PromptValue类对象 |
| 传参方式 | .format(k=v,k=v,…) | .invoke({“k”:v,”k”:v,…}) |
| 占位符支持 | 支持解析 {}占位符 | 支持解析 {}占位符,还支持 MessagesPlaceholder结构化占位符 |
ChatPromptTemplate会话模板
ChatPromptTemplate:支持注入任意数量的历史会话信息。注意,历史会话信息并不是静态的(固定的),而是随着对话的进行不停地积攒,即动态的。所以,历史会话信息需要支持动态注入。通过 from_messages方法,从列表中获取多轮次会话作为聊天的基础模板。

注意,前面 PromptTemplate 类用的 from_template仅能接入一条消息,而from_messages可以接入一个list的消息。这个history应当是个字符串,起到占位符的作用,因此历史消息可以注入到提示词中。这里,必须使用chat_template.invoke而不能使用format,因为这里使用的是MessagesPlaceholder类型,而format返回值是字符串类型。
举例:

Chain链的工作原理
Chain链的工作原理:将组件串联,上一个组件的输出作为下一个组件的输入。这也是链式调用的核心价值:实现数据的自动化流转与组件的协同工作,如下:chain = prompt_template | model。链式调用就像流水线(行业术语:管道串联)——上一道工序的半成品自动流到下一道工序。
这里|是重写了__or__。这样,返回值得到的是runnableserializable对象,它仍然是runnable接口的子类,可以使用invoke、stream。runnable的每一个类都有invoke、stream API。注意:只有Runnable子类对象才能入链(以及 Callable、Mapping 接口子类对象也可加入,用得不多)。Langchain里面绝大多数组件都是runnable的对象。我们目前所学习到的组件,均是 Runnable 接口的子类。
使用Chain链和模版访问聊天大模型,代码如图:

注意,chain = chat_prompt_template | model;res = chain.invoke(input={“history”:history_data})。这两行代码是先执行下面一行,先把history_data输入到history中,然后再进行生成chat_prompt_template,然后再放入model中,模型进行回答。
在 Python 中,运算符(如 +、|)的行为由类的魔法方法决定。例如:a + b本质调用的是 a.__add__(b);a | b本质调用的是 a.__or__(b)。只需要自行实现类的__or__方法,即可对|符号的功能进行重写。
LangChain官方中对__or__的重写代码如图:

字符串输出解析器
如果我想用第一次模型给出的答案再去提问模型,使用如下代码:

但是以上代码会报错:ValueError: Invalid input type <class ‘langchain_core.messages.ai.AIMessage’>. Must be a PromptValue, str, or list of BaseMessages.这是因为ChatTongyi的invoke的input必须是提示词PromptValue或字符串str或sequence,但是prompt|model会返回AIMessage类型,所以会报错。
解决方法:做类型转换,可以借助LangChain内置的解析器StrOutputParser字符串输出解析器。StrOutputParser是Runnable接口的子类(可以加入链)。StrOutputParser的作用是可以将AIMessage解析为简单的字符串,符合了模型 invoke方法。
示范代码如下:


JSON输出解析器
前面我们把第一个模型回答的结果直接又去提问了模型,其实这样做没啥逻辑,符合逻辑的做法应该是中间对模型第一个输出结果进行一些处理再去提问模型:即,得到第一个模型回答的结果之后,转为字典,注入到第二个提示词模版中,然后二次调用模型。可以使用JsonOutputParser来完成把AIMessage类型转为Dict(Json)类型。
注意invoke输入的类型应该是字典类型,JsonOutputParser的输入的类型应该是Json类型。所以在第一个模型回答时候,必须要求模型按照Json类型返回结果。
代码如图:

chain = first_prompt | model | json_parser | second_prompt | model | str_parser这行代码运行的过程如下:

RunnableLambda自定义转换
除了JsonOutputParser这类固定功能的解析器之外,我们也可以自己编写Lambda匿名函数来完成自定义逻辑的数据转换,想怎么转换就怎么转换,更自由。想要完成这个功能,可以基于RunnableLambda类实现。RunnableLambda类是LangChain内置的,将普通函数等转换为Runnable接口实例,方便自定义函数加入chain。
语法: RunnableLambda(函数对象或lambda匿名函数)。示例代码如图:

其中RunnableLambda(lambda ai_msg: {“name”:ai_msg.content}) 中ai_msg是上一个模型的结果,然后返回一个字典。
简写形式:跳过RunnableLambda类,直接让函数加入链也是可以的。因为Runnable接口类在实现 __or__的时候,支持Callable接口的实例。函数就是Callable接口的实例。
代码如图:

这行代码中(lambda ai_msg: {“name”:ai_msg.content}) 会自动转为RunnableLambda类型。
与AI智能体的多轮对话:LangChain链式调用
第三轮对话:LangChain链式调用
“我想把基础模型调用迁移到LangChain框架:用ChatPromptTemplate管理提示词、通过管道符把模板和模型串成一条链,并支持在运行时注入会话历史。请给出完整代码。”
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain_core.prompts import ChatPromptTemplate
# 构建聊天提示词模板:预留历史占位
chat_tpl = ChatPromptTemplate.from_messages([
("system", "你是一名严谨的智能客服助手"),
("placeholder", "{chat_hist}"),
("human", "用户tiwen:{user_ask}")
])
# 实例化模型
llm = ChatTongyi(model="qwen3-max", streaming=True)
# 用管道符串联模板与模型形成链
qa_chain = chat_tpl | llm
# 运行时注入历史与问题
result = qa_chain.invoke({"chat_hist": history_msgs, "user_ask": "订单退款需要几天到账?"})
print(result.content)
会话记忆:从内存到本地文件
基于内存的历史记录
如果想要封装历史记录,除了自行维护历史消息外,也可以借助LangChain内置的历史记录附加功能。LangChain提供了History功能,帮助模型在有历史记忆的情况下回答。具体提供了两种方式:
A、基于RunnableWithMessageHistory在原有链的基础上创建带有历史记录功能的新链(新Runnable实例)。
B、基于InMemoryChatMessageHistory为历史记录提供内存存储(临时用),InMemoryChatMessageHistory 类继承自 BaseChatMessageHistory。
示范代码如图:


其中,get_history函数的输入为会话id,返回InMemoryChatMessageHistory类对象。大模型可能有多个会话,所以会话id的作用是区别不同的会话,每个用户有自己的会话session_id。store是个字典,key就是session_id,value就是InMemoryChatMessageHistory类对象,用来存放多个会话id所对应的历史会话记录。
base_chain没有记忆历史记录功能,第一次运行的时候,chat_history是空的,我们此时只需要提供input就可以了。当前程序自己的session_id是需要自己配置的。每次invoke都不仅会把新的信息输入到input中,而且还会自动调用get_history函数,自动把本次输出结果放入chat_history中,这样就能看到以前的历史信息了。print_prompt的功能是每次执行时打印出信息,便于知道发生了什么。
三次执行的结果如下:



第一次执行:小明有2只猫。第二次执行:小刚有1只狗。第三次执行:小明有2只猫,小刚有1只狗,所以总共有 2 + 1 = 3 只宠物。可以看到第三次执行时,模型已经记住了前两轮的对话内容。
基于JSON文件的长期会话记忆
使用InMemoryChatMessageHistory仅可以在内存中临时存储会话记忆,一旦程序退出,则记忆丢失。我们可以自行实现一个基于 Json 格式和本地文件的会话数据保存。
实现长期会话记忆示范代码如图:





代码解释:
A、基于文件存储会话记录,以session_id为文件名,不同session_id有不同文件存储消息。
B、add_messages:同步模式,添加消息。
C、messages:同步模式,获取消息。
D、clear:同步模式,清除消息。
E、self.file_path=os.path.join(storage_path,session_id)的作用是把文件夹路径和会话id拼接到一起,形成完整的文件路径。
F、os.makedirs(os.path.dirname(self.file_path),exist_ok=True) 这行代码是确保文件夹是存在的。
G、Sequence可以认为是list。
H、message_to_dict: 官方提供的函数,作用是可以把单个消息对象(BaseMessage类实例) 转为字典。
I、messages_from_dict:官方提供的函数,作用是可以把 [字典、字典…] 转为 [消息、消息…]。
J、AIMessage、HumanMessage、SystemMessage 都是BaseMessage的子类。
K、其他代码解释在代码注释里。
程序分别运行”第一次和第二次执行”和”第三次执行”的程序结果如图:



文档加载与文本分割
Document与文档加载器
LangChain官方提供了文档加载器,用于将不同来源(如CSV、PDF或JSON等)的数据读取为LangChain的文档格式。这确保了无论数据来源如何,都能对其进行一致性处理。
注意,内置或自行实现文档加载器需实现BaseLoader接口,因为文档加载器都继承于BaseLoader类。所有文档加载器最终都返回Class Document类的实例。
创建Document实例代码如图:

其中,page_content表示文档内容;metadata表示文档元数据(字典)。
不同的文档加载器可能定义了不同的参数,但是其都实现了统一的接口:A、load():一次性加载全部文档。返回值是[Documents,Documents,…]类型的。B、lazy_load():延迟流式传输文档,对大型数据集很有用,避免内存溢出。返回值是一个迭代器,迭代器里面存的都是[Document]。
CSVLoader
示范CSVLoader代码如图:

其中,”delimiter”:”,” 意思是指定分隔符;”quotechar”:'”‘ 意思是指定带有分隔符文本的引号包围是单引号还是双引号,即单引号或双引号包围起来的信息视为一个信息,即使里面有逗号,仍然视为一个信息。
结果如图:

如果原数据中没有表头,可以指定表头,在csv_args中增加”fieldnames”:[‘a’,’b’,’c’,’d’]。
结果如图:

JSONLoader与jq
JSONLoader用于将JSON数据加载为Document类型对象。使用JSONLoader需要额外安装pip install jq。jq是一个跨平台的json解析工具,LangChain底层对JSON的解析就是基于jq工具实现的。
将JSON数据的信息抽取出来,封装为Document对象,抽取的时候依赖jq_schema语法。
jq基础语法如图:

jq基础语法演示代码如图:

TextLoader与递归字符文本分割器
除了前文学习的三个Loader以外,还有一个基本的加载器:TextLoader。它的作用是读取文本文件,并将全部内容放入一个Document对象中。TextLoader是一个简单的文档加载器,可以加载文本文件内容,但无论文本文件有多大,它的返回值是仅有一个对象的list,若文本文件很大,则放在一个Document对象中就不太合适了,因此就需要文本分割器。
RecursiveCharacterTextSplitter(递归字符文本分割器),是LangChain 官方推荐的分割器,它会按自然段落分割大文档。它在保持上下文完整性和控制片段大小之间实现了良好平衡,开箱即用效果佳。
有递归字符文本分割器的TextLoader演示代码如图:

结果如图:


PyPDFLoader
LangChain内支持许多PDF的加载器,我们选择其中的PyPDFLoader使用。PyPDFLoader加载器,依赖PyPDF库。需要安装:pip install pypdf。
PyPDFLoader演示代码如图:

向量数据库与RAG问答闭环
向量存储、删除与检索
前面已经学过,LangChain的运行包括两部分,读数据和提问:读数据:从各个地方读数据,转成向量放入向量数据库中;提问:把问题转成向量,在向量数据库中进行检索,把相关数据一起合并为prompt交给模型,然后回答。前面已经学习了如何把文本转成向量和检索的相似度算法原理,那么对向量有什么操作呢?
A、文本转向量(前文已经学习过),可以使用add_documents。
B、删除向量,可以使用delete。
C、相似性向量检索,可以使用similarity_search。
在内存中进行向量存储、删除、检索的演示代码如图:


结果如图:


Chroma持久化向量数据库
使用外部向量的持久化存储,可以使用Chroma数据库,这是一个轻量级的基于SQL light的数据库。使用外部向量的持久化存储的演示代码如图:


只需要把内存存储的代码中的vector_store改成如下:
vector_store=Chroma(
collection_name=”test”, #给向量存储起一个名字,可以认为是数据库的表名
embedding_function=DashScopeEmbeddings(), #使用哪一个文本转向量的模型,DashScopeEmbeddings是阿里云千问官方提供的模型
persist_directory=”./chromadb” #指定数据存储的文件夹
)
并增加from langchain_chroma import Chroma,就可以了。
检索结果注入提示词的RAG问答
用户提问,模型先从向量数据库中检索与提问相关的结果,将此结果和用户的提问一起封装到提示词模版中,一起去询问模型,代码如图:


结果如图:


上面的代码,可以进行简写,把向量的检索也入链,代码如下:


与AI智能体的多轮对话:RAG检索问答
第四轮对话:RAG检索问答
“现在要把向量检索也并入链中:先从Chroma向量库检索与问题相关的文本块,再把检索结果和问题一起封装进提示词模板交给模型回答,请给出完整代码,并用简洁的检索器链式写法。”
from langchain_chroma import Chroma
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
# 打开持久化向量库
vec_db = Chroma(
collection_name="company_docs",
embedding_function=DashScopeEmbeddings(),
persist_directory="./chroma_db"
)
# 提示词模板:注入检索到的资料
rag_tpl = ChatPromptTemplate.from_messages([
("system", "你是一名严谨的智能客服助手,请依据下方资料回答:\n{ctx_docs}"),
("human", "用户tiwen:{user_ask}")
])
llm = ChatTongyi(model="qwen3-max", streaming=True)
# 检索器入链:问题先检索,再与原文一起交给模型
rag_chain = {"ctx_docs": vec_db.as_retriever(), "user_ask": RunnablePassthrough()} | rag_tpl | llm
answer = rag_chain.invoke("订单退款需要几天到账?")
print(answer.content)
RAG项目实战:工程结构
项目结构如前述目录截图所示,各文件职责如下:
A、app_file_upload.py:主要用于上传文件,进行知识库的更新。
B、app_qa.py:项目的主程序,会启动一个web页面,在web中可以进行对话。
C、config_data.py:配置文件。
D、file_history_store.py:长期会话记忆存储服务。
E、knowledge_base.py:知识库更新服务。
F、rag.py:rag的核心服务。
G、vector_store.py:向量存储服务。
H、data文件夹:是模型的私有知识。
I、chroma_db文件夹:向量数据库。
J、chat_history:记录了长期的会话记忆。
Agent智能体:从会回答到会做事
什么是智能体
智能体Agent:指一种能够自主规划、决策、执行任务的组件,核心是让大语言模型(LLM)根据任务需求,选择并调用工具,完成单靠模型自身无法解决的复杂问题。大模型只负责思考应该用什么工具,由agent去完成工具的调用,并且把结果给大模型,从而返回更好地结果。
可以认为Agent智能体 = 大语言模型(大脑) + 工具集(手脚) + 决策逻辑(思维)。Agent是让 LLM 从”只会回答”升级为”会做事(影响现实世界)”的智能助手。
Agent的作用:A、没有Agent时,LLM只能基于自身训练数据回答问题,很难处理遇到需要实时数据、复杂计算、外部工具调用的场景。B、有了Agent后,LLM就像一个指挥官,能思考任务步骤→选择合适工具→执行工具调用→根据结果调整策略,直到完成任务。
Agent的工作流程:用户需求提交给智能体后,它不会直接去回复,而是先做规划,拆解目标为可执行的子任务,再动态调度搜索引擎、计算器、数据库等工具协同工作;而且在回答的整个流程中还可以保存短期的记忆,根据保存的记忆,动态地调整后续工具的调用,从而更好回答完成用户的问题。
Agent和普通chain的区别:
| 对比维度 | 普通 Chain | Agent智能体 |
|---|---|---|
| 执行流程 | 固定,按预设步骤运行 | 动态,根据任务和结果自主调整 |
| 工具调用 | 路径写死在代码里 | 工具选择由LLM(大语言模型)思考决定 |
| 适用任务 | 简单、标准化的任务 | 复杂、多步骤、需要决策的任务 |
agent示范代码如下:

agent智能体也可以流式输出,代码如图:


ReAct思考与行动框架
Agent ReAct是大模型智能体的核心思考与行动框架,全称 Reasoning + Acting(推理+行动),是让 Agent 像人类一样遇到问题之后,先思考问题,再制定策略,再执行行动,再验证结果。简单来说:ReAct 让 Agent 不再是直接回答问题,而是通过自然语言思考过程指导工具调用,一步步解决复杂问题,完美适配需要多步推理、工具协作的场景。
一个典型的ReAct 范式的 Agent工作流程:
①思考(Reasoning):分析问题,判断现有信息是否足够,决策是否需要调用外部工具获取更多信息用来回答。
②行动(Action):基于模型决策的结果,执行思考阶段指定的策略,调用工具获取信息。
③观察(Observation):获取行动的结果,提取有效信息,获取工具返回值并判断工具是否正常工作,为下一轮思考提供信息。
④观察之后,(再)思考 → (再)行动 → (再)观察 → 循环往复,直到结束。
LangChain 的 Agent 对象遵循 ReAct 框架要求,在执行的过程中会持续的自我思考、自我行动、自我观察。Agent的前几个代码都遵循ReAct框架。
> 答辩高频提问:Agent与普通Chain的本质区别是什么?标准回答:普通Chain的执行流程固定、按预设步骤运行,适合标准化任务;Agent的执行流程动态,工具选择由大模型思考决定,适合复杂、多步骤、需要决策的任务。
middleware中间件
middleware中间件的作用是对智能体的每一步工作进行控制和自定义的执行。
作用场景:A、日志记录、分析、调试;B、转换提示词、工具选择;C、重试、备用、提前终止等逻辑控制;D、安全防护、个人身份检测等。
作用时间:1. Agent执行前;2. Agent执行后;3. 模型执行前;4. 模型执行后;5. 模型执行中;6. 工具执行中。以上六个时间,middleware中间件都可以对智能体的工作进行拦截,然后进行自定义的工作。
LangChain中内置了一些基础的中间件,具体见:https://docs.langchain.com/oss/python/langchain/middleware/built-in
中间件通过Hooks钩子来实现拦截,自定义中间件可以简单的使用装饰器来定义。
节点式钩子(执行点顺序拦截):A、before_agent: agent 执行之前拦截;B、after_agent: agent 执行后拦截;C、before_model: 模型执行前拦截;D、after_model: 模型执行后拦截。
针对工具和模型的包装式钩子:A、wrap_model_call: 每个模型调用时候拦截;B、wrap_tool_call: 每个工具调用时候拦截。
总结
核心问题与解决方案
问题一:如何安全调用云端模型并在本地部署大模型?
解决方案: 通过环境变量隐藏API密钥(OPENAI_API_KEY用于openai库,DASHSCOPE_API_KEY用于langchain库),使用OpenAI官方SDK的base_url参数切换服务商;本地部署使用Ollama,4G显存可运行8b以内蒸馏模型,8G显存可运行14b以内。
问题二:如何降低模型幻觉、让回答有据可依?
解决方案: 采用RAG检索增强生成技术,离线模块完成文档分割、嵌入向量化并存入向量数据库(内存或Chroma持久化);实时模块把问题转成向量做相似度检索,将检索到的资料与问题合并为提示词交给模型,即RAG = 检索技术 + LLM提示。
问题三:如何让大模型从”只会回答”升级为”会做事”?
解决方案: 构建Agent智能体(大语言模型+工具集+决策逻辑),基于ReAct框架循环执行思考、行动、观察,并用middleware中间件在Agent执行前后、模型执行前后等六个时间点拦截控制执行过程。
技术创新与业务价值
1. 提出”配置→提示→数据→框架→记忆→文档→向量→问答→智能体”的九步递进式学习链路,把散落的知识点整合为一条可落地的工程路径。
2. 通过RAG检索增强生成同时解决知识时效性、领域知识缺乏与幻觉三大痛点,相比微调无需重新训练模型,只需更新知识库,成本更低、效率更高。
3. 将内存会话记忆升级为基于JSON本地文件的长期记忆,程序退出后历史对话不丢失,为生产系统提供可持久化的会话方案。
4. 基于ReAct框架与middleware中间件实现智能体的可观测、可控制执行,为客服问答、文档检索等业务场景提供可复用的工程骨架。
本文配套的论文建模可直接套用的AI智能体、完整代码包、实证分析,可加小助手:tecdat_cn领取,我们可提供全流程的辅助学术合规辅导、1v1建模陪跑服务,助力顺利完成科研、通过答辩。

每日分享最新报告和数据资料至会员群
关于会员群
- 本会员社群以垂直产业数据研究、深度行业报告分享、AI数据工具实操交流为核心定位;
- 入群即可解锁全行业数据内容免费阅读与下载权限,同步更新海内外一手优质研究报告文档与产业数据;
- 会员老用户享受专属 9 折续费优惠,可长期锁定社群全部权益;
- 为会员提供一对一免费 PDF 报告专属代找服务。
非常感谢您阅读本文,如需帮助请联系我们!

Python定制模拟退火与马科维茨期望-方差组合优化模型进行农作物种植策略规划|附AI智能体、代码和数据
2026年中国AI芯片发展趋势报告:散热替代图谱 | 附100+报告、数据合集下载
视频:开发CNN‑LSTM组合融合SVR、LSTM、岭回归模型预测黄金现货价格
Python开发稀疏PCA、Lasso回归与Fama-French三因子模型融合的A股投资组合优化|附AI智能体、代码和数据

