MoreRSS

site iconQborfy修改

毕业于广大华软学院,软件工程师一枚。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Qborfy的 RSS 预览

09篇 AI从零开始 - LangChain学习与实战(6) Agent智能体开发

2025-05-26 15:00:00

做一个有温度和有干货的技术分享作者 —— Qborfy

前面我们已经学习如何通过 Langchain调用独立部署的DeepSeek-R1:32b模型,且完成一些简单的应用,如: RAG 知识库的实现。

接下来我们学习如何通过LangChain实现一个Agent智能体,从而让 AI 模型帮忙实现做更多事情。

1. Agent智能体

1.1 是什么

在开发之前我们先来了解下Agent智能体是什么,它主要解决什么问题?

Agent是使用 LLM 大模型作为推理引擎的系统,用于确定应采取哪些行动(Action)以及这些行动输入应该是什么,然后会把行动的输出结果反馈给到Agent,用于判断是否需要更多行动或者结束然后输出返回。

结合网上的资料和个人理解,Agent智能体的出现主要是解决以下问题:

  • LLM大模型与人类之间的交互是基于 prompt 实现的,prompt 是否清晰明确会影响大模型回答的效果
  • LLM大模型只能进行推理,无法进行实际性的行动
  • LLM大模型只能单独使用,无法结合多个 LLM 大模型进行组合使用
  • LLM大模型只能输出文本,却无法符合用户需要的数据结构,如:如何标准的 JSON Schema
  • LLM大模型只能依据当前输入进行推理,无法进行长期记忆

AI Agent是模仿人类思考技术,具体如下图所示:

一句话弄明白,就是AI大模型可以作为一个大脑进行学习推理,但是 Agent技术利用 LLM大模型的推理能力,能根据人类输入,代替人类去做一些真正想要的事情,最后输出人类真正想要结果或者去执行某些行动。

为了更好理解 Agent技术, 我们可以看看目前AI协同工作类型,具体如下:

  1. Embbedding 模式: 人类输入目标,AI输出几个意见,人类自主决定采用哪个意见,代表产品为: RAG 智能客服机器人
  2. Copilot 模式: 人类输入目标,AI经过几个流程确定初步输出,人类可以自由调整输出,代表产品为: Copilot代码提示
  3. Agent 模式: 人类输入目标,AI 根据输入自主拆分任务,然后根据任务所需要的选择工具,最终完成任务最终结束,代表产品为: Manus AI 助手

1.2 怎么做

上面说到Agent技术是模仿人类思考技术,利用大模型进行推理,拆分人类输入的任务,那么 Agent其实最大的重点在于激发 LLM 大模型的推理能力,去拆分人类输入的任务。

那么如何激发 LLM 大模型的推理能力呢? 主要有以下几点:

1. Prompt 的思考链

思维链(Chain of Thoughts)已成为一种标准的提示技术,用于提高模型在复杂任务中的表现。模型被要求 “一步一步地思考”,将艰巨的任务分解为更小更简单的步骤。思维链将大任务转化为多个可管理的任务,并帮助人们理解模型的思维过程。

无思考链的 Prompt:

1
2
3
4
5
问:罗杰有5个网球,他又买了两盒网球,每盒有3个网球。他现在有多少网球?
答:答案是11
问:食堂有23个苹果,如果他们用掉20个后又买了6个。他们现在有多少个苹果?
模型输出:
答:答案是27

有思考链的 Prompt:

1
2
3
4
5
问:罗杰有5个网球,他又买了两盒网球,每盒有3个网球。他现在有多少网球?
答:罗杰一开始有5个网球,2盒3个网球,一共就是2*3=6个网球,5+6=11。答案是11.
问:食堂有23个苹果,如果他们用掉20个后又买了6个。他们现在有多少个苹果?
模型输出:
答:食堂原来有23个苹果,他们用掉20个,所以还有23-20=3个。他们又买了6个,所以现在有6+3=9。答案是9

PS: DeepSeek的推理模式出来后,现在大部分模型默认就支持思考链的回答了。

思维树 是思维链的另一种表现形式,通过在任务的每一步探索多种推理可能性来扩展思维链。它首先将问题分解为多个思考步骤,并在每个步骤中生成多个想法,从而创建一个树状结构。试错和纠错在现实世界的任务决策中是不可避免且至关重要的步骤。自我反思帮助 AI Agent 完善过去的行动决策、纠正以前的错误、从而不断改进。主要可以包括以下几种模式:

  1. ReAct: 结合推理(Reasoning)和行动(Action)​​,动态与环境交互。简单的理解就是: 推理+动作。
  2. Reflexion: 让 AI Agent 具备动态记忆和自我反思能力以提高推理能力的框架。简单的理解就是: 重复步骤(记忆+推理+动作)。
  3. Hindsight: 利用已知结果优化过去决策​​,从失败经验中学习。简单理解就是: 根据已知结果进行反向推理。

2. 记忆

记忆模块负责存储信息,包括过去的交互、学习到的知识,甚至是临时的任务信息。

AI有了记忆后,就可以根据已知的记忆知识去更好回答用户提问。而在 AI Agent 定义里:

用户在与其交互过程中产生的内容都可以认为是 Agent 的记忆,和人类记忆的模式能够产生对应关系。

记忆可以分为几类:

  • 感觉记忆: 初始输入文本、图片等数据,如:看一张照片后,当不看照片,还能想起照片的印象,这是感觉记忆
  • 短期记忆: 本次与 AI 对话的上下文,如:进行记忆几个数字,短期内还是可以记住这几个数字,这就是短期记忆
  • 长期记忆: Agent在工作时需要查询向量数据库,如:学会骑自行车,及时很长时间没骑自行车,但是骑自行车这个技能还在,那么骑自行车这个技能就是长期记忆

而针对记忆这方面的技术, Embedding 技术和向量相似度计算就是记忆(向量数据库的核心),具体可以做以下理解:

  • Embedding 技术:将文本、图片等数据转换为向量,从而实现记忆功能
  • 向量相似度计算:通过数学方法来计算两个向量之间的相似度,常见的算法有:余弦相似度、欧式距离、汉明距离等,通过相似度计算可以判断两个向量是否相似,从而获取我们所需要的记忆数据

3. 工具

AI 懂得使用工具才会更像人类。

AI Agent 除了记忆,还需要在获取到每一步子任务的工作后,Agent 都会判断是否需要通过调用外部工具来完成该子任务,并在完成后获取该外部工具返回的信息提供给 LLM,进行下一步子任务的工作。

目前AI 大模型接入工具的使用方式如下:

  1. 函数调用(Function Call): 向大模型描述函数,如:函数的作用和参数结构(如:JSON对象),从让 AI 大模型在执行任务能够调用外部工具,如:open("test.txt")
  2. 插件系统​(Plugin)​:通过标准化接口扩展大模型能力,等同于公共的函数调用,让大家都能用。
  3. 模型内嵌工具: 不同模型会提供不同内置工具,如:OpenAI 的file_searchcode_interpreter,能AI直接调用去搜索文件和解析文件的能力。

