(14-2)Langchin基础知识:使用LangChain构建应用

2.2  使用LangChain构建应用

通过使用LangChain,使构建与外部数据源和计算过程无缝集成的应用成为可能。在接下来的内容中,将演示使用不同的方法来实现这种集成的过程。

2.2.1  LLM链

LLM链是指语言模型(Language Model,通常缩写为LLM)与其他组件按照一定顺序和规则连接在一起的结构。在LangChain中,这些组件包括输入提示模板(ChatPromptTemplate)、语言模型(例如ChatOpenAI)、输出解析器(如StrOutputParser)等。这个链的目的是将多个处理步骤有序地连接在一起,形成一个处理流程。用户可以通过这个链提供输入,链中的组件逐步处理输入,最终生成输出。这种组件化的设计使得用户可以更灵活地构建自己的自然语言处理应用,根据需要定制不同的处理步骤和模型。

在LangChain中,LLM链通常用于构建能够连接外部数据源和计算资源到语言模型的应用程序。链的每个步骤都可以执行不同的任务,如处理提示、调用语言模型、解析输出等。在LangChain官网中提供了两种模型进行举例:OpenAI(通过API提供的流行模型)或使用本地的开源模型 Ollama。在本书的内容中,只展示使用OpenAI模型的用法。

(1)首先,通过如下命令安装LangChain x OpenAI集成包。

pip install langchain-openai

(2)在使用OpenAI API时需要提供API密钥,API密钥是一种用于验证您的身份并访问OpenAI服务的凭据。在获取到OpenAI密钥后,通过如下命令将其设置为环境变量。

export OPENAI_API_KEY="..."

(3)然后,我们可以初始化模型:

from langchain_openai import ChatOpenAI
llm = ChatOpenAI()

如果不想设置环境变量,可以在初始化OpenAI LLM类时直接通过openai_api_key参数传递密钥:

from langchain_openai import ChatOpenAI
llm = ChatOpenAI(openai_api_key="...")

(4)在安装并初始化所选的LLM后,可以尝试使用它,例如询问它“LangSmith是什么”,因为目前这是在训练数据中不存在的内容,所以它可能没有提供很好的响应。

llm.invoke("how can langsmith help with testing?")

(5)还可以使用提示模板引导响应,提示模板用于将原始用户输入转换为LLM的更好输入。

from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a world-class technical documentation writer."),
    ("user", "{input}")
])

(6)现在,将前面提到的组件(包括ChatPromptTemplate、ChatOpenAI、和StrOutputParser)组合成一个简单的LLM链,以构建一个完整的处理流程。

chain = prompt | llm

(7)现在我们可以调用它并问同样的问题,虽然它可能不知道答案,但它应该以技术撰写人的更正式语气回应。

chain.invoke({"input": "how can langsmith help with testing?"})

在LangChain中,ChatModel是一种模型,其输出是一条消息(ChatMessage)。然而,在某些情况下,为了方便处理,用户可能更倾向于将输出转换为字符串形式。为了实现这一点,可以添加一个简单的输出解析器(output parser)。

from langchain_core.output_parsers import StrOutputParser
output_parser = StrOutputParser()

注意:输出解析器是一种组件,用于将一个类型的输出转换为另一种类型,通常是更易处理的类型。在这里,将输出消息(ChatMessage)转换为字符串(String)形式。这可以通过在LangChain流程中添加一个名为StrOutputParser的输出解析器来完成。

这个解析器的作用是将聊天消息转换为字符串,这使得后续的处理更加方便,因为字符串是一种常见的文本表示形式,易于处理和输出。这是为了满足用户在处理输出时的特定需求。然后将其添加到先前的链中:

chain = prompt | llm | output_parser

现在以调用它并问同样的问题,此时的答案将是一个字符串(而不是一个ChatMessage)。

chain.invoke({"input": "how can langsmith help with testing?"})

我们已经成功设置了一个基本的LLM链,其中涉及到了提示、模型和输出解析器的基础知识。

2.2.2  Retrieval Chain(检索链)

Retrieval Chain(检索链)是LangChain中的一个概念,用于处理对话系统中的信息检索。在对话系统中,当用户提出问题时,可能需要从大量的文档或数据中检索相关的信息,然后将这些信息传递给语言模型(LLM)进行进一步处理。

检索链的工作流程如下:

(1)Retriever(检索器):检索链的第一步是通过检索器获取相关的文档或信息。检索器可以基于各种方式工作,比如使用向量存储、数据库查询等。在LangChain中,你可以配置一个检索器,该检索器能够从一个向量存储中检索相关文档。

(2)Documents(文档):检索到的文档被传递到下一步。这些文档包含了对用户问题有用的信息。

(3)LLM处理:下一个步骤涉及将检索到的文档和用户的问题传递给语言模型(LLM)。这样,LLM可以基于上下文信息提供更准确的答案。

(4)生成答案:最终,语言模型处理输入并生成对用户问题的答案,这个答案被返回给用户。

检索链的功能使得在处理大量信息时更加高效,因为它允许系统首先从整个数据集中检索最相关的部分,然后仅将这些部分传递给语言模型。这提高了系统的性能和响应速度,特别是当数据集很庞大时。

