Buckets:
문서 분석 그래프[[document-analysis-graph]]
저 알프레드는 Mr.웨인님의 신뢰받는 집사로서, 그분의 다양한 문서 업무를 어떻게 지원할지 기록해두었습니다. 그가 밤 활동을 하러 나간 동안, 저는 모든 서류, 훈련 일정, 영양 계획을 분석하고 정리합니다.
그분이 외출하기 전, 이번 주 훈련 프로그램이 적힌 쪽지를 남기셨습니다. 그 쪽지를 보고 저는 내일 식사를 위한 메뉴를 직접 짜기로 했죠.
앞으로도 이런 일이 있을 때를 대비해, LangGraph를 활용해 웨인님을 위한 문서 분석 시스템을 만들어봅시다. 이 시스템은 다음을 수행 할 수 있습니다:
- 이미지 문서 처리
- 비전 모델(비전 언어 모델)로 텍스트 추출
- 필요시 계산 수행(일반 툴 시연)
- 내용 분석 및 요약 제공
- 문서 관련 특정 지시 실행
집사의 워크플로우[[the-butlers-workflow]]
만들 워크플로우의 구조는 다음과 같습니다:
이 노트북을 따라가며 Google Colab에서 코드를 실행해볼 수 있습니다.
환경 설정[[setting-up-the-environment]]
%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
에이전트 상태 정의[[defining-agent's-state]]
이 상태는 앞서 본 것보다 조금 더 복잡합니다.
AnyMessage는 Langchain의 메시지 클래스이고, add_messages는 최신 메시지를 누적하는 연산자입니다.
LangGraph에서는 상태에 연산자를 추가해 상호작용 방식을 정의할 수 있습니다.
class AgentState(TypedDict):
# 제공된 문서
input_file: Optional[str] # 파일 경로(PDF/PNG)
messages: Annotated[list[AnyMessage], add_messages]
툴 준비하기[[preparing-tools]]
vision_llm = ChatOpenAI(model="gpt-4o")
def extract_text(img_path: str) -> str:
"""
멀티모달 모델로 이미지 파일에서 텍스트 추출
Wayne 주인님은 종종 훈련 일정이나 식단이 적힌 쪽지를 남깁니다.
이 기능으로 내용을 제대로 분석할 수 있습니다.
"""
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": (
"이 이미지에서 모든 텍스트를 추출하세요. 설명 없이 추출된 텍스트만 반환하세요."
),
},
{
"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"텍스트 추출 오류: {str(e)}"
print(error_msg)
return ""
def divide(a: int, b: int) -> float:
"""주인님의 계산 요청을 위한 나눗셈 함수"""
return a / b
# 집사, 도구(tool) 장착
tools = [
divide,
extract_text
]
llm = ChatOpenAI(model="gpt-4o")
llm_with_tools = llm.bind_tools(tools, parallel_tool_calls=False)
노드[[the-nodes]]
def assistant(state: AgentState):
# 시스템 메시지
textual_description_of_tool="""
extract_text(img_path: str) -> str:
멀티모달 모델로 이미지 파일에서 텍스트 추출
Args:
img_path: 로컬 이미지 파일 경로(문자열)
Returns:
각 이미지에서 추출된 텍스트를 합친 문자열
divide(a: int, b: int) -> float:
a를 b로 나눔
"""
image=state["input_file"]
sys_msg = SystemMessage(content=f"당신은 Mr. Wayne과 배트맨을 섬기는 친절한 집사 알프레드입니다. 아래 도구로 문서를 분석하고 계산을 수행할 수 있습니다:\n{textual_description_of_tool} \n 현재 로드된 이미지는: {image}")
return {
"messages": [llm_with_tools.invoke([sys_msg] + state["messages"])],
"input_file": state["input_file"]
}
ReAct 패턴: 집사의 지원 방식[[the-react-pattern-how-i-assist-mr-wayne]]
이 에이전트는 ReAct 패턴(Reason-Act-Observe)을 따릅니다.
- 문서와 요청을 이해(Reason)
- 적절한 도구를 실행(Act)
- 결과를 관찰(Observe)
- 필요시 반복
아주 간단한 LangGraph 에이전트 구현 예시입니다.
# 그래프 생성
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로 라우팅
# 아니면 직접 응답
tools_condition,
)
builder.add_edge("tools", "assistant")
react_graph = builder.compile()
# 집사의 사고 과정을 시각화
display(Image(react_graph.get_graph(xray=True).draw_mermaid_png()))
tools 노드는 도구 리스트를, assistant 노드는 도구가 바인딩된 모델을 의미합니다.
그래프에는 assistant와 tools 노드가 있습니다.
tools_condition 엣지는 모델이 도구를 호출하면 tools로, 아니면 End로 흐름을 보냅니다.
이제 한 단계 더 추가합니다:
tools 노드를 다시 assistant로 연결해 루프를 만듭니다.
assistant노드 실행 후,tools_condition이 모델 출력이 도구 호출인지 확인합니다.- 도구 호출이면 흐름이
tools노드로 이동합니다. tools노드는 다시assistant로 연결됩니다.- 이 루프는 모델이 도구 호출을 결정하는 한 계속됩니다.
- 모델 응답이 도구 호출이 아니면 흐름이 END로 이동해 프로세스가 종료됩니다.
집사의 실제 활용 예시[[the-butler-in-action]]
예시 1: 간단한 계산[[example-1-simple-calculations]]
아래는 LangGraph에서 도구를 사용하는 간단한 예시입니다.
messages = [HumanMessage(content="6790을 5로 나눠줘")]
messages = react_graph.invoke({"messages": messages, "input_file": None})
# 메시지 출력
for m in messages['messages']:
m.pretty_print()
대화 예시:
Human: 6790을 5로 나눠줘
AI Tool Call: divide(a=6790, b=5)
Tool Response: 1358.0
Alfred: 6790을 5로 나누면 1358.0입니다.
예시 2: Wayne 주인님의 훈련 문서 분석[[example-2-analyzing-master-waynes-training-documents]]
주인님이 훈련 및 식단 쪽지를 남겼을 때:
messages = [HumanMessage(content="제공된 이미지에 있는 Wayne 주인님의 쪽지에 따라 저녁 메뉴에 필요한 재료 목록을 알려줘.")]
messages = react_graph.invoke({"messages": messages, "input_file": "Batman_training_and_meals.png"})
대화 예시:
Human: 제공된 이미지에 있는 Wayne 주인님의 쪽지에 따라 저녁 메뉴에 필요한 재료 목록을 알려줘.
AI Tool Call: extract_text(img_path="Batman_training_and_meals.png")
Tool Response: [훈련 일정 및 메뉴 세부 정보가 추출됨]
Alfred: 저녁 메뉴를 위해 다음 재료를 구입하세요:
1. 목초 사육 소고기 등심 스테이크
2. 유기농 시금치
3. 피키요 고추
4. 감자(오븐에 구울 허브 감자용)
5. 피쉬 오일(2그램)
스테이크는 목초 사육, 시금치와 고추는 유기농으로 준비하면 최고의 식사가 됩니다.
핵심 요약[[key-takeaways]]
나만의 문서 분석 집사를 만들고 싶다면 다음을 고려하세요:
- 문서 관련 작업별로 명확한 도구 정의
- 도구 호출 간 맥락 유지를 위한 견고한 상태 추적기
- 도구 실패 시 오류 처리 고려
- 이전 상호작용 맥락 유지(add_messages 연산자 활용)
이 원칙을 따르면 Wayne 저택에 어울리는 문서 분석 서비스를 제공할 수 있습니다.
이 설명이 도움이 되었길 바랍니다. 이제 주인님의 망토를 다림질하러 가보겠습니다.
Xet Storage Details
- Size:
- 9.42 kB
- Xet hash:
- c404a752870a6a593ae670a1b80e67942e5f689619806269aafa9506bb3a2c06
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.

