前言

Github:https://github.com/HealerJean

博客:http://blog.healerjean.com

一、名词解释

1、LLM 大语言模型

解释:大语言模型(Large Language Model,简称 LLM)是一种基于深度学习的人工智能系统,专门用于理解和生成人类语言。它通过在海量文本数据上进行训练,学习语言的统计规律和语义结构,从而能够回答问题、创作文本、翻译语言,甚至进行逻辑推理。

LLM 的“大”主要体现在参数量巨大——从数十亿到数千亿不等。这种规模带来了所谓的“涌现能力”:当模型足够大时,它会突然展现出小模型不具备的理解力、泛化能力和上下文推理能力。就像你描述的那样,给它一个输入(“刺激”),它会根据内部学到的知识和概率分布,生成一个看似合理、有时甚至富有创意的回应。而且,即使是相同的输入,由于采样策略或随机性,输出也可能略有不同。

关键特点

  • 理解和生成自然语言
  • 回答基于概率和训练数据
  • 不具备记忆、目标或行动能力(除非被设计成有)
    • 举例说明
      • 你问 LLM:“北京明天天气怎么样?”
      • LLM 回答:“抱歉,我无法获取实时天气信息。”

2、Agent:智能体

解释:Agent(智能体),它是构建在 LLM 之上的更高级系统。如果说 LLM 是“大脑”,能理解与表达,那么 Agent 就是“有行动能力的个体”。它不仅能理解用户意图,还能结合历史对话、用户身份、当前环境等信息,自主规划任务、调用工具(比如查天气、订机票)、做出决策并执行操作,实现端到端的智能服务。可以说,AgentLLM 从“会说话”进化到“会做事”。

Agent 是在 LLM 基础上构建的“智能系统”。它不仅理解语言,还能自主规划、调用工具、执行任务,就像一个有执行力的助理。

关键特点

  • 能感知上下文(你是谁、之前聊过什么)
  • 能设定目标(比如“帮我订一张机票”)
  • 能调用外部工具(查天气、发邮件、操作数据库等)
  • 能分步骤完成复杂任务

举例说明::你对 Agent:“我想知道北京明天天气,如果下雨就提醒我带伞。”,Agent 会这样做

  • 理解意图:用户想知道天气,并有条件判断;

  • 调用工具:自动连接天气 API,获取北京明天的预报;

  • 判断结果:发现预报是“中雨”;

  • 执行动作:回复你:“北京明天有雨,记得带伞!”

image-20260119181218181

3、MCP:模型上下文协议

MCP 是 Agent 与外部世界沟通的“语言规范”

Agent 是“谁在做事”,MCP 是“怎么规范地做事”。MCPAgent 提供了与外部世界安全、高效、标准化交互的桥梁。

角色 职责 依赖关系
Agent 决策者 + 执行者:理解用户意图 → 规划步骤 → 调用工具 → 整合结果 需要 MCP(或类似协议)来规范它如何调用外部工具
MCP 通信规则制定者:定义“怎么调用工具”“怎么传参数”“怎么返回结果” 为 Agent 提供标准化、安全的工具调用接口

举例说明:假设你有一个 旅行规划 Agent,任务是:“帮我订一张下周从北京到上海的 cheapest 机票。”

没有 MCP(传统方式):

  • 开发者要为这个 Agent 硬编码如何调用某个特定机票 API
  • 如果换一个航班平台,就得重写代码;
  • 安全性和错误处理也各不相同。

二、Ollama

Ollama 是一个开源项目,旨在简化在本地设备(如笔记本电脑或个人服务器)上运行大型语言模型(LLMs)的过程。它最初由 Jeffrey Huang 和其他贡献者开发,目标是让开发者、研究人员和爱好者能够轻松地下载、运行和与各种开源大模型进行交互,而无需依赖云端服务。

1、主要特点:

  • 本地运行Ollama 允许用户在自己的机器上直接运行 LLM,无需联网,保障数据隐私和安全性。
  • 支持多种模型: 支持包括 LlamaLlama2Llama3MistralGemmaPhiQwen 等主流开源模型
  • 一键部署: 通过简单的命令(如 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 listollama runollama 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 调用之间的逻辑链式调用,避免手动拼接提示词。

我们要完成一个 三步自动化流程

  1. 改写:把口语化中文 → 正式书面语
  2. 翻译:正式中文 → 英文
  3. 生成标题:根据英文内容 → 生成英文标题

这三步像流水线一样自动执行,前一步的输出就是后一步的输入。LangChain 用 “Runnable 管道” 实现这种自动化。

1、整体流程(7 步)

1)第 1 步:导入必要的模块

from langchain_ollama import OllamaLLM
from langchain_core.prompts import PromptTemplate
  • OllamaLLMLangChain 官方为 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 会自动执行:

  1. Step 1rewrite_chain.invoke({"text": "今天我去了公园..."})
    • → 输出:"今日前往公园,目睹繁花盛开,心情愉悦。"
  2. Step 2LangChain 自动把上一步的输出 作为 {"text": "今日前往公园..."}
    • 传给下一步translate_chain.invoke({"text": "今日前往公园..."})
    • → 输出:"Today I went to the park and saw many flowers in bloom, feeling especially happy."
  3. 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_IDGOOGLE_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:7bOllama 中已验证支持。

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 利用 OpenAIFunction 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.namearguments
  • 注意事项:需手动匹配函数名并执行。

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 # 防止重复
)
  • 作用:启用经过微调的模型,提升特定任务表现。

  • 背后原理

    • LoRALow-Rank Adaptation 是一种轻量级微调技术,只更新少量参数,保留原始模型知识。
    • Ollama 中,可通过 ollama pull <model> 加载带 LoRA 的版本。
  • 推荐模型(示例):

    ollama pull qwen2:7b-lora-customer-service
    

    注:目前 Ollama 官方未提供预训练 LoRA,但你可以用 llama.cpphuggingface 自行训练后导入。

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 自定义模型

ContactAuthor