langchain实践

发布时间 2023-12-26 22:52:57作者: 唐钰逍遥

原理篇

LLM模型,学术意义上这个大体现在供给给模型的训练样本很大,对我们使用者来说我们可以认为他海纳百川,有容乃大。学的足够多, 博学。所以你要跟它对话,需要先告诉它你充当的角色是谁,也就是你要获得知识的来源。

LangChain可以标准化和抽象化整个大语言模型使用过程。
1703472919101

  • 文本加载
    • 从各种数据源中加载文本语料。
    • 你关心的数据,可以是自己现成的也可以是第三方供给的。
  • 文本转换(切割)
    嵌入容器入口有大小限制,切割后的文档有重合,便于关联文档上下文。
  • 文本词嵌入(数字向量化)
    文本词数字向量化,多维化。
  • 词嵌入向量的存储和检索
    存入向量数据库返回检索器

W模型,是一种用于写作文的方法,它指的是根据Who(谁),What(什么),When(何时),Where(何地),Why(为什么),How(如何)等问题,来组织文章的内容和结构;W模型可以帮助我们清晰地回答文章的基本问题,使文章更有逻辑和条理。

在这个例子中,我们可以根据以下方式来分析Prompt架构中的W模型:

  • Who:谁是文章的作者,谁是文章的读者,谁是文章的主角。在这个例子中,文章的作者是我们,文章的读者是预训练语言模型(LLM),文章的主角是文本分类任务。
  • What:文章的主题是什么,文章的目的是什么,文章的内容是什么。在这个例子中,文章的主题是情感分析,文章的目的是指导LLM完成文本分类任务,文章的内容是一个cloze prompt,包括输入部分和输出部分。
  • When:文章发生在什么时间,文章的时效性如何,文章的时序关系如何。在这个例子中,文章发生在当前,文章的时效性较高,文章的时序关系是先输入后输出。
  • Where:文章发生在什么地点,文章的空间关系如何,文章的范围如何。在这个例子中,文章发生在计算机中,文章的空间关系是输入部分和输出部分之间用分隔符隔开,文章的范围是一句话。
  • Why:文章的动机是什么,文章的意义是什么,文章的论点是什么。在这个例子中,文章的动机是利用LLM的泛化能力和迁移能力,减少对有监督数据的依赖,文章的意义是实现小样本学习的目标,文章的论点是根据输入部分的情感倾向,输出正确的类别。
  • How:文章的方法是什么,文章的结构是什么,文章的技巧是什么。在这个例子中,文章的方法是使用cloze prompt,文章的结构是输入部分和输出部分,文章的技巧是使用特殊符号或标记来分隔输入部分和输出部分,以及使用变量或元数据来表示参数。

开发篇

上传一个pdf文档,将文档转换为向量化语料喂给OpenAILLM模型,同时向它提问问题,回答内容和文档强相关。

  • PostMan中API验证

1703601981145

  • 实现结果

1703601867156

  • python-dotenv

    初始化各种环境变量

    pip install -U python-dotenv
    touch .env
    
    配置项 配置值
    OPENAI_API_KEY 'sk-xx'
    OPENAI_API_BASE "https://xxx"
    request_timeout "10000"
    https_proxy "http://127.0.0.1:58309"
    http_proxy "http://127.0.0.1:58309"
    upload_path_prefix xxxx/
    save_vector_prefix xxxx/
  • fastapi
    快速构建Backend API接口,类似Java Servlet、Spring MVC

    pip install -U fastapi
    
  • streamlit

    快速构建 Frontend UI ,提供UI和后端进行交互。

    pip install -U streamlit
    
  • chromadb
    本地化向量数据库

    pip install chromadb
    
  • 构建后端服务

    uvicorn backend_api:app --reload
    
  • 构建前端

     streamlit run frontend_face.py
    

项目结构

1703601377051

