예제
LangGraph — 그래프를 처음부터 구성하기

실제로 실행 가능한 미니 프로젝트입니다. 받아서 Docker로 실행해 보세요.

2026년 6월 28일

LangGraph — 그래프를 처음부터 구성하기

langgraph_1이 미리 만들어진 create_agent를 쓰는 것과 달리, 이 예제는 LangGraph StateGraph API로 ReAct 루프를 직접 구성합니다 — agent 노드, tools 노드, 그리고 모델이 도구 요청을 멈출 때까지 반복하는 조건부 엣지로 구성하고, 각 단계를 스트리밍해서 추론 과정을 지켜볼 수 있습니다.

모델은 LiteLLM을 통해 라우팅되므로 같은 코드가 Anthropic Claude, OpenAI, Google AI Studio (Gemini) 에서 모두 동작합니다 — .envMODEL만 바꾸면 됩니다.

그래프

flowchart LR
    START([START]) --> agent[agent]
    agent -->|tool calls?| tools[tools]
    agent -->|no| END([END])
    tools -->|loop back to agent| agent

stream_mode="values"는 각 노드 실행 후 전체 상태를 내보내므로, 실행하면 최종 답이 나올 때까지 agent → tools → agent → … 순서로 출력됩니다.

설정

cd samples/langgraph_2
cp .env.sample .env
# .env 편집: MODEL과 해당 제공자의 키를 설정

MODEL이 제공자를 결정합니다:

제공자 MODEL 예시 .env의 키
Anthropic Claude claude-opus-4-8 ANTHROPIC_API_KEY
OpenAI gpt-4o OPENAI_API_KEY
Google AI Studio gemini/gemini-2.5-flash GEMINI_API_KEY
Ollama (local) ollama_chat/qwen3.5:9b OLLAMA_API_BASE

.env는 gitignore 처리되어 있고, .env.sample만 커밋됩니다.

Ollama (로컬 모델): 먼저 호스트에서 모델을 받아두세요 — ollama pull qwen3.5:9b(또는 ollama run qwen3.5:9b)를 실행합니다. 그다음 MODEL=ollama_chat/qwen3.5:9b로 설정하고 OLLAMA_API_BASE를 서버 주소로 지정하세요 — API 키는 필요 없습니다. DooD를 쓰는 devcontainer에서는 컨테이너가 호스트의 Ollama에 http://host.docker.internal:11434로 접근하고, 로컬 실행 시에는 http://localhost:11434를 씁니다. 도구 호출은 Ollama의 chat 엔드포인트가 필요하므로 위의 ollama_chat/ prefix를 쓰세요 — ollama/가 아닙니다. ollama/로는 빈 출력에 도구 호출이 나오지 않습니다. 로컬 모델도 도구를 지원해야 합니다 — 예를 들어 gemma는 지원하지 않습니다.

Docker로 실행

cd samples/langgraph_2
docker build -t aas-langgraph2 .
docker run --rm --env-file .env aas-langgraph2 \
  "How many times does the letter r appear in strawberry? Show it uppercased."

Docker로 실행 (DooD를 쓰는 devcontainer에서)

호스트 Docker 데몬과 통신하는 dev container(Docker-outside-of-Docker)에서는 위의 포그라운드 docker run이 종종 아무것도 출력하지 않고 exit 0으로 끝납니다. 하지만 실행 자체는 성공한 것입니다. 에이전트는 끝까지 실행되고 출력도 Docker가 모두 캡처하는데, 실시간 attach 스트림만 그 출력을 VM 경계에서 흘려버립니다. 같은 컨테이너에 docker logs를 실행하면 전체 출력이 그대로 보이고, 컨테이너는 exit 0으로 끝나며 OOM도 아닙니다. detached로 실행하고 로그를 따라가세요:

cd samples/langgraph_2
docker build -t aas-langgraph2 .
docker logs -f "$(docker run -d --env-file .env aas-langgraph2 \
  "How many times does the letter r appear in strawberry? Show it uppercased.")"

로컬에서 실행

cd samples/langgraph_2
pip install -r requirements.txt
python app.py "How many times does the letter r appear in strawberry? Show it uppercased."

python-dotenv.env를 자동으로 불러옵니다. 키는 다음에서 발급받으세요: Anthropic, OpenAI, Google AI Studio.


실행 결과

모델과 실행마다 결과가 달라집니다 — LLM은 비결정적이라 표현(그리고 에이전트의 구체적 단계)이 매번 다릅니다. 아래는 claude-opus-4-8로 실행한 한 예입니다.

================================ Human Message =================================

How many times does the letter r appear in strawberry? Show it uppercased.
================================== Ai Message ==================================

I'll count the letter "r" in "strawberry" and show it uppercased.
Tool Calls:
  count_letter (toolu_01XSgSztFUZEggtCkxLYr7BF)
 Call ID: toolu_01XSgSztFUZEggtCkxLYr7BF
  Args:
    word: strawberry
    letter: r
  to_upper (toolu_01Jozend9wz6cpjoyjMFq9Xx)
 Call ID: toolu_01Jozend9wz6cpjoyjMFq9Xx
  Args:
    text: strawberry
================================= Tool Message =================================
Name: to_upper

STRAWBERRY
================================== Ai Message ==================================

The letter **r** appears **3 times** in "STRAWBERRY".

=== answer ===
The letter **r** appears **3 times** in "STRAWBERRY".

파일

  • tools/
.env.sample
# Copy this file to `.env` and fill in the key for the provider you want.
#   cp .env.sample .env
#
# One codebase, many providers — LiteLLM routes the request based on MODEL.

# Pick the model. Examples:
#   claude-opus-4-8              Anthropic Claude   -> needs ANTHROPIC_API_KEY
#   gpt-4o                       OpenAI             -> needs OPENAI_API_KEY
#   gemini/gemini-2.5-flash      Google AI Studio   -> needs GEMINI_API_KEY
#   ollama_chat/qwen3.5:9b       Ollama (local)     -> needs OLLAMA_API_BASE, no key
MODEL=claude-opus-4-8

# Set only the key for the provider you chose; leave the rest blank.
ANTHROPIC_API_KEY=
OPENAI_API_KEY=
GEMINI_API_KEY=

# Ollama only — where the Ollama server runs (pull the model first, e.g.
# `ollama pull qwen3.5:9b`). Other providers ignore this line.
#   Local, no Docker:           http://localhost:11434
#   Docker / DooD on the host:  http://host.docker.internal:11434
OLLAMA_API_BASE=http://host.docker.internal:11434