为了正确回答原始问题("how can langsmith help with testing?"),我们需要向LLM提供额外的上下文信息,我们可以通过检索(Retrieval)来实现这一点。检索链在有太多数据需要传递给LLM时非常有用,我们可以使用检索仅提取最相关的信息并将其传递给LLM。在这个过程中,将从一个检索器中查找相关文档,然后将它们传递到提示中。检索器可以由任何东西支持 - SQL表、互联网等,但在这个实例中,我们将填充一个向量存储并将其用作检索器。

(1)首先,使用WebBaseLoader加载要进行索引的数据,这需要安装BeautifulSoup:

pip install beautifulsoup4

(2)接下来,可以导入并使用WebBaseLoader。

from langchain_community.document_loaders import WebBaseLoader
loader = WebBaseLoader("https://docs.smith.langchain.com/overview")
docs = loader.load()

(3)接下来,要将其索引到一个向量存储中。这需要一些组件,即嵌入模型和向量存储。对于嵌入模型,再次提供通过OpenAI或通过本地模型访问的示例。

from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()

现在,可以使用这个嵌入模型将文档纳入向量存储中。出于简单起见,将使用一个简单的本地向量存储FAISS。先通过下面的命令安装所需的软件包。

pip install faiss-cpu

(4)然后可以构建我们的索引:

from langchain_community.vectorstores import FAISS
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter()
documents = text_splitter.split_documents(docs)
vector = FAISS.from_documents(documents, embeddings)

现在已经将这些数据索引到一个向量存储中,我们将创建一个检索链。此链将接收一个传入的问题,查找相关文档,然后将这些文档与原始问题一起传递到LLM中,并要求它回答原始问题。

(5)设置一个链,该链将接收一个问题和检索到的文档,并生成一个答案。

from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_template("""Answer the following question based only on the provided context:

<context>
{context}
</context>

Question: {input}""")

document_chain = create_stuff_documents_chain(llm, prompt)

如果我们愿意,我们可以通过直接传递文档来运行这个链:

from langchain_core.documents import Document

document_chain.invoke({
    "input": "how can langsmith help with testing?",
    "context": [Document(page_content="langsmith can let you visualize test results")]
})

(5)我们希望在检索链中首先使用一个检索器来获取文档,这个检索器的目的是从大量的可能相关的文档中筛选出最相关的那些。这么做的好处是,对于用户提出的特定问题,检索器可以根据问题的内容动态选择最相关的文档,然后将这些文档传递给语言模型(LLM)进行处理。

from langchain.chains import create_retrieval_chain
retriever = vector.as_retriever()
retrieval_chain = create_retrieval_chain(retriever, document_chain)

(6)现在可以调用这个链,这将返回一个字典 - LLM的响应在answer键中:

response = retrieval_chain.invoke({"input": "how can langsmith help with testing?"})
print(response["answer"])

到现在为止,这个恢复的答案应该更准确了。

2.2.3  对话检索链

到目前为止,我们创建的链只能回答单一问题。在目前的应用中,构建的语言模型主要被应用在聊天系统中。那么,我们如何将这个链转变为能够回答跟进问题的聊天机器人呢?我们仍然可以使用create_retrieval_chain函数来实现,但需要更改两个方面。

  1. 现在检索方法不仅仅要处理最新的输入,而且还要考虑整个对话历史。
  2. 最终的语言模型链(LLM链)同样应考虑整个对话历史。

(1)更新检索

为了更新检索,需要创建一个新的链。该链将接收最新的输入(input)和对话历史(chat_history),并使用语言模型(LLM)生成一个搜索查询。

from langchain.chains import create_history_aware_retriever
from langchain_core.prompts import MessagesPlaceholder

# 首先,我们需要一个提示,可以传递给LLM以生成搜索查询

prompt = ChatPromptTemplate.from_messages([
    MessagesPlaceholder(variable_name="chat_history"),
    ("user", "{input}"),
    ("user", "根据上面的对话,生成一个搜索查询以获取与对话相关的信息")
])
retriever_chain = create_history_aware_retriever(llm, retriever, prompt)

通过使用这个新的检索链,可以根据对话历史和跟进问题生成相关文档。

(2)测试

通过传递用户询问跟进问题的实例,我们可以测试这个新的检索链。

from langchain_core.messages import HumanMessage, AIMessage

chat_history = [HumanMessage(content="LangSmith是否可以帮助测试我的LLM应用?"), AIMessage(content="是的!")]
retriever_chain.invoke({
    "chat_history": chat_history,
    "input": "告诉我怎么做"
})

现在应该会看到它返回了有关在LangSmith中进行测试的文档。,是因为LLM生成了一个新的查询,将对话历史与跟进问题结合在一起。在有了这个新的检索器,我们可以创建一个新的链,以在考虑这些检索到的文档的情况下继续对话。

prompt = ChatPromptTemplate.from_messages([
    ("system", "根据以下上下文回答用户的问题:\n\n{context}"),
    MessagesPlaceholder(variable_name="chat_history"),
    ("user", "{input}"),
])
document_chain = create_stuff_documents_chain(llm, prompt)
retrieval_chain = create_retrieval_chain(retriever_chain, document_chain)

(3)现在可以进行端到端的测试,此时可以看到会给出一个连贯的答案,这表明成功地将我们的检索链转变为了一个聊天机器人。

chat_history = [HumanMessage(content="LangSmith是否可以帮助测试我的LLM应用?"), AIMessage(content="是的!")]
retrieval_chain.invoke({
    "chat_history": chat_history,
    "input": "告诉我怎么做"
})

