AI之_4_大模型实战
前言
Github:https://github.com/HealerJean
一、名词解释
1、LLM 大语言模型
解释:大语言模型(Large Language Model,简称 LLM)是一种基于深度学习的人工智能系统,专门用于理解和生成人类语言。它通过在海量文本数据上进行训练,学习语言的统计规律和语义结构,从而能够回答问题、创作文本、翻译语言,甚至进行逻辑推理。
LLM 的“大”主要体现在参数量巨大——从数十亿到数千亿不等。这种规模带来了所谓的“涌现能力”:当模型足够大时,它会突然展现出小模型不具备的理解力、泛化能力和上下文推理能力。就像你描述的那样,给它一个输入(“刺激”),它会根据内部学到的知识和概率分布,生成一个看似合理、有时甚至富有创意的回应。而且,即使是相同的输入,由于采样策略或随机性,输出也可能略有不同。
关键特点:
- 理解和生成自然语言
- 回答基于概率和训练数据
- 不具备记忆、目标或行动能力(除非被设计成有)
- 举例说明:
- 你问
LLM:“北京明天天气怎么样?” LLM回答:“抱歉,我无法获取实时天气信息。”
- 你问
- 举例说明:
2、Agent:智能体
解释:Agent(智能体),它是构建在 LLM 之上的更高级系统。如果说 LLM 是“大脑”,能理解与表达,那么 Agent 就是“有行动能力的个体”。它不仅能理解用户意图,还能结合历史对话、用户身份、当前环境等信息,自主规划任务、调用工具(比如查天气、订机票)、做出决策并执行操作,实现端到端的智能服务。可以说,Agent 让 LLM 从“会说话”进化到“会做事”。
Agent 是在 LLM 基础上构建的“智能系统”。它不仅理解语言,还能自主规划、调用工具、执行任务,就像一个有执行力的助理。
关键特点:
- 能感知上下文(你是谁、之前聊过什么)
- 能设定目标(比如“帮我订一张机票”)
- 能调用外部工具(查天气、发邮件、操作数据库等)
- 能分步骤完成复杂任务
举例说明::你对 Agent 说:“我想知道北京明天天气,如果下雨就提醒我带伞。”,Agent 会这样做:
-
理解意图:用户想知道天气,并有条件判断;
-
调用工具:自动连接天气
API,获取北京明天的预报; -
判断结果:发现预报是“中雨”;
-
执行动作:回复你:“北京明天有雨,记得带伞!”

