LongChain实战课 -ai练中学(1)| 豆包MarsCode AI刷题

156 阅读13分钟

关于LongChain

作为一种专为开发基于语言模型的应用而设计的框架,通过LangChain,我们不仅可以通过API调用如 ChatGPT、GPT-4、Llama 2 等大型语言模型,还可以实现更高级的功能。

我们相信,真正有潜力且具有创新性的应用,不仅仅在于能通过API调用语言模型,更重要的是能够具备以下两个特性:

  1. 数据感知:  能够将语言模型与其他数据源连接起来,从而实现对更丰富、更多样化数据的理解和利用。
  2. 具有代理性:  能够让语言模型与其环境进行交互,使得模型能够对其环境有更深入的理解,并能够进行有效的响应。

因此,LangChain框架的设计目标,是使这种AI类型的应用成为可能,并帮助我们最大限度地释放大语言模型的潜能。

关于LongChain系统安装和快速入门

大语言模型

大语言模型是一种人工智能模型,通常使用深度学习技术,比如神经网络,来理解和生成人类语言。因为其参数数量非常多,使得它们能够理解和生成高度复杂的语言模式。 而LangChain 是一个全方位的、基于大语言模型这种预测能力的应用开发工具,其灵活性和模块化特性使得处理语言模型变得极其简便。

API的配置

LangChain本质上就是对各种大模型提供的API的套壳,是为了方便我们使用这些API,搭建起来的一些框架、模块和接口。

通过火山方舟官网进行注册api并通过README.md文件进行配置

在控制台的安排API Key管理页面中创建API Key

image.png

在控制台的在线推理页面中创建推理接入点

image.png

编辑项目中的/home/cloudide/.cloudiderc文件,将API_Key、base_url、model_endpoint 配置在环境变量中,在命令行中执行 source ~/.cloudiderc 

export OPENAI_API_KEY=<YOUR_API_KEY> 
export OPENAI_BASE_URL=<YOUR_MODEL_BASE_URL> 
export LLM_MODELEND=<YOUR_MODEL_ENDPOINT>

Text模型和Chat模型

调用Text模型

对于简单的单轮文本生成任务,使用Text模型会更简单、更直接。例如,如果你只需要模型根据一个简单的提示生成一段文本。

比如“请给我的花店起个名”

以下是代码示例

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ.get("ARK_API_KEY"),
    base_url="YOUR_MODEL_BASE_URL", 
)

# 使用 text 模型进行文本生成
response = client.completions.create(
  model="YOUR_MODEL_NAME",
  temperature=0.5,
  max_tokens=100,
  prompt="请给我的花店起个名")

print(response.choices[0].text.strip())  # 输出生成的文本

这里因为OpenAI的Completions API已经在2023年7月完成最后一次更新并废弃,该接口仅适用于早期版本的少量模型("gpt-3.5-turbo-instruct", "davinci-002", "babbage-002");相关功能可以被ChatCompletion接口替代。Doubao API兼容最新版本的API调用,对废弃接口不再支持,本文件代码仅做示意,因此在ai练中学中编译会出现报错

调用Chat模型

相较于Text模型,Chat模型的设计更适合处理对话或者多轮次交互的情况。这是因为它可以接受一个消息列表作为输入,而不仅仅是一个字符串。这个消息列表可以包含system、user和assistant的历史信息,从而在处理交互式对话时提供更多的上下文信息。

以下是代码示例

import os
from openai import OpenAI


client = OpenAI(
    api_key = os.environ.get("ARK_API_KEY"),
      base_url="YOUR_MODEL_BASE_URL",
)

# text = client.invoke("请给我写一句情人节红玫瑰的中文宣传语")
completion = client.chat.completions.create(
     model="YOUR_MODEL_NAME",
    messages=[
        {"role": "system", "content": "You are a creative AI."},
        {"role": "user", "content": "请给我写一句情人节红玫瑰的中文宣传语"},
    ],
    temperature=0.8,
    max_tokens=600,
)

print(completion.choices[0].message.content)
关于Text模型和Chat模型的对比

1. 适用场景

  • Text模型:Text模型通常用于简单的单轮文本生成任务,它接收一个单独的提示(prompt),并根据这个提示生成相应的文本。这种模型适合一些无需上下文或历史交互的任务,比如:

    • 问答任务
    • 简单的文本补全
    • 文本生成(如:自动写作、标题生成等)
  • Chat模型:Chat模型设计用于处理多轮次对话任务。它接受一个由多个消息(message)组成的列表,每个消息代表对话中的一次交互。这些消息可以有不同的角色(system、user、assistant),允许模型处理更复杂的对话和上下文信息。适用于以下场景:

    • 聊天机器人(客服、智能助手)
    • 复杂的对话系统,要求模型记住和理解之前的对话内容
    • 多轮次推理和交互式任务(如:长对话、问题逐步解答)