2.2.4  实现Agent

到目前为止,我们已经创建了使用链的例子,其中每个步骤都是预先知道的。接下来将要实现最后一个步骤:实现Agent代理。

(1)构建Agent代理的第一件事是决定它应该访问哪些工具,在本演示中,我们将让代理访问两个工具:

  1. 我们刚刚创建的检索器。这将使它能够轻松回答有关LangSmith的问题。
  2. 搜索工具。这将使它能够轻松回答需要最新信息的问题。

首先,设置一个用于刚刚创建的检索器的工具:

from langchain.tools.retriever import create_retriever_tool
retriever_tool = create_retriever_tool(
    retriever,
    "langsmith_search",
    "Search for information about LangSmith. For any questions about LangSmith, you must use this tool!",
)

在本演示中将使用搜索工具Tavily,这需要用到一个API密钥。在其平台上创建后,需要先将其设置为环境变量:

export TAVILY_API_KEY=...

如果不想设置API密钥,可以跳过创建此工具的步骤。

from langchain_community.tools.tavily_search import TavilySearchResults
search = TavilySearchResults()

现在可以创建要使用的工具列表:

tools = [retriever_tool, search]

我们现在有了工具,可以创建一个Agent代理来使用它们。

(1)首先安装langchain hub,安装命令如下所示。

pip install langchainhub

(2)现在可以使用它来获取预定义的提示:

from langchain_openai import ChatOpenAI
from langchain import hub
from langchain.agents import create_openai_functions_agent
from langchain.agents import AgentExecutor


# 获取要使用的提示 - 您可以修改这个!
prompt = hub.pull("hwchase17/openai-functions-agent")
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
agent = create_openai_functions_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

(3)现在可以调用代理并查看其响应,例如可以询问OpenAI关于LangSmith的问题:

agent_executor.invoke({"input": "how can langsmith help with testing?"})

也可以询问它关于天气的问题:

agent_executor.invoke({"input": "what is the weather in SF?"})

我们也可以与它进行对话:

chat_history = [HumanMessage(content="Can LangSmith help test my LLM applications?"), AIMessage(content="Yes!")]
agent_executor.invoke({
    "chat_history": chat_history,
    "input": "Tell me how"
})

2.2.5  LangServe服务

现在已经构建了一个应用程序,我们需要为其提供服务,这一功能需要LangServe实现。通使用LangServe可以将LangChain链部署为REST API。我们不需要使用LangServe来使用LangChain,但在本展示中,将演示使用LangServe部署应用程序的过程。

(1)在使用LangServe之前需要使用以下命令进行安装:

pip install "langserve[all]"

(2)为我们的应用程序创建一个Server(服务器),创建程序文件serve.py,在里面包含我们提供应用程序的逻辑,这由如下三个部分组成。

  1. 我们刚刚构建的链的定义。
  2. 我们的FastAPI应用程序。
  3. 一个定义路由以从中提供链的路由,使用langserve.add_routes完成。

文件serve.py的具体实现代码如下所示。

from typing import List

from fastapi import FastAPI
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_community.document_loaders import WebBaseLoader
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.tools.retriever import create_retriever_tool
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_openai import ChatOpenAI
from langchain import hub
from langchain.agents import create_openai_functions_agent
from langchain.agents import AgentExecutor
from langchain.pydantic_v1 import BaseModel, Field
from langchain_core.messages import BaseMessage
from langserve import add_routes

# 1. 加载检索器
loader = WebBaseLoader("https://docs.smith.langchain.com/overview")
docs = loader.load()
text_splitter = RecursiveCharacterTextSplitter()
documents = text_splitter.split_documents(docs)
embeddings = OpenAIEmbeddings()
vector = FAISS.from_documents(documents, embeddings)
retriever = vector.as_retriever()

# 2. 创建工具
retriever_tool = create_retriever_tool(
    retriever,
    "langsmith_search",
    "Search for information about LangSmith. For any questions about LangSmith, you must use this tool!",
)
search = TavilySearchResults()
tools = [retriever_tool, search]

# 3. 创建代理
prompt = hub.pull("hwchase17/openai-functions-agent")
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
agent = create_openai_functions_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

# 4. 应用定义
app = FastAPI(
  title="LangChain Server",
  version="1.0",
  description="A simple API server using LangChain's Runnable interfaces",
)

# 5. 添加链路由

# 我们需要添加这些输入/输出模式,因为当前的 AgentExecutor
# 在模式方面缺乏。

class Input(BaseModel):
    input: str
    chat_history: List[BaseMessage] = Field(
        ...,
        extra={"widget": {"type": "chat", "input": "location"}},
    )

class Output(BaseModel):
    output: str

add_routes(
    app,
    agent_executor.with_types(input_type=Input, output_type=Output),
    path="/agent",
)

if __name__ == "__main__":
    import uvicorn

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

执行文件serve.py后,可以在localhost:8000中看到我们的Langchin正在提供服务。

(3)Playground

每个LangServe服务都附带一个简单的内置UI,用于配置和调用具有流式输出和查看中间步骤,转到 http://localhost:8000/agent/playground/ 可以体验Playground的功能。

(4)Client(客户端)

现在设置一个客户端,用于以编程方式与我们的服务进行交互,我们可以使用 langserve.RemoteRunnable实现这一功能。通过使用langserve.RemoteRunnable,可以像在客户端运行一样与提供服务的链进行交互。

