LangChain实战2 | 豆包MarsCode AI刷题

154 阅读10分钟

生成回答并展示

这一步仅需将答案展示到UI界面即可。

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

网页的HTML代码如下:

<body>
    <div class="container">
        <div class="header">
            <h1>易速鲜花内部问答系统</h1>
            <img src="{{ url_for('static', filename='flower.png') }}" alt="flower logo" width="200"> 
        </div>
        <form method="POST">
            <label for="question">Enter your question:</label><br>
            <input type="text" id="question" name="question"><br>
            <input type="submit" value="Submit">
        </form>
        {% if result is defined %}
            <h2>Answer</h2>
            <p>{{ result.result }}</p>
        {% endif %}
    </div>
</body>

目录结构如下:

运行程序之后,跑起一个网页 http://127.0.0.1:5000/。与网页进行互动时,可以发现,问答系统完美生成了专属于异速鲜花内部资料的回答。

上图是整个系统的处理过程,通过LangChain仅需一小段代码就能实现问答系统的设计。

全部代码:

import os
from langchain_community.document_loaders import PyPDFLoader
from langchain_community.document_loaders import Docx2txtLoader
from langchain_community.document_loaders import TextLoader
from transformers.utils import is_torch_cuda_available, is_torch_mps_available
from typing import Dict, List, Any
os.environ['HUGGINGFACEHUB_API_TOKEN'] = 'xxxxxx'
​
​
# 加载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.Split 将Documents切分成块以便后续进行嵌入和向量存储
from langchain.text_splitter import RecursiveCharacterTextSplitter
​
text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=10)
chunked_documents = text_splitter.split_documents(documents)
​
# 3.Store 将分割嵌入并存储在矢量数据库Qdrant中
from langchain_community.vectorstores import Qdrant
from langchain_huggingface import HuggingFaceEmbeddings
EMBEDDING_DEVICE = "cuda" if is_torch_cuda_available() else "mps" if is_torch_mps_available() else "cpu"
embeddings_model = HuggingFaceEmbeddings(model_name='moka-ai/m3e-base', model_kwargs={'device': EMBEDDING_DEVICE})
​
​
vectorstore = Qdrant.from_documents(
    documents=chunked_documents, # 以分块的文档
    embedding=embeddings_model, # 
    location=":memory:",  # in-memory 存储
    collection_name="my_documents",) # 指定collection_name# 4. Retrieval 准备模型和Retrieval链
import logging  # 导入Logging工具
from langchain_huggingface import ChatHuggingFace  #ChatHuggingFace模型
from langchain_huggingface.llms import HuggingFacePipeline
​
​
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)
​
# hf = HuggingFacePipeline.from_model_id(
#     model_id="Qwen/Qwen2.5-0.5B",
#     task="text-generation",
#     pipeline_kwargs={"max_new_tokens": 100},
# )# # 实例化大模型
# chat_model = ChatHuggingFace(llm = hf)# 导入LangChain中的OpenAI模型接口
from langchain_community.llms import HuggingFaceHub
# 创建模型实例
model= HuggingFaceHub(repo_id="Qwen/Qwen2.5-72B-Instruct" )
chat_model = ChatHuggingFace(llm = model)
#实例化一个MultiQueryRetriever
retriever_from_llm = MultiQueryRetriever.from_llm(
    retriever=vectorstore.as_retriever(), llm=chat_model
)
​
#实例化一个RetrievalQA链
qa_chain = RetrievalQA.from_chain_type(chat_model, 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)

模型IO

模型在LangChain框架的最底层,它是基于语言模型构建的应用的核心元素,LangChain应用开发就是以LangChain作为框架,通过API调用大模型来解决具体问题的过程。因此LangChain的框架逻辑可以说都是由LLM来进行驱动的。

Model I/O

对模型的使用过程可以拆解为:输入提示调用模型输出解析三部分。这三部分形成了一个整体,被统一称为Model I/O

  1. 提示模板:创建LangChain的提示模板,根据实际需求动态选择不同的输入,针对特定的任务和应用调整输入。
  2. 语言模型:LangChain允许使用接口来调用模型。无论使用哪种语言模型,都可以通过同一种方式进行调用,这样就提供了灵活性和便利性。
  3. 输出解析:LangChain提供了从模型输出中提取信息的功能,通过输出解析器可以精确地从模型的输出中获取需要的信息而不需要处理冗余或不相关的数据,还可以把非结构化文本转换为结构化数据。

提示模板

Prompt Engineering 用于研究大语言模型的提示构建。吴恩达老师对于prompt构建的原则如下:

  • 给予模型清晰明确的指示
  • 让模型慢慢思考

