You need to enable JavaScript to run this app.
导航

使用 ES 构建智能问答系统

最近更新时间2024.04.07 19:15:43

首次发布时间2023.09.08 16:10:09

本文基于火山引擎云搜索服务 ES、火山方舟大模型服务平台,以及开源框架 LangChain,快速搭建一套智能问答系统。

背景信息

大型语言模型(Large Language Model,LLM)在图像生成,书写文稿,信息搜索等领域被广泛应用,但在垂直领域由于受到特定领域数据集的训练和时效性限制,在 LLM 的基础上构建垂直领域的产品时,需要将特定的知识库输入到模型中来训练或者推理。
目前输入知识库的方法有微调(Fine-Tuning)和提示学习(Prompt-Tuning)这两种方式。微调是通过新数据集在已有模型上进一步训练,训练成本较高,时效性较差;提示学习在训练成本、时效性上都比较灵活。
本文将基于提示学习方式,介绍如何基于火山引擎云搜索服务和火山方舟大模型服务平台来构建专属的智能问答系统。利用嵌入技术(embedding),通过嵌入模型,将数据集内容转化为向量,然后借助 ES 的向量搜索能力,将这些向量和数据保存起来。查询时,通过相似度查询,匹配出关联的 topK 结果,然后将这些结果辅以提示词提供给 LLM,最终生成相应的答案。
本文选择从火山方舟大模型服务平台中选取一个模型(ChatGLM)作为 LLM 来推理答案。此外,选用开源框架 LangChain 作为构建端到端语言模型应用框架,简化整个智能问答链路。
图片

步骤一:准备云搜索 VectorStore

  1. 登录云搜索服务控制台,然后创建一个 7.10 版本的 ES 实例。
    图片
  2. 创建索引。
    在 ES 实例中创建一个索引(langchain_faq),并为其配置 mappings 和 settings。
    示例代码如下:
    PUT langchain_faq
    {
      "mappings": {
        "properties": {
          "message": { "type": "text" },
          "message_embedding": { "type": "knn_vector", "dimension": 768 },
          "metadata": { "type": "text" }
        }
      },
      "settings": {
        "index": {
          "refresh_interval": "10s",
          "number_of_shards": "3",
          "knn": true,
          "knn.space_type": "cosinesimil",
          "number_of_replicas": "1"
        }
      }
    }
    

步骤二:准备 Python Client

  1. 安装 Python Client 依赖。

    pip install volcengine --user
    pip install langchain --user
    
  2. 初始化。

    #Embedding
    from langchain.embeddings import HuggingFaceEmbeddings
    #VectorStore
    from langchain.vectorstores import OpenSearchVectorSearch
    #LLM Base
    from langchain.llms.base import LLM
    #Document loader
    from langchain.document_loaders import WebBaseLoader
    #LLM Cache
    from langchain.cache import InMemoryCache
    #Volcengine
    from volcengine.ApiInfo import ApiInfo
    from volcengine import Credentials
    from volcengine.base.Service import Service
    from volcengine.ServiceInfo import ServiceInfo
    
    import json
    import os
    from typing import Optional, List, Dict, Mapping, Any
    
    #加载Embeddings,这里使用huggingFace 作为embedding
    embeddings = HuggingFaceEmbeddings()
    
    # 启动llm的缓存
    llm_cache = InMemoryCache()
    

步骤三:选择方舟模型

在火山引擎方舟大模型平台中选取一个模型(ChatGLM)。
图片
选择模型后,您可以在模型的右上角单击 API调用,查看样例。

maas_host = "maas-api.ml-platform-cn-beijing.volces.com"
api_chat = "chat"
API_INFOS = {api_chat: ApiInfo("POST", "/api/v1/" + api_chat, {}, {}, {})}

class MaaSClient(Service):
    def __init__(self, ak, sk):
        credentials = Credentials.Credentials(ak=ak, sk=sk, service="ml_maas", region="cn-beijing")
        self.service_info = ServiceInfo(maas_host, {"Accept": "application/json"}, credentials, 60, 60, "https")
        self.api_info = API_INFOS
        super().__init__(self.service_info, self.api_info)

