从RefineDocumentsChain到LangGraph:优化长文本分析的新视角

130 阅读4分钟

从RefineDocumentsChain到LangGraph:优化长文本分析的新视角

在对长文本进行分析时,RefineDocumentsChain是一种常用的方法。该方法将文本分割为多个小文档,先处理第一个文档,然后逐步更新或优化结果,直到文档序列处理完毕。RefineDocumentsChain通常用于总结任务,能够在处理过程中修改运行中的总结,这在文本超长的情况下尤为有用。然而,LangGraph框架为这一问题提供了一些独特的优势,如更好的流程监控和模块化扩展能力。本文将对这两种实现进行详细阐述,并提供实用的代码示例。

主要内容

RefineDocumentsChain介绍

RefineDocumentsChain通过在类内部的for循环来调整总结,其流程自动化程度较高。以下是RefineDocumentsChain的基本使用方法:

  1. 文档拆分: 将长文本拆分为多个小文档。
  2. 文本处理: 对第一个文档进行处理,得到初步总结。
  3. 更新总结: 使用后续文档不断更新初步总结。

LangGraph的优势

LangGraph实现提供了一些显著优势:

  • 执行监控: 允许用户逐步跟踪或调整执行过程。
  • 支持流处理: 可以流式处理执行步骤和单个token。
  • 模块化和可扩展性: 由于LangGraph是由模块化组件构成,因而容易扩展或修改。

代码示例

让我们通过一个简单的例子来说明如何分别使用RefineDocumentsChain和LangGraph实现对文档序列的总结:

RefineDocumentsChain示例

from langchain.chains import LLMChain, RefineDocumentsChain
from langchain_core.prompts import ChatPromptTemplate, PromptTemplate
from langchain_openai import ChatOpenAI

# 使用API代理服务提高访问稳定性
llm = ChatOpenAI(model="gpt-4o-mini")

documents = [
    Document(page_content="Apples are red", metadata={"title": "apple_book"}),
    Document(page_content="Blueberries are blue", metadata={"title": "blueberry_book"}),
    Document(page_content="Bananas are yellow", metadata={"title": "banana_book"}),
]

document_prompt = PromptTemplate(
    input_variables=["page_content"], template="{page_content}"
)
document_variable_name = "context"
summarize_prompt = ChatPromptTemplate(
    [("human", "Write a concise summary of the following: {context}")]
)
initial_llm_chain = LLMChain(llm=llm, prompt=summarize_prompt)
initial_response_name = "existing_answer"

refine_template = """
Produce a final summary.

Existing summary up to this point:
{existing_answer}

New context:
------------
{context}
------------

Given the new context, refine the original summary.
"""
refine_prompt = ChatPromptTemplate([("human", refine_template)])
refine_llm_chain = LLMChain(llm=llm, prompt=refine_prompt)
chain = RefineDocumentsChain(
    initial_llm_chain=initial_llm_chain,
    refine_llm_chain=refine_llm_chain,
    document_prompt=document_prompt,
    document_variable_name=document_variable_name,
    initial_response_name=initial_response_name,
)

result = chain.invoke(documents)
print(result["output_text"])

LangGraph示例

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableConfig
from langchain_openai import ChatOpenAI
from langgraph.graph import END, START, StateGraph

# 使用API代理服务提高访问稳定性
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

summarize_prompt = ChatPromptTemplate(
    [("human", "Write a concise summary of the following: {context}")]
)
initial_summary_chain = summarize_prompt | llm | StrOutputParser()

# Refining summary
refine_template = """
Produce a final summary.

Existing summary up to this point:
{existing_answer}

New context:
------------
{context}
------------

Given the new context, refine the original summary.
"""
refine_prompt = ChatPromptTemplate([("human", refine_template)])
refine_summary_chain = refine_prompt | llm | StrOutputParser()

class State(TypedDict):
    contents: List[str]
    index: int
    summary: str

async def generate_initial_summary(state: State, config: RunnableConfig):
    summary = await initial_summary_chain.ainvoke(state["contents"][0], config)
    return {"summary": summary, "index": 1}

async def refine_summary(state: State, config: RunnableConfig):
    content = state["contents"][state["index"]]
    summary = await refine_summary_chain.ainvoke(
        {"existing_answer": state["summary"], "context": content},
        config,
    )
    return {"summary": summary, "index": state["index"] + 1}

def should_refine(state: State) -> Literal["refine_summary", END]:
    return END if state["index"] >= len(state["contents"]) else "refine_summary"

graph = StateGraph(State)
graph.add_node("generate_initial_summary", generate_initial_summary)
graph.add_node("refine_summary", refine_summary)
graph.add_edge(START, "generate_initial_summary")
graph.add_conditional_edges("generate_initial_summary", should_refine)
graph.add_conditional_edges("refine_summary", should_refine)
app = graph.compile()

async for step in app.astream(
    {"contents": [doc.page_content for doc in documents]},
    stream_mode="values",
):
    if summary := step.get("summary"):
        print(summary)

常见问题和解决方案

如何处理API访问受限的问题?

在某些地区,由于网络限制,访问API可能会出现困难。开发者可以考虑使用API代理服务来提高访问稳定性,这可以通过设置代理URL或者请求头来实现。

如何扩展LangGraph流程?

LangGraph的模块化设计允许用户灵活地添加新的节点和边来实现复杂流程。用户可以定义自定义节点函数,并通过add_nodeadd_edge方法将其添加到图中。

总结和进一步学习资源

本文介绍了在长文本处理中,从传统的RefineDocumentsChain方法迁移到LangGraph所带来的便利与优势。在复杂文本处理场景中,LangGraph的可调性和扩展性为开发者提供了更好的工具。

参考资料

  • LangChain官方文档
  • LangGraph官方文档

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

---END---