LangChain实战课-轻松上手,逃离配环境的噩梦 | 豆包MarsCode AI刷题

408 阅读14分钟

前言

第一次参加字节跳动青训营的AI方向,让我有机会深入学习LangChain这个强大工具。在AI飞速发展的今天,应用开发正迎来一个“奇点”时刻。这篇文章主要就是介绍:

  • 开篇词|带你亲证AI应用开发的“奇点”时刻
  • LangChain系统安装和快速入门
  • 用LangChain快速构建基于“易速鲜花”本地知识库的智能问答系统

逃离配环境的工具-AI练中学

你还在为学习之前需要浪费时间配置复杂环境的问题而苦恼吗?
你还在为环境配置频繁出错的问题的报错而抓狂吗?
你还在为无法在线运行教学课程中的代码而担忧吗?

快来试试AI练中学功能吧!

AI练中学这个功能是在开营班会时老师强烈推荐的,所以我迫不及待地进行了尝试。它为每个人提供了一个专属的服务器空间,虽然配置不算高,但足够满足我们学习的需求。在这个空间里,我们可以自由探索各种AI工具,进行项目实践和实验,不再受限于本地环境的配置。

特别棒的一点是,在AI练中学功能中,不仅课程中的代码已经预先编写好,还可以直接进行运行。这意味着我不需要手动配置各种环境,所有的设置都已经妥善处理,随时可以使用。这样一来,我可以将更多的精力集中在代码的功能实现和逻辑思考上,而不必为那些繁琐的环境问题浪费时间。这种便利大大提升了我的学习效率,让我能够更专注地探索AI的各种应用和开发技巧。

image.png image.png

下面所有的代码实现以及运行都是在AI练中学中实现。

API 配置

在使用AI练中学时,如果没有特殊需求,其实是不需要进行API的申请和配置的。这个平台已经为我们精心配置好了所有的环境变量,确保我们可以直接开箱即用,享受无缝的学习体验。这种便利性大大降低了技术门槛,让任何人都可以轻松上手。

当然,如果你想使用自己的API,也完全可以做到。只需参考README中的说明,按照简单的步骤进行配置即可。这种灵活性让我们既可以享受平台提供的优质资源,也能根据自己的需要进行个性化设置,真的是非常贴心!这样一来,无论是新手还是有经验的开发者,都能在这个平台上找到适合自己的学习方式。

以豆包API为例:

  1. 访问火山方舟官网完成账号注册和服务授权
  2. API-KEY管理 -> 创建API -> 得到API_Key
  3. 在线推理页面 -> 创建推理接入点 -> 选择模型
  4. 创建后得到base_url、model_endpoint
  5. 编辑项目中的  /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)的应用开发。它提供了一个易于使用的接口,帮助开发者构建复杂的应用,如智能问答系统、对话生成、文本分析等。

关键特性

  • 数据感知:能够将语言模型与多种数据源连接,实现对更丰富和多样化数据的理解和利用。这种能力使得模型不仅仅局限于语言本身,而是能结合外部信息进行智能分析和决策。
  • 具有代理性:使语言模型能够与其环境进行交互,深入理解周围的动态情况,并做出有效响应。通过这种代理能力,模型不仅能理解信息,还能采取行动,从而变得更具智能化。

六个关键组件

  1. 模型(Models) :这个组件提供了各类语言模型的接口和调用细节,让用户可以轻松使用多种强大的模型,推动应用智能化。
  2. 提示模板(Prompts) :通过优化提示设计,提示模板帮助用户更高效地激发语言模型的潜力,从而得到更准确的输出。
  3. 数据检索(Indexes) :这个组件帮助构建和管理文档,支持用户快速查询相关信息,方便搭建本地知识库,提升信息获取效率。
  4. 记忆(Memory) :通过短期和长期记忆,记忆组件让对话中的数据得以存储和检索,从而使ChatBot能“记住”用户的身份和交互历史,提供更个性化的体验。
  5. 链(Chains) :这是LangChain的核心机制,通过组合不同功能,自动化处理常见任务,帮助开发者高效构建复杂的应用流程。
  6. 代理(Agents) :这个组件允许模型自主调用外部和内部工具,实现智能代理的功能,让应用能够根据用户需求自动响应,提升交互体验。

