우리가 연극 배우(Agent)를 무대에 세운다고 상상해 봅시다. 배우가 연기를 하려면 대본(LLM)만 있어서는 안 됩니다.
LangChain의 create_agent는 내부적으로 LangGraph의 런타임 위에서 동작하며, 이 모든 정보를 '런타임 객체'라는 가방에 담아 에이전트, 도구(Tools), 미들웨어(Middleware)가 공유하게 해줍니다.
왜 이것이 중요할까요?
전역 변수(Global Variable)를 쓰지 않고, 실행 시점(Invoke)에 필요한 정보(DB 접속 정보, 사용자 ID 등)를 주입할 수 있기 때문에 테스트가 쉽고 코드가 깔끔해집니다.
온라인 쇼핑몰의 '개인화 쇼핑 에이전트'를 만기
user_id: 사용자 식별자membership_level: 멤버십 등급db_connection_str: 주문 조회용 DB 연결 문자열가장 먼저 해야 할 일은 이 에이전트가 실행될 때 "어떤 정보들이 가방(Runtime)에 들어있어야 하는지" 규격을 정하는 것입니다.
from dataclasses import dataclass
# 1. 런타임에 주입될 데이터의 구조(Schema)를 정의합니다.
@dataclass
class ShoppingContext:
user_id: str
user_name: str
membership_level: str # 예: "VIP", "GOLD", "SILVER"
db_connection_str: str # 실제로는 DB 커넥션 객체일 수 있습니다.
에이전트가 생각(LLM 호출)을 시작하기 전에, 런타임 정보를 이용해 시스템 프롬프트를 동적으로 바꿔봅시다. VIP 고객에게는 더 정중하게 대해야 하니까요.
from langchain.agents.middleware import dynamic_prompt, ModelRequest
# 2. 동적 프롬프트 미들웨어
# 요청(request) 안에는 런타임(runtime) 정보가 들어있습니다.
@dynamic_prompt
def personalize_greeting(request: ModelRequest) -> str:
# 런타임 컨텍스트에서 정보 꺼내기
context = request.runtime.context
base_prompt = "당신은 유능한 쇼핑 어시스턴트입니다."
if context.membership_level == "VIP":
# VIP를 위한 특별 지침 추가
return f"{base_prompt} 현재 대화 중인 {context.user_name}님은 VIP 고객입니다. 극존칭을 사용하고 배송비 무료 혜택을 강조하세요."
else:
return f"{base_prompt} 사용자 {context.user_name}님에게 친절하게 응대하세요."
이제 에이전트가 실제로 데이터를 조회할 때(도구 사용), 런타임 정보를 어떻게 쓰는지 봅니다. 여기서 ToolRuntime을 사용합니다.
from langchain.tools import tool, ToolRuntime
# 3. 도구(Tool) 정의
# ToolRuntime[ShoppingContext]를 통해 타입 힌팅과 함께 런타임에 접근합니다.
@tool
def check_order_status(order_id: str, runtime: ToolRuntime[ShoppingContext]) -> str:
"""사용자의 주문 상태를 확인합니다."""
# 런타임에서 DB 접속 정보와 사용자 ID를 가져옵니다.
# (함수 인자로 받지 않아도 런타임에서 꺼내 쓸 수 있어 보안에 좋습니다)
db_conn = runtime.context.db_connection_str
user_id = runtime.context.user_id
# 가상의 DB 조회 로직
print(f"[시스템] DB({db_conn})에 접속하여 User({user_id})의 주문({order_id}) 조회 중...")
return f"주문 번호 {order_id}은 현재 '배송 중'입니다."
@tool
def save_user_preference(preference: str, runtime: ToolRuntime[ShoppingContext]) -> str:
"""사용자의 쇼핑 취향(예: 붉은색 선호)을 장기 기억(Store)에 저장합니다."""
user_id = runtime.context.user_id
# 런타임의 Store(장기 기억) 기능 사용
if runtime.store:
# ("users", user_id) 라는 키 경로에 취향 저장
runtime.store.put(("users", user_id), "shopping_pref", {"value": preference})
return "고객님의 취향을 메모해 두었습니다."
return "기억 저장소(Store)가 연결되지 않았습니다."
이제 정의한 스키마, 미들웨어, 도구를 합쳐 에이전트를 만들고, 실제 데이터를 주입하여 실행합니다.
from langchain.agents import create_agent
# 4. 에이전트 생성
agent = create_agent(
model="gpt-4o", # 예시 모델
tools=[check_order_status, save_user_preference],
middleware=[personalize_greeting], # 미들웨어 등록
context_schema=ShoppingContext # 컨텍스트 구조 등록
)
# 5. 실행 (Invoke) - 여기서 의존성 주입이 일어납니다!
# 김철수 VIP 고객이 접속했다고 가정합니다.
vip_context = ShoppingContext(
user_id="user_999",
user_name="김철수",
membership_level="VIP",
db_connection_str="jdbc:mysql://prod-db:3306/shop"
)
response = agent.invoke(
{"messages": [{"role": "user", "content": "내 주문 1234번 어디쯤 왔어?"}]},
context=vip_context # <--- 핵심: 런타임 컨텍스트 주입
)
# 결과 출력 (가정)
# 미들웨어 덕분에 "김철수 VIP 고객님, 주문하신..." 형태로 답변이 생성됩니다.
print(response["messages"][-1].content)
이 모든 과정이 어떻게 흘러가는지 한눈에 파악할 수 있도록 도식화해 보겠습니다.

context_schema와 invoke(context=...)를 통해 실행 시점에 넣어주세요.Context만 바꾸면 '일반 고객용 봇'이 되기도 하고 'VIP 전용 봇'이 되기도 합니다.request.runtime.context로 접근하여 프롬프트를 제어합니다.runtime: ToolRuntime[...] 인자를 통해 함수 내부에서 안전하게 외부 자원(DB 등)에 접근합니다.Context, 영구적으로 기억해야 할 데이터는 Store를 활용하세요.이 구조를 이해하면, 단순히 말만 잘하는 AI가 아니라 실제 비즈니스 로직과 깊게 연동된 지능형 애플리케이션을 구축할 수 있습니다.