下面是LangChain中提示模板的创建方法:

# 导入LangChain中的提示模板
from langchain.prompts import PromptTemplate
# 创建原始模板
template = """您是一位专业的鲜花店文案撰写员。\n
对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?
"""
# template 是python的模板字符串,其中{}为占位符,用于给定输入的占位
# 根据原始模板创建LangChain提示模板
prompt = PromptTemplate.from_template(template) 
# 打印LangChain提示模板的内容
print(prompt)

prompt中的具体内容可以为如下:

input_variables=['flower_name', 'price'] 
output_parser=None partial_variables={} 
template='/\n您是一位专业的鲜花店文案撰写员。
\n对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?\n'
template_format='f-string' 
validate_template=True

from_template是一个类方法,该方法直接从一个字符串模板中创建一个PromptTemplate对象,打印这个对象可以发现由输入变量、输出解析器、模板格式、是否验证模板等构成。

因此PromptTemplate的from_template方法就是将一个原始的模板字符串转化为一个更丰富、更方便操作的PromptTemplate对象,LangChain 提供了多个类和函数,也为各种应用场景设计了很多内置模板,使构建和使用提示变得容易

语言模型

LangChain支持的语言模型有以下几类:

  1. 大语言模型(LLM),即Text Model,这些模型将文本字符串作为输入,返回文本字符串。
  2. 聊天模型(Chat Model),通常由语言模型支持,但API更加结构化,将聊天消息列表作为输入,返回聊天消息。
  3. 文本嵌入模型(Embedding Model),将文本作为输入,返回浮点数列表即Embedding。文本嵌入模型主要负责把文档嵌入向量数据库。
# 设置OpenAI API Key
import os
os.environ["OPENAI_API_KEY"] = '你的Open AI API Key'# 导入LangChain中的OpenAI模型接口
from langchain_openai import OpenAI
# 创建模型实例
model = OpenAI(model_name='gpt-3.5-turbo-instruct')
# 输入提示
input = prompt.format(flower_name=["玫瑰"], price='50')#实例化提示模板,将占位符替换
# 得到模型的输出
output = model.invoke(input)
# 打印输出内容
print(output)  
​

如果使用huggongface的开源模型,调用API

# 导入LangChain中的提示模板
from langchain.prompts import PromptTemplate
# 创建原始模板
template = """You are a flower shop assitiant。\n
For {price} of {flower_name} ,can you write something for me?
"""
# 根据原始模板创建LangChain提示模板
prompt = PromptTemplate.from_template(template) 
# 打印LangChain提示模板的内容
print(prompt)
import os
os.environ['HUGGINGFACEHUB_API_TOKEN'] = '你的HuggingFace API Token'
# 导入LangChain中的OpenAI模型接口
from langchain_community.llms import HuggingFaceHub
# 创建模型实例
model= HuggingFaceHub(repo_id="google/flan-t5-large")
# 输入提示
input = prompt.format(flower_name=["rose"], price='50')
# 得到模型的输出
output = model(input)
# 打印输出内容
print(output)
​

可以得到如下输出:

让你心动!50元就可以拥有这支充满浪漫气息的玫瑰花束,让TA感受你的真心爱意。

提示模板可以复用,这也是LangChain的方便之处,若是直接使用openAI的API,则需要编写更多的代码。(其实可以看封装为类,然后不断实例化)

提示模板的复用:

# 导入LangChain中的提示模板
from langchain import PromptTemplate
# 创建原始模板
template = """您是一位专业的鲜花店文案撰写员。\n
对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?
"""
# 根据原始模板创建LangChain提示模板
prompt = PromptTemplate.from_template(template) #模板封装
# 打印LangChain提示模板的内容
print(prompt)
​
# 设置OpenAI API Key
import os
os.environ["OPENAI_API_KEY"] = '你的Open AI API Key'
​
# 导入LangChain中的OpenAI模型接口
from langchain import OpenAI
# 创建模型实例
model = OpenAI(model_name='gpt-3.5-turbo-instruct')
​
# 多种花的列表
flowers = ["玫瑰", "百合", "康乃馨"]
prices = ["50", "30", "20"]
​
# 生成多种花的文案
for flower, price in zip(flowers, prices):
    # 使用提示模板生成输入
    input_prompt = prompt.format(flower_name=flower, price=price)#实例化
​
    # 得到模型的输出
    output = model.invoke(input_prompt)
​
    # 打印输出内容
    print(output)
​

如果直接调用OpenAI的API