from langserve import RemoteRunnable
remote_chain = RemoteRunnable("http://localhost:8000/agent/")
remote_chain.invoke({"input": "how can langsmith help with testing?"})

2.2.6  第一个LangChain程序:Q&A问答系统

本项目是一个基于自然语言处理技术的问答系统,通过结合语言模型和文档检索器,能够根据用户提出的问题自动检索相关文档并给出相应答案,同时提供源文档以支持答案的可信度,并具备上下文压缩功能以提高检索效率,整体实现了自动化的问答过程,为用户提供高效准确的信息查询服务。

实例2-1:中文版的Q&A问答系统(源码路径:codes\2\chatbot_llama2-main\chatbot_demo.ipynb

1. 准备环境

(1)通过如下命令检查和管理 NVIDIA GPU 设备的状态和性能。

LangChain

执行后会输出下面的信息,展示了NVIDIA GPU的各种信息,包括 GPU 的型号、驱动程序版本、GPU 的使用率、温度、内存使用情况等等。

Thu Jan 24 16:25:43 2024       
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 535.86.01              Driver Version: 536.67       CUDA Version: 12.2     |
|-----------------------------------------+----------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |         Memory-Usage | GPU-Util  Compute M. |
|                                         |                      |               MIG M. |
|=========================================+======================+======================|
|   0  NVIDIA GeForce GTX 1080        On  | 00000000:01:00.0 Off |                  N/A |
|  0%   38C    P2              35W / 198W |     15MiB /  8192MiB |      0%      Default |
|                                         |                      |                  N/A |
+-----------------------------------------+----------------------+----------------------+
                                                                                         
+---------------------------------------------------------------------------------------+
| Processes:                                                                            |
|  GPU   GI   CI        PID   Type   Process name                            GPU Memory |
|        ID   ID                                                             Usage      |
|=======================================================================================|
|    0   N/A  N/A        23      G   /Xwayland                                 N/A      |
+---------------------------------------------------------------------------------------+

(2)通过如下命令安装库PyTorch、torchvision 和 torchaudio,这些库用于进行深度学习任务和音频处理。通过 --index-url 参数指定了 PyTorch 的安装源为 https://download.pytorch.org/whl/cu122,这是针对 CUDA 12.2 版本的 PyTorch 库的安装源。

pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu122

(3)通过下面的代码导入多个 Python库和模块,用于构建自然语言处理任务的工具和模型。其中包括 PyTorch 深度学习框架、Hugging Face Transformers 库中的模型和管道、以及 LangChain 中的文本处理、嵌入和问答模块。这些工具和模型可用于文本分割、嵌入、语言模型训练和问答任务等自然语言处理任务。

import torch
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.embeddings import HuggingFaceInstructEmbeddings

from langchain import HuggingFacePipeline
from langchain import PromptTemplate,  LLMChain
from langchain.chains import RetrievalQA
from transformers import AutoModelForCausalLM, AutoTokenizer, GenerationConfig
from langchain.document_loaders import PyPDFLoader
from transformers import pipeline
import json
import textwrap

2. 文件分割

下面代码的功能是从名为 "finance.pdf" 的 PDF 文件中加载文本,并将其分割成一系列小块。

pdf_file_path = "finance.pdf"
pdf_loader = PyPDFLoader(pdf_file_path)
text_splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=0)
splitted_docs = text_splitter.split_documents(pdf_loader.load())
print(splitted_docs[0])

上述代码的实现流程如下所示:

  1. 首先,创建了一个 PyPDFLoader 对象 pdf_loader,用于加载 PDF 文件。
  2. 然后,使用 RecursiveCharacterTextSplitter 对象 text_splitter 对加载的文本进行分割。在这里,指定了分割的参数 chunk_size=300 和 chunk_overlap=0,表示每个小块的大小为 300 个字符,且没有重叠部分。
  3. 接着,调用 text_splitter.split_documents() 方法对加载的文本进行分割,得到了一个列表 splitted_docs,其中每个元素代表一个分割后的文档。
  4. 最后,打印输出分割后的第一个文档,即 splitted_docs[0]。执行后会输出:

page_content='中信证券研报表示,选取了14家具有代表性的海外CRO/CDMO企业以及生命科学上\n游服务商,对其中报业绩进行总结并作为对国内企业的参考。中信证券发现,全球临床\n阶段的新药研发依然活跃,早期研发的需求虽然暂时疲软,但随着海外投融资的回暖,\n也有望回归快速增长常态。生命科学上游服务商的收入,则受到了下游企业去库存周期\n的影响,但相关公司也普遍认为四季度将出现新增需求的拐点。综上,中信证券认为一\n体化服务于全球中后期临床研发阶段/商业化阶段外包需求,以及高校/科研院所客户收\n入占比高的企业业绩相对稳定,且海外收入占比较高的企业将有望率先受益于投融资回\n暖带来的需求增加。\n全文如下' metadata={'source': './finance.pdf', 'page': 0}

3. 为中文构建嵌入向量

(1)从 Hugging Face 模型库加载预训练的中文文本嵌入模型,然后使用该模型生成句子级别的嵌入向量。

from langchain.embeddings.huggingface import HuggingFaceEmbeddings
chinese_embedding_name = "/mnt/h/text2vec-base-chinese"
embeddings = HuggingFaceEmbeddings(
    model_name=chinese_embedding_name,
    model_kwargs={"device": "cuda"},
)

