MoreRSS

site iconQborfy修改

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

Inoreader Feedly Follow Feedbin Local Reader

Qborfy的 RSS 预览

09篇 AI从零开始 - LangChain企业级应用开发——低代码平台的智能体Agent

2025-03-31 15:00:00

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

经过LangChain系列文章的学习, 实战一个有趣的小应用,通过Agent调用工具+大模型的推理能力开发一个 【低代码平台的 AI 小助手】,通过用户输入自然语言能实现 低代码平台页面生成。

如无意外,这篇应该是 LangChain最后一篇学习内容,将之前文章进行汇总,具体如下:

参考资料

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

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
15
# 初始化一个langserver的项目
# https://github.com/langchain-ai/langchain/tree/master/templates
langchain app add mylangserver

pipx run langchain app add 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模型生成。

04篇 AI从零开始 - LangChain学习与实战(1) 基础知识

2025-02-18 15:00:00

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

上一节学习了03篇 AI从零开始 - AI从零开始 - Prompt提示语学习与应用,但是我们发现,Prompt提示语虽然可以让我们得到想要的答案,但是它也有缺点,比如:

  • 只调用单个大模型的对话提示语来生成答案,且无法验证答案的正确性;
  • 大模型是没有记忆能力,且有token上下对话长度限制,无法实现多轮对话;
  • 多步骤推理任务,需要多次调用大模型,效率低下

如果要开发一个完整 LLM应用,开发者需要手动处理:

  • 多组件集成:模型调用、外部数据源、记忆存储、业务逻辑等模块的拼接
  • 上下文管理:对话历史、长期记忆、知识库检索的复杂交互
  • 流程编排:多步骤推理、条件分支、循环控制等逻辑

接下来,我们学习一下LangChain,它是一个基于链式调用的LLM框架,可以让我们更加方便地使用大模型,实现多轮对话、多步骤推理等复杂功能。

1. 是什么

Langchain是开发由大型语言模型(LLMS)提供支持的应用程序的框架。

从我理解的是, LangChain 是一个用于开发大语言模型(LLM)应用的框架,它的核心价值在于简化复杂语言模型应用的开发流程,并提供标准化的工具链。

1.1 基础功能

Langchain 提供了以下基础功能:

  • LLM调用: 支持调用 OpenAI、Hugging Face、Azure 等主流的 LLM 服务, 同时支持缓存。
  • Prompt管理: 拥有大量的文档加载器,比如 PDF、Markdown等
  • 对索引的支持: 文档分割器,向量化,对接向量存储与搜索,比如 Chroma、Pinecone、Qdrand等
  • Chains链路调用: LLMChain、各种工具Chain等

1.2 必知概念

LLM模型和Prompt提示语

Langchain 针对所有的LLM大模型的 API 进行抽象,统一了大模型访问API,同时也提供了 Prompt 模板管理机制。

Chain 链

可以把 Chain 理解为任务。一个 Chain 就是一个任务,当然也可以像链条一样,一个一个的执行多个链。

LCEL 表达式

LCEL: LangChain Expression Language,通过表达式解决工作流编排问题,可以灵活自定义 AI任务处理流程,也就是自定义链。

数据增强生成 RAG

RAG: Retrieval Augmented Generation,用于增强大模型的知识内容,录入新的信息到大模型中的一种模式。

Agents 智能体

Agent其实是大模型的一种应用设计模式,利用 LLM自然语言理解能力和推理能力,去实现用户输入需求自动调用外部系统、设置去共同完成任务。

常见的智能体:

  • 对话机器人,值班客服,智能客服等
  • 知识库问答,基于某个知识库进行回答
  • 智能写作,如:创意写作,文本摘要等

Memory 模型记忆

LangChain提供一套内存机制,让LLM可以记住对话上下文内容,从而实现模型记忆。

OutParsesr 输出解释器

Langchain接收大模型返回文本内容(原始数据基本上是 markdown格式)后,可以使用专门的输出解析器转换数据结果,比如转成 json, 或者转换为 python对象等。

Vectorstores 向量数据库

将 Document 文档转换成向量存储,才能进行向量存储。因为大模型只能处理向量,所以需要将文本转换成向量。

转换成向量也很简单,只需要我们把数据存储到对应的向量数据库中即可完成向量的转换。

官方也提供了很多的向量数据库供我们使用。

https://python.langchain.com/en/latest/modules/indexes/vectorstores.html

Embedding 嵌入

Embedding(嵌入) 是将文本转化为数值向量(vector)的核心技术,用于捕捉语义信息并实现机器可理解的表示。

Langchain 提供了多种 Embedding 模型调用,具体可以到官网查看,Embedding models 嵌入模型

更多概念可以到官方文档查看:LangChain官方概念指南

1.3 基础框架

LangChain(v0.3版本)的框架图如下:

langchain-architecture

更加详细说明

  • LangChain-Core: 抽象了不同组件和组合在一起的方法,包括:聊天模型、向量存储、工具等核心组件的接口,尽量不依赖其他库。
  • LangChain: LangChain对外提供主要入口框架,集成绝大部分功能点。
  • Integration packages: 主流库的集成,比如:langchain-openai、langchain-anthropic,这里他们可以自主控制版本,这里可以看到集成包的信息
  • LangChain-community: Langchain社区提供的工具包,比如:langchain-ollama、langchain-duckduckgo、langchain-google、langchain-bing等。
  • LangGraph: 提供给 Langchain 链中更多可扩展性,用于创建常见代理类型的高级接口,以及用于组成自定义流程的底层应用程序接口。
  • langServe: 可以让你的链转换为Restful服务暴露给外部系统
  • LangSmith: 一个开发人员平台,可让您调试,测试,评估和监视LLM应用程序。

2. 怎么做

为了更好地使用 LangChain,我们先来写一个简单的例子,来了解下 LangChain 的使用流程。

2.1 安装和初始化项目

1
2
3
4
5
6
# 新建目录
mkdir ai-learn04-langchain && cd ai-learn04-langchain
# 初始化venv环境
python -m venv venv
# 激活venv环境
. venv/bin/activate

安装后续依赖pip 新建文件requirements.txt,写入以下内容:

1
2
3
langchain==0.3.19
langchain-community==0.3.17
langchain-ollama==0.2.3

执行pip install -r requirements.txt安装依赖。

2.2 第一次调用大模型问答

新建一个demo1.py文件,写入以下代码:

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

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

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

# 执行链式调用
response = chain.invoke({
"input":"请写一篇关于 AI 的文章,字数不大于 100"
})
print(response)

输出结果如下图所示:

langchain-1

3. 总结

Langchain让我们实现调用一个大模型API 变得更加简单,只需要几行代码就会实现一个简单对话。主要实现逻辑为:

  • 引入 Langchain 封装好的大模型的库
  • 创建一个 PromptTemplate 模板
  • 创建一个链式调用,包括:PromptTemplate | 大模型 | StrOutputParser
  • 执行链式调用并输出结果

记录问题: 如果我部署的大模型 Langchain 没有找到对应的模型,应该怎么做?

参考资料

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