目前绝大部分 AI Agent开发使用的工具的方式都是 Function Call的方式,目前主流大模型基本都支持这个能力,具体如下:

模型 支持Function Call 说明
GPT-4 Turbo、GPT-4o、GPT-3.5 Turbo OpenAI 系列模型,闭源
​Claude 3(Opus/Sonnet/Haiku)、Claude 3.5 Anthropic 系列模型,闭源​
Gemini Pro、Gemini 1.5 Pro、Gemini Flash OpenAI 系列模型,闭源​
Llama 3(8B/70B)、Llama 3.1 Meta Llama 系列​,开源​, 需要微调后才支持,不过有现成的 Gorilla 微调框架
Mistral Large、Mistral-7B-Instruct Mistral AI 系列​,开源​,轻量级模型适合本地部署​
通义千问(Qwen-Chat) 阿里云系列​ ​,开源​,基于 ReAct Prompting 原理优化工具
GLM-Z1-32B-0414、ChatGLM3-6B 清华智谱系列模型,开源​
​​DeepSeek V3​​ ​​DeepSeek,开源​,专为函数调用优化,支持多工具协同

2. Langchain AI Agent开发实战

完整了解 Agent的知识后,解析来我们要通过Langchain框架去实现一个 AI Agent,获取获取当天的龙虎榜数据。

2.1 基础概念

  • langchain_core.tools.tool: Langchain用来创建工具的方法
  • langchain.agents.create_tool_calling_agent: 创建工具调用Agent的函数
  • langchain.agents.AgentExecutor: 创建 Agent执行器的类

开始实现思路如下:

  1. 编写工具函数和工具描述
  2. 创建LLM模型
  3. 创建符合工具调用Agent的Prompt
  4. 创建Agent和 AgentExecutor
  5. 通过LangSmith查看 Agent执行过程(调试使用)

2.2 工具声明

获取当前日期

接下来我们声明一个工具函数,用于解决大模型无法判断当前的日期。

1
2
3
4
5
6
7
from langchain_core.tools import tool

@tool
def get_current_day():
"""获取今天的时间"""
return date.today().strftime("%Y-%m-%d")

获取龙虎榜数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pydantic import BaseModel, Field

# 定义输入参数格式说明
class LhbInput(BaseModel):
date: str = Field(description="date, format is YYYY-MM-DD")


@tool(args_schema=LhbInput)
def get_lhb(date: str):
"""获取龙虎榜数据"""
print("开始获取%s龙虎榜" % date)
callback = "jQuery1123029212767559503716_1747729555061"
url = f"https://datacenter-web.eastmoney.com/api/data/v1/get?callback={callback}&sortColumns=SECURITY_CODE%2CTRADE_DATE&sortTypes=1%2C-1&pageSize=200&pageNumber=1&reportName=RPT_DAILYBILLBOARD_DETAILSNEW&columns=SECURITY_CODE%2CSECUCODE%2CSECURITY_NAME_ABBR%2CTRADE_DATE%2CEXPLAIN%2CCLOSE_PRICE%2CCHANGE_RATE%2CBILLBOARD_NET_AMT%2CBILLBOARD_BUY_AMT%2CBILLBOARD_SELL_AMT%2CBILLBOARD_DEAL_AMT%2CACCUM_AMOUNT%2CDEAL_NET_RATIO%2CDEAL_AMOUNT_RATIO%2CTURNOVERRATE%2CFREE_MARKET_CAP%2CEXPLANATION%2CD1_CLOSE_ADJCHRATE%2CD2_CLOSE_ADJCHRATE%2CD5_CLOSE_ADJCHRATE%2CD10_CLOSE_ADJCHRATE%2CSECURITY_TYPE_CODE&source=WEB&client=WEB&filter=%28TRADE_DATE%3C%3D%27{date}%27%29%28TRADE_DATE%3E%3D%27{date}%27%29"
response = requests.get(url)
content = response.content.decode("utf-8")
content = content.split(callback + "(")[1].split(");")[0]
return json.loads(content).get("result")

2.3 创建LLM模型

不同的模型对于工具调用不同, 目前已知支持最好的模型是GPT-4 Turbo, 但是这个模型是闭源的, 国内支持较好是 DeepSeek但是也收费,对于调试模型更加友好可以利用g模型厂商 - siliconflow AI 云服务平台,从而减少我们的调试成本。

1
2
3
4
5
6
7
8
from langchain_openai import ChatOpenAI

api_key = "xxxxxx"
llm = ChatOpenAI(
base_url="https://api.siliconflow.cn",
model="Qwen/Qwen3-235B-A22B", # 这里可以更换不同模型 更好的达到实现效果
api_key=api_key
)

2.4 创建Prompt

Prompt对于LLM大模型的实现有关键作用, 其中agent_scratchpad的占位符对于 AI 大模型实现非常重要,主要用于 ​​记录和传递 Agent 执行过程中的中间推理步骤,同时还会强制按照 tool所需要的参数进行输入调用。

Prompt 具体实现如下:

1
2
3
4
5
6
7
8
9
10
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder

#
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是人工智能助手"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
("human", "{input}"),
]
)

2.5 创建Agent和 AgentExecutor

创建Agent, Langchain也提供不同的类型,如下:

  • Tool Calling Agent (create_tool_calling_agent)​ : 依赖模型原生工具调用能力,自动将工具描述注入模型上下文, 直接返回工具调用参数对象,部分LLM模型支持
  • ReAct Agent (create_react_agent)​ : 遵循 Thought → Action → Observation 循环,每步根据上下文选择工具,结合自然语言与工具调用
  • ​​Structured Chat Agent (create_structured_chat_agent)​:必须遵循预定义响应模板,严格匹配工具参数格式,通常一次性完成工具选择

目前我们使用的是 create_tool_calling_agent, 具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
from langchain.agents import create_tool_calling_agent, AgentExecutor

tools = [get_current_day, get_lhb]

agent_executor = AgentExecutor(
agent=agent,
tools=tools,
handle_parsing_errors=True,
verbose=True, format="json"
)

# 调用
print(agent_executor.invoke({"input": "帮我查询一下今天的龙虎榜数据"}))

PS:

  • handle_parsing_errors: 当 Agent执行发生异常的时候,如传入参数不符合工具描述,是否抛出异常,当为 True错误信息会通过 intermediate_steps 传递到下一轮推理,模型基于历史步骤中的错误反馈重新生成正确的工具调用指令
  • verbose: 是否打印中间步骤,从而更好的理解模型推理过程

以下是完整代码实现过程截图:


2.6 Smith调试

在 Agent中避免不了调试,尤其不同大模型对于工具的调用和判断是不一样的,同时执行过程异常 Langchain也比较难定位,所以因此我们可以使用LangSmith进行调试。