上述代码的具体功能如下所示:

  1. 导入langchain.embeddings.huggingface 模块中的类HuggingFaceEmbeddings。
  2. 设置要使用的中文嵌入模型的名称chinese_embedding_name,本项目使用的中文模型为 "/mnt/h/text2vec-base-chinese"。
  3. 创建一个 HuggingFaceEmbeddings 对象 embeddings,使用指定的模型名称和参数 model_kwargs={"device": "cuda"},以便于在 CUDA 设备上运行模型。

(2)创建一个 Chroma 数据库对象,用于存储嵌入向量,并指定了数据库的名称、嵌入函数和持久化目录。

collection_name = 'llama2_demo'
db = Chroma(
    collection_name=collection_name,
    embedding_function=embeddings,
    persist_directory='./'
)

对上述代码的具体说明如下所示:

  1. 定义要创建的 Chroma 数据库的名称 collection_name,在这里指定为 'llama2_demo'。
  2. 创建Chroma 对象 db,并传入数据库的名称、嵌入函数和持久化目录参数。
  3. 在参数中指定嵌入函数 embedding_function,这是之前创建的 HuggingFaceEmbeddings 对象 embeddings,用于生成文档的嵌入向量。
  4. 指定持久化目录 persist_directory='./',这表示嵌入向量将被存储在当前工作目录下。

(3)将之前分割得到的文档 splitted_docs 添加到 Chroma 数据库中进行存储,这些文档经过嵌入函数处理后生成了对应的嵌入向量,然后将这些嵌入向量与其原始文本一起存储在数据库中。这样,就可以在后续的检索和分析过程中使用这些嵌入向量来计算文本之间的相似度或执行其他相关任务。

db.add_documents(splitted_docs)

执行后会输出一组文档的唯一标识符,这代表成功将这些文档添加到 Chroma 数据库中的文档 ID,这些 ID以便在需要时进行检索或其他操作。

[Document(page_content='医疗健康|海外CXO生命科学上游公司中报盘点:投融资回暖趋势已现,需求拐\n点在望\n我们选取了14家具有代表性的海外CRO/CDMO企业以及生命科学上游服务商,\n对其中报业绩进行总结并作为对国内企业的参考。我们发现,全球临床阶段的新药研发\n依然活跃,早期研发的需求虽然暂时疲软,但随着海外投融资的回暖,也有望回归快速\n增长常态。生命科学上游服务商的收入,则受到了下游企业去库存周期的影响,但相关\n公司也普遍认为23Q4将出现新增需求的拐点。综上,我们认为一体化服务于全球中后\n期临床研发阶段/商业化阶段外包需求,以及高校/科研院所客户收入占比高的企业业绩', metadata={'page': 0, 'source': './finance.pdf'}), Document(page_content='增长常态。生命科学上游服务商的收入,则受到了下游企业去库存周期的影响,但企业\n普遍认为23Q4将出现新增需求的拐点。综上,我们认为一体化服务于全球中后期临床\n研发阶段/商业化阶段外包需求,以及高校/科研院所/大药企客户收入占比高的企业业\n绩相对稳定;且海外收入占比较高的企业将有望率先受益于投融资回暖带来的需求增加。', metadata={'page': 4, 'source': './finance.pdf'}), Document(page_content='了项目投资计划。来自资金相对更为充沛的高校、科研院所、大药企的需求则更为稳定。\n我们认为投融资的改善有望促进下游企业的资本开支恢复常态。\n3)中国区需求收入有一定波动。2023年H1,海外企业均表示中国区需求出现了\n下降,这一部分是因为国内同样正在经历投融资下滑+去库存的周期,一部分则是因为\n中国本土供应商的崛起:Sartorius认为中国本土供应商的市场份额已显著上升。\n▍风险因素:\n生物医药投融资恢复不及预期;去库存周期长于预期;宏观经济恢复不及预期;研\n发投入不及预期;研发管线推进不及预期。\n▍投资策略:\n我们选取了14家具有代表性的海外CRO/CDMO企业以及生命科学上游服务商,', metadata={'page': 3, 'source': './finance.pdf'}), Document(page_content='中信证券研报表示,选取了14家具有代表性的海外CRO/CDMO企业以及生命科学上\n游服务商,对其中报业绩进行总结并作为对国内企业的参考。中信证券发现,全球临床\n阶段的新药研发依然活跃,早期研发的需求虽然暂时疲软,但随着海外投融资的回暖,\n也有望回归快速增长常态。生命科学上游服务商的收入,则受到了下游企业去库存周期\n的影响,但相关公司也普遍认为四季度将出现新增需求的拐点。综上,中信证券认为一\n体化服务于全球中后期临床研发阶段/商业化阶段外包需求,以及高校/科研院所客户收\n入占比高的企业业绩相对稳定,且海外收入占比较高的企业将有望率先受益于投融资回\n暖带来的需求增加。\n全文如下', metadata={'page': 0, 'source': './finance.pdf'})]

4. 数据检索

下面的代码执行了一个与查询文本“医疗健康投资的风险因素有哪些?”相关的搜索操作,输出的结果是与查询文本相似的文档集合,这些文档包含了与查询文本语义相关的内容。

