- 강의 질문
- AI TECH
Part2-ch2 Agentic RAG예시코드 실행 안되는 문제와 해결방법
현재 예시로 제공된 코드가 실행이 안되서 관련 자료 로그 남깁니다.
실행시 아래와 같은 에러 발생
1) if hasattr(last_message, "tool_calls") and last_message.tool_calls: > Cannot access attribute "tool_calls" for class
2) result = search_app.invoke( > ValueError
GPT5.1답변처럼 변경해주니 실행이 됩니다. 이게 맞는 해결책인지 궁금하네요 아님 버전을 올리면 또 달라질지
참고로 langchain/langgraph 패키지 2025.11.30기준 최신버전으로 테스트해봐도 마찬가지임.
```
"langgraph>=1.0.4",
"langchain>=1.1.0",
"langchain-core>=1.1.0",
"langchain-openai>=1.1.0",
"langchain-anthropic>=1.2.0",
"langchain-google-genai>=3.2.0",
"langchain-community>=0.4",
"langgraph-cli[inmem]>=0.4.0",
"langchain-mcp-adapters",
```
pyproject.toml
```toml
[project]
# Only needed so uv/pip recognizes it as a project
name = "fc-multiagent"
version = "0.0.0"
description = "A collection of tutorial notebooks"
requires-python = ">=3.11,<3.15"
# Dependencies you need in the notebooks
dependencies = [
"langgraph>=1.0.3",
"langchain>=1.0.8",
"langchain-core>=1.1.0",
"langchain-openai>=1.0.3",
"langchain-anthropic>=1.1.0",
"langchain-google-genai>=3.1.0",
"langchain-community>=0.4",
"langgraph-cli[inmem]>=0.4.0",
"langchain-mcp-adapters",
"rich>=14.2.0",
"icecream>=2.1.8",
"debugpy>=1.8.17",
"nbconvert>=7.16.6",
"langchain-text-splitters>=1.0.0",
"langchain-tavily>=0.2.13",
"langchain-qdrant>=1.1.0",
"python-dotenv>=1.2.1",
"arxiv>=2.3.1",
"jupyterlab>=4.5.0",
]
[project.optional-dependencies]
# Extra tools for development
dev = [
"nbdime>=4.0.2",
"ruff>=0.14.6",
"mypy>=1.18.1"
]
[tool.ruff]
# What to check
lint.select = [
"E", # pycodestyle
"F", # pyflakes
"I", # isort
"D", # pydocstyle
"D401", # docstring first line imperative
"UP", # pyupgrade
]
# Rules to ignore
lint.ignore = [
"T201", # allow print()
"UP006", # keep using typing.List etc.
"UP007", # allow Union[X, Y]
"UP035", # ignore Self type suggestion
"D417", # don't require param docs
"E501", # ignore line length
"D101", # ignore docstring for these labs
"D103", # ignore docstring for these labs
]
[tool.ruff.lint.per-file-ignores]
"tests/*" = ["D", "UP"]
[tool.ruff.lint.pydocstyle]
convention = "google"
```
"""ToolRuntime과 Command를 활용한 Tool 작성"""
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
from langchain.tools import tool, ToolRuntime
from langgraph.types import Command
from langchain.messages import HumanMessage, AIMessage, ToolMessage, AnyMessage
from langchain.chat_models import init_chat_model
class SearchState(TypedDict):
"""검색 에이전트 State"""
messages: Annotated[list[AnyMessage], add_messages]
search_count: int # 검색 횟수 추적
user_name: str # 사용자 이름
@tool
def search_knowledge_base(
query: str,
runtime: ToolRuntime, # 핵심: ToolRuntime 자동 DI 주입
) -> Command:
"""
지식 베이스에서 검색하며 State를 업데이트합니다.
Args:
query: 검색어
runtime: LangGraph runtime (자동 주입)
Returns:
Command 객체 (State 업데이트 포함)
"""
# 1. 현재 State 접근
current_state = runtime.state
search_count = current_state.get("search_count", 0)
user_name = current_state.get("user_name", "사용자")
print(f"\n[Tool] search_knowledge_base 실행")
print(f" - 사용자: {user_name}")
print(f" - 현재까지 검색 횟수: {search_count}")
print(f" - 검색어: {query}")
# 2. 실제 검색 수행 (여기서는 모의 데이터)
mock_results = {
"langgraph": "LangGraph는 LLM 기반 애플리케이션을 위한 상태 기반 워크플로우 프레임워크입니다.",
"tool": "Tool은 LLM이 외부 기능을 호출할 수 있게 하는 인터페이스입니다.",
}
result = mock_results.get(
query.lower(), f"'{query}'에 대한 검색 결과를 찾을 수 없습니다."
)
# 3. Command로 State 업데이트
# return Command(
# update={
# "search_count": search_count + 1, # 검색 횟수 증가
# },
# # tool 결과는 자동으로 messages에 추가됨
# )
# Tool의 반환값(여기서는 Command)은 자동으로 ToolMessage로 변환되어 messages에 추가됩니다
return Command(
update={
"search_count": search_count + 1,
"messages": [ToolMessage(content=result, tool_call_id=runtime.tool_call_id)],
}
)
@tool
def reset_search_count(runtime: ToolRuntime) -> Command:
"""
검색 횟수를 초기화합니다.
Args:
runtime: LangGraph runtime
"""
current_count = runtime.state.get("search_count", 0)
print(f"\n[Tool] 검색 카운터 초기화 (현재: {current_count} → 0)")
# return Command(update={"search_count": 0})
return Command(
update={
"search_count": 0,
"messages": [
ToolMessage(
content=f"검색 횟수를 {current_count}에서 0으로 초기화했습니다.",
tool_call_id=runtime.tool_call_id,
)
],
}
)
# Tool 리스트
search_tools = [search_knowledge_base, reset_search_count]
llm = init_chat_model("openai:gpt-4.1-mini", temperature=0)
# LLM 설정
llm_with_tools = llm.bind_tools(search_tools)
def agent_node(state: SearchState) -> dict:
"""LLM Agent Node"""
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
# def should_continue(state: SearchState) -> str:
# """Tool 호출 여부 확인"""
# last_message = state["messages"][-1]
# if hasattr(last_message, "tool_calls") and last_message.tool_calls:
# return "tools"
# return "end"
def should_continue(state: SearchState) -> str:
last = state["messages"][-1]
# v1 표준 방식 – content_blocks 검사
for block in getattr(last, "content_blocks", []):
if block.get("type") == "tool_call":
return "tools"
return "end"
# 그래프 생성
search_graph = StateGraph(SearchState)
search_graph.add_node("agent", agent_node)
search_graph.add_node("tools", ToolNode(search_tools))
search_graph.add_edge(START, "agent")
search_graph.add_conditional_edges(
"agent", should_continue, {"tools": "tools", "end": END}
)
search_graph.add_edge("tools", "agent")
search_app = search_graph.compile()
# 테스트
print("\n" + "=" * 60)
print("ToolRuntime 데모 시작")
print("=" * 60)
result = search_app.invoke(
{
"messages": [HumanMessage(content="LangGraph에 대해 검색해줘")],
"search_count": 0,
"user_name": "홍길동",
}
)
print("\n=== 최종 State ===")
print(f"총 검색 횟수: {result['search_count']}")
print(f"사용자: {result['user_name']}")
print(f"메시지 수: {len(result['messages'])}")
result['messages']
GPT참고 답변과 정리
강의 원본 예시코드 문제점과 해결책
1) 오류 1 — last_message.tool_calls 접근 불가
● 원인
LangChain v1에서는 message.tool_calls 필드가 삭제되었다.
모든 tool call 정보는 content_blocks 안에 들어간다.
● 해결
should_continue에서 content_blocks 기반으로 tool call을 탐지해야 한다.
def should_continue(state: SearchState) -> str:
msg = state["messages"][-1]
for block in getattr(msg, "content_blocks", []):
if block.get("type") == "tool_call":
return "tools"
return "end"
2) 오류 2 — ToolNode: “ToolMessage가 Command.update에 없다”
● 원인
LangGraph v1은 모든 tool 호출에 대해 Command(update={"messages": [ToolMessage(...)]}) 형식을 반드시 요구한다.
즉, Tool이 반환한 결과를 ToolMessage로 직접 State에 넣어야만 한다.
구버전처럼 “Tool 반환하면 자동 message append”는 없어졌다.
● 해결
모든 tool에서 Command.update에 ToolMessage를 직접 넣는다.
from langchain.messages import ToolMessage
@tool
def search_knowledge_base(query: str, runtime: ToolRuntime) -> Command:
result = ...
return Command(
update={
"search_count": runtime.state["search_count"] + 1,
"messages": [
ToolMessage(
content=result,
tool_call_id=runtime.tool_call_id
)
]
}
)
reset tool도 동일하게 ToolMessage 포함.
🔥 한 문장 요약
LangChain v1에서는 tool_calls 속성을 직접 읽으면 안 되고, 모든 Tool은 반드시 Command.update에 ToolMessage를 명시적으로 넣어야 한다.
이 두 가지를 수정하면 example3는 완전히 정상 동작한다.