2. 输入和输出格式

  • Text模型

    • 输入格式:单一字符串(prompt)。
    • 输出格式:根据输入生成的一段文本。
  • Chat模型

    • 输入格式:消息列表,每个消息有 role 和 content。消息角色包括 system(系统消息)、user(用户消息)、assistant(助手消息)。
    • 输出格式:生成的对话消息,通常是助手角色的回应。

3. 交互性

  • Text模型:不具备对话上下文记忆。每次请求都是独立的,输入和输出之间没有历史的上下文传递。适用于简单、一次性的生成任务。
  • Chat模型:具备对话上下文的能力,可以记住历史对话(基于提供的消息列表)。模型根据历史消息生成更有连贯性和上下文的回应。因此,Chat模型适合处理多轮对话、逐步交互和更复杂的任务。

4. 响应生成

  • Text模型:响应是基于一个给定的提示生成的。每个请求只关注当前提示,没有考虑之前的对话或历史。
  • Chat模型:响应生成考虑了所有消息(包括系统消息、用户消息和助手消息),这使得模型能够在多轮对话中生成更相关、连贯的内容。例如,系统消息可以指导模型如何回应,用户消息提供了当前的需求,助手消息则可以提供已有的回答。

5. 灵活性

  • Text模型:简单而直接,但不适合处理多轮对话或复杂的交互任务。
  • Chat模型:更灵活,能够处理复杂的对话情境,可以灵活适应不同的角色设置和多轮次对话。

6. 多样性和创造性

  • Text模型:可以使用温度(temperature)等参数控制文本生成的随机性和创造性。温度值高时,生成的文本可能更具创意,但也可能更离散;温度值低时,生成的文本更确定性,偏向稳定和可靠。
  • Chat模型:同样支持温度等参数控制,但由于包含了多轮交互,因此生成的文本通常会更自然、连贯,并且能根据上下文调整创意和内容。

7. 调用方法对比

  • Text模型的调用: 只需要传入一个 prompt,生成简单的文本:

  • Chat模型的调用: 需要提供消息列表,并指定角色信息:

总结

  • Text模型:适合处理简单、独立的任务,输入和输出是简单的字符串。没有上下文的记忆能力,适用于单轮任务。
  • Chat模型:适合多轮次对话、需要上下文记忆的任务。能够处理更复杂的交互,适用于聊天机器人、智能助手等场景。

通过LongChain调用Text和Chat模型

调用Text模型

先导入LangChain的OpenAI类,创建一个LLM(大语言模型)对象,指定使用的模型和一些生成参数。使用创建的LLM对象和消息列表调用OpenAI类的__call__方法,进行文本生成。生成的结果被存储在response变量中。

import os
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model=os.environ.get("LLM_MODELEND"),
    temperature=0.8,
    max_tokens=600,
)
response = llm.predict("请给我的花店起个名")

print(response)
调用Chat模型

通过导入LangChain的ChatOpenAI类,创建一个Chat模型对象,指定使用的模型和一些生成参数。然后从LangChain的schema模块中导入LangChain的SystemMessage和HumanMessage类,创建一个消息列表。消息列表中包含了一个系统消息和一个人类消息。你已经知道系统消息通常用来设置一些上下文或者指导AI的行为,人类消息则是要求AI回应的内容。之后,使用创建的chat对象和消息列表调用ChatOpenAI类的__call__方法,进行文本生成。生成的结果被存储在response变量中。

输出:

import os
from langchain_openai import ChatOpenAI
from langchain.schema import HumanMessage, SystemMessage

chat = ChatOpenAI(model=os.environ.get("LLM_MODELEND"), temperature=0.8, max_tokens=600)

messages = [
    SystemMessage(content="你是一个很棒的智能助手"),
    HumanMessage(content="请给我的花店起个名"),
]
response = chat(messages)
print(response)

用LongChain快速构建基于“易速鲜花”本地知识库的智能问答系统

项目名称:“易速鲜花”内部员工知识库问答系统