这些组件是LangChain的基础,互相协作,形成了一个强大灵活的系统。在基础篇中,我们将详细探讨这些组件的工作原理和实际使用方法,并提供丰富的案例,帮助你更好地理解和应用这些核心要素。通过掌握这些组件,你将能够开发出更具创意和实用性的AI应用。

案例

1. 海报文案生成器

下面的代码实现了一个海报文案生成工具,并利用LangChain框架创建了一个智能体,可以根据输入的图片链接生成描述或推广文案。以下是代码的详细介绍:

Part 0: 导入所需类

首先,代码导入了一系列所需的库和类:

  • os:用于处理系统路径和环境变量。
  • requests:用于发送HTTP请求。
  • PIL.Image:用于图像处理。
  • transformers:用于加载和使用预训练的BLIP模型。
  • langchain.toolslangchain_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: 初始化图像字幕生成模型

这部分代码初始化了一个用于生成图像字幕的模型:

  1. 模型选择:指定了要使用的HuggingFace模型(Salesforce/blip-image-captioning-large),这是一个用于图像字幕生成的预训练模型。

  2. 处理器和模型初始化

    • 创建了一个图像处理器(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

  1. 名称和描述:提供工具的名称和功能描述,指明需要传入图片的URL。

  2. _run 方法:实现了生成图像字幕的主要逻辑:

    • 使用 requests 下载图像,并将其转换为PIL对象。
    • 使用处理器对图像进行预处理。
    • 调用模型生成字幕,并解码输出,得到图像的描述。
  3. _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智能体

在这部分,代码完成了智能体的初始化和运行:

  1. 初始化语言模型:使用 ChatOpenAI 创建了一个大语言模型实例,并从环境变量中获取模型配置。

  2. 工具和智能体初始化

    • ImageCapTool 实例添加到工具列表中。
    • 使用 initialize_agent 函数创建一个智能体,配置为 ZERO_SHOT_REACT_DESCRIPTION 类型,这意味着它能够根据输入动态生成响应。
  3. 运行智能体

    • 定义了一个图像链接(以龙猫的图片为例)。
    • 通过调用 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… image.png

崩铁图片测试结果 img.3dmgame.com/uploads/ima… image.png 可惜的是图片中并没有持剑的人...

总结

这段代码展示了如何利用LangChain框架结合图像处理和语言模型,构建一个能够根据图像生成描述的智能应用。通过合理的模块化设计,用户可以轻松扩展和修改功能,以满足特定需求。

我测试了多种不同的图片,结果各异。对于一些经典的图片,比如星夜月和龙猫,识别效果相当不错。然而,针对一些新出的图片,例如崩铁,识别效果却不尽如人意。我期待能得到关于这个游戏的推广词,但结果并未达到预期。我推测这可能与Blip模型的知识库有关,它在这方面的信息较为欠缺。

2.文档QA系统

这段代码实现了一个基于LangChain框架的文档检索问答系统,支持从PDF、Word和文本文件中加载文档,并利用向量存储和检索技术来回答用户的问题。以下是代码的详细分解:

1. 导入文档加载器

首先,代码导入了所需的库和类:

  • os:用于处理文件和环境变量。
  • langchain_community.document_loaders:包含不同文档加载器的类,用于读取PDF、Word和文本文件。
  • langchain.embeddings.baselangchain.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 的嵌入类,继承自 BaseModelEmbeddings

  • 初始化:在构造函数中设置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预览”就可以查看结果。

提问:员工手册中的内容是什么?
回答image.png

在测试结果中,仍有些地方不尽如人意。例如,我询问:“你知道向日葵的花语是什么吗?”下图是模型的回答: image.png 然而,文档中明确提到了“sunflower”作为向日葵的花语,这说明模型没能看到和理解学习这部分文档内容。

总结

这段代码展示了如何构建一个文档检索问答系统,结合文档加载、文本切分、向量存储和用户交互。通过合理的结构和模块化设计,用户可以灵活扩展功能,处理不同类型的文档,为用户提供高效的信息检索和回答服务。

经过多次测试后,我发现模型在文档中学习到的内容非常有限,许多信息并没有被提取出来。推测问题可能出现在文本读取或向量化的环节。接下来,我会继续深入调查,以找出具体原因并进行改进。