前往LangSmith官网,注册账号,创建项目,获取API_KEY,设置环境变量,具体如下:

1
2
3
4
5
# 设置LangSimth 环境变量
os.environ["LANGSMITH_TRACING"] = "true"
os.environ["LANGSMITH_ENDPOINT"] = "https://api.smith.langchain.com"
os.environ["LANGSMITH_API_KEY"] = "xxxxxxx"
os.environ["LANGSMITH_PROJECT"] = "test_agent"

再次运行Agent,可以前往LangSmith查看调试结果,如下图所示:

3.总结

AI Agent的实现,对于我们来说,可以更好的理解大模型的能力,同时也可以更好的利用大模型能力,从而更好的实现业务场景。 回顾一下,AI Agent的知识点:

  1. AI Agent是利用 AI 大模型的推理能力,结合记忆+工具调用能力,实现扩展 AI 大模型能力的一种技术方案
  2. 实现 AI Agent的方案有目前主流是通过大模型 Function Call
  3. 实现 AI Agent中的 Prompt中,agent_scratchpad占位符是非常重要,记录和传递 Agent 执行过程中的中间推理步骤,同时还会强制按照 tool 所需要的参数进行输入调用。

以上就是 AI Agent的实现过程,希望对你有所帮助。

参考资料

声明:本文部分材料是基于DeepSeek-R1模型生成。

10篇 AI从零开始 - LangChain学习与实战(5) 低代码平台AI助手 开发

2025-03-31 15:00:00

做一个有温度和有干货的技术分享作者 —— Qborfy

回顾一下LangChain系列学习文章:

经过LangChain系列文章的学习后, 现在我们需要通过 LangChain + 大模型 + 低代码平台, 开发具备实际功能的 AI 应用:

低代码平台AI助手:通过用户输入自然语言能实现 低代码平台页面生成、编辑等

1. 前期与实现方案

目标:通过用户输入自然语言能实现,完成低代码平台页面的生成,同时能低代码平台页面组件元素进行属性调整。

实际原理: 利用 AI大模型实现用户输入的自然语言与低代码平台特定 DSL语言(JSON Schema)互相转换

1.1 前期准备

  1. 搭建低代码平台服务,可以参考文档: Formily 表单设计器
  2. Langchain 和 LangServer环境准备, 参考: 04篇 AI从零开始 - LangChain学习与实战(1) 基础知识

实现思路

  1. LangChain开发实现大模型理解低代码平台DSL语言的输入,并能输出是低代码平台DSL语言
  2. 通过 LangServer提供大模型 API
    1. 输入当前页面 json schema
    2. 用户输入自然语言,LangServer调用大模型API,返回结果
    3. LangServer将返回结果转换成低代码平台DSL语言
  3. 低代码平台新增 AI助手 UI,更改低代码页面JSON内容

2. 实战开发

2.1 LangChain Agent + Server开发

2.2 Formily 插件 AI 助手前端开发

总结

参考资料

声明:本文部分材料是基于DeepSeek-R1模型生成。

对 Agent的一些思考, 首先 agent本身的 Prompt很重要, LLM 大模型会依据Prompt+用户输入去判断需要使用哪个工具

然后创建Agent, Langchain也提供不同的类型,如下:

  • Tool Calling Agent (create_tool_calling_agent)​ : 依赖模型原生工具调用能力,自动将工具描述注入模型上下文, 直接返回工具调用参数对象,部分LLM模型支持
  • ReAct Agent (create_react_agent)​ : 遵循 Thought → Action → Observation 循环,每步根据上下文选择工具,结合自然语言与工具调用
  • ​​Structured Chat Agent (create_structured_chat_agent)​:必须遵循预定义响应模板,严格匹配工具参数格式,通常一次性完成工具选择

利用 Agent 开发一个完整的低代码平台 AI 助手,整体实现过程如下:

https://langchain-ai.github.io/langgraph/agents/agents/#1-install-dependencies

08篇 AI从零开始 - LangChain学习与实战(5) 基于RAG开发问答机器人

2025-03-15 15:00:00

做一个有温度和有干货的技术分享作者 —— Qborfy

前面几篇学习LangChain,基本上完成对 LangChain从开发到上线有一个整体的了解,那么接下来我们就要开始实战了,我们将会使用LangChain开发一个问答机器人,这个问答机器人将会使用到RAG模型,那么接下来我们开始学习RAG。

RAG是什么

RAG是一种用额外数据增强大模型知识的技术,俗称“RAG”(Retrieval-Augmented Generation),中文叫检索增强生成。

RAG是LangChain中一个重要的应用场景,它能够将检索和生成结合起来,从而生成更加精准的答案。

RAG模型由两个部分组成:Retriever和Generator。

  • Generator 生成索引向量,根据文档中问题和答案生成索引向量数据库。
  • Retriever 检索对比类似,根据用户的问题,从生成的知识库中检索召回最相关的问题和答案。

RAG技术是通过检索和生成相结合的方式找最相关的知识内容, 加入到大模型的提示语中,通过大模型推理得到用户问题在知识库中最合适的答案。

下面是我个人依据网上相关资料整理的通用RAG模型架构图:

生成向量索引(Indexing)

生成向量索引有三个步骤,分别如下:

  1. 加载(load),加载所需数据,LangChain中提供各种加载器,如:PDFLoader、TextLoader、ImageLoader等。
  2. 分割(Split),将加载的数据进行分割成一个个块,LangChain中提供各种分割器,如:TextSplitter、ImageSplitter等。
  3. 存储(Store),得到分割的Chunks,需要将Chunk转成向量索引并存储,这里我们会依赖Embeddings模型进行生成索引,然后存储到向量数据库VectorStore

Embeddings嵌入模型

RAG模型使用Embeddings模型将问题和答案进行编码,生成向量数据,这里我们必不可免需要对Embeddings模型进行初步了解。

Embeddings模型,也叫嵌入模型,是一种将高维度的数据,如:自然语言、图片、视频等,转换成低维度的向量数据,如:多维矩阵数组等,方便后续进行相似度对比。

或者我们可以更加直观的理解,Embeddings模型可以把我们人类能够理解的内容,转换成计算机能够计算理解的数据,从而实现更多的算法对比逻辑。

检索和生成

  1. 检索:通过用户输入的内容,使用检索器将内容转换为向量,然后从向量数据库中检索最相关的向量数据。
  2. 生成:通过检索器检索到的向量数据,使用生成器生成新的向量数据,然后存储到向量数据库中。

这两个步骤一般都是同时进行,一般也是 通过Embeddings嵌入模型去转换搜索内容为向量,然后通过检索到最后生成内容。

实战:做一个问答机器人

实现过程

  1. 用户上传问题知识内容上传文件
  2. 服务端对上传文件进行解析,拆分chunk
  3. 将 chunk 传给 Embeddings模型,生成向量索引
  4. 将 向量索引存储到向量数据库中
  5. 用户输入问题,通过检索器检索到最相关的向量数据
  6. 然后将最相关向量数据传给对话大模型,组织推理得到答案,返回给用户