test_query = '医疗健康投资的风险因素有哪些?'
# test_query = 'what is dominant sequence transduction models?'
search_docs = db.similarity_search(test_query)
print(search_docs)

执行后会会发现输出的查询结果是文件finance.pdf中的内容:

[Document(page_content='医疗健康|海外CXO生命科学上游公司中报盘点:投融资回暖趋势已现,需求拐\n点在望\n我们选取了14家具有代表性的海外CRO/CDMO企业以及生命科学上游服务商,\n对其中报业绩进行总结并作为对国内企业的参考。我们发现,全球临床阶段的新药研发\n依然活跃,早期研发的需求虽然暂时疲软,但随着海外投融资的回暖,也有望回归快速\n增长常态。生命科学上游服务商的收入,则受到了下游企业去库存周期的影响,但相关\n公司也普遍认为23Q4将出现新增需求的拐点。综上,我们认为一体化服务于全球中后\n期临床研发阶段/商业化阶段外包需求,以及高校/科研院所客户收入占比高的企业业绩', metadata={'page': 0, 'source': './finance.pdf'}), Document(page_content='增长常态。生命科学上游服务商的收入,则受到了下游企业去库存周期的影响,但企业\n普遍认为23Q4将出现新增需求的拐点。综上,我们认为一体化服务于全球中后期临床\n研发阶段/商业化阶段外包需求,以及高校/科研院所/大药企客户收入占比高的企业业\n绩相对稳定;且海外收入占比较高的企业将有望率先受益于投融资回暖带来的需求增加。', metadata={'page': 4, 'source': './finance.pdf'}), Document(page_content='了项目投资计划。来自资金相对更为充沛的高校、科研院所、大药企的需求则更为稳定。\n我们认为投融资的改善有望促进下游企业的资本开支恢复常态。\n3)中国区需求收入有一定波动。2023年H1,海外企业均表示中国区需求出现了\n下降,这一部分是因为国内同样正在经历投融资下滑+去库存的周期,一部分则是因为\n中国本土供应商的崛起:Sartorius认为中国本土供应商的市场份额已显著上升。\n▍风险因素:\n生物医药投融资恢复不及预期;去库存周期长于预期;宏观经济恢复不及预期;研\n发投入不及预期;研发管线推进不及预期。\n▍投资策略:\n我们选取了14家具有代表性的海外CRO/CDMO企业以及生命科学上游服务商,', metadata={'page': 3, 'source': './finance.pdf'}), Document(page_content='中信证券研报表示,选取了14家具有代表性的海外CRO/CDMO企业以及生命科学上\n游服务商,对其中报业绩进行总结并作为对国内企业的参考。中信证券发现,全球临床\n阶段的新药研发依然活跃,早期研发的需求虽然暂时疲软,但随着海外投融资的回暖,\n也有望回归快速增长常态。生命科学上游服务商的收入,则受到了下游企业去库存周期\n的影响,但相关公司也普遍认为四季度将出现新增需求的拐点。综上,中信证券认为一\n体化服务于全球中后期临床研发阶段/商业化阶段外包需求,以及高校/科研院所客户收\n入占比高的企业业绩相对稳定,且海外收入占比较高的企业将有望率先受益于投融资回\n暖带来的需求增加。\n全文如下', metadata={'page': 0, 'source': './finance.pdf'})]

5. 构建模型和变换器管道

(1)加载预训练的中文语言模型,并为其设置相应的tokenizer和模型参数,以便后续在给定文本上进行模型推断或生成。

model_path = '/mnt/h/Chinese-Llama-2-7b-4bit'
tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=False)
model = AutoModelForCausalLM.from_pretrained(
    model_path,
    load_in_4bit=True,
    torch_dtype=torch.float16,
    device_map='auto'
)

对上述代码的具体说明如下所:

  1. 模型路径(model_path):设置预训练语言模型所在的路径为/mnt/h/Chinese-Llama-2-7b-4bit。
  2. 加载 tokenizer:使用类AutoTokenizer中的方法from_pretrained从指定的模型路径加载 tokenizer,参数 use_fast=False表示不使用快速tokenization。
  3. 加载语言模型:使用类AutoModelForCausalLM的 from_pretrained从指定的模型路径加载语言模型。参数load_in_4bit=True表示加载模型时使用 4 位精度,torch_dtype=torch.float16 表示使用float16数据类型,device_map='auto' 表示自动选择设备进行模型加载。

(2)加载预训练的中文语言模型和相应的tokenizer,然后创建一个文本生成管道,用于生成与给定模型和参数相关的文本。

generation_config = GenerationConfig.from_pretrained(model_path)
pipe = pipeline(
    "text-generation",
    model=model,
    torch_dtype=torch.bfloat16,
    device_map='auto',
    max_length=2048,
    temperature=0,
    top_p=0.95,
    repetition_penalty=1.15,
    tokenizer=tokenizer,
    generation_config=generation_config,
)

6. 构建提示

在下面的代码中定义了一个名为 get_prompt 的函数,用于构建提示文本。函数get_prompt接受一个指令参数 instruction 和一个新的系统提示参数 new_system_prompt,并返回构建好的提示模板。

B_INST, E_INST = "[INST]", "[/INST]"
B_SYS, E_SYS = "<<SYS>>\n", "\n<</SYS>>\n\n"
DEFAULT_SYSTEM_PROMPT = """\
You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.

If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information."""

