前言
第一次参加字节跳动青训营的AI方向,让我有机会深入学习LangChain这个强大工具。在AI飞速发展的今天,应用开发正迎来一个“奇点”时刻。这篇文章主要就是介绍:
- 开篇词|带你亲证AI应用开发的“奇点”时刻
- LangChain系统安装和快速入门
- 用LangChain快速构建基于“易速鲜花”本地知识库的智能问答系统
逃离配环境的工具-AI练中学
你还在为学习之前需要浪费时间配置复杂环境的问题而苦恼吗?
你还在为环境配置频繁出错的问题的报错而抓狂吗?
你还在为无法在线运行教学课程中的代码而担忧吗?
快来试试AI练中学功能吧!
AI练中学这个功能是在开营班会时老师强烈推荐的,所以我迫不及待地进行了尝试。它为每个人提供了一个专属的服务器空间,虽然配置不算高,但足够满足我们学习的需求。在这个空间里,我们可以自由探索各种AI工具,进行项目实践和实验,不再受限于本地环境的配置。
特别棒的一点是,在AI练中学功能中,不仅课程中的代码已经预先编写好,还可以直接进行运行。这意味着我不需要手动配置各种环境,所有的设置都已经妥善处理,随时可以使用。这样一来,我可以将更多的精力集中在代码的功能实现和逻辑思考上,而不必为那些繁琐的环境问题浪费时间。这种便利大大提升了我的学习效率,让我能够更专注地探索AI的各种应用和开发技巧。
下面所有的代码实现以及运行都是在AI练中学中实现。
API 配置
在使用AI练中学时,如果没有特殊需求,其实是不需要进行API的申请和配置的。这个平台已经为我们精心配置好了所有的环境变量,确保我们可以直接开箱即用,享受无缝的学习体验。这种便利性大大降低了技术门槛,让任何人都可以轻松上手。
当然,如果你想使用自己的API,也完全可以做到。只需参考README中的说明,按照简单的步骤进行配置即可。这种灵活性让我们既可以享受平台提供的优质资源,也能根据自己的需要进行个性化设置,真的是非常贴心!这样一来,无论是新手还是有经验的开发者,都能在这个平台上找到适合自己的学习方式。
以豆包API为例:
- 访问火山方舟官网完成账号注册和服务授权
- API-KEY管理 -> 创建API -> 得到API_Key
- 在线推理页面 -> 创建推理接入点 -> 选择模型
- 创建后得到base_url、model_endpoint
- 编辑项目中的 /home/cloudide/.cloudiderc 文件,将API_Key、base_url、model_endpoint 配置在环境变量中,在命令行中执行
source ~/.cloudiderc,之后就都可以使用自己豆包模型API执行课程代码。
export OPENAI_API_KEY=<YOUR_API_KEY>
export OPENAI_BASE_URL=<YOUR_MODEL_BASE_URL>
export LLM_MODELEND=<YOUR_MODEL_ENDPOINT>
Langchain 核心介绍
LangChain 是一个强大的框架,旨在简化和加速基于自然语言处理(NLP)的应用开发。它提供了一个易于使用的接口,帮助开发者构建复杂的应用,如智能问答系统、对话生成、文本分析等。
关键特性
- 数据感知:能够将语言模型与多种数据源连接,实现对更丰富和多样化数据的理解和利用。这种能力使得模型不仅仅局限于语言本身,而是能结合外部信息进行智能分析和决策。
- 具有代理性:使语言模型能够与其环境进行交互,深入理解周围的动态情况,并做出有效响应。通过这种代理能力,模型不仅能理解信息,还能采取行动,从而变得更具智能化。
六个关键组件
- 模型(Models) :这个组件提供了各类语言模型的接口和调用细节,让用户可以轻松使用多种强大的模型,推动应用智能化。
- 提示模板(Prompts) :通过优化提示设计,提示模板帮助用户更高效地激发语言模型的潜力,从而得到更准确的输出。
- 数据检索(Indexes) :这个组件帮助构建和管理文档,支持用户快速查询相关信息,方便搭建本地知识库,提升信息获取效率。
- 记忆(Memory) :通过短期和长期记忆,记忆组件让对话中的数据得以存储和检索,从而使ChatBot能“记住”用户的身份和交互历史,提供更个性化的体验。
- 链(Chains) :这是LangChain的核心机制,通过组合不同功能,自动化处理常见任务,帮助开发者高效构建复杂的应用流程。
- 代理(Agents) :这个组件允许模型自主调用外部和内部工具,实现智能代理的功能,让应用能够根据用户需求自动响应,提升交互体验。
这些组件是LangChain的基础,互相协作,形成了一个强大灵活的系统。在基础篇中,我们将详细探讨这些组件的工作原理和实际使用方法,并提供丰富的案例,帮助你更好地理解和应用这些核心要素。通过掌握这些组件,你将能够开发出更具创意和实用性的AI应用。
案例
1. 海报文案生成器
下面的代码实现了一个海报文案生成工具,并利用LangChain框架创建了一个智能体,可以根据输入的图片链接生成描述或推广文案。以下是代码的详细介绍:
Part 0: 导入所需类
首先,代码导入了一系列所需的库和类:
os:用于处理系统路径和环境变量。requests:用于发送HTTP请求。PIL.Image:用于图像处理。transformers:用于加载和使用预训练的BLIP模型。langchain.tools和langchain_openai:用于构建LangChain的工具和集成OpenAI的语言模型。langchain.agents:用于初始化和管理智能体。
# ---- Part 0 导入所需要的类
import os
import requests
from PIL import Image
from transformers import BlipProcessor, BlipForConditionalGeneration
from langchain.tools import BaseTool
from langchain_openai import ChatOpenAI
from langchain.agents import initialize_agent, AgentType
Part I: 初始化图像字幕生成模型
这部分代码初始化了一个用于生成图像字幕的模型:
-
模型选择:指定了要使用的HuggingFace模型(
Salesforce/blip-image-captioning-large),这是一个用于图像字幕生成的预训练模型。 -
处理器和模型初始化:
- 创建了一个图像处理器(
BlipProcessor),用于准备图像输入。 - 初始化了图像生成模型(
BlipForConditionalGeneration)。
- 创建了一个图像处理器(
# ---- Part I 初始化图像字幕生成模型
# 指定要使用的工具模型(HuggingFace中的image-caption模型)
hf_model = "Salesforce/blip-image-captioning-large"
print("正在初始化图像字幕生成模型...")
# 初始化处理器和工具模型
# 预处理器将准备图像供模型使用
processor = BlipProcessor.from_pretrained(hf_model)
# 然后我们初始化工具模型本身
model = BlipForConditionalGeneration.from_pretrained(hf_model)
print("初始化图像字幕生成模型成功")
Part II: 定义图像字幕生成工具类
这里定义了一个名为 ImageCapTool 的工具类,继承自 BaseTool:
-
名称和描述:提供工具的名称和功能描述,指明需要传入图片的URL。
-
_run方法:实现了生成图像字幕的主要逻辑:- 使用
requests下载图像,并将其转换为PIL对象。 - 使用处理器对图像进行预处理。
- 调用模型生成字幕,并解码输出,得到图像的描述。
- 使用
-
_arun方法:未实现异步支持,抛出一个未实现的异常。
# ---- Part II 定义图像字幕生成工具类
class ImageCapTool(BaseTool):
name = "Image captioner"
description = "使用该工具可以生成图片的文字描述,需要传入图片的URL."
def _run(self, url: str):
# 下载图像并将其转换为PIL对象
image = Image.open(requests.get(url, stream=True).raw).convert("RGB")
# 预处理图像
inputs = processor(image, return_tensors="pt")
# 生成字幕
out = model.generate(**inputs, max_new_tokens=20)
# 获取字幕
caption = processor.decode(out[0], skip_special_tokens=True)
print("ImageCapTool:", caption)
return caption
def _arun(self, query: str):
raise NotImplementedError("This tool does not support async")
Part III: 初始化并运行LangChain智能体
在这部分,代码完成了智能体的初始化和运行:
-
初始化语言模型:使用
ChatOpenAI创建了一个大语言模型实例,并从环境变量中获取模型配置。 -
工具和智能体初始化:
- 将
ImageCapTool实例添加到工具列表中。 - 使用
initialize_agent函数创建一个智能体,配置为ZERO_SHOT_REACT_DESCRIPTION类型,这意味着它能够根据输入动态生成响应。
- 将
-
运行智能体:
- 定义了一个图像链接(以龙猫的图片为例)。
- 通过调用
agent.run方法,向智能体传入图片链接,并要求生成合适的中文推广文案。
# ---- PartIII 初始化并运行LangChain智能体
# llm = OpenAI(temperature=0.2)
llm = ChatOpenAI(
model=os.environ.get("LLM_MODEL_4K_FUNCTION_CALL"),
temperature=0.2,
)
print("初始化大语言模型成功")
# 使用工具初始化智能体并运行
tools = [ImageCapTool()]
agent = initialize_agent(
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
tools=tools,
llm=llm,
verbose=True,
handle_parsing_errors=True,
)
# img_url = "https://lf3-static.bytednsdoc.com/obj/eden-cn/lkpkbvsj/ljhwZthlaukjlkulzlp/eec79e20058499.563190744f903.jpg" # 玫瑰
# img_url = "https://img.3dmgame.com/uploads/images/news/20240607/1717770107_121512.jpg" # 崩铁
# img_url = "https://b0.bdstatic.com/194005015b7a61a4ef6e42ec81f7062a.jpg" # 星夜月
img_url = "https://img.zcool.cn/community/0370d5955f6c88c6ac7251df8839820.jpg" # 龙猫
# agent.run(input=f"{img_url}\n请创作合适的中文推广文案")
agent.invoke(input=f"图片链接如下:{img_url}\n 请为这张图创作合适的中文推广文案")
运行结果
龙猫图片测试结果 img.zcool.cn/community/0…
崩铁图片测试结果 img.3dmgame.com/uploads/ima…
可惜的是图片中并没有持剑的人...
总结
这段代码展示了如何利用LangChain框架结合图像处理和语言模型,构建一个能够根据图像生成描述的智能应用。通过合理的模块化设计,用户可以轻松扩展和修改功能,以满足特定需求。
我测试了多种不同的图片,结果各异。对于一些经典的图片,比如星夜月和龙猫,识别效果相当不错。然而,针对一些新出的图片,例如崩铁,识别效果却不尽如人意。我期待能得到关于这个游戏的推广词,但结果并未达到预期。我推测这可能与Blip模型的知识库有关,它在这方面的信息较为欠缺。
2.文档QA系统
这段代码实现了一个基于LangChain框架的文档检索问答系统,支持从PDF、Word和文本文件中加载文档,并利用向量存储和检索技术来回答用户的问题。以下是代码的详细分解:
1. 导入文档加载器
首先,代码导入了所需的库和类:
os:用于处理文件和环境变量。langchain_community.document_loaders:包含不同文档加载器的类,用于读取PDF、Word和文本文件。langchain.embeddings.base和langchain.pydantic_v1:用于定义嵌入模型的基类。volcenginesdkarkruntime:用于处理嵌入请求的库。
# 1.Load 导入Document Loaders
import os
from langchain_community.document_loaders import PyPDFLoader
from langchain_community.document_loaders import Docx2txtLoader
from langchain_community.document_loaders import TextLoader
from typing import Dict, List, Any
from langchain.embeddings.base import Embeddings
from langchain.pydantic_v1 import BaseModel
from volcenginesdkarkruntime import Ark
2. 加载文档
接下来,代码定义了文档存放目录,并遍历该目录下的文件,使用不同的加载器加载文档:
- 如果文件是PDF格式,使用
PyPDFLoader加载。 - 如果是Word文档,使用
Docx2txtLoader。 - 如果是文本文件,使用
TextLoader。
加载的文档将被存储在一个列表中,供后续处理。
# 2. 加载Documents
base_dir = "./OneFlower" # 文档的存放目录
documents = []
for file in os.listdir(base_dir):
# 构建完整的文件路径
file_path = os.path.join(base_dir, file)
if file.endswith(".pdf"):
loader = PyPDFLoader(file_path)
documents.extend(loader.load())
elif file.endswith(".docx"):
loader = Docx2txtLoader(file_path)
documents.extend(loader.load())
elif file.endswith(".txt"):
loader = TextLoader(file_path)
documents.extend(loader.load())
3. 文档切分
使用 RecursiveCharacterTextSplitter 将加载的文档切分成更小的块,以便后续的嵌入和存储。这一步的主要目的是提高检索效率和准确性,确保生成的块不会过长。
# 3.Split 将Documents切分成块以便后续进行嵌入和向量存储
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=10)
chunked_documents = text_splitter.split_documents(documents)
4. 嵌入和存储到向量数据库
定义了一个名为 DoubaoEmbeddings 的嵌入类,继承自 BaseModel 和 Embeddings:
- 初始化:在构造函数中设置API密钥和客户端,确保能与OpenAI的API进行交互。
embed_query方法:接收输入文本并返回其嵌入表示。embed_documents方法:用于处理一批文本并生成其嵌入。
随后,将切分后的文档嵌入并存储在Qdrant向量数据库中,指定了存储位置和集合名称。
# 4.Store 将分割嵌入并存储在矢量数据库Qdrant中
from langchain_community.vectorstores import Qdrant
class DoubaoEmbeddings(BaseModel, Embeddings):
client: Ark = None
api_key: str = ""
model: str
def __init__(self, **data: Any):
super().__init__(**data)
if self.api_key == "":
self.api_key = os.environ["OPENAI_API_KEY"]
print(self.api_key)
self.client = Ark(
base_url=os.environ["OPENAI_BASE_URL"],
api_key=self.api_key
)
def embed_query(self, text: str) -> List[float]:
"""
生成输入文本的 embedding.
Args:
texts (str): 要生成 embedding 的文本.
Return:
embeddings (List[float]): 输入文本的 embedding,一个浮点数值列表.
"""
embeddings = self.client.embeddings.create(model=self.model, input=text)
return embeddings.data[0].embedding
def embed_documents(self, texts: List[str]) -> List[List[float]]:
return [self.embed_query(text) for text in texts]
class Config:
arbitrary_types_allowed = True
vectorstore = Qdrant.from_documents(
documents=chunked_documents, # 以分块的文档
embedding=DoubaoEmbeddings(
model=os.environ["EMBEDDING_MODELEND"],
), # 用OpenAI的Embedding Model做嵌入
location=":memory:", # in-memory 存储
collection_name="my_documents",
) # 指定collection_name
5. 检索模型和问答链的准备
这部分代码设置了检索模型和问答链:
- 导入
ChatOpenAI用于初始化大语言模型。 - 使用
MultiQueryRetriever将向量存储与语言模型结合,支持多查询。 - 创建
RetrievalQA链,结合检索和问答功能。
# 5. Retrieval 准备模型和Retrieval链
import logging # 导入Logging工具
from langchain_openai import ChatOpenAI # ChatOpenAI模型
from langchain.retrievers.multi_query import (
MultiQueryRetriever,
) # MultiQueryRetriever工具
from langchain.chains import RetrievalQA # RetrievalQA链
# 设置Logging
logging.basicConfig()
logging.getLogger("langchain.retrievers.multi_query").setLevel(logging.INFO)
# 实例化一个大模型工具 - OpenAI的GPT-3.5
llm = ChatOpenAI(model=os.environ["LLM_MODELEND"], temperature=0)
# 实例化一个MultiQueryRetriever
retriever_from_llm = MultiQueryRetriever.from_llm(
retriever=vectorstore.as_retriever(), llm=llm
)
# 实例化一个RetrievalQA链
qa_chain = RetrievalQA.from_chain_type(llm, retriever=retriever_from_llm)
6. 问答系统的UI实现
最后,代码使用Flask框架创建了一个简单的Web应用:
- 定义了根路由,支持GET和POST请求。
- 当用户提交问题时,使用
qa_chain处理问题并生成答案。 - 通过渲染HTML模板,将回答结果返回给用户。
# 6. Output 问答系统的UI实现
from flask import Flask, request, render_template
app = Flask(__name__) # Flask APP
@app.route("/", methods=["GET", "POST"])
def home():
if request.method == "POST":
# 接收用户输入作为问题
question = request.form.get("question")
# RetrievalQA链 - 读入问题,生成答案
result = qa_chain({"query": question})
# 把大模型的回答结果返回网页进行渲染
return render_template("index.html", result=result)
return render_template("index.html")
if __name__ == "__main__":
app.run(host="0.0.0.0", debug=True, port=5000)
错误处理
报错信息:
raise self._make_status_error_from_response( volcenginesdkarkruntime._exceptions.ArkRateLimitError: Error code: 429 - {'error': {'code': 'tpm_or_qpm_exceeded', 'message': 'TPM or QPM is exceeded, please try again later.', 'type': 'invalid_request_error', 'param': None}}, request_id: 20241103032143e9yiyN35XMvNRh5FCYrX
错误原因:
超过TPM or QPM限制
解决方案:
老师给出的方案是过几分钟再试试,因为用的是统一的模型,保留了并发的限制,防止滥用。
也可以选择按照API配置的方式配置自己的API,这样就可以用自己的API啦,不会有出现这样的问题。
但是要注意的是,在这个代码中,需要再申请一个doubao-embedding的模型,并在cloudiderc中设置export EMBEDDING_MODELEND=<YOUR_MODEL_END>,不然没有办法将分词模型将文本转换为embedding。
运行结果
点击运行按钮后,稍等片刻让网页启动,然后点击右侧的“Web预览”就可以查看结果。
提问:员工手册中的内容是什么?
回答:
在测试结果中,仍有些地方不尽如人意。例如,我询问:“你知道向日葵的花语是什么吗?”下图是模型的回答:
然而,文档中明确提到了“sunflower”作为向日葵的花语,这说明模型没能看到和理解学习这部分文档内容。
总结
这段代码展示了如何构建一个文档检索问答系统,结合文档加载、文本切分、向量存储和用户交互。通过合理的结构和模块化设计,用户可以灵活扩展功能,处理不同类型的文档,为用户提供高效的信息检索和回答服务。
经过多次测试后,我发现模型在文档中学习到的内容非常有限,许多信息并没有被提取出来。推测问题可能出现在文本读取或向量化的环节。接下来,我会继续深入调查,以找出具体原因并进行改进。