准备工作

Ollama安装模型:

1
2
3
4
# 对话大模型
ollama install deepseek-r1:7b
# embeddings模型
ollama install shaw/dmeta-embedding-zh:latest

LangChain和 Streamlit安装

1
2
3
4
5
6
# python环境 3.12.4
# streamlit 1.39.0
# toml

pip install langchain
pip install streamlit

requirements.txt文件内容如下:

1
2
3
4
5
streamlit==1.39.0
langchain==0.3.21
langchain-chroma==0.2.2
langchain-community==0.3.20
langchain-ollama==0.2.3

streamlit是构建和共享数据应用程序的更快方法, 几分钟内将您的数据脚本转换为可共享的网络应用程序,全部采用纯 Python 编写,无需前端经验。
官方文档:https://docs.streamlit.io/get-started/installation

代码实现

新建一个文件bot_chat.py, 分步骤实现。

1. stremlit页面搭建

代码参考如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import streamlit as st

# 设置 st 的标题和布局
st.set_page_config(page_title="RAG测试问答", layout="wide")

# 设置应用标题
st.title("RAG测试问答")

# 支持上传 txt 文件
upload_file = st.sidebar.file_uploader(label="上传文件", type=["txt"])

if not upload_file:
st.info("请上传 txt 文件")
st.stop()

执行效果如下:

2. 解析文档并生成知识库检索器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

import streamlit as st
import tempfile
import os
from langchain.memory import ConversationBufferMemory # 会话记录到内存
from langchain_community.chat_message_histories import StreamlitChatMessageHistory # Streamlit聊天记录存储
from langchain_community.document_loaders import TextLoader # 文本加载器
from langchain_ollama.embeddings import OllamaEmbeddings # Ollama Eembeddings 语言模型
from langchain_chroma import Chroma # Chroma 向量数据库
from langchain_text_splitters import RecursiveCharacterTextSplitter # 文本分割器

# 设置 st 的标题和布局
st.set_page_config(page_title="RAG测试问答", layout="wide")

# 设置应用标题
st.title("RAG测试问答")

# 支持上传 txt 文件
upload_file = st.sidebar.file_uploader(label="上传文件", type=["txt"])

if not upload_file:
st.info("请上传 txt 文件")
st.stop()

# step1 实现知识库生成
@st.cache_resource(ttl="1h")
def get_knowledge_base(uploaded_file):
# 读取上传的文档
docs = []
# 将 uploaded_file 存到 /tmp
temp_dir = tempfile.TemporaryDirectory(dir=r"/tmp")
tempfilepath = os.path.join(temp_dir.name, uploaded_file.name)
with open(tempfilepath, "wb") as f:
f.write(uploaded_file.getvalue())
# 使用 TextLoader 加载文档
docs = TextLoader(tempfilepath, encoding="utf-8").load()
# 使用 RecursiveCharacterTextSplitter 分割文档
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = splitter.split_documents(docs)
# 使用 OllamaEmbeddings 生成文档向量
embeddings = OllamaEmbeddings(base_url="http://127.0.0.1:11434", model="shaw/dmeta-embedding-zh")
# 使用 Chroma 向量数据库存储文档向量
chroma_db = Chroma.from_documents(splits, embeddings)
# 创建文档检索器 约等于 知识库
retriever = chroma_db.as_retriever()
return retriever

retriever = get_knowledge_base(upload_file)

上文件后, 执行效果如下:

3. 初始化聊天消息界面

聊天功能主要几点:

  • 记录聊天内容
  • 显示聊天内容
  • 用户输入框

具体实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# step2 初始化聊天消息界面
# 如果用户输入"清空聊天记录",则重新初始化界面
if "messages" not in st.session_state or st.sidebar.button("清空聊天记录"):
st.session_state["messages"] = [{
"role": "你好",
"content": "我是测试 RAG 问答小助手"
}]

# 显示历史聊天记录
for msg in st.session_state["messages"]:
st.chat_message(msg["role"], msg["content"]) #

# 创建历史聊天记录
msgs = StreamlitChatMessageHistory()
# 创建对话缓存
memory = ConversationBufferMemory(
chat_memory=msgs,
return_messages=True,
memory_key="chat_history",
output_key="out"
)
# 创建 UI 输入框
user_query = st.chat_input(placeholder="请输入要测试的问题")

4. 创建LLM 检索 agent执行

这里实现一个Agent, 过程是 去调用一个检索工具,支持模板和用户输入,调用大模型进行检索,然后返回结果。

具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# step3 创建检索 agent 
from langchain.tools.retriever import create_retriever_tool

# step3-1 用于创建文档检索的工具
tool = create_retriever_tool(
retriever=retriever,
name="文档检索",
description="根据输入的关键词,检索相关文档",
)
tools = [tool]

# step3-2 创建 LLM对话模型
# 创建指令 Prompt
instruction = """你是一个设计用于查询文档回答问题的代理
您可以使用文档检索工具,并基于检索内容来回答问题。
可能你不查询文档就知道答案,但是仍然要去查询文档来获得答案。
如果从文档找不到任何信息和答案来回答问题,则需要返回“非常抱歉,这个问题暂时没有录入到知识库中。”作为答案。
"""

base_template = """
{instruction}

TOOLS:
----------

你可以使用以下工具:

{tools}

使用工具中,你可以参考这样子:

ZWJ```
思考:我是否需要使用工具? 是的
动作:我需要使用工具:[{tool_names}]
动作:输入:{input}
动作执行后: 返回动作执行后的结果
ZWJ```

当你需要返回一个答案,且这个答案不需要使用工具时,你可以参考这样子:

ZWJ```
思考:我是否需要使用工具? 不是
答案: [你的答案]
ZWJ```

开始!

上一次历史对话内容如下:

{chat_history}

新的问题是:{input}
{agent_scratchpad}"""

# agent_scratchpad 是 agent 的 scratchpad,用于存储 agent 的状态

# 基础模板
base_prompt = PromptTemplate.from_template(base_template)

# 填充基础模板
prompt = base_prompt.partial(instruction=instruction)

# 创建 LLM 模型
llm = OllamaLLM(base_url="http://127.0.0.1:11434", model="deepseek-r1:7b")


# step3-3 创建 agent
agent = create_react_agent(llm=llm, prompt=prompt, tools=tools)

agent_excutor = AgentExecutor(
agent=agent,
tools=tools,
memory=memory,
verbose=True,
handle_parsing_errors="从知识库没找到对应内容或者答案"
)

5. 用户输入与 Agent返回

这一步基本上就是解决用户输入显示与 Agent返回结果,同时通过 streamlit的 callbank函数去 展示 Agent的执行过程,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# step5 用户输入查询与返回
if user_query:
# 添加到 session 历史记录
st.session_state["messages"].append({"role": "user", "content": user_query})
# 显示用户信息
st.chat_message("user").write(user_query)

