Skip to main content

使用OpenAI函数结构化答案

OpenAI函数允许对响应输出进行结构化。在问答时,这通常很有用,因为您不仅想要获取最终答案,还想要获取支持证据、引用等。

在这个笔记本中,我们展示了如何使用LLM链,该链使用OpenAI函数作为整体检索流程的一部分。

from langchain.chains import RetrievalQA
from langchain.document_loaders import TextLoader
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma
loader = TextLoader("../../state_of_the_union.txt", encoding="utf-8")
documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_documents(documents)
for i, text in enumerate(texts):
text.metadata["source"] = f"{i}-pl"
embeddings = OpenAIEmbeddings()
docsearch = Chroma.from_documents(texts, embeddings)
from langchain.chat_models import ChatOpenAI
from langchain.chains.combine_documents.stuff import StuffDocumentsChain
from langchain.prompts import PromptTemplate
from langchain.chains import create_qa_with_sources_chain
llm = ChatOpenAI(temperature=0, model="gpt-3.5-turbo-0613")
qa_chain = create_qa_with_sources_chain(llm)
doc_prompt = PromptTemplate(
template="内容:{page_content}\n来源:{source}",
input_variables=["page_content", "source"],
)
final_qa_chain = StuffDocumentsChain(
llm_chain=qa_chain,
document_variable_name="context",
document_prompt=doc_prompt,
)
retrieval_qa = RetrievalQA(
retriever=docsearch.as_retriever(), combine_documents_chain=final_qa_chain
)
query = "总统对俄罗斯说了什么"
retrieval_qa.run(query)
    '{\n  "answer": "总统对俄罗斯的行动表示强烈谴责,并宣布采取措施孤立俄罗斯并为乌克兰提供支持。他表示,俄罗斯入侵乌克兰将对俄罗斯产生长期影响,并强调捍卫北约国家的承诺。总统还提到通过制裁采取有力行动,并释放石油储备以缓解天然气价格。总体而言,总统传达了对乌克兰的团结和保护美国利益的决心。",\n  "sources": ["0-pl", "4-pl", "5-pl", "6-pl"]\n}'

使用Pydantic

如果需要,我们可以将链设置为返回Pydantic格式。请注意,如果下游链使用此链的输出(包括内存),它们通常会期望它以字符串格式返回,因此只有在这是最终链时才应使用此链。

qa_chain_pydantic = create_qa_with_sources_chain(llm, output_parser="pydantic")
final_qa_chain_pydantic = StuffDocumentsChain(
llm_chain=qa_chain_pydantic,
document_variable_name="context",
document_prompt=doc_prompt,
)
retrieval_qa_pydantic = RetrievalQA(
retriever=docsearch.as_retriever(), combine_documents_chain=final_qa_chain_pydantic
)
retrieval_qa_pydantic.run(query)
    AnswerWithSources(answer="总统对俄罗斯的行动表示强烈谴责,并宣布采取措施孤立俄罗斯并为乌克兰提供支持。他表示,俄罗斯入侵乌克兰将对俄罗斯产生长期影响,并强调捍卫北约国家的承诺。总统还提到通过制裁采取有力行动,并释放石油储备以缓解天然气价格。总体而言,总统传达了对乌克兰的团结和保护美国利益的决心。", sources=['0-pl', '4-pl', '5-pl', '6-pl'])

在ConversationalRetrievalChain中使用

我们还可以展示在ConversationalRetrievalChain中使用的情况。请注意,因为此链涉及内存,我们将不使用Pydantic返回类型。

from langchain.chains import ConversationalRetrievalChain
from langchain.memory import ConversationBufferMemory
from langchain.chains import LLMChain

memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
_template = """给定以下对话和后续问题,请将后续问题重新表述为一个独立的问题,使用其原始语言。
确保避免使用任何不清晰的代词。

聊天记录:
{chat_history}
后续输入:{question}
独立问题:"""
CONDENSE_QUESTION_PROMPT = PromptTemplate.from_template(_template)
condense_question_chain = LLMChain(
llm=llm,
prompt=CONDENSE_QUESTION_PROMPT,
)
qa = ConversationalRetrievalChain(
question_generator=condense_question_chain,
retriever=docsearch.as_retriever(),
memory=memory,
combine_docs_chain=final_qa_chain,
)
query = "总统对Ketanji Brown Jackson说了什么"
result = qa({"question": query})
result
    {'question': '总统对Ketanji Brown Jackson说了什么',
'chat_history': [HumanMessage(content='总统对Ketanji Brown Jackson说了什么', additional_kwargs={}, example=False),
AIMessage(content='{\n "answer": "总统提名Ketanji Brown Jackson为巡回上诉法院法官,并称赞她是全国顶级法律专家之一,将继续继承布雷耶法官的卓越传统。",\n "sources": ["31-pl"]\n}', additional_kwargs={}, example=False)],
'answer': '{\n "answer": "总统提名Ketanji Brown Jackson为巡回上诉法院法官,并称赞她是全国顶级法律专家之一,将继续继承布雷耶法官的卓越传统。",\n "sources": ["31-pl"]\n}'}
query = "他对她的前任说了什么?"
result = qa({"question": query})
result
    {'question': '他对她的前任说了什么?',
'chat_history': [HumanMessage(content='总统对Ketanji Brown Jackson说了什么', additional_kwargs={}, example=False),
AIMessage(content='{\n "answer": "总统提名Ketanji Brown Jackson为巡回上诉法院法官,并称赞她是全国顶级法律专家之一,将继续继承布雷耶法官的卓越传统。",\n "sources": ["31-pl"]\n}', additional_kwargs={}, example=False),
HumanMessage(content='他对她的前任说了什么?', additional_kwargs={}, example=False),
AIMessage(content='{\n "answer": "总统向军队退伍军人、宪法学者和美国最高法院退休法官Stephen Breyer表示敬意。",\n "sources": ["31-pl"]\n}', additional_kwargs={}, example=False)],
'answer': '{\n "answer": "总统向军队退伍军人、宪法学者和美国最高法院退休法官Stephen Breyer表示敬意。",\n "sources": ["31-pl"]\n}'}

使用自定义输出模式

我们可以通过传入自定义模式来更改链的输出。此模式的值和描述将通知我们传递给OpenAI API的函数,这意味着它不仅会影响我们如何解析输出,还会改变OpenAI的输出本身。例如,我们可以在模式中添加countries_referenced参数,并描述我们希望此参数的含义,这将导致OpenAI的输出包含响应中的发言者描述。

除了之前的示例,我们还可以向链中添加自定义提示。这将允许您向响应添加附加上下文,这对于问答非常有用。

from typing import List

from pydantic import BaseModel, Field

from langchain.chains.openai_functions import create_qa_with_structure_chain

from langchain.prompts.chat import ChatPromptTemplate, HumanMessagePromptTemplate
from langchain.schema import SystemMessage, HumanMessage
class CustomResponseSchema(BaseModel):
"""对所提问的问题的回答,附带来源。"""

answer: str = Field(..., description="所提问的问题的回答")
countries_referenced: List[str] = Field(
..., description="在来源中提到的所有国家"
)
sources: List[str] = Field(
..., description="用于回答问题的来源列表"
)


prompt_messages = [
SystemMessage(
content=(
"您是一个世界级的算法,以特定格式回答问题。"
)
),
HumanMessage(content="使用以下上下文回答问题"),
HumanMessagePromptTemplate.from_template("{context}"),
HumanMessagePromptTemplate.from_template("问题:{question}"),
HumanMessage(
content="提示:确保以正确的格式回答。以大写字符返回在来源中提到的所有国家。"
),
]

chain_prompt = ChatPromptTemplate(messages=prompt_messages)

qa_chain_pydantic = create_qa_with_structure_chain(
llm, CustomResponseSchema, output_parser="pydantic", prompt=chain_prompt
)
final_qa_chain_pydantic = StuffDocumentsChain(
llm_chain=qa_chain_pydantic,
document_variable_name="context",
document_prompt=doc_prompt,
)
retrieval_qa_pydantic = RetrievalQA(
retriever=docsearch.as_retriever(), combine_documents_chain=final_qa_chain_pydantic
)
query = "他对俄罗斯说了什么"
retrieval_qa_pydantic.run(query)
    CustomResponseSchema(answer="他宣布将关闭美国领空对所有俄罗斯航班,进一步孤立俄罗斯并对其经济施加额外压力。卢布贬值了30%,俄罗斯股市贬值了40%。他还提到普京独自对俄罗斯陷入困境的经济负责。美国及其盟友正在向乌克兰提供支持,包括军事、经济和人道援助。美国将直接向乌克兰提供超过10亿美元的援助。他明确表示,美军没有参与并将不会与俄罗斯军队在乌克兰发生冲突,但他们部署在那里是为了保卫北约盟国,以防普京决定继续向西方推进。他还提到普京对乌克兰的袭击是有预谋的和无端的,西方和北约通过建立一个由热爱自由的国家组成的联盟来应对普京。自由世界通过强大的经济制裁追究普京的责任,切断俄罗斯最大的银行与国际金融体系的联系,并阻止俄罗斯央行保卫俄罗斯卢布。美国司法部还组建了一个特别小组,追查俄罗斯寡头的犯罪行为。", countries_referenced=['美国', '俄罗斯', '乌克兰'], sources=['4-pl', '5-pl', '2-pl', '3-pl'])