client = MaaSClient(os.getenv("VOLC_ACCESSKEY"), os.getenv("VOLC_SECRETKEY"))

#引入LLM Base,构造Volc GLM Client, 用于和LLM 对话。
from langchain.llms.base import LLM
class ChatGLM(LLM):
    @property
    def _llm_type(self) -> str:
        return "chatglm"
    def _construct_query(self, prompt: str) -> Dict:
        query = "human_input is: " + prompt
        return query
    @classmethod
    def _post(cls, query: Dict) -> Any:
        request = ({
            "model": {
                "name": "chatglm-130b"
            },
            "parameters": {
                "max_tokens": 2000,
                "temperature": 0.8
            },
            "messages": [{
                "role": "user",
                "content": query
            }]
        })
        print(request)
        resp = client.json(api=api_chat, params={}, body=json.dumps(request))
        return resp
    def _call(self, prompt: str, 
        stop: Optional[List[str]] = None) -> str:
        query = self._construct_query(prompt=prompt)
        resp = self._post(query=query)
        return resp

步骤四:写入数据集

  1. 在 ES 实例详情页面,获取实例访问地址。
    如果需要在公网环境访问 ES 实例,请提前为实例开启公网访问。相关文档,请参见开启实例公网访问
    图片
  2. 写入数据集。
    本文使用 LangChain 的 Loader 导入一些 Web 的数据集,然后利用 HuggingFaceEmbeddings (768 维度)生成特征值,再用 VectorStore 写入云搜索服务 ES 的向量索引。
    # Document loader
    from langchain.document_loaders import WebBaseLoader
    loader = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-agent/")
    data = loader.load()
    # Split
    from langchain.text_splitter import RecursiveCharacterTextSplitter
    text_splitter = RecursiveCharacterTextSplitter(chunk_size = 500, chunk_overlap = 0)
    all_splits = text_splitter.split_documents(data)
    #Embeddings
    from langchain.embeddings import HuggingFaceEmbeddings
    embeddings = HuggingFaceEmbeddings()
    #VectorStore 
    # URL 为云搜索VectorStore的访问URL。
    # http_auth 为访问云搜索的用户密码。如果遗忘实例访问用户(admin)的密码,可以选择重置密码。
    from langchain.vectorstores import OpenSearchVectorSearch
    vectorstore = OpenSearchVectorSearch.from_documents(
            documents = all_splits,
            embedding = HuggingFaceEmbeddings(),
            opensearch_url = "URL", 
            http_auth = ("user", "password"),
            verify_certs = False,
            ssl_assert_hostname = False,
            index_name = "langchain_faq",
            vector_field ="message_embedding",
            text_field = "message",
            metadata_field = "message_metadata",
            ssl_show_warn = False,)
    

步骤五:Query & Retriever

query = "What are the approaches to Task Decomposition?"
docs = vectorstore.similarity_search(
        query,
        vector_field="message_embedding",
        text_field="message",
        metadata_field="message_metadata",)
retriever = vectorstore.as_retriever(search_kwargs={"vector_field": "message_embedding", "text_field":"message", "metadata_field":"message_metadata"})        

步骤六:LLM Chat

调用您选择的模型(ChatGLM)的 ChatAPI,使用 LangChain 自带的 Prompt,以及前文的 Query,通过相似度查询,匹配出关联结果,然后将这些结果辅以提示词提供给 LLM,最终生成相应的答案。
图片

from langchain.chains import RetrievalQA
llm = ChatGLM()
retriever = vectorstore.as_retriever(search_kwargs={"vector_field": "message_embedding", "text_field":"message", "metadata_field":"message_metadata"})
qa_chain = RetrievalQA.from_chain_type(llm,retriever=retriever)
qa_chain({"query": query})

调试时返回如下类似信息:
图片
进行问答时,返回如下类似信息:
图片