with st.chat_message("assistant"):
# 创建回调
callback = StreamlitCallbackHandler(st.container())
# 将 agent执行过程 显示在 streamlit中,如:思考、选择工具、执行查询等等
config = {"callbacks": [callback]}
# agent 执行
response = agent_excutor.invoke({"input": user_query}, config=config)
# 保存agent 执行结果到聊天记录
st.session_state["messages"].append({"role": "assistant", "content": response["output"]})
# 显示在 streamlit中
st.write(response["output"])

最终运行

1
streamlit run bot_chat.py

执行效果如下:
对话中:

返回答案:

参考资料

声明:本文部分材料是基于DeepSeek-R1模型生成。

07篇 AI从零开始 - LangChain学习与实战(4) LangServer部署

2025-03-12 15:00:00

做一个有温度和有干货的技术分享作者 —— Qborfy

从 LangChain系列文章开始到现在,我们学习 LangChain的基础知识和实战撸代码,现在我们假设已经开发好一个 LangChain链式任务,那么如何部署以及如何与 web服务进行互相调用呢?

那么接下来我们应该就学习LangServer与LangSmith,如何让 LangChain进行企业级开发。

LangServer: 帮开发者将 LangChain 部署一个 Restful 服务。

LangSmith: 监控 LangChain调用链路服务和提供更加友好的可视化界面。

认识 LangServer

LangServer是集成了FastApi + Pydantic的Restful框架。它提供了一些特性:

  • 每次 API调用都会返回丰富的错误信息
  • 自带 JSON Schema和 Swagger API文档
  • 高效的/invokebatch/stream接口服务,支持单个服务器多个并发请求
  • 支持调用/stream_log接口,实现链式任务中间态步骤返回
  • /stream_events更加清晰任务事件状态信息
  • 提供LangServer SDK,调用 Restful 和 直接调用大模型一样简单

实战教程

环境准备

安装langchain-cli全局命令,方便快速启动 Lang Server项目

1
pip install -U langchain-cli 

安装poetry管理项目的依赖,更好管理服务依赖 pip包。

poetry Python packaging and dependency management made easy, 翻译过来就是更加轻松管理 python项目和依赖

1
2
3
4
5
6
# 安装 pipx
pip install pipx
# pipx添加到环境变量
pipx ensurepath
# 安装 poetry
pipx install poetry

初始化和运行项目

  1. 项目初始化
    利用langchain-cli 脚手架, 初始化一个 langserver项目
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 初始化一个langserver的项目
langchain app new mylangserver

pipx run langchain app new mylangserver

# 进入项目目录
cd mylangserver

# 安装项目依赖
poetry install

# 安装后续依赖的包
poetry add langchain
poetry add langchain_ollama
  1. 项目结构说明
    生成的目录结构与说明如下:
1
2
3
4
5
6
7
8
9
10
11
mylangserver # 项目名
├─.gitignore
├─Dockerfile
├─README.md
├─pyproject.toml # 项目结构说明文件
├─poetry.lock # poetry依赖锁文件 类似前端的yarn.lock
├─packages
| └README.md
├─app
| ├─__init__.py
| ├─server.py # 服务主入口 后续开发都在这里
  1. 运行项目

在根目录下运行如下命令:

1
langchain server

就可以直接访问 http://localhost:8080,效果具体如下图:

接口开发

  1. server.py文件
    在接口开发之前我们先看看 server.py文件,具体如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
from langserve import add_routes

app = FastAPI()

# 这里是定义路由和对应路由实现的方法
@app.get("/")
async def redirect_root_to_docs():
return RedirectResponse("/docs")


# 这里我们可以添加路由
# add_routes(app, NotImplemented)

if __name__ == "__main__":
import uvicorn
# 这里通过 uvicorn启动服务,端口为8000
uvicorn.run(app, host="0.0.0.0", port=8000)
  1. 添加一个调用DeepSeek-R1模型的接口

通过 add_routes新增一个模型对象,会自动封装成对应的 Restful接口,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
from langserve import add_routes
from langchain_ollama.llms import OllamaLLM

deepseek = OllamaLLM(base_url="http://127.0.0.1:11434", model="deepseek-r1:32b")

app = FastAPI(
title="Qborfy个人 LangServer",
version="0.1.0",
description="Qborfy个人 LangServer,学习测试使用",
)


@app.get("/")
async def redirect_root_to_docs():
return RedirectResponse("/docs")

# 添加 deepseek路由
add_routes(app, deepseek, path="/deepseek")


if __name__ == "__main__":
import uvicorn

uvicorn.run(app, host="0.0.0.0", port=8000)

接下来我们访问 http://localhost:8000/,就会出现和/deepseek相关的文档,具体如下图:

接口测试

接口测试我们可以通过ApiFox (一个和 Postman类似 API 测试工具)进行测试,具体如下.

  1. /invoke 发起一个对话请求, 具体如下图:

  1. /stream 流式调用,具体如下图:

SDK调用

在LangChain中是可以通过 LangServe提供的 RemoteRunable 进行远程调用,和原来的调用大模型的使用方式其实是一样的, 具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 实现langchain调用远端调用 LangServer
from langchain.schema.runnable import RunnableMap
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langserve import RemoteRunnable

llm = RemoteRunnable("http://127.0.0.1:8000/deepseek")

template = "你是世界级的 AI 技术专家, {input}"
# 这里我们使用一个简单的模板
prompt = PromptTemplate(
input_variables=["input"],
template=template
)
# 创建一个简单的链式调用
chain = prompt | llm | StrOutputParser()

# 执行链式调用
for chunk in llm.stream("海洋是什么颜色"):
print(chunk, end="|", flush=True)

这样子我们不仅可以在本地调用大模型,还能调用其他人提供 LangChain服务,从而实现更加复杂的功能。

生产部署

LangServer生产部署,按照 LangChain官方推荐是通过Dockerfile打包进行部署,其中也比较简单,具体执行如下命令:

1
2
3
4
5
# 打包镜像
docker build . -t my-langserve-app

# 运行镜像
docker run -p 8080:8080 my-langserve-app

或者是通过 docker-compose启动 docker服务,具体如下:

1
2
3
4
5
6
version: '3'
services:
langserver:
image: my-langserve-app
ports:
- 8080:8080

最终执行docker-compose up -d启动服务后,就可以通过http://localhost:8080/docs访问到服务了。

监控与日志

LangSmith监控

LangSmith是 LangChain官方提供的监控工具,可以监控模型运行情况,一个 SASS服务需要我们将服务相关信息注册到 LangSmith网站上,因此可以按个人或公司需要判断是否允许使用。

LangSmith是一个用于构建生产级应用的平台。它允许您密切监控和评估您的应用,以便您可以快速而自信地发布应用。

传统软件应用程序是通过编写代码来构建的,而 AI 应用程序则需要编写提示来指导应用程序执行哪些操作。