def get_prompt(instruction, new_system_prompt=DEFAULT_SYSTEM_PROMPT ):
    SYSTEM_PROMPT = B_SYS + new_system_prompt + E_SYS
    prompt_template =  B_INST + SYSTEM_PROMPT + instruction + E_INST
    return prompt_template

instruction = "What is the temperature in Melbourne?"
get_prompt(instruction)

对上述代码的具体说明如下所示:

  1. 定义了一些常量,例如指令起始和结束标记 (B_INST, E_INST),系统提示起始和结束标记 (B_SYS, E_SYS),以及默认系统提示文本 (DEFAULT_SYSTEM_PROMPT)。
  2. 在函数内部,将新的系统提示文本插入到系统提示起始和结束标记之间,形成完整的系统提示。
  3. 将系统提示、指令和指令结束标记结合在一起,形成完整的提示模板,并返回该模板。

总的来说,这段代码的功能是根据给定的指令和系统提示,构建一个带有系统提示的完整提示模板。执行后会输出一个字符串,表示构建好的提示模板。这个提示模板包含了指令部分和系统提示部分,并且按照指定的格式进行了组合。具体的输出内容为:

[INST]<<SYS>>\nYou are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.\n\nIf a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information.\n<</SYS>>\n\nWhat is the temperature in Melbourne?[/INST]