项目介绍:“易速鲜花”作为一个大型在线鲜花销售平台,有自己的业务流程和规范,也拥有针对员工的SOP手册。新员工入职培训时,会分享相关的信息。但是,这些信息分散于内部网和HR部门目录各处,有时不便查询;有时因为文档过于冗长,员工无法第一时间找到想要的内容;有时公司政策已更新,但是员工手头的文档还是旧版内容。

基于上述需求,我们将开发一套基于各种内部知识手册的 “Doc-QA” 系统。这个系统将充分利用LangChain框架,处理从员工手册中产生的各种问题。这个问答系统能够理解员工的问题,并基于最新的员工手册,给出精准的答案。

项目框架

1. 数据源(Data Sources)

数据源是整个系统的基础,可以分为以下几种类型:

  • 非结构化数据:如PDF、Word文档、文本文件等。这些文档大多没有明确的结构,信息比较散乱,因此需要通过自然语言处理技术来提取和组织信息。
  • 结构化数据:如数据库、表格等,数据是有明确格式和结构的。这类数据通常可以直接用于回答特定问题。
  • 代码:例如Python、Java等编程代码,这些代码也可能包含某些公司流程或系统实现的关键信息。

在本项目中,重点处理的是非结构化数据,即PDF文档、Word文档等员工手册、SOP、内部流程文件等。

2. 大模型应用(LLM App)

通过利用大语言模型(LLM)进行问题解答,系统能够根据员工的提问从大量文档中提取相关信息,并生成精准的回答。LangChain框架作为中间层,能够帮助将这些不同的数据源和大语言模型整合,处理复杂的任务。

3. 用例(Use-Cases)

  • QA问答系统:员工通过提问获取公司相关政策、流程、操作规范等的答案。
  • 聊天机器人:员工可以通过自然语言进行互动,获取想要的信息,类似于与HR、同事等的对话。

4. 核心实现机制——数据处理管道(Pipeline)

LangChain框架为实现整个数据处理流程提供了必要的工具。具体步骤包括:

加载(Loading)

文档加载器负责将各类文档(如PDF、Word等)加载为LangChain能够读取的标准格式。加载器会将原始文件内容转化为可以进一步处理的文本数据。

分割(Splitting)

文本分割器将文档按一定的规则进行分割,例如按段落、章节或一定字符长度分割成多个“文档块”或“文档片”。这样可以方便系统在后续的检索中找到更小范围的信息,提高检索效率和回答准确度。

存储(Storage)

分割后的文档块会通过嵌入(embedding)技术转化为向量(数字表示),然后存储在向量数据库(Vector DB)中。嵌入是将文档块内容转化为高维度向量的过程,使得系统可以根据相似度快速检索到相关的文档块。

检索(Retrieval)

当员工提出问题时,系统会根据问题的文本特征检索到与之相关的文档块。这一步通常是通过计算余弦相似度等算法来判断哪些嵌入向量与输入问题向量最为接近。检索的结果是与问题最相关的文档片段。

输出(Output)

最后,将问题与检索到的文档块一起传递给大语言模型(LLM)。LLM根据这些信息生成最终的答案,提供给员工。

核心技术点解析

  • 嵌入(Embedding) :嵌入是一种将文本转化为数字向量的技术,使得相似的文本在高维空间中距离较近。使用嵌入技术,可以使得问答系统能够通过比较文档与用户问题的向量,快速找到最相关的内容。
  • 向量数据库(Vector DB) :存储文档嵌入的数据库,能够高效地支持相似度检索。在检索时,可以通过计算问题向量与文档向量之间的距离(如余弦相似度)来找到最相关的文档片段。

代码如下

# 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
from openai import OpenAI

# os.environ["OPENAI_API_KEY"] = '你的OpenAI API Key'
# os.environ["OPENAI_BASE_URL"] = 'OpenAI 的 API URL'
client = OpenAI(
    api_key = os.environ.get("ARK_API_KEY"),
    base_url = "https://ark.cn-beijing.volces.com/api/v3",
)

# 加载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())

# 2.SplitDocuments切分成块以便后续进行嵌入和向量存储
from langchain.text_splitter import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=10)
chunked_documents = text_splitter.split_documents(documents)

# 3.Store 将分割嵌入并存储在矢量数据库Qdrantfrom 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"]
        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"],
    ),  # 用OpenAIEmbedding Model做嵌入
    location=":memory:",  # in-memory 存储
    collection_name="my_documents",
)  # 指定collection_name

# 4. Retrieval 准备模型和Retrievalimport 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)

# 实例化一个大模型工具 - OpenAIGPT-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)

# 5. 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)

运行结果

image.png

image.png