LangSmith 提供了一套旨在实现和促进提示工程的工具,以帮助您找到适合您的应用程序的完美提示。

PS:LangSmith这里提示未来编程开发者的思维转变,我们实现功能的思路不再是针对一些实现逻辑,而是面向不同的 AI 模型,优化提示语实现我们想要的功能。

在 LangServer 中使用 LangSmith,需要先注册一个账号,然后获取 api key 添加到 LangServer中,具体使用代码如下:

1
2
3
4
5
6
7
8
import os

# 设置LangSimth 环境变量
os.environ["LANGSMITH_TRACING"] = "true"
os.environ["LANGSMITH_ENDPOINT"] = "https://api.smith.langchain.com"
os.environ["LANGSMITH_API_KEY"] = "<LANG_SIMTH_KEY>"
os.environ["LANGSMITH_PROJECT"] = "mylangserver"

具体使用教程可以参考LangSmith官方文档

LangSmith主要作用是监控 链路任务节点调用和扭转情况,可以更加清晰的分析链的运行情况和日志,具体效果如下图所示:

Verbose关键日志

如果我们不想把自己的服务相关的日志信息暴露给 LangSmith, 我们还可以通过设置set_verbose设置详细日志开关, 从而实现我们调用 LangChain链路的完整日志,具体如下:

1
2
3
4
5
6
7
from langchain.globals import set_verbose
# 全局 verbose配置开关
set_verbose(True)

# 针对部分链接调用设置详细日志开关

llm = OllamaLLM(base_url="http://127.0.0.1:11434", model="deepseek-r1:32b", verbose=True)

打开开关后,我们再调用helloworld.py大模型,可以看到关键的日志信息,但是verbose只会输出关键日志,下面我们还可以 debug查看更加详细的日志信息。

Debug日志

我们可以通过debug设置日志级别,从而查看更加详细的日志信息,具体如下:

1
2
3
from langchain.globals import set_debug
# 全局 debug配置开关
set_debug(True)

打开开关后,我们再调用helloworld.py大模型,可以看到更加详细的日志信息,具体如下图:

总结

本文我们主要学习了LangServer的去部署一个 LangChain链,通过 LangServe对于第三方就能加友好的访问我们的提供 LangChain服务,从而实现更加复杂的功能。回顾一下,我们主要学习了以下内容:

  • LangServer 安装与运行:通过 LangChain-Cli脚手架创建项目和langchain serve运行项目
  • LangServer 接口开发:add_routes添加接口,可以直接把一个 LangChain添加到 LangServer中且自动生成 Swagger文档
  • LangServer监控与日志:LangSmith是LangChain官方提供的监控工具,但是会上报我们服务相关的日志信息,因此我们可以设置set_verbose或者set_debug设置详细日志开关, 从而实现我们调用 LangChain链路的完整日志

参考资料

声明:本文部分材料是基于DeepSeek-R1模型生成。

06篇 AI从零开始 - LangChain学习与实战(3) LCEL工作流编排原理与实战

2025-03-03 15:00:00

做一个有温度和有干货的技术分享作者 —— Qborfy

上一篇文章我们学习05篇 AI从零开始 - LangChain学习与实战(2) PromptTemplate降低AI幻觉,对 LangChain实际应用有了基本的了解,接下我们会进入 LangChain最重要的一次学习,就是 LangChain工作流编排原理与实战(LCEL), 学了本文基本上后续 LangChain实际开发都可以独立完成,本文内容较多,建议大家收藏,慢慢学习。

1. LCEL介绍

LCEL(LangChain Expression Language) 是 LangChain提出来一种强大的工作流编排模式,可以通过基础组件去构建复杂任务链条式,包括但不限于:

  • 流式处理
  • 并行处理
  • 日志记录
  • …等等

1.1 LCEL的特性

  • 一流的流式支持: 通过 LCEL 构建链时, 可以获得链式最佳时间(从第一个任务到最后一个输出所经历的时间),如:你通过不同链路调用 LLM 大模型到输出 ≈ 直接调用 LLM 大模型输出
  • 异步支持:LCEL链路中任何一个任务都可以异步执行,如:你可以在一个任务中调用另一个任务,而无需等待该任务完成
  • 优化的并行任务处理:针对多个并行步骤时候,LCEL会自动优化并行任务,达到最小延迟
  • 重试和回退:LCEL 链路中的任何任务都可以重试,如果失败,可以回退到上一个任务
  • 访问中间结果:可以访问 LCEL 链中的任何任务的结果,可以给用户提供实时结果和方便开发调试
  • 标准化的输入和输出模式:每个 LCEL链都可以直接使用 Pydantic对象和 JSON对象作为输入和输出,从而更好验证链路的正确性,并且LangServer的重要组成部分

2. LCEL原理设计 —— Runable Interface

LCEL之所以有如此强大的功能,离不开它的设计理念 —— Runable Interface,LangChain一套标准且强大的接口设计规范,让所有的组件都按照这个去实现,从而实现 LCEL 工作流编排。

Runable Interface接口设计有以下几个方面“

2.1 标准调用方法

同步调用方法:

  • stream: 支持按照 stream流式返回
  • invoke: 支持同步调用
  • batch: 批量调用,等于多个invoke调用

结合await实现异步调用的方法:

  • astream: 异步调用stream流式返回
  • ainvoke: 异步调用
  • abatch: 异步调用批量调用,等于多个ainvoke调用
  • astream_log: 异步返回中间步骤,可以监控异步调用,最终会返回最后的结果
  • astream_envent: stream的异步事件监听,如:开始调用和结束调用触发

2.2 输入和输出

LangChain的输入和输出都是遵循schema规范,从可运行对象结构自动生成对应的Pydantic模型。

Pydantic是用于数据建模/解析的Python库,它允许您定义一个数据模型,然后使用该模型验证输入和输出。
它可以帮助您确保输入和输出数据符合预期的格式和类型,从而提高代码的健壮性和可维护性。
同时 Pydantic 内置了对JSON编码和解码的支持。

不过不同组件输入类型和输出类型是不同的,下面常用 LangChain组件输入和输出类型:

组件 输入类型 输出类型
提示 Prompt string PromptTemplate提示值
聊天模型 string、聊天信息列表、提示值 string
LLM string、聊天信息列表、提示值 string
输出解析器 LLM、 LLM的输出 取决于解析器的类型,如 jsonparser输出的是 json格式

流式运行对于基于 LLM 开发的应用会对用户使用体验上有更好的体验,所以目前LangChain中重要的组件都实现 LangChain Runable Interface中的 streamastream,如:PromptTemplateChatModelLLMOutputParser等。

2.3 Stream流

上面弄清楚 LCEL的运行原理,我们还需要了解 Stream这一概念,才能更好的理解 LCEL工作流编排。