import openai # 导入OpenAI
openai.api_key = 'Your-OpenAI-API-Key' # API Keyprompt_text = "您是一位专业的鲜花店文案撰写员。对于售价为{}元的{},您能提供一个吸引人的简短描述吗?" # 设置提示flowers = ["玫瑰", "百合", "康乃馨"]
prices = ["50", "30", "20"]
​
# 循环调用Text模型的Completion方法,生成文案
for flower, price in zip(flowers, prices):
    prompt = prompt_text.format(price, flower)
    response = openai.completions.create(
        engine="gpt-3.5-turbo-instruct",
        prompt=prompt,
        max_tokens=100
    )
    print(response.choices[0].text.strip()) # 输出文案

因此LangChain提示模板的好处是将prompt格式封装,可以复用。

输出:

这支玫瑰,深邃的红色,传递着浓浓的深情与浪漫,令人回味无穷! 百合:美丽的花朵,多彩的爱恋!30元让你拥有它! 康乃馨—20元,象征爱的祝福,送给你最真挚的祝福。

LangChain和提示模板的好处:

  1. 代码的可读性:使用模板,可以使得提示文本更易于阅读和理解,特别是对于复杂的提示或多变量的情况。
  2. 可复用性:模板可以在多个地方复用,不需要在每个生成提示的地方重新构造提示字符串。
  3. 维护:后续修改提示使用模板即可,不需要在所有用到提示的地方修改。
  4. 变量处理:如果提示中涉及多个变量,模板可以自动处理变量的插入,不需要手动拼接字符串。
  5. 参数化:模板可以根据不同的参数生成不同的提示,对于个性化生成文本非常有用。

输出解析

LangChain的输出解析能够更容易地从模型中获取结构化信息。

# 导入OpenAI Key
import os
os.environ["OPENAI_API_KEY"] = '你的OpenAI API Key'
​
# 导入LangChain中的提示模板
from langchain.prompts import PromptTemplate
# 创建原始提示模板
prompt_template = """您是一位专业的鲜花店文案撰写员。
对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?
{format_instructions}"""# 通过LangChain调用模型
from langchain_openai import OpenAI
# 创建模型实例
model = OpenAI(model_name='gpt-3.5-turbo-instruct')
​
# 导入结构化输出解析器和ResponseSchema
from langchain.output_parsers import StructuredOutputParser, ResponseSchema
# 定义我们想要接收的响应模式
response_schemas = [
    ResponseSchema(name="description", description="鲜花的描述文案"),
    ResponseSchema(name="reason", description="问什么要这样写这个文案")
]
# 创建输出解析器
output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
​
# 获取格式指示
format_instructions = output_parser.get_format_instructions()
# 根据原始模板创建提示,同时在提示中加入输出解析器的说明
prompt = PromptTemplate.from_template(prompt_template, 
                partial_variables={"format_instructions": format_instructions}) 
​
# 数据准备
flowers = ["玫瑰", "百合", "康乃馨"]
prices = ["50", "30", "20"]
​
# 创建一个空的DataFrame用于存储结果
import pandas as pd
df = pd.DataFrame(columns=["flower", "price", "description", "reason"]) # 先声明列名
​
for flower, price in zip(flowers, prices):
    # 根据提示准备模型的输入
    input = prompt.format(flower_name=flower, price=price)
​
    # 获取模型的输出
    output = model.invoke(input)
    
    # 解析模型的输出(这是一个字典结构)
    parsed_output = output_parser.parse(output)
​
    # 在解析后的输出中添加“flower”和“price”
    parsed_output['flower'] = flower
    parsed_output['price'] = price
​
    # 将解析后的输出添加到DataFrame中
    df.loc[len(df)] = parsed_output  
​
# 打印字典
print(df.to_dict(orient='records'))
​
# 保存DataFrame到CSV文件
df.to_csv("flowers_with_descriptions.csv", index=False)
​

输出

[{'flower': '玫瑰', 'price': '50', 'description': 'Luxuriate in the beauty of this 50 yuan rose, with its deep red petals and delicate aroma.', 'reason': 'This description emphasizes the elegance and beauty of the rose, which will be sure to draw attention.'}, {'flower': '百合', 'price': '30', 'description': '30元的百合,象征着坚定的爱情,带给你的是温暖而持久的情感!', 'reason': '百合是象征爱情的花,写出这样的描述能让顾客更容易感受到百合所带来的爱意。'}, {'flower': '康乃馨', 'price': '20', 'description': 'This beautiful carnation is the perfect way to show your love and appreciation. Its vibrant pink color is sure to brighten up any room!', 'reason': 'The description is short, clear and appealing, emphasizing the beauty and color of the carnation while also invoking a sense of love and appreciation.'}]