具体实现

  • backend_api

    from fastapi import FastAPI, UploadFile, File, Form
    from fastapi.responses import JSONResponse
    from langchain_utils import *
    app = FastAPI()
    @app.post("/upload_query")
    async def upload_query(file: UploadFile = File(...), query: str = Form(...)):
        try:
            upload_file(file)
            res = query_with_index(query,file.filename)
            return JSONResponse(content={"message": res})
        except Exception as e:
            return JSONResponse(status_code=500, content={"message": f"Error occurred: {e}"})
    
  • frontend_face

    import streamlit as st
    import requests
    
    form = st.form(key='my_form')
    
    uploaded_file = form.file_uploader('参考文件', type=['pdf'])
    text = form.text_input('检索关键词')
    
    submit_button = form.form_submit_button('上传并检索')
    
    if submit_button:
        # assuming the server is running on localhost and port 8000
        url = "http://127.0.0.1:8000/upload_query"
    
        if uploaded_file is not None:
            # convert to bytes
            bytes_data = uploaded_file.getvalue()
            files = {'file': (uploaded_file.name, uploaded_file.getvalue())}
        else:
            file = None
    
        data = {'query': text}
        response = requests.post(url, files=files, data=data)
    
        if response.ok:
            # 获取后端返回的结果
            result = response.json()
            # 显示结果
            # 使用 st.markdown 来渲染结果,并添加一些样式
            st.markdown(f""" <p style="text-indent:2em">{result['message']}</p> """, unsafe_allow_html=True)
        else:
            st.write('在提交过程中出现了错误')
    
    
    
  • langchain_utils

    #
    # # 1.从Http Multi File文件流加载文件到服务器,这一步在API已完成
    # # 2.从服务器路径加载文件到向量数据库
    import os.path
    import shutil
    from dotenv import load_dotenv
    from langchain.indexes import VectorstoreIndexCreator
    from langchain.indexes.vectorstore import VectorStoreIndexWrapper
    from langchain_community.document_loaders import PyPDFLoader
    from langchain_community.embeddings import OpenAIEmbeddings
    from langchain_community.vectorstores.chroma import Chroma
    
    load_dotenv()
    local_persist_path = os.getenv("save_vector_prefix")
    upload_path_prefix = os.getenv("upload_path_prefix")
    
    # 从网络IO上传文件存到服务器端
    def upload_file(file):
        save_path = get_file_path(upload_path_prefix,file.filename)
        if not (os.path.exists(save_path)):
            with open(f"{save_path}", "wb") as buffer:
                shutil.copyfileobj(file.file, buffer)
    # 构建地址和文件的相对路径
    def get_file_path(path_prefix,indexName):
        return os.path.join(path_prefix,indexName)
    # 加载服务端上传好的文件到Chroma本地向量数据库
    def load_file_2_VectorStore(file):
        loader = PyPDFLoader(file_path=get_file_path(upload_path_prefix,file))
        save_path = get_file_path(local_persist_path,file)
        # 存在加载保存的数据进Chroma内存向量数据库
        if (os.path.exists(save_path)):
            vectorstore = Chroma(
                embedding_function= OpenAIEmbeddings(),
                persist_directory = save_path
            )
            return  VectorStoreIndexWrapper(vectorstore=vectorstore)
        # 不存在生成并返回同时保存到本地
        else:
            return   (VectorstoreIndexCreator(vectorstore_kwargs={"persist_directory":save_path}).from_loaders(loaders=[loader]))
    
    # 用生成的向量数据库加载数据到OpenAI生成知识库,用知识库检索问题的答案
    def query_with_index(query,filename):
        return load_file_2_VectorStore(filename).query_with_sources(query,chain_type="map_reduce")
    
    
    

文献

Agent ReAct

  • Action agents

ReAct: Synergizing Reasoning and Acting in Language Models – Google Research Blog

  • Plan And Execute Agent

[2305.04091] Plan-and-Solve Prompting: Improving Zero-Shot Chain-of-Thought Reasoning by Large Language Models (arxiv.org)