Stream 指的是一个数据流,它表示一个连续的数据序列,可以是一个数据块、一个文件、一个数据库表、一个网络连接等。在计算机科学中,流通常用于表示实时数据传输,例如从网络连接中接收的数据、从文件中读取的数据等。流数据具有连续性、实时性和不可预测性等特点,因此处理流数据需要特殊的算法和数据结构。

在 LangChain中所有 Runable对象都实现了 stream(同步)astream(异步)接口,通过 streamastream接口,LangChain链式中的每个任务步骤都可以按照流式输入与输出。
从简单的任务,如发起一个 LLM调用,到复杂的任务,如:传输json数据等。

3. LCEL工作流编排实战

了解完 LCEL工作流编排原理,我们开始实战,下面我们通过几个的例子,来更好理解 LCEL工作流编排。

3.1 一次基础的流式调用

我们去调用一个 Ollama 大模型,然后调用 stream方法,看看最终会输出什么?

1
2
3
4
5
6
7
8
9
10
# 实现langchain调用 ollama 大模型
from langchain_ollama.llms import OllamaLLM

llm = OllamaLLM(base_url="http://127.0.0.1:11434", model="deepseek-r1:32b")

chunks = []
# llm.stream 会返回一个流
for chunk in llm.stream("海洋是什么颜色"):
chunks.append(chunk)
print(chunk, end="|", flush=True)

最终输出效果,按照一块块输出,如下图:

3.2 astream 异步调用

进一步看看astream调用和stream调用有什么区别?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 实现langchain调用 ollama 大模型
from langchain_ollama.llms import OllamaLLM
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
import asyncio

llm = OllamaLLM(base_url="http://127.0.0.1:11434", model="deepseek-r1:32b")
propmt = ChatPromptTemplate.from_template("给我讲一个关于{input}的笑话")
parser = StrOutputParser()
chain = propmt | llm | parser

# 异步调用需要定义 async 方法
async def async_stream():
async for chunk in chain.astream("公鸡"):
print(chunk, end="|", flush=True)

# 调用的话需要通过 asyncio.run() 方法
asyncio.run(async_stream())

异步调用需要定义 async 方法,调用的话需要通过 asyncio.run() 方法,最终效果和stream调用效果一样,不过在并行任务较多的情况下,astream调用会利用更多的CPU资源,从而提高并行任务处理效率。

3.3 json输出格式

在实际应用中,我们很多场景其实 web服务通过 http协议传输,而且希望能被其他服务调用因此json格式输出会更好,下面我们看看如何实现?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 实现langchain调用 ollama 大模型
from langchain_ollama.llms import OllamaLLM
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser
import asyncio

llm = OllamaLLM(base_url="http://127.0.0.1:11434", model="deepseek-r1:32b")
propmt = ChatPromptTemplate.from_template(
"""
以 JSON格式返回{x}的人口列表
使用一个`省份`作为字段列表返回
每个省份都应有有字段`省份名`+`人口`字段
""")
parser = JsonOutputParser() # 保证每次输出都是json格式
chain = propmt | llm | parser

# 异步调用需要定义 async 方法
async def async_stream():
async for chunk in chain.astream("广东省、福建省、广西省"):
# 可以看到每次 chunk都是一个完整的 json 格式
print(chunk, end="\n", flush=True)

# 调用的话需要通过 asyncio.run() 方法
asyncio.run(async_stream())

上文我们可以看到用到JsonOutputParser,它保证每次输出都是json格式,所以最终输出效果如下:

3.4 stream_event监听

我们先看调用一个 LLM模型会产生哪些事件?

1
2
3
4
5
6
7
8
9
10
# 实现langchain调用 ollama 大模型
from langchain_ollama.llms import OllamaLLM
import asyncio

llm = OllamaLLM(base_url="http://127.0.0.1:11434", model="deepseek-r1:32b")
async def async_stream():
async for event in llm.astream_events("你好", version="v2"):
print(event)
# 调用的话需要通过 asyncio.run() 方法
asyncio.run(async_stream())

输出如下图:

返回event数据结构如下:

  • event事件类型,如:stream_startstream_endstream_chunk
  • data事件数据,如:stream_chunk事件数据为chunkstream_end事件数据为end
  • run_id本次调用id,当多次任务并发的时候可以找到对应任务
  • metadata事件元数据,包括模型版本、模型名称、模型参数等
  • 其他一些其他信息,如:tagsnameparent_ids

完整的事件类型我们可以到 LangChain官方文档去查看,地址为如何使用 Stream Events

总结

通过本文我们完整了解 LangChain LCEL流式调用和工作原理,从而为后续使用LangChain进入实际开发提供基础知识。 LCEL主要包含以下内容:

  • 流式调用方法 streamastream
  • 调用事件监听 astream_events
  • 输出格式要求 JsonOutputParser

参考资料

声明:本文部分材料是基于DeepSeek-R1模型生成。

05篇 AI从零开始 - LangChain学习与实战(2) PromptTemplate降低AI幻觉

2025-02-20 15:00:00

做一个有温度和有干货的技术分享作者 —— Qborfy

上一节学习了04篇 AI从零开始 - LangChain学习与实战(1) 基础知识,对Langchain有了基础的认知和简单应用,其中我们使用PromptTemplate去实现一次大模型对话。

同时我们在03篇 AI从零开始 - Prompt提示语学习与应用也学习了提示语生成规范,但是在结合 LangChain中我们应该如何利用PromptTemplate提示模板+提示语规范去降低 AI幻觉呢?

AI幻觉, 指人工智能(尤其是大语言模型)生成看似合理但实际错误、虚构或与现实不符的内容的现象。本质是模型在缺乏真实理解能力的情况下,基于统计模式生成的「自信错误」。

下面是 Langchain执行一次 LLM 调用的流程图:

1. 什么是PromptTemplate

提示词模板跟平时大家使用邮件模板、短信模板类型,本质上是一个字符串模板,模板里包含了模板参数,可以通过输入参数来生成最终的提示词。

一个提示词模板包括以下内容:

  • 发送给大模型的指令
  • 一个问答示例,提醒大模型用什么格式返回
  • 发给大模型的问题

2. 创建提示词模板

可以使用PromptTemplate类来创建一个提示词模板,它接收一个prompt参数,这个参数是一个字符串,用于指定提示词模板。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from langchain_core.prompts import ChatPromptTemplate

# 通过一个消息数组创建聊天消息模板
# 数组每一个元素代表一条消息
# 第一个参数是消息角色 system 代表系统消息, 第二个参数代表消息内容
# 消息角色 system 代表系统消息
# 消息角色 human 代表系统消息代表人类
# 消息角色 ai 代表LLM大模型返回的消息内容
# {xxx} 定义 模板参数,如下定义两个模板参数 name代表人工智能名字 user_input 代表用户输入的文本
chat_template = ChatPromptTemplate.from_messages(
[
("system", "你是人工智能助手, 你的名字是{name}"),
("human", "你好"),
("ai", "你好,我是人工智能助手{name},很高兴为您服务"),
("human", "{user_input}"),
]
)