3、MCP:模型上下文协议
MCP是 Agent 与外部世界沟通的“语言规范”。
Agent是“谁在做事”,MCP是“怎么规范地做事”。MCP为Agent提供了与外部世界安全、高效、标准化交互的桥梁。
| 角色 | 职责 | 依赖关系 |
|---|---|---|
Agent |
决策者 + 执行者:理解用户意图 → 规划步骤 → 调用工具 → 整合结果 | 需要 MCP(或类似协议)来规范它如何调用外部工具 |
MCP |
通信规则制定者:定义“怎么调用工具”“怎么传参数”“怎么返回结果” | 为 Agent 提供标准化、安全的工具调用接口 |
举例说明:假设你有一个 旅行规划 Agent,任务是:“帮我订一张下周从北京到上海的 cheapest 机票。”
没有 MCP(传统方式):
- 开发者要为这个
Agent硬编码如何调用某个特定机票API; - 如果换一个航班平台,就得重写代码;
- 安全性和错误处理也各不相同。
二、Ollama
Ollama是一个开源项目,旨在简化在本地设备(如笔记本电脑或个人服务器)上运行大型语言模型(LLMs)的过程。它最初由 Jeffrey Huang 和其他贡献者开发,目标是让开发者、研究人员和爱好者能够轻松地下载、运行和与各种开源大模型进行交互,而无需依赖云端服务。
1、主要特点:
- 本地运行:
Ollama允许用户在自己的机器上直接运行 LLM,无需联网,保障数据隐私和安全性。 - 支持多种模型: 支持包括
Llama、Llama2、Llama3、Mistral、Gemma、Phi、Qwen等主流开源模型 - 一键部署: 通过简单的命令(如
ollama run llama3),即可自动下载并运行指定模型,极大降低使用门槛。 - 跨平台支持: 提供
macOS、Linux 和 Windows(通过 WSL 或原生支持)版本。 - API 接口:
Ollama提供 RESTful API,方便集成到其他应用中,例如 Web 应用、桌面工具或自动化脚本。 - 模型管理: 用户可以轻松列出、拉取、删除和切换模型,例如:
2、模型管理
http://localhost:11434
ollama list
ollama pull qwen:7b
ollama run llama3
ollama rm llama2
3、代码示例
from ollama import Client
client = Client()
# 注意:messages 是一个列表,每个元素是 {"role": "...", "content": "..."}
response = client.chat(
model='qwen3:14b',
messages=[{'role': 'user', 'content': '写一首关于春天的诗'}]
)
print(response['message']['content'])
└─[$] python qwen_first.py [20:29:35]
《春的十四行》
风在解冻的河面写下第一行诗
柳条垂钓起碎银般的光
泥土正翻动潮湿的舌根
蚯蚓的篆书在黑暗里发芽
候鸟衔来更南方的暖
冰层裂开处
蒲公英举起毛茸茸的灯盏
照亮所有被寒冬遗忘的门牌号
孩子们奔跑时
风筝割裂了天空的绸缎
而老槐树正在数算年轮
那些被蝉声蛀空的黄昏
我站在季节的门槛上
听见万物正在重新排列
蝴蝶翅膀扇动的涟漪里
有整个宇宙的星辰正在发芽
4、FAQ
1)Ollama 的“强大管理能力”从何而来?
Ollama的“强大”主要体现在 易用性、抽象封装和本地运行优化,而不是依赖云端。具体来说:
-
模型已经“训练好了”
-
像
qwen:7b这类模型,是在阿里云的超级计算机上用海量数据提前训练好的。 -
Ollama只是把训练好的“成品大脑”(权重文件)下载到你电脑上。 -
所以推理(即回答问题)时,不需要再联网学习或查询,直接用已有知识生成答案。
-
-
本地推理引擎高度优化
-
Ollama内部集成了高效的推理后端(如 llama.cpp),专门针对 CPU/GPU 做了优化。 -
支持 Metal(
Mac)、CUDA(NVIDIA)、AVX 等硬件加速,让大模型在笔记本上也能跑得动。 -
所有计算都在你设备上完成 → 无需网络,保护隐私,响应快。
-
-
“管理功能”是本地程序逻辑,不依赖云端
-
ollama list、ollama run、ollama rm这些命令只是在操作你本地的文件和进程。 -
模型元数据、配置、缓存都存在你电脑上(如
~/.ollama/目录)。 -
就像 Docker 管理镜像一样:拉取一次,之后全本地操作。
-
-
API 也是本地服务
-
当你运行
ollama serve(默认自动启动),它会在http://localhost:11434启动一个本地 Web 服务。 -
其他程序(如 Python 脚本、VS Code 插件)通过这个本地 API 与模型通信,全程不经过互联网。
-
2)为什么有些 AI 必须联网
Ollama 的“强大”不是靠实时联网获取能力,而是:把强大的 AI 模型“装进你的电脑”,让你拥有一个私有的、随时可用的 AI 助手。
- 联网
AI:像打电话问专家——每次都要拨号(联网),等对方回答。 Ollama本地模型:像请了一位专家住你家里——不用打电话,随时面对面交流。
| 类型 | 是否需要联网 | 原因 |
|---|---|---|
Ollama(本地 LLM) |
仅下载时需要 | 模型完整部署在本地 |
ChatGPT / 文心一言 / 通义app |
必须联网 | 模型在服务器上,你的输入要传到云端处理 |
3)几个 GB 的文件,却能“记住”海量知识、回答各种问题
几个
GB不是“知识的总量”,而是“生成知识的能力”的压缩包
| 原因 | 说明 |
|---|---|
| 参数编码知识 | 知识以权重形式分布式存储在 70 亿参数中 |
| 高度压缩 | 压缩冗余、提取共性,用少量参数捕捉大量语义。 |
| 泛化能力 | 学的是“规律”而非“原文”,能举一反三 |
| 语言冗余低 | 模型只需捕捉核心语义,无需存储全部文本 |
三、让 AI 阅读一本书
基于本地大模型(Ollama + Qwen3)的 RAG(Retrieval-Augmented Generation,检索增强生成)系统,用于“让 AI 阅读并理解你的一本 PDF 书
1、整体流程(5 步)
1)第 1 步:加载 PDF 文档
# 2. 加载文档
print("正在加载 PDF...")
documents = SimpleDirectoryReader(input_files=[pdf_path]).load_data()
if not documents:
raise FileNotFoundError(f"未加载到任何内容,请检查 PDF 路径:{pdf_path}")
print(f"✅ 成功加载 {len(documents)} 个文本块")
- 作用:把
PDF文件解析成纯文本。 - 背后技术:
SimpleDirectoryReader默认使用pymupdf(即fitz)解析PDF。- 自动将长文档切分成多个“文本块”(
chunks),每个块约几百字。- 自动分块是为了后续向量化和检索时更高效。
- 输出:一个
Document对象列表,每个对象包含一段书中的文字。
2)第 2 步:文本向量化(Embedding)
embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-zh-v1.5")
- 作用:把每一段文字转换成一串数字(向量),语义相近的句子向量也相近。
- 为什么需要?
- 计算机无法直接“理解”文字,但可以计算向量之间的距离。
- 后续提问时,系统会把你的问题也转成向量,然后找书中“最相似”的段落。
- 模型选择:
BAAI/bge-small-zh-v1.5是专为中文优化的小型嵌入模型,速度快、精度高。- 支持中文语义匹配,适合国内用户使用。
3)第 3 步:构建向量索引(Vector Index)
# 4. 构建索引
index = VectorStoreIndex.from_documents(
documents,
embed_model=embed_model
)
- 作用:将所有文本块的向量存储在一个高效的结构中(默认是内存中的向量数据库),支持快速搜索。
- 结果:形成一个“智能搜索引擎”,以后提问时能快速找到相关上下文。
- 内部机制:
- 所有文本块被编码为向量。
- 存入索引后,可通过余弦相似度快速检索。
4)第 4 步:设置本地大模型(LLM)
llm = Ollama(model="qwen2:7b",
request_timeout=600.0,
base_url="http://127.0.0.1:11434" )
- 作用:指定用于生成最终答案的大语言模型。
Ollama是什么?- 一款可在本地运行大模型的工具,无需联网、不产生费用。
- 支持多种开源模型,包括通义千问系列。
- 模型说明:
qwen2:7b:通义千问第二代 70 亿参数版本,推理能力强,适合中文场景。- 若你想用更强的模型,可替换为
qwen3:14b(需提前下载)。
- 注意:
- 必须先启动
Ollama服务:ollama run qwen2:7b - 或者在终端运行:
ollama serve启动服务端口11434
- 必须先启动
5)第 5 步:提问 & 回答(RAG 核心)
response = query_engine.query("这本书主要讲了哪些 Java 开发规范?")
print("\n🤖 AI 回答:", response.response)
这一步内部发生了 两阶段操作:
a、阶段 1:检索(Retrieval)
- 把问题 “这本书讲了什么?” 转换成向量(使用
embed_model)。 - 在向量索引中搜索与之最相似的 Top-K 个文本块(如
similarity_top_k=5)。 - 返回这些文本块作为上下文。
b、阶段 2:生成(Generation)
-
构造一个提示(
Prompt)给 ·Qwen3 ·模型,例如:根据以下上下文回答问题: 【上下文】 - 阿里巴巴 Java 手册规定:常量命名全部大写... - 异常不要用 Exception 捕获,要具体... 【问题】 这本书讲了什么? 【回答】 - 将此
Prompt发送给Qwen2:7b模型,由其生成自然语言回答。 - 最终返回的是结合真实内容的、准确且流畅的回答。
2、示例代码
# rag_book.py
import os
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
from llama_index.llms.ollama import Ollama
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
# 启用 HF 镜像(加速中文模型下载)
os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"
# 1. 设置文档路径(单个 PDF 文件)
pdf_path = "/Users/zhangyujin1/Desktop/阿里巴巴-Java开发手册(详尽版).pdf"
# 2. 加载文档
print("正在加载 PDF...")
documents = SimpleDirectoryReader(input_files=[pdf_path]).load_data()
if not documents:
raise FileNotFoundError(f"未加载到任何内容,请检查 PDF 路径:{pdf_path}")
print(f"✅ 成功加载 {len(documents)} 个文本块")
# 3. 设置嵌入模型(使用中文优化模型)和 LLM
embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-zh-v1.5")
# 使用 Ollama 官方支持的真实存在的模型
llm = Ollama(model="qwen2:7b",
request_timeout=600.0,
base_url="http://127.0.0.1:11434" )
# 4. 构建索引
index = VectorStoreIndex.from_documents(
documents,
embed_model=embed_model
)
# 5. 查询(此时会连接 Ollama,确保服务已运行!)
query_engine = index.as_query_engine(
llm=llm,
similarity_top_k=5, # 默认是 2,改为 10
response_mode="compact", # 合并多个 chunk 再回答(推荐)
verbose=True # 开启日志,看传给 LLM 的 prompt
)
response = query_engine.query("这本书主要讲了哪些 Java 开发规范?")
print("\n🤖 AI 回答:", response.response)
─[$] python qwen_first.py [16:36:20]
正在加载 PDF...
✅ 成功加载 42 个文本块
2026-01-14 17:28:22,519 - INFO - Load pretrained SentenceTransformer: BAAI/bge-small-zh-v1.5
2026-01-14 17:28:30,336 - INFO - 1 prompt is loaded, with the key: query
2026-01-14 17:28:31,589 - INFO - HTTP Request: POST http://127.0.0.1:11434/api/show "HTTP/1.1 200 OK"
2026-01-14 17:28:50,912 - INFO - HTTP Request: POST http://127.0.0.1:11434/api/chat "HTTP/1.1 200 OK"
🤖 AI 回答: 这本书主要讲述了以下几方面的Java开发规范:
1. **命名风格**:指导如何以清晰、统一的方式对类名、变量名和方法名进行命名。
2. **常量定义**:关于如何合理地定义并使用常量,确保代码的可读性和维护性。
3. **代码格式**:提供一些建议来提高代码的一致性和可读性,比如空格、缩进等的使用标准。
4. **面向对象(OOP)规约**:关于类、继承和封装的最佳实践。
5. **集合处理**:对使用如ArrayList、HashMap等Java集合框架时的建议和规范。
6. **并发处理**:推荐如何在多线程环境下编写安全且高效的代码,避免常见错误。
7. **控制语句**:指导如何正确使用循环、条件判断等结构化代码部分,以提高程序的清晰度和效率。
8. **注释规约**:关于何时和如何添加注释来增强代码文档性和可维护性。
9. **异常处理**:提供错误处理策略和日志记录的最佳实践。
10. **单元测试**:强调编写可测试代码的重要性,及如何进行有效的单元测试以确保软件质量。
此外,书中还涉及了一些更具体的主题如MySQL数据库的建表规则、索引优化、SQL语句规范、对象关系映射(ORM)方法、工程结构的设计、安全规约等。整体上,该书旨在为Java开发者提供一套全面的编程指南和最佳实践集合,帮助他们编写高效且高质量的代码。
三、LangChain 多步骤任务自动化
实现多个
LLM调用之间的逻辑链式调用,避免手动拼接提示词。
我们要完成一个 三步自动化流程:
- 改写:把口语化中文 → 正式书面语
- 翻译:正式中文 → 英文
- 生成标题:根据英文内容 → 生成英文标题
这三步像流水线一样自动执行,前一步的输出就是后一步的输入。LangChain 用 “Runnable 管道” 实现这种自动化。
1、整体流程(7 步)
1)第 1 步:导入必要的模块
from langchain_ollama import OllamaLLM
from langchain_core.prompts import PromptTemplate
OllamaLLM:LangChain官方为Ollama提供的 **大模型接口类PromptTemplate:用于构建 带变量的提示词模板,比如"请总结:{text}",其中{text}是占位符。
2)第 2 步:初始化大模型
llm = OllamaLLM(
model="qwen2:7b",
base_url="http://127.0.0.1:11434"
)
model="qwen2:7b":指定使用本地 Ollama 中的qwen2:7b模型。base_url="http://127.0.0.1:11434":Ollama 默认 API 地址(确保你运行了ollama serve)。- 这个
llm对象本身就是一个 可调用的Runnable,你可以直接llm.invoke("你好")。
3)第 3 步:定义三个任务的提示模板
a、任务1:改写为正式中文
rewrite_prompt = PromptTemplate.from_template(
"请将以下内容改写为正式、书面化的中文:\n{text}"
)
-
.from_template()是快捷方式,自动识别{text}作为输入变量。 -
当你调用
rewrite_prompt.invoke({"text": "今天好开心"}),它会生成:请将以下内容改写为正式、书面化的中文: 今天好开心
b、任务2:翻译成英文
translate_prompt = PromptTemplate.from_template(
"请将以下中文内容准确翻译为英文:\n{text}"
)
- 同样,
{text}是占位符,会被上一步的输出填充。
c、任务3:生成英文标题
title_prompt = PromptTemplate.from_template(
"Based on the following text, generate a concise and informative English title:\n{text}"
)
- 这里用英文提示,因为输入已经是英文(来自上一步)。
- 所有
Prompt都使用 相同的输入变量名text,这是为了管道能自动传递数据!
4)第 4 步:构建每一步的“小链”
rewrite_chain = rewrite_prompt | llm
translate_chain = translate_prompt | llm
title_chain = title_prompt | llm
-
关键概念:**
|**管道操作符- 在
LangChain新版中,prompt | llm表示:- “先用
prompt格式化输入,再把结果喂给 LLM”,这会自动创建一个Runnable序列,等价于:
- “先用
def rewrite_chain(input_dict): formatted_prompt = rewrite_prompt.invoke(input_dict) return llm.invoke(formatted_prompt) - 在
5)第 5 步:串联所有步骤(核心!)
full_chain = rewrite_chain | translate_chain | title_chain
a、数据流如何工作?
假设输入是:
{"text": "今天我去了公园,看到好多花开了,心情特别好。"}
LangChain 会自动执行:
- Step 1:
rewrite_chain.invoke({"text": "今天我去了公园..."})- → 输出:
"今日前往公园,目睹繁花盛开,心情愉悦。"
- → 输出:
- Step 2:
LangChain自动把上一步的输出 作为{"text": "今日前往公园..."}- 传给下一步
translate_chain.invoke({"text": "今日前往公园..."}) - → 输出:
"Today I went to the park and saw many flowers in bloom, feeling especially happy."
- 传给下一步
- Step 3: 再把英文句子传给标题生成器
title_chain.invoke({"text": "Today I went to the park..."})- → 输出:
"A Joyful Stroll Through a Blooming Park"
- → 输出:
6)第 6 步:执行整个链
input_text = "今天我去了公园,看到好多花开了,心情特别好。"
final_title = full_chain.invoke({"text": input_text})
- 注意:必须传字典
{"text": ...},因为Prompt模板期待text变量。 .invoke()是LangChain新版统一的同步调用方法(旧版用.run())。
7)第 7 步:打印结果
print("\n🎯 最终输出(生成的英文标题):")
print(final_title)
输出就是第三步的结果 —— 一个英文标题!
2、示例代码
# multi_step_chain.py
from langchain_ollama import OllamaLLM
from langchain_core.prompts import PromptTemplate
# === 第 1 步:初始化大模型 ===
llm = OllamaLLM(
model="qwen2:7b",
base_url="http://127.0.0.1:11434"
)
# === 第 2 步:定义三个任务的提示模板 ===
# 任务1:改写为正式语气
rewrite_prompt = PromptTemplate.from_template(
"请将以下内容改写为正式、书面化的中文:\n{text}"
)
# 任务2:翻译成英文
translate_prompt = PromptTemplate.from_template(
"请将以下中文内容准确翻译为英文:\n{text}"
)
# 任务3:生成一个简洁的英文标题
title_prompt = PromptTemplate.from_template(
"Based on the following text, generate a concise and informative English title:\n{text}"
)
# === 第 3 步:构建多步骤链(使用 | 管道)===
# 每一步:Prompt + LLM → 自动组成 Runnable
rewrite_chain = rewrite_prompt | llm
translate_chain = translate_prompt | llm
title_chain = title_prompt | llm
# 串联合并:输入 → 改写 → 翻译 → 生成标题
full_chain = rewrite_chain | translate_chain | title_chain
# === 第 4 步:执行任务 ===
input_text = """
今天我去了公园,看到好多花开了,心情特别好。
"""
print("📝 原始输入:")
print(input_text)
# 执行整个流水线
final_title = full_chain.invoke({"text": input_text})
print("\n🎯 最终输出(生成的英文标题):")
print(final_title)
└─[$] python qwen_first.py [18:42:19]
📝 原始输入:
今天我去了公园,看到好多花开了,心情特别好。
🎯 最终输出(生成的英文标题):
"Joyous Encounter with Blooming Flowers at the Park"
五、工具集成 —— 让 AI 能上网、会计算
1、整体流程(5 步)
1)第 1 步:初始化本地大模型与工具
from langchain_community.llms import Ollama
from langchain_community.utilities import GoogleSearchAPIWrapper
from langchain_community.tools import Calculator
from langchain.tools import Tool
# 初始化模型
llm = Ollama(model="qwen2:7b", request_timeout=600.0, base_url="http://127.0.0.1:11434")
# 工具1:Google 搜索(需设置环境变量)
search = GoogleSearchAPIWrapper()
search_tool = Tool(
name="Search",
func=search.run,
description="用于查询实时信息,如新闻、天气、事件等"
)
# 工具2:计算器
calc_tool = Tool(
name="Calculator",
func=Calculator().run,
description="执行数学运算,支持加减乘除、括号等"
)
- 作用:赋予 AI 调用外部能力的“手和眼”。
- 依赖说明:
google-search-results需安装:pip install google-search-results- 必须设置
GOOGLE_CSE_ID和GOOGLE_API_KEY环境变量。
- 替代方案:若无 Google API,可用
DuckDuckGoSearchRun(无需密钥)。
2)第 2 步:构建 Agent 的决策提示词
from langchain_core.prompts import PromptTemplate
# 定义 Agent 的思考模板
prompt = PromptTemplate.from_template(
"""你是一个智能助手,可以使用以下工具:
{tools}
请按以下格式回答:
Question: 用户的问题
Thought: 你是否需要使用工具?为什么?
Action: 要使用的工具名称(只能选 {tool_names})
Action Input: 工具的输入
Observation: 工具返回的结果
...(可重复多次)
Final Answer: 最终答案
Begin!
Question: {input}
"""
)
- 作用:引导模型按照 ReAct(Reason + Act)范式进行推理。
- 关键字段:
{tools}:工具列表及其描述{tool_names}:允许使用的工具名(防止幻觉调用不存在的工具)
- 原理:通过结构化提示,强制模型“先思考再行动”。
3)第 3 步:创建 Zero-Shot Agent
from langchain.agents import create_react_agent, AgentExecutor
# 创建 ReAct Agent
agent = create_react_agent(
llm=llm,
tools=[search_tool, calc_tool],
prompt=prompt
)
# 创建执行器
agent_executor = AgentExecutor(
agent=agent,
tools=[search_tool, calc_tool],
verbose=True,
handle_parsing_errors=True # 自动处理格式错误
)
- 作用:将模型、工具、提示词组装成可执行的智能体。
- Agent 类型:
create_react_agent基于 ReAct 框架,适合复杂任务。- 支持多轮“思考-行动-观察”循环。
- 安全机制:
handle_parsing_errors=True防止因格式错误崩溃。
4)第 4 步:准备测试问题
test_questions = [
"123 * 456 + 789 / 3 是多少?", # 需要计算
"Python 是谁发明的?" # 可直接回答(无需工具)
]
- 设计原则:
- 覆盖“需工具”、“无需工具”两种场景。
- 验证
Agent的决策能力。
5)第 5 步:执行并分析决策路
for i, question in enumerate(test_questions, 1):
print(f"\n🔍 问题 {i}: {question}")
try:
response = agent_executor.invoke({"input": question})
print(f"✅ 回答: {response['output']}")
except Exception as e:
print(f"❌ 错误: {e}")
-
典型输出(以计算为例):
Thought: 这是一个数学问题,我需要使用 Calculator。 Action: Calculator Action Input: 123 * 456 + 789 / 3 Observation: 56271.0 Final Answer: 结果是 56271。
2、示例代码
# lesson7_tools.py
import os
from langchain_community.llms import Ollama
from langchain_community.tools import Calculator
from langchain.tools import Tool
from langchain_core.prompts import PromptTemplate
from langchain.agents import create_react_agent, AgentExecutor
# === 第 1 步:初始化本地大模型与工具 ===
llm = Ollama(model="qwen2:7b", request_timeout=600.0, base_url="http://127.0.0.1:11434")
# 仅使用计算器(避免 Google API 依赖)
calc_tool = Tool(
name="Calculator",
func=Calculator().run,
description="执行数学运算,支持加减乘除、括号等"
)
tools = [calc_tool]
# === 第 2 步:构建 Agent 的决策提示词 ===
prompt = PromptTemplate.from_template(
"""你是一个智能助手,可以使用以下工具:
{tools}
请按以下格式回答:
Question: 用户的问题
Thought: 你是否需要使用工具?为什么?
Action: 要使用的工具名称(只能选 {tool_names})
Action Input: 工具的输入
Observation: 工具返回的结果
...(可重复多次)
Final Answer: 最终答案
Begin!
Question: {input}
"""
)
# === 第 3 步:创建 Zero-Shot Agent ===
agent = create_react_agent(llm=llm, tools=tools, prompt=prompt)
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
handle_parsing_errors=True
)
# === 第 4-5 步:测试问题并执行 ===
test_questions = [
"123 * 456 + 789 / 3 是多少?",
"地球到月球的距离大约是多少公里?", # 模型应直接回答(无搜索工具)
]
for i, question in enumerate(test_questions, 1):
print(f"\n🔍 问题 {i}: {question}")
try:
response = agent_executor.invoke({"input": question})
print(f"✅ 回答: {response['output']}")
except Exception as e:
print(f"❌ 错误: {e}")
五、对话记忆 —— 让 AI 记住你是谁
1、整体流程(5 步)
1)第 1 步:初始化带记忆的聊天链
from langchain_community.llms import Ollama
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain
llm = Ollama(model="qwen2:7b", request_timeout=600.0, base_url="http://127.0.0.1:11434")
# 创建记忆缓冲区(最多保留最近 4 条消息)
memory = ConversationBufferMemory(
memory_key="history",
max_len=4,
return_messages=True
)
# 创建对话链
conversation = ConversationChain(
llm=llm,
memory=memory,
verbose=True
)
- 作用:在多轮对话中保留上下文,避免“失忆”。
- 记忆类型:
ConversationBufferMemory:最简单,保存完整历史。max_len=4限制内存占用,防止长对话拖慢响应。
- 底层机制:每次调用时,自动将历史消息拼接到提示词中。
2)第 2 步:模拟用户身份声明
response1 = conversation.predict(input="你好,我是张三,来自杭州。")
print("👤 用户:你好,我是张三,来自杭州。")
print("🤖 AI:", response1)
-
预期行为:AI 应记住“张三”和“杭州”。
-
提示词实际内容(隐式):
The following is a friendly conversation between a human and an AI. Current conversation: Human: 你好,我是张三,来自杭州。 AI:
3)第 3 步:测试身份记忆
response2 = conversation.predict(input="你是谁?")
print("\n👤 用户:你是谁?")
print("🤖 AI:", response2)
-
理想回答:应包含“张三”或“杭州”,如:
“我是AI助手,记得您是来自杭州的张三!”
-
失败表现:回答“我不知道”或忽略上下文。
4)第 4 步:测试长期记忆边界
for msg in ["今天天气怎么样?", "我喜欢吃西湖醋鱼。", "你记得我叫什么吗?"]:
resp = conversation.predict(input=msg)
print(f"\n👤 用户:{msg}")
print("🤖 AI:", resp)
- 验证点:
- 当对话超过 4 轮后,早期信息(如“张三”)是否被遗忘?
- 是否优先保留最近的关键信息?
5)第 5 步:查看内部记忆状态
print("\n🧠 当前记忆内容:")
for msg in memory.chat_memory.messages:
role = "👤" if msg.type == "human" else "🤖"
print(f"{role} {msg.content}")
- 作用:调试记忆是否按预期工作。
2、示例代码
# lesson8_memory.py
from langchain_community.llms import Ollama
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain
# === 第 1 步:初始化带记忆的聊天链 ===
llm = Ollama(model="qwen2:7b", request_timeout=600.0, base_url="http://127.0.0.1:11434")
memory = ConversationBufferMemory(
memory_key="history",
max_len=4,
return_messages=True
)
conversation = ConversationChain(llm=llm, memory=memory, verbose=False)
# === 第 2-4 步:多轮对话测试 ===
messages = [
"你好,我是张三,来自杭州。",
"你是谁?",
"今天天气怎么样?",
"我喜欢吃西湖醋鱼。",
"你记得我叫什么吗?"
]
for msg in messages:
resp = conversation.predict(input=msg)
print(f"👤 用户:{msg}")
print(f"🤖 AI:{resp}\n")
# === 第 5 步:打印当前记忆 ===
print("🧠 当前记忆内容:")
for msg in memory.chat_memory.messages:
role = "👤" if msg.type == "human" else "🤖"
print(f"{role} {msg.content}")
六、自主决策 Agent —— 让 AI 自己选动作
1、整体流程(5 步)
1)第 1 步:定义自定义业务工具
from langchain.tools import tool
@tool
def get_order_status(order_id: str):
"""根据订单号查询物流状态"""
mock_db = {"ORD1001": "已发货", "ORD1002": "正在打包"}
return mock_db.get(order_id, "订单未找到")
@tool
def cancel_order(order_id: str):
"""取消指定订单"""
return f"订单 {order_id} 已成功取消。"
- 作用:模拟企业内部系统接口。
- 设计原则:工具必须有清晰 docstring,便于 Agent 理解功能。
2)第 2 步:注册工具并初始化 Agent
from langchain_community.llms import Ollama
from langchain.agents import create_react_agent, AgentExecutor
from langchain_core.prompts import PromptTemplate
llm = Ollama(model="qwen2:7b", request_timeout=600.0, base_url="http://127.0.0.1:11434")
tools = [get_order_status, cancel_order]
prompt = PromptTemplate.from_template(
"Answer the following questions as best you can. You have access to the following tools:\n\n{tools}\n\nUse the following format:\n\nQuestion: ...\nThought: ...\nAction: ...\nAction Input: ...\nObservation: ...\n... (repeat N times)\nFinal Answer: ...\n\nBegin!\n\nQuestion: {input}\n"
)
agent = create_react_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
3)第 3 步:测试查询类任务
result = agent_executor.invoke({"input": "我的订单 ORD1001 到哪了?"})
print("✅ 查询结果:", result["output"])
4)第 4 步:测试操作类任务
result = agent_executor.invoke({"input": "帮我取消订单 ORD1002"})
print("✅ 操作结果:", result["output"])
5)第 5 步:测试模糊问题处理
result = agent_executor.invoke({"input": "我有个订单,但不知道编号"})
print("✅ 模糊问题处理:", result["output"])
2、示例代码
# lesson9_agent.py
from langchain.tools import tool
from langchain_community.llms import Ollama
from langchain.agents import create_react_agent, AgentExecutor
from langchain_core.prompts import PromptTemplate
# === 第 1 步:定义工具 ===
@tool
def get_order_status(order_id: str):
"""根据订单号查询物流状态"""
mock_db = {"ORD1001": "已发货,预计明天送达", "ORD1002": "正在打包"}
return mock_db.get(order_id, "订单未找到")
@tool
def cancel_order(order_id: str):
"""取消指定订单"""
return f"订单 {order_id} 已成功取消。"
# === 第 2 步:初始化 Agent ===
llm = Ollama(model="qwen2:7b", request_timeout=600.0, base_url="http://127.0.0.1:11434")
tools = [get_order_status, cancel_order]
prompt = PromptTemplate.from_template(
"Answer the following questions as best you can. You have access to the following tools:\n\n{tools}\n\nUse the following format:\n\nQuestion: the input question you must answer\nThought: you should always think about what to do\nAction: the action to take, should be one of [{tool_names}]\nAction Input: the input to the action\nObservation: the result of the action\n... (this Thought/Action/Action Input/Observation can repeat N times)\nThought: I now know the final answer\nFinal Answer: the final answer to the original input question\n\nBegin!\n\nQuestion: {input}\n"
)
agent = create_react_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# === 第 3-5 步:测试 ===
test_cases = [
"我的订单 ORD1001 到哪了?",
"帮我取消订单 ORD1002",
"我有个订单,但不知道编号"
]
for q in test_cases:
print(f"\n🔍 问题: {q}")
try:
res = agent_executor.invoke({"input": q})
print(f"✅ 回答: {res['output']}")
except Exception as e:
print(f"❌ 错误: {e}")
七、流式输出 + 提示工程 —— 打造真人级对话体验
1、整体流程(5 步)
1)第 1 步:设计角色化提示模板
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "你是一名耐心、专业的电商客服,名叫小智。请用亲切、简洁的中文回答用户问题。"),
("human", "{input}")
])
- 作用:通过 System Message 设定角色人格。
2)第 2 步:启用流式生成
from langchain_community.llms import Ollama
llm = Ollama(
model="qwen2:7b",
request_timeout=600.0,
base_url="http://127.0.0.1:11434",
streaming=True # 启用流式
)
3)第 3 步:构建流式调用链
from langchain_core.output_parsers import StrOutputParser
chain = prompt | llm | StrOutputParser()
4)第 4 步:实现逐字打印效果
import asyncio
async def stream_answer(question: str):
print(f"\n👤 用户:{question}")
print("🤖 小智:", end="", flush=True)
async for chunk in chain.astream({"input": question}):
print(chunk, end="", flush=True)
print()
5)第 5 步:运行多轮对话
if __name__ == "__main__":
questions = ["你们支持货到付款吗?", "退货怎么操作?"]
for q in questions:
asyncio.run(stream_answer(q))
2、示例代码
# lesson10_streaming.py
import asyncio
from langchain_community.llms import Ollama
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# === 第 1 步:角色化提示 ===
prompt = ChatPromptTemplate.from_messages([
("system", "你是一名耐心、专业的电商客服,名叫小智。请用亲切、简洁的中文回答用户问题。"),
("human", "{input}")
])
# === 第 2 步:启用流式 ===
llm = Ollama(
model="qwen2:7b",
request_timeout=600.0,
base_url="http://127.0.0.1:11434",
streaming=True
)
# === 第 3 步:构建链 ===
chain = prompt | llm | StrOutputParser()
# === 第 4-5 步:流式输出 ===
async def stream_answer(question: str):
print(f"\n👤 用户:{question}")
print("🤖 小智:", end="", flush=True)
async for chunk in chain.astream({"input": question}):
print(chunk, end="", flush=True)
print()
if __name__ == "__main__":
questions = [
"你们支持货到付款吗?",
"退货怎么操作?",
"能开发票吗?"
]
for q in questions:
asyncio.run(stream_answer(q))
八、让大模型稳定返回结构化JSON 输出
让大模型稳定返回结构化 JSON 输出,并理解“Fingerprint”机制,避免幻觉和格式错误。
1、整体流程(5 步)
1)第 1 步:启用 JSON 模式(Structured Output)
from langchain_community.llms import Ollama
from pydantic import BaseModel, Field
# 定义输出结构(Pydantic 模型)
class User(BaseModel):
name: str = Field(..., description="用户姓名")
age: int = Field(..., description="用户年龄")
city: str = Field(..., description="所在城市")
# 初始化模型并开启 JSON 模式
llm = Ollama(
model="qwen2:7b",
request_timeout=600.0,
base_url="http://127.0.0.1:11434",
format="json" # 关键:强制输出 JSON 格式
)
- 作用:确保模型输出符合预定义的
JSON结构。 - 原理:
format="json"是Ollama支持的参数,会提示模型“请以 JSON 形式回答”。- 配合
Pydantic模型进行自动解析,防止字符串误读。
- 注意:不是所有模型都支持
format=json,但qwen2:7b在Ollama中已验证支持。
2)第 2 步:构建结构化提示词
from langchain_core.prompts import PromptTemplate
prompt = PromptTemplate.from_template(
"""请根据以下信息提取用户资料:
{input}
请严格按照以下 JSON 格式返回结果:
{
"name": "姓名",
"age": 年龄,
"city": "城市"
}
"""
)
- 作用:引导模型按指定结构输出。
- 为什么需要?
- 即使开启了
format=json,也需要明确的提示词来指导模型生成正确的字段。 - 防止模型“自由发挥”,如多出字段或类型错误。
- 即使开启了
- 设计技巧:提供示例
JSON,帮助模型理解格式。
3)第 3 步:创建链并调用模型
from langchain_core.output_parsers import PydanticOutputParser
# 创建解析器
parser = PydanticOutputParser(pydantic_object=User)
# 构建链
chain = prompt | llm | parser
- 作用:将模型输出自动转换为
Python对象。 - 技术细节:
PydanticOutputParser会尝试解析 JSON 并验证字段类型。- 若格式错误,会抛出异常,便于调试。
- 优势:无需手动
json.loads()+ 类型检查。
4)第 4 步:测试输入数据
test_input = """
用户名叫张三,今年 28 岁,住在杭州。
他喜欢喝茶,平时工作很忙。
"""
- 设计原则:
- 输入应包含足够信息,但不包含干扰项。
- 测试边界情况(如缺失字段、多余描述)。
5)第 5 步:执行并验证输出
result = chain.invoke({"input": test_input})
print("✅ 解析结果:")
print(result)
print("\n🛠️ 字段类型:")
print(f"name: {type(result.name)}")
print(f"age: {type(result.age)}")
print(f"city: {type(result.city)}")
-
预期输出:
name='张三' age=28 city='杭州' -
验证点:
- 是否成功解析?
- 字段类型是否正确(str / int)?
- 是否忽略无关内容(如“喜欢喝茶”)?
2、示例代码
# bonus1_json.py
from langchain_community.llms import Ollama
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
# === 第 1 步:定义模型与 JSON 模式 ===
class User(BaseModel):
name: str = Field(..., description="用户姓名")
age: int = Field(..., description="用户年龄")
city: str = Field(..., description="所在城市")
llm = Ollama(
model="qwen2:7b",
request_timeout=600.0,
base_url="http://127.0.0.1:11434",
format="json" # 强制 JSON 输出
)
# === 第 2 步:构建提示模板 ===
prompt = PromptTemplate.from_template(
"""请根据以下信息提取用户资料:
{input}
请严格按照以下 JSON 格式返回结果:
{
"name": "姓名",
"age": 年龄,
"city": "城市"
}
"""
)
# === 第 3 步:创建解析器和链 ===
parser = PydanticOutputParser(pydantic_object=User)
chain = prompt | llm | parser
# === 第 4-5 步:测试 ===
test_input = """
用户名叫张三,今年 28 岁,住在杭州。
他喜欢喝茶,平时工作很忙。
"""
try:
result = chain.invoke({"input": test_input})
print("✅ 解析结果:")
print(result)
print("\n🛠️ 字段类型:")
print(f"name: {type(result.name)}")
print(f"age: {type(result.age)}")
print(f"city: {type(result.city)}")
except Exception as e:
print(f"❌ 错误:{e}")
九、不用LangChain 也可函数:Function Calling
绕过 LangChain,直接使用 Ollama 的原生 Function Calling 能力,实现“AI 自动选函数”。
工作原理差异
OpenAI Function Calling(原生派):这是OpenAI模型本身的一种能力。当你发送一个包含函数定义的请求时,模型会判断是否需要调用函数。如果需要,它会返回一个包含函数名和参数的JSON,而不是普通文本。- 你的工作:你需要自己写代码去捕获这个
JSON,解析参数,调用真正的函数,然后再把结果拼接回去发给模型获取最终回答。 - 特点:“直给”。你清楚地知道每一步发生了什么,
Token消耗更高效,响应速度通常更快1。
- 你的工作:你需要自己写代码去捕获这个
LangChain Agent(框架派):LangChain利用OpenAI的Function Calling能力,但将其封装成了一个“智能代理(Agent)”。这个Agent拥有“大脑”(LLM)和“工具”(Tools)。- 工作流:你只需要把工具(函数)交给
Agent。当用户提问时,Agent会自动进行“推理-行动-观察”的循环(ReAct模式)。它会自己决定先查天气再定闹钟,或者先搜索再总结。 - 特点:“自治”。它擅长处理复杂的逻辑链条,且自带记忆(
Memory)和上下文管理,不需要你手动拼接消息历史23。
- 工作流:你只需要把工具(函数)交给
使用建议
-
使用
OpenAI原生Function Calling的场景:- 如果做一个简单的插件(比如钉钉/飞书机器人,只负责查个数据),直接用
OpenAI原生的 Function Calling,轻量且高效。 -
简单的工具调用:比如你只需要一个“查天气”或“计算器”功能,逻辑非常单一。
-
对性能要求极高:你不想引入额外的框架开销,希望请求响应时间最短。
-
学习和调试:如果你想真正理解
Function Calling的底层原理,或者在做 PoC(概念验证)时希望逻辑清晰可见。 - 微服务架构:你希望保持服务的轻量化,不希望引入庞大的第三方依赖。
- 如果做一个简单的插件(比如钉钉/飞书机器人,只负责查个数据),直接用
-
使用
LangChain的场景:- 构建一个真正的智能助手(它需要记事、查资料、做计算、甚至控制智能家居),请使用
LangChain,它能帮你省去大量处理逻辑流转和上下文的繁琐工作。 -
复杂的 AI 应用:你需要结合数据库查询、API 调用、数学计算等多种步骤。
-
需要记忆功能:你需要多轮对话,且上下文管理比较复杂(LangChain 的
ConversationBufferMemory等组件非常方便)。 -
RAG(检索增强生成):你需要让大模型基于你的私有文档(PDF、数据库)回答问题,LangChain 在文档加载、切片和检索方面有大量现成组件36。 -
多模型支持:你不想被锁定在 OpenAI 上,未来可能切换成 Anthropic 或本地模型,LangChain 可以让你只需改一行代码23。
- 快速原型开发:你想快速搭建一个具备多种能力的 Demo,不想重复造轮子。
- 构建一个真正的智能助手(它需要记事、查资料、做计算、甚至控制智能家居),请使用
1、整体流程(5 步)
1)第 1 步:定义工具函数(Python 函数)
def search_weather(city: str) -> str:
"""查询城市天气"""
mock_data = {
"北京": "晴,20°C",
"上海": "多云,22°C",
"杭州": "小雨,18°C"
}
return mock_data.get(city, "未知城市")
def calculate_discount(price: float, rate: float) -> float:
"""计算折扣价"""
return price * (1 - rate)
- 作用:模拟真实业务逻辑。
- 设计原则:
- 函数必须有清晰文档(
docstring),用于模型理解。 - 返回值为基本类型(str / float),便于序列化。
- 函数必须有清晰文档(
2)第 2 步:定义函数签名(OpenAPI Schema)
import json
# 手动构造函数签名(类似 OpenAPI)
functions = [
{
"name": "search_weather",
"description": "查询指定城市的天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"}
},
"required": ["city"]
}
},
{
"name": "calculate_discount",
"description": "计算商品折扣价",
"parameters": {
"type": "object",
"properties": {
"price": {"type": "number", "description": "原价"},
"rate": {"type": "number", "description": "折扣率(0~1)"}
},
"required": ["price", "rate"]
}
}
]
- 作用:告诉模型有哪些函数可用,以及如何调用。
- 原理:
Ollama支持tools参数,接受JSON格式的函数列表。- 模型会根据问题选择最合适的函数,并生成调用参数。
3)第 3 步:发送带 tools 的请求
import requests
url = "http://127.0.0.1:11434/api/generate"
payload = {
"model": "qwen2:7b",
"prompt": "北京今天天气怎么样?",
"stream": False,
"tools": functions,
"tool_choice": "auto" # 自动选择工具
}
- 作用:向
Ollama发送带有工具能力的请求。 - 关键参数:
tools: 函数列表(JSON 格式)tool_choice:"auto"表示让模型决定是否调用工具stream=False:一次性返回完整响应
4)第 4 步:处理模型返回的 JSON
response = requests.post(url, json=payload)
result = response.json()
if "error" in result:
print(f"❌ 错误:{result['error']}")
else:
# 检查是否调用了工具
if "tool_calls" in result:
tool_call = result["tool_calls"][0]
func_name = tool_call["function"]["name"]
args = tool_call["function"]["arguments"]
print(f"🔧 调用函数:{func_name}")
print(f"📌 参数:{args}")
# 执行对应函数
if func_name == "search_weather":
weather = search_weather(args["city"])
print(f"🌤️ 天气:{weather}")
elif func_name == "calculate_discount":
discount_price = calculate_discount(args["price"], args["rate"])
print(f"💰 折扣价:{discount_price:.2f}")
else:
print("💬 直接回答:", result["response"])
- 作用:解析模型返回的工具调用指令。
- 内部结构:
tool_calls: 包含一个或多个工具调用- 每个调用包含
function.name和arguments
- 注意事项:需手动匹配函数名并执行。
5)第 5 步:测试不同问题
test_questions = [
"北京今天天气怎么样?",
"买一台 5000 元的手机打 8 折多少钱?",
"你能帮我做什么?"
]
for q in test_questions:
print(f"\n🔍 问题:{q}")
payload["prompt"] = q
response = requests.post(url, json=payload)
result = response.json()
if "error" in result:
print(f"❌ 错误:{result['error']}")
elif "tool_calls" in result:
tool_call = result["tool_calls"][0]
func_name = tool_call["function"]["name"]
args = tool_call["function"]["arguments"]
print(f"🔧 调用:{func_name}({args})")
if func_name == "search_weather":
print(f"🌤️ 天气:{search_weather(args['city'])}")
elif func_name == "calculate_discount":
print(f"💰 折扣价:{calculate_discount(args['price'], args['rate']):.2f}")
else:
print(f"💬 回答:{result['response']}")
- 预期行为:
- “天气” → 调用
search_weather - “折扣” → 调用
calculate_discount - “你能做什么?” → 直接回答(无工具)
- “天气” → 调用
2、示例代码
# bonus2_function_calling.py
import requests
import json
# === 第 1 步:定义工具函数 ===
def search_weather(city: str) -> str:
"""查询城市天气"""
mock_data = {"北京": "晴,20°C", "上海": "多云,22°C", "杭州": "小雨,18°C"}
return mock_data.get(city, "未知城市")
def calculate_discount(price: float, rate: float) -> float:
"""计算折扣价"""
return price * (1 - rate)
# === 第 2 步:定义函数签名 ===
functions = [
{
"name": "search_weather",
"description": "查询指定城市的天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"}
},
"required": ["city"]
}
},
{
"name": "calculate_discount",
"description": "计算商品折扣价",
"parameters": {
"type": "object",
"properties": {
"price": {"type": "number", "description": "原价"},
"rate": {"type": "number", "description": "折扣率(0~1)"}
},
"required": ["price", "rate"]
}
}
]
# === 第 3-5 步:发送请求并处理结果 ===
url = "http://127.0.0.1:11434/api/generate"
test_questions = [
"北京今天天气怎么样?",
"买一台 5000 元的手机打 8 折多少钱?",
"你能帮我做什么?"
]
for q in test_questions:
print(f"\n🔍 问题:{q}")
payload = {
"model": "qwen2:7b",
"prompt": q,
"stream": False,
"tools": functions,
"tool_choice": "auto"
}
response = requests.post(url, json=payload)
result = response.json()
if "error" in result:
print(f"❌ 错误:{result['error']}")
elif "tool_calls" in result:
tool_call = result["tool_calls"][0]
func_name = tool_call["function"]["name"]
args = tool_call["function"]["arguments"]
print(f"🔧 调用:{func_name}({args})")
if func_name == "search_weather":
print(f"🌤️ 天气:{search_weather(args['city'])}")
elif func_name == "calculate_discount":
print(f"💰 折扣价:{calculate_discount(args['price'], args['rate']):.2f}")
else:
print(f"💬 回答:{result['response']}")
└─[$] python qwen_first.py [17:37:22]
🔍 问题:北京今天天气怎么样?
💬 回答:我作为一个AI助手,无法即时提供最新的天气信息。您可以查看可靠的气象网站或应用(如中国国家气象局的官方网站、墨迹天气、天气通等)获取北京最新的实时天气情况,这样能确保您得到最准确和最新的数据。通常包括气温、风速、湿度以及是否会有雨雪等信息。
🔍 问题:买一台 5000 元的手机打 8 折多少钱?
💬 回答:如果原价是 5000 元,打 8 折就是支付商品价格的 80%,即:
\[5000 \times 0.8 = 4000\]
所以,买这台手机需要支付 4000 元。
🔍 问题:你能帮我做什么?
💬 回答:当然,我可以帮助你完成各种自然语言处理任务。比如:
1. **回答问题**:对于大部分的一般知识、技术性问题或是特定领域的知识,我都能提供答案。
2. **提供建议**:如果你需要在决策上获得建议,无论是个人生活中的小事,还是职业规划、学习方法等大事情,我会基于可用信息给出建议。
3. **解释概念**:对复杂或抽象的概念进行解释和阐述,帮助你更好地理解它们的含义及其应用。
4. **翻译文本**:将文本从一种语言翻译成另一种语言,以满足沟通需求。
5. **撰写文档**:如果需要,我可以协助编写文章、报告、论文等各类文档的内容部分。
6. **提供建议和指导**:在编程、写作、艺术创作等领域给出创意和建议。
7. **组织信息**:整理数据、事实或观点,帮助你理解复杂的信息结构。
8. **生成文本**:根据特定主题或情境,生成连贯的文本内容。
9. **辅助学习**:提供关于特定学科的学习资源、解释概念以及解答疑问。
10. **情感支持和交流**:如果你需要有人倾诉或者只是想要谈话,我也可以作为虚拟伙伴存在。
请注意,在涉及到个人隐私信息(如密码、银行详细信息等)时,请务必谨慎。我也尊重版权和知识产权,不提供抄袭或侵犯他人作品的行为。
十、流式生成与对话记忆
1、整体流程(5 步)
1)第 1 步:下载并加载 LoRA 微调后的模型
from langchain_community.llms import Ollama
# 启动 Ollama 并拉取微调模型
# 示例:使用 qwen2:7b + 客服领域 LoRA
llm = Ollama(
model="qwen2:7b",
request_timeout=600.0,
base_url="http://127.0.0.1:11434",
streaming=True,
temperature=0.7, # 控制创造性
top_p=0.9, # 样本多样性
repeat_penalty=1.1 # 防止重复
)
-
作用:启用经过微调的模型,提升特定任务表现。
-
背后原理:
LoRA(Low-Rank Adaptation) 是一种轻量级微调技术,只更新少量参数,保留原始模型知识。- 在
Ollama中,可通过ollama pull <model>加载带LoRA的版本。
-
推荐模型(示例):
ollama pull qwen2:7b-lora-customer-service注:目前 Ollama 官方未提供预训练 LoRA,但你可以用
llama.cpp或huggingface自行训练后导入。
2)第 2 步:定义角色化提示模板(Prompt Engineering)
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", """
你是一名专业的电商客服,名叫小智。请遵循以下规则:
1. 语气亲切、简洁,避免啰嗦。
2. 回答必须基于事实,不编造信息。
3. 若无法回答,请说:“我正在查询,请稍等。”
4. 支持中文和英文双语回复。
"""),
("human", "{input}")
])
- 作用:设定
AI的行为边界和人格。 - 设计技巧:
- 使用
system消息固定角色身份。 - 明确指令防止幻觉(如“不要编造”)。
- 使用
- 优势:即使模型未微调,也能通过提示词控制输出风格。
3)第 3 步:构建流式对话链
from langchain_core.output_parsers import StrOutputParser
# 构建 Runnable 链
chain = prompt | llm | StrOutputParser()
- 作用:将提示、模型、解析器串联成可执行流程。
LangChain新范式:- 使用
|运算符连接组件,支持异步流式处理。 StrOutputParser()将AIMessage转为字符串。
- 使用
4)第 4 步:实现流式输出(逐字打印)
import asyncio
async def stream_response(question: str):
print(f"\n👤 用户:{question}")
print("🤖 小智:", end="", flush=True)
async for chunk in chain.astream({"input": question}):
print(chunk, end="", flush=True)
print() # 换行
- 作用:模拟人类打字效果,提升用户体验。
- 关键点:
astream()返回异步生成器,每个chunk是一个 token。flush=True确保立即显示,不缓存。
5)第 5 步:添加上下文记忆与多轮对话
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain
memory = ConversationBufferMemory(
memory_key="history",
max_len=5,
return_messages=True
)
conversation_chain = ConversationChain(
llm=llm,
memory=memory,
verbose=False
)
# 测试多轮对话
questions = [
"你们支持货到付款吗?",
"退货怎么操作?",
"能开发票吗?"
]
for q in questions:
response = conversation_chain.predict(input=q)
print(f"👤 用户:{q}")
print(f"🤖 AI:{response}\n")
- 作用:让 AI 记住用户身份和历史问题。
- 机制:
ConversationBufferMemory保存最近几轮对话。- 每次调用时自动拼接上下文到提示词中。
2、示例代码
# lesson10_streaming_and_finetuning.py
import asyncio
from langchain_community.llms import Ollama
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain
# === 第 1 步:初始化模型(支持 LoRA 微调)===
llm = Ollama(
model="qwen2:7b",
request_timeout=600.0,
base_url="http://127.0.0.1:11434",
streaming=True,
temperature=0.7,
top_p=0.9,
repeat_penalty=1.1
)
# === 第 2 步:角色化提示 ===
prompt = ChatPromptTemplate.from_messages([
("system", """
你是一名专业的电商客服,名叫小智。请遵循以下规则:
1. 语气亲切、简洁,避免啰嗦。
2. 回答必须基于事实,不编造信息。
3. 若无法回答,请说:“我正在查询,请稍等。”
4. 支持中文和英文双语回复。
"""),
("human", "{input}")
])
# === 第 3 步:构建流式链 ===
chain = prompt | llm | StrOutputParser()
# === 第 4 步:流式输出函数 ===
async def stream_response(question: str):
print(f"\n👤 用户:{question}")
print("🤖 小智:", end="", flush=True)
async for chunk in chain.astream({"input": question}):
print(chunk, end="", flush=True)
print()
# === 第 5 步:多轮对话测试 ===
if __name__ == "__main__":
# 测试流式输出
test_questions = [
"你们支持货到付款吗?",
"退货怎么操作?",
"能开发票吗?"
]
for q in test_questions:
asyncio.run(stream_response(q))
十一、模型微调
LoRA(Low-Rank Adaptation,低秩自适应)是一种用于高效微调大型预训练模型(如大语言模型 LLM 或视觉模型)的技术。它由 Microsoft Research 在 2021 年提出(论文:LoRA: Low-Rank Adaptation of Large Language Models),旨在以更低的计算成本和显存占用,实现与全参数微调(Full Fine-tuning)相近甚至相当的性能。
1、前提条件
| 要求 | 说明 |
|---|---|
| GPU | 至少 24GB 显存(RTX 3090/4090 或 A10/A100) |
| Python | ≥ 3.10 |
| 磁盘空间 | ≥ 50GB(用于模型、数据、缓存) |
| 基础模型 | Qwen/Qwen2-7B(Hugging Face) |
# 安装核心库
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
pip install transformers accelerate peft datasets bitsandbytes wandb sentencepiece
- 使用
bitsandbytes实现 4-bit 量化,降低显存占用。 wandb可选,用于训练日志可视化。
2、整体流程
1)第 1 步:环境搭建与依赖安装
# 创建虚拟环境(推荐)
python -m venv lora_env
source lora_env/bin/activate # Linux/Mac
# lora_env\Scripts\activate # Windows
# 升级 pip
pip install --upgrade pip
# 安装核心库
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
pip install transformers accelerate peft datasets bitsandbytes wandb sentencepiece
⚠️ 注意:
- 使用
bitsandbytes实现 4-bit 量化,降低显存占用。wandb可选,用于训练日志可视化。
2)第 2 步:准备微调数据集(以“客服对话”为例)
a、构建 JSONL 格式数据
创建 customer_service.jsonl:
{"instruction": "用户问:你们支持货到付款吗?", "output": "您好,我们目前不支持货到付款,仅支持在线支付。"}
{"instruction": "用户问:退货怎么操作?", "output": "您可以在订单页面点击“申请退货”,填写原因后等待审核。"}
{"instruction": "用户问:能开发票吗?", "output": "可以的!下单时勾选“需要发票”,或联系客服补开。"}
格式要求:
- 每行一个 JSON 对象
- 包含
instruction(输入)和output(期望回答)
b、加载数据集(Python)
from datasets import load_dataset
dataset = load_dataset("json", data_files="customer_service.jsonl")
train_dataset = dataset["train"]
3)第 3 步:LoRA 微调训练
a、 加载 Qwen2-7B 模型(4-bit 量化)
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch
model_name = "./qwen2-7b" # 或 "Qwen/Qwen2-7B"
# 4-bit 量化配置(大幅降低显存)
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16
)
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=bnb_config,
device_map="auto",
trust_remote_code=True
)
# 启用梯度检查点(节省显存)
model.gradient_checkpointing_enable()
b、配置 LoRA 参数
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
# 准备模型用于 k-bit 训练
model = prepare_model_for_kbit_training(model)
# 定义 LoRA 配置
lora_config = LoraConfig(
r=8, # 秩(rank)
lora_alpha=16, # 缩放因子
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], # Qwen2 的注意力层
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
# 应用 LoRA
model = get_peft_model(model, lora_config)
model.print_trainable_parameters() # 查看可训练参数量(应 < 1%)
🔍
target_modules说明:
- Qwen2 使用
q_proj,k_proj,v_proj,o_proj作为注意力投影层。- 可通过
model.named_modules()查看具体模块名。
c、定义训练参数
from transformers import TrainingArguments, Trainer
training_args = TrainingArguments(
output_dir="./qwen2-lora-customer-service",
per_device_train_batch_size=2,
gradient_accumulation_steps=4,
learning_rate=2e-4,
num_train_epochs=3,
logging_steps=10,
save_strategy="epoch",
evaluation_strategy="no",
fp16=False, # 使用 bf16 已在 bnb 中设置
optim="paged_adamw_8bit",
lr_scheduler_type="cosine",
warmup_ratio=0.03,
report_to="none" # 关闭 wandb
)
d、数据预处理
def formatting_prompts_func(example):
output_texts = []
for i in range(len(example['instruction'])):
text = f"<|im_start|>system\n你是一名专业客服<|im_end|>\n<|im_start|>user\n{example['instruction'][i]}<|im_end|>\n<|im_start|>assistant\n{example['output'][i]}<|im_end|>"
output_texts.append(text)
return output_texts
# 使用 SFTTrainer(推荐)
from trl import SFTTrainer
trainer = SFTTrainer(
model=model,
args=training_args,
train_dataset=train_dataset,
formatting_func=formatting_prompts_func,
max_seq_length=512,
tokenizer=tokenizer
)
💡
SFTTrainer是 Hugging Face TRL 库提供的监督微调工具,简化流程。
d、开始训练
trainer.train()
# 保存 LoRA 适配器
model.save_pretrained("./qwen2-lora-customer-service-final")
tokenizer.save_pretrained("./qwen2-lora-customer-service-final")
4)第 4 步:合并模型并转换为 GGUF(供 Ollama 使用)
a、合并 LoRA到基础模型
from peft import PeftModel
from transformers import AutoModelForCausalLM
# 加载原始模型(非量化版,需足够内存)
base_model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen2-7B",
trust_remote_code=True,
device_map="auto"
)
# 加载 LoRA 适配器
model = PeftModel.from_pretrained(base_model, "./qwen2-lora-customer-service-final")
# 合并权重
merged_model = model.merge_and_unload()
# 保存完整模型
merged_model.save_pretrained("./qwen2-7b-customer-service-merged")
tokenizer.save_pretrained("./qwen2-7b-customer-service-merged")
⚠️ 注意:此步骤需大量 CPU 内存(≥ 64GB),建议在服务器运行。
b、转换为 GGUF 格式(使用 llama.cpp)
# 克隆 llama.cpp
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
# 安装依赖
pip install -r requirements.txt
# 转换 Hugging Face 模型为 GGUF
python convert-hf-to-gguf.py \
../qwen2-7b-customer-service-merged \
--outfile qwen2-7b-customer-service.Q4_K_M.gguf \
--quantize Q4_K_M
✅ 量化选项说明:
Q4_K_M:平衡速度与精度,推荐用于 7B 模型- 其他选项:
Q5_K_M,Q6_K(更高精度,更大体积)
5)第 5 步:导入 Ollama 并测试
a、创建 Modelfile
创建 Modelfile:
FROM ./qwen2-7b-customer-service.Q4_K_M.gguf
TEMPLATE ""
PARAMETER temperature 0.7
PARAMETER top_p 0.9
PARAMETER repeat_penalty 1.1
PARAMETER num_ctx 2048
SYSTEM """
你是一名专业的电商客服,名叫小智。请用亲切、简洁的中文回答用户问题。
"""
b、创建 Ollama 模型
ollama create qwen2-cs -f Modelfile
c、测试运行
ollama run qwen2-cs "退货怎么操作?"
预期输出:
您可以在订单页面点击“申请退货”,填写原因后等待审核。
2、训练流程
原始 Qwen2-7B
↓
4-bit 量化 + LoRA 微调(PEFT)
↓
合并 LoRA 权重 → 完整微调模型
↓
转换为 GGUF(llama.cpp)
↓
Ollama Modelfile 封装
↓
ollama run 自定义模型