7. 构建一个使用 Hugging Face 库的管道(pipeline

创建一个 HuggingFacePipeline 对象,构建了一个使用 Hugging Face 库的管道(pipeline)。然后定义了一个解析文本的函数parse_text()。

llm = HuggingFacePipeline(pipeline=pipe, model_kwargs={'temperature':0})
def parse_text(text):
    wrapped_text = textwrap.fill(text, width=100)
    print(wrapped_text +'\n\n')
from langchain.memory import ConversationBufferMemory
from langchain.prompts import PromptTemplate
template = """Use the following pieces of context to answer the question at the end. If you don't know the answer,\
just say that you don't know, don't try to make up an answer. Must use Chinese to answer the question.

{context}

{history}
Question: {question}
Helpful Answer:"""
prompt = PromptTemplate(input_variables=["history", "context", "question"], template=template)
memory = ConversationBufferMemory(input_key='question', memory_key='history')
from langchain.retrievers.multi_query import MultiQueryRetriever

retriever_from_llm = MultiQueryRetriever.from_llm(retriever=db.as_retriever(), llm=llm)

对上述代码的具体说明如下所:

  1. 创建 HuggingFacePipeline 对象:使用类HuggingFacePipeline创建了一个名为 llm 的对象。该对象接受一个管道参数 pipeline,以及模型参数字典 model_kwargs,其中包括温度参数设置为 0。
  2. 定义解析文本的函数parse_text:用于将给定的文本进行换行处理,以便更好地展示出来。
  3. 导入其他模块和类:导入类ConversationBufferMemory、PromptTemplate 和 MultiQueryRetriever,这些类可分别用于对话管理、提示模板生成和检索器创建等任务。
  4. 创建提示模板:定义一个模板字符串,用于生成提示文本,在模板中包含了要填充的变量和提示文本的格式。
  5. 设置记忆管理:创建了一个 ConversationBufferMemory 对象,用于管理对话的历史记录。
  6. 创建检索器:使用类MultiQueryRetriever中的方法from_llm创建了一个检索器对象 retriever_from_llm,该方法用于从预训练语言模型和数据库中创建一个检索器对象。

总的来说,这段代码的功能是设置了一个文本生成管道,定义了一个解析文本的函数,导入了其他必要的模块和类,并创建了一个检索器对象。

注意:在自然语言处理中,管道通常是指一系列数据处理步骤的组合,用于执行特定的任务。Hugging Face 提供了一系列强大的预训练模型和工具,可以用于文本生成、文本分类、命名实体识别等任务。因此,构建 HuggingFacePipeline 的过程可能涉及到了选择合适的模型和配置工作,以及定义数据处理和输出的方式,从而创建一个完成特定任务的管道。

8. 添加上下文压缩

上下文压缩是指对文本中的上下文信息进行压缩或简化,以便在给定的令牌数量限制下处理文档。在处理文档时需要进行上下文压缩工作,因为 Llama2 模型的最大令牌数量为 2048。

(1)请看下面的代码,功能是使用库 LangChain创建一个上下文压缩的检索器。

from langchain.retrievers.document_compressors import LLMChainExtractor
from langchain.retrievers import ContextualCompressionRetriever

compressor = LLMChainExtractor.from_llm(llm)
compression_retriever = ContextualCompressionRetriever(base_compressor=compressor, base_retriever=retriever_from_llm)
import logging
logging.getLogger("langchain.retrievers.multi_query").setLevel(logging.DEBUG)
retri_docs = compression_retriever.get_relevant_documents('医疗健康投资的风险因素有哪些?')
print(retri_docs)

上述代码的功能是利用 LangChain 库中的功能创建了一个上下文压缩的检索器,并执行了一次文档检索操作,返回了相关的文档。对上述代码的具体说明如下所示:

  1. 导入模块和类:首先导入了所需的模块和类,其中包括了文档压缩器类 LLMChainExtractor 和上下文压缩的检索器类 ContextualCompressionRetriever。
  2. 创建文档压缩器:使用 LLMChainExtractor.from_llm 方法基于已经创建的 llm 对象创建了一个文档压缩器 compressor。
  3. 创建上下文压缩的检索器:使用类ContextualCompressionRetriever创建了一个上下文压缩的检索器 compression_retriever,这个检索器结合了之前创建的文档压缩器和从语言模型和数据库中创建的检索器。
  4. 配置日志:通过设置日志级别为 DEBUG,启用了详细的日志记录,以便调试和跟踪检索过程中的细节。
  5. 执行检索:调用 compression_retriever中的方法get_relevant_documents,传入一个查询文本 '医疗健康投资的风险因素有哪些?',执行文档检索操作。
  6. 打印检索结果:打印输出检索结果,以便查看检索到的相关文档。执行后输出一系列文档,每个文档都包含了检索到的相关内容和元数据。具体输出如下:
[Document(page_content='"全球临干阶段的新药研发依然活跃"', metadata={'page': 0, 'source': './finance.pdf'}), Document(page_content='"生命科学上游服务商的收入,则受到了下游企业去库存周期的影响"', metadata={'page': 0, 'source': './finance.pdf'}), Document(page_content='"生命科学上游服务商的收入,则受到了下游企业去库存周期的影响"', metadata={'page': 4, 'source': './finance.pdf'}), Document(page_content='"全球生物医药领域的投融资已出现回暖"', metadata={'page': 2, 'source': './finance.pdf'}), Document(page_content='"生物医药投融资恢复不及预期" and "去库存周期长于预期".', metadata={'page': 3, 'source': './finance.pdf'}), Document(page_content='Repligen, Waters, MerckKGaA, Sartorius are representatives of overseas life science upstream suppliers.', metadata={'page': 2, 'source': './finance.pdf'}), Document(page_content='IQVIA, Medpace, CRO, Q2, RFP, business growth, new contracts, revenue increase, client engagement, overall performance, industry outlook.', metadata={'page': 1, 'source': './finance.pdf'})]

(2)创建了一个用于检索问答的对象,该对象将使用预训练语言模型和上下文压缩的检索器来检索相关文档,并使用指定的提示模板和记忆对象来生成答案。

qa = RetrievalQA.from_chain_type(
    llm = llm,
    chain_type = 'stuff',
    retriever = compression_retriever,
    return_source_documents = True,
    chain_type_kwargs = {"prompt": prompt, "memory": memory}
)

方法RetrievalQA.from_chain_type用于根据给定的链类型创建 RetrievalQA 对象,在上述代码中,此方法指定了链类型为 'stuff',各个参数的具体说明如下所示。

  1. 参数 llm 是一个预训练语言模型对象,用于生成答案。
  2. 参数 chain_type 是指定的链类型,指定为 'stuff'。
  3. 参数 retriever 是之前创建的上下文压缩的检索器对象 compression_retriever,用于检索相关文档。
  4. 参数 return_source_documents 设置为 True,表示返回检索到的源文档。
  5. 参数 chain_type_kwargs 是链类型的参数字典,其中包括了提示模板和记忆对象。

9. Q&A问答系统

根据用户输入的查询问题,返回与之相关的答案和文档。

query = input("\nEnter a query: ")
res = qa(query)
print(res)

上述代码的实现流程如下所示:

  1. 提示用户输入一个问题。
  2. 将用户输入的问题存储在变量 query 中。
  3. 问题被传递给之前创建的 Q&A 对象 qa 进行处理,触发问答检索程序。
  4. 将检索到的答案和相关文档存储在变量 res 中,并打印输出,供用户查看。

加入用户输入的问题是“医疗健康投资的风险因素有哪些?”,则执行后输出一个字典,其中包含了查询的问题、返回的结果和相关的源文档。

{
    'query': '医疗健康投资的风险因素有哪些?', 
    'result': '在医疗健康行业中进行投资时,可能会面临以下一些主要风险因素:1)政府法规和监管变化;2)技术创新与市场需求的失衡;3)公司治理问题;4)经济波动对消费者支付力度的影响;5)人才流失等。此外,还应注意其他特定行业或项目的风险,如基因工程、生物制品安全性等方面的风险。', 
    'source_documents': [
        Document(page_content='"全球临干阶段的新药研发依然活跃"', metadata={'page': 0, 'source': './finance.pdf'}), 
        Document(page_content='"生命科学上游服务商的收入,则受到了下游企业去库存周期的影响"', metadata={'page': 0, 'source': './finance.pdf'}), 
        Document(page_content='"生命科学上游服务商的收入,则受到了下游企业去库存周期的影响"', metadata={'page': 4, 'source': './finance.pdf'}), 
        Document(page_content='"全球生物医药领域的投融资已出现回暖"', metadata={'page': 2, 'source': './finance.pdf'}), 
        Document(page_content='"生物医药投融资恢复不及预期" and "去库存周期长于预期".', metadata={'page': 3, 'source': './finance.pdf'}), 
        Document(page_content='Repligen, Waters, MerckKGaA, Sartorius are representatives of overseas life science upstream suppliers.', metadata={'page': 2, 'source': './finance.pdf'}), 
        Document(page_content='IQVIA, Medpace, CRO, Q2, RFP, business growth, new contracts, revenue increase, client engagement, business outlook, industry trends.', metadata={'page': 1, 'source': './finance.pdf'})
    ]
}

注意:本项目的参考资料是开源项目chatbot_llama2,此项目在Github开源。

  • 28
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农三叔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值