Agents 课程文档

文档分析图

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

文档分析图

阿尔弗雷德为您服务。作为韦恩先生信任的管家,我冒昧地记录了我如何协助韦恩先生处理他的各种文档需求。当他外出处理他的... 夜间活动时,我确保他所有的文书工作、训练计划和营养计划都得到妥善的分析和组织。

在离开之前,他留下了一张便条,上面写着他一周的训练计划。然后我负责为明天的膳食制定菜单。

对于未来类似的事件,让我们创建一个使用 LangGraph 的文档分析系统,以满足韦恩先生的需求。该系统可以

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

管家的工作流程

我们将构建的工作流程,遵循一个结构化的模式

Butler's Document Analysis Workflow

你可以跟随这个 notebook 中的代码,你可以使用 Google Colab 运行它。

设置环境

%pip install langgraph langchain_openai langchain_core

和导入

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

定义 Agent 的状态

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

这是 LangGraph 中的一个新概念,你可以在你的状态中添加操作符来定义它们应该如何相互交互。

class AgentState(TypedDict):
    # The document provided
    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:
        # Read image and encode as base64
        with open(img_path, "rb") as image_file:
            image_bytes = image_file.read()

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

        # Prepare the prompt including the base64 image data
        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}"
                        },
                    },
                ]
            )
        ]

        # Call the vision-capable model
        response = vision_llm.invoke(message)

        # Append extracted text
        all_text += response.content + "\n\n"

        return all_text.strip()
    except Exception as e:
        # A butler should handle errors gracefully
        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

# Equip the butler with tools
tools = [
    divide,
    extract_text
]

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

节点

def assistant(state: AgentState):
    # System message
    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 模式:我如何协助韦恩先生

请允许我解释这个 agent 中的方法。该 agent 遵循所谓的 ReAct 模式(推理-行动-观察)

  1. 推理 关于他的文档和请求
  2. 行动 通过使用适当的工具来行动
  3. 观察 结果
  4. 重复 根据需要重复,直到我完全满足了他的需求

这是使用 langGraph 的 agent 的一个简单实现。

# The graph
builder = StateGraph(AgentState)

# Define nodes: these do the work
builder.add_node("assistant", assistant)
builder.add_node("tools", ToolNode(tools))

# Define edges: these determine how the control flow moves
builder.add_edge(START, "assistant")
builder.add_conditional_edges(
    "assistant",
    # If the latest message requires a tool, route to tools
    # Otherwise, provide a direct response
    tools_condition,
)
builder.add_edge("tools", "assistant")
react_graph = builder.compile()

# Show the butler's thought process
display(Image(react_graph.get_graph(xray=True).draw_mermaid_png()))

我们用我们的工具列表定义一个 tools 节点。assistant 节点只是我们绑定了工具的模型。我们创建一个包含 assistant 和 tools 节点的图。

我们添加 tools_condition 边,它根据 assistant 是否调用工具,路由到 End 或 tools。

现在,我们添加一个新的步骤

我们将 tools 节点连接回 assistant,形成一个循环。

  • 在 assistant 节点执行后,tools_condition 检查模型的输出是否为工具调用。
  • 如果它是工具调用,则流程被定向到 tools 节点。
  • tools 节点连接回 assistant。
  • 只要模型决定调用工具,这个循环就会继续。
  • 如果模型响应不是工具调用,则流程被定向到 END,终止进程。

ReAct Pattern

管家的行动

示例 1:简单计算

这是一个示例,展示了在 LangGraph 中使用工具的 agent 的一个简单用例。

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

# Show the messages
for m in messages['messages']:
    m.pretty_print()

对话将进行如下

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:分析韦恩少爷的训练文档

当韦恩少爷留下他的训练和膳食笔记时

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

互动将进行如下

Human: 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?

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

Tool Response: [Extracted text with training schedule and menu details]

Alfred: For the dinner menu, you should buy the following items:

1. Grass-fed local sirloin steak
2. Organic spinach
3. Piquillo peppers
4. Potatoes (for oven-baked golden herb potato)
5. Fish oil (2 grams)

Ensure the steak is grass-fed and the spinach and peppers are organic for the best quality meal.

主要收获

如果你希望创建你自己的文档分析管家,这里有一些关键的考虑因素

  1. 为特定的文档相关任务定义清晰的工具
  2. 创建一个强大的状态跟踪器 以在工具调用之间保持上下文
  3. 考虑工具失败的错误处理
  4. 保持对先前交互的上下文感知(由操作符 add_messages 确保)

有了这些原则,您也可以提供堪称韦恩庄园的典范文档分析服务。

我相信这个解释已经令人满意了。现在,如果可以的话,请允许我离开一下,韦恩少爷的披风需要在今晚的活动之前熨烫一下。

< > 在 GitHub 上更新