# 通过模板参数格式化模板内容
message = chat_template.format_messages(name="小爱同学", user_input="你的名字叫什么?")

print(message)

这样子我们就可以得到一个正确的Message了, 如上代码执行结果如下:

返回的内容如下说明:

  • SystemMessage: 系统设定,设定大模型的角色
  • HumanMessage: 人类消息,代表用户输入的消息
  • AIMessage: 人工智能消息,代表大模型返回的消息

LangChain还抽象了其他提示语模版,具体如下:

  • PromptTemplate: 普通提示词模板,返回一个字符串
  • ChatPromptTemplate: 聊天消息模板,返回一个ChatPromptTemplate对象,可以设定大模型角色和示例

3. 上下文 MessagesPlaceHolder

MessagesPlaceHolder主要作用在特定位置添加消息列表(等于占位符), 可以集中管理消息列表,更好聊天过程注入上下文。

1
2
3
4
5
6
7
8
9
10
11
12
13
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage

chat_template = ChatPromptTemplate.from_messages(
[
("system", "你是人工智能助手"),
MessagesPlaceholder("msgs")
]
)
# 这里我们可以之前的定义的消息列表放在一起
msgs=[SystemMessage(content='你的名字是小爱同学'), HumanMessage(content='你好'), AIMessage(content='你好,我是人工智能助手,很高兴为您服务')]

print(chat_template.invoke(msgs))

4.提示词示例 FewShot

在之前03篇 AI从零开始 - Prompt提示语学习与应用中也提到过示例的重要性, 这里我们不在说示例对应大模型应用中的重要性了。

我们看看 在 LangChain中如何讲示例集 给到大模型中,其中FewShot主要作用是给大模型提供示例,让大模型更好的理解用户输入,从而生成更符合用户预期的结果,从而降低 AI 幻觉。

这里可以理解成模型的微小型训练,让大模型能依据我们提供少量示例(有点类似小RAG知识库),从而更加更加准确的答案。

在 LangChain 创建一个示例集,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from langchain.prompts.few_shot import FewShotPromptTemplate
from langchain.prompts import PromptTemplate

examples = [
{
"question": "谁的寿命更长, 穆罕默德二世还是爱因斯坦?",
"answer":
"""
爱因斯坦活了 76 岁。
穆罕默德二世活了 89 岁。
因此,穆罕默德二世比爱因斯坦活得更长。
"""
},
{
"question": "目前电影票房第一名是谁?",
"answer":
"""
《阿凡达》的票房是 27.9 亿美元。
《复仇者联盟 4:终局之战》的票房是 27.8 亿美元。
因此,《阿凡达》的票房更高。
"""
},
]
# 这里我们创建一个简单示例模板,用来分析解析示例的 question 和 answer
example_prompt = PromptTemplate(input_variables=["question", "answer"], template="问题:{question}\\n答案:{answer}")

# 用 FewShotPromptTemplate 可以根据模板+示例去生成一个拥有示例集的提示语模板
prompt = FewShotPromptTemplate(
examples=examples,
example_prompt=example_prompt,
suffix="问题:{input}",
input_variables=["input"],
)

print(prompt.format(input="谁的寿命更长, 穆罕默德二世还是爱因斯坦?"))

输出结果如下:

5. 示例选择器 ExampleSelector

示例集合等同于一个知识库,在正式环境中我们不可能把完整的知识库都给到模型,因此我们需要去分析用户输入,从而选择合适的示例,LangChain 提供了ExampleSelector类来帮助我们实现这个功能。

LangChain 提供了不同的ExampleSelector,具体如下:

  • SemanticSimilarityExampleSelector: 语义相似性示例选择, 会根据用户输入,然后通过嵌入模型计算输入与示例之间的相似性,然后使用向量数据库进行相似搜索,从示例集合中选择最相似的示例。
  • MaxMarginalRelevanceExampleSelector: 基于 最大边际相关性(MMR) 的示例选择器, 希望在从示例集中选择与输入 既相关又多样化 的示例, 通过平衡 相关性 与 多样性 来优化示例选择效果。

通常情况下,我们使用SemanticSimilarityExampleSelector, 根据上面示例集合,我们根据问题去选择示例,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# ExapleSelector 示例筛选器
from langchain.prompts.example_selector import SemanticSimilarityExampleSelector
from langchain_chroma import Chroma
from langchain_ollama.embeddings import OllamaEmbeddings
from langchain.prompts import FewShotPromptTemplate, PromptTemplate

# 引用 shaw/dmeta-embedding-zh 模型做为嵌入模型,其对中文支持度更加友好
ollama_emb = OllamaEmbeddings(
base_url="http://127.0.0.1:11434",
model="shaw/dmeta-embedding-zh",
)

examples = [
{
"question": "谁的寿命更长, 穆罕默德二世还是爱因斯坦?",
"answer":
"""
爱因斯坦活了 76 岁。
穆罕默德二世活了 89 岁。
因此,穆罕默德二世比爱因斯坦活得更长。
"""
},
{
"question": "目前电影票房第一名是谁?",
"answer":
"""
《阿凡达》的票房是 27.9 亿美元。
《复仇者联盟 4:终局之战》的票房是 27.8 亿美元。
因此,《阿凡达》的票房更高。
"""
},
{
"question": "深圳第一高楼是哪个?",
"answer":
"""
深圳平安国际金融中心(平安中心)的楼高是 593米。
深圳京基100的楼高是441.8米。
所以深圳第一高楼是平安国际金融中心。
"""
},
]

example_selector = SemanticSimilarityExampleSelector.from_examples(
# 这里是示例集合
examples=examples,
# 用户生成嵌入的嵌入类,用于衡量语义的相似度
embeddings=ollama_emb,
# 用于计算相似性的向量存储库,这里使用的是 Chroma, 一个保存在内容的向量存储库
vectorstore_cls=Chroma(),
# 选择前 k 个最相似的示例 这里设置为 1
k=1,
)

question = "穆罕默德二世?"
# 选择最相似的示例
selected_examples = example_selector.select_examples({"question": question})

# 接下来结合 FewShotPromptTemplate 我们就可以得到更加准备且少量的示例 PromptTemplate
example_prompt = PromptTemplate(input_variables=["question", "answer"], template="问题:{question}\\n答案:{answer}")

prompt = FewShotPromptTemplate(
# 这里调整完最相似的示例集合
examples=selected_examples,
example_prompt=example_prompt,
suffix="问题:{input}",
input_variables=["input"],
)

print(prompt.format(input=question))

具体输出效果如下图:

总结

本文我们完整学习了 LangChain 中如何使用 PromptTemplate去更好的创建一个提示语模板,从而降低 大模型的自我创新性(降低AI 幻觉)。

这里总结一下整个实现过程 约等于 后续 RAG知识库训练过程,如下图所示:

参考资料

声明:本文部分材料是基于DeepSeek-R1模型生成。