Agents Course documentation

文档分析图

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

文档分析图

Alfred 为您服务。作为韦恩先生值得信赖的管家,我已记录下协助处理各类文档需求的工作流程。当 Mr Wayne 外出进行…夜间活动时,我会确保所有文件、训练计划和营养方案都得到妥善分析和整理。

在离开前,他留下了本周训练计划的笔记。我随后负责拟定了明日餐点的菜单

为应对未来的类似需求,让我们使用 LangGraph 构建一个文档分析系统来服务 Mr Wayne。该系统能够:

  1. 处理图像文档
  2. 使用视觉模型 (Vision Language Model) 提取文本
  3. 在需要时执行计算(用于演示常规工具)
  4. 分析内容并提供简明摘要
  5. 执行与文档相关的特定指令

管家的工作流程

我们将构建的工作流程遵循以下结构化方案:

Butler's Document Analysis Workflow

您可以在 这个 notebook 中查看代码,并通过 Google Colab 运行。
## 设置环境
%pip install langgraph langchain_openai Pillow base64 langchain_core

and imports :

import base64
from typing import List, TypedDict, Annotated, Optional
from langchain.schema import HumanMessage
from langchain_openai import ChatOpenAI
from langchain_core.messages import AnyMessage, SystemMessage
from langgraph.graph.message import add_messages
from langgraph.graph import START, StateGraph
from langgraph.prebuilt import tools_condition
from langgraph.prebuilt import ToolNode
from IPython.display import Image, display

定义智能体的状态

这个状态比我们之前见过的稍微复杂些。 AnyMessage 是来自 langchain 的类,用于定义消息,而 add_messages 是一个操作符,它会添加最新消息而不是覆盖现有状态。

这是 langGraph 中的一个新概念,您可以在状态中添加 operators 来定义它们之间的交互方式。

class AgentState(TypedDict):
    # 提供的文件
    input_file: Optional[str]  # Contains file path (PDF/PNG)
    messages: Annotated[list[AnyMessage], add_messages]

准备工具

vision_llm = ChatOpenAI(model="gpt-4o")

def extract_text(img_path: str) -> str:
    """
    Extract text from an image file using a multimodal model.
    
    Master Wayne often leaves notes with his training regimen or meal plans.
    This allows me to properly analyze the contents.
    """
    all_text = ""
    try:
        # 读取图像并编码为 base64
        with open(img_path, "rb") as image_file:
            image_bytes = image_file.read()

        image_base64 = base64.b64encode(image_bytes).decode("utf-8")

        # 准备包含 base64 图像数据的提示
        message = [
            HumanMessage(
                content=[
                    {
                        "type": "text",
                        "text": (
                            "Extract all the text from this image. "
                            "Return only the extracted text, no explanations."
                        ),
                    },
                    {
                        "type": "image_url",
                        "image_url": {
                            "url": f"data:image/png;base64,{image_base64}"
                        },
                    },
                ]
            )
        ]

        # 调用具有视觉功能的模型
        response = vision_llm.invoke(message)

        # 附加提取的文本
        all_text += response.content + "\n\n"

        return all_text.strip()
    except Exception as e:
        # 管家应优雅地处理错误
        error_msg = f"Error extracting text: {str(e)}"
        print(error_msg)
        return ""

def divide(a: int, b: int) -> float:
    """Divide a and b - for Master Wayne's occasional calculations."""
    return a / b

# 为管家配备工具
tools = [
    divide,
    extract_text
]

llm = ChatOpenAI(model="gpt-4o")
llm_with_tools = llm.bind_tools(tools, parallel_tool_calls=False)

节点

def assistant(state: AgentState):
    # 系统消息
    textual_description_of_tool="""
extract_text(img_path: str) -> str:
    Extract text from an image file using a multimodal model.

    Args:
        img_path: A local image file path (strings).

    Returns:
        A single string containing the concatenated text extracted from each image.
divide(a: int, b: int) -> float:
    Divide a and b
"""
    image=state["input_file"]
    sys_msg = SystemMessage(content=f"You are an helpful butler named Alfred that serves Mr. Wayne and Batman. You can analyse documents and run computations with provided tools:\n{textual_description_of_tool} \n You have access to some optional images. Currently the loaded image is: {image}")

    return {
        "messages": [llm_with_tools.invoke([sys_msg] + state["messages"])],
        "input_file": state["input_file"]
    }

ReAct 模式:我如何协助 Wayne 先生

请允许我解释该智能体的工作方式。该智能体遵循被称为 ReAct 的模式(推理-行动-观察)

  1. 推理 分析文档和请求内容
  2. 行动 通过调用合适的工具执行操作
  3. 观察 工具执行结果
  4. 重复 上述步骤直到完全满足需求

这是使用 langGraph 实现的简单智能体实现。

## The graph
builder = StateGraph(AgentState)

# 定义节点:这些节点完成工作
builder.add_node("assistant", assistant)
builder.add_node("tools", ToolNode(tools))

# 定义边:这些决定了控制流如何移动
builder.add_edge(START, "assistant")
builder.add_conditional_edges(
    "assistant",
    # 如果最新消息需要工具,则路由至工具
    # 否则,请直接回复
    tools_condition,
)
builder.add_edge("tools", "assistant")
react_graph = builder.compile()

# 展现管家的思考过程
display(Image(react_graph.get_graph(xray=True).draw_mermaid_png()))

ReAct Pattern

管家实战示例

示例1:简单计算

在以下示例中,我们添加这个 divide 示例仅作为演示用途。

messages = [HumanMessage(content="Divide 6790 by 5")]
messages = react_graph.invoke({"messages": messages, "input_file": None})

对话将会继续:

Human: Divide 6790 by 5

AI Tool Call: divide(a=6790, b=5)

Tool Response: 1358.0

Alfred: The result of dividing 6790 by 5 is 1358.0.

示例 2:分析 Wayne 大师的训练文档

当 Wayne 大师留下他的训练和​​用餐笔记时:

messages = [HumanMessage(content="According to the note provided by Mr. Wayne in the provided images. What's the list of items I should buy for the dinner menu?")]
messages = react_graph.invoke({"messages": messages, "input_file": "Batman_training_and_meals.png"})

交互将进行:

用户:根据 Wayne 先生在提供的图像中的注释。我应该为晚餐菜单购买哪些物品?

AI Tool Call: extract_text(img_path="Batman_training_and_meals.png")

Tool Response: [包含训练计划和菜单详情的提取文本]

Alfred:根据晚餐菜单,您应该购买以下物品:

1. 草饲本地西冷牛排(Grass-fed local sirloin steak)
2. 有机菠菜(Organic spinach)
3. 皮克略辣椒(Piquillo peppers) 
4. 土豆(用于烤制金黄香草土豆)
5. 鱼油(2 克)

确保牛排是草饲的,并且菠菜和辣椒是有机的,以获得最佳品质的餐点。

关键要点

若您希望创建自己的文档分析管家,请遵循以下关键原则:

  1. 定义清晰的工具(Define clear tools)用于特定文档相关任务
  2. 创建强大的状态跟踪器(Create a robust state tracker)以保持工具调用之间的上下文
  3. 考虑错误处理机制(Consider error handling)应对工具调用失败
  4. 保持上下文感知能力(Maintain contextual awareness)通过 add_messages 操作符确保历史交互的连贯性

遵循这些原则,您也能提供符合韦恩庄园标准的卓越文档分析服务。

相信以上解释已足够详尽。恕我失陪,韦恩老爷的披风还需在夜巡前熨烫妥当。

< > Update on GitHub