Intro

LangChain Agent에서 Middleware는 “루프를 가로채서” 상태/모델/도구를 바꾸는 강력한 레버였습니다.
이번 편에서는 그 개념을 실제로 활용해 Dynamic Prompt / Dynamic Model / Dynamic Tools를 구현하는 방법과, Sub Agent를 라우팅하여 활용하는 방법을 다룹니다.


1. Dynamic Prompt

LLM의 응답 품질을 안정적으로 높이려면 “시스템 프롬프트를 고정하지 않는 것”이 중요합니다.
사용자 속성(언어, 역할, 권한 등)에 따라 시스템 프롬프트를 동적으로 생성할 수 있습니다.

1.1. 예제 코드 (dynamic_prompt)

from dataclasses import dataclass
from langchain.agents.middleware import dynamic_prompt, ModelRequest

@dataclass
class LanguageContext:
    user_language: str = "English"

@dynamic_prompt
def user_language_prompt(request: ModelRequest) -> str:
    """Generate system prompt based on user role."""
    user_language = request.runtime.context.user_language
    base_prompt = "You are a helpful assistant."

    if user_language != "English":
        return f"{base_prompt} only respond in {user_language}."
    elif user_language == "English":
        return base_prompt
  • dynamic_prompt매 루프마다 시스템 프롬프트를 재생성합니다.
  • request.runtime.context를 통해 사용자 특성 기반 프롬프트를 만들 수 있습니다.
  • 실제 서비스에서는 언어, 레벨, 권한, 업무 도메인 등에 따라 prompt를 바꾸는 패턴이 가장 효과적입니다.

2. Dynamic Model

짧은 대화는 저렴한 모델, 긴 대화는 큰 컨텍스트 모델로 갈아끼워, 비용과 성능을 동시에 관리할 수 있습니다.

2.1. 예제 코드 (wrap_model_call)

from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from langchain.chat_models import init_chat_model
from typing import Callable

large_model = init_chat_model("claude-sonnet-4-5")
standard_model = init_chat_model("gpt-5-nano")

@wrap_model_call
def state_based_model(request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse]) -> ModelResponse:
    """Select model based on State conversation length."""
    # request.messages is a shortcut for request.state["messages"]
    message_count = len(request.messages)

    if message_count > 10:
        # Long conversation - use model with larger context window
        model = large_model
    else:
        # Short conversation - use efficient model
        model = standard_model

    request = request.override(model=model)

    return handler(request)
  • 일반적으로 운영 환경에서는 비용, 컨텍스트 길이, 속도를 기준으로 모델 라우팅 룰을 만드는 방식이 안정적입니다.

3. Dynamic Tools

도구도 “모두에게 항상 열어둘 필요”가 없습니다.
예: 내부 직원은 SQL 조회 가능, 외부 고객은 검색만 허용 등.

3.1. 예제 코드 (wrap_model_call)

from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from typing import Callable

@wrap_model_call
def dynamic_tool_call(request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse]) -> ModelResponse:
    """Dynamically call tools based on the runtime context"""

    user_role = request.runtime.context.user_role

    if user_role == "internal":
        pass # internal users get access to all tools
    else:
        tools = [web_search] # external users only get access to web search
        request = request.override(tools=tools)

    return handler(request)
  • 권한/역할 기반 접근 제어(RBAC)를 구현하는 가장 단순한 방법입니다.
  • 실제 프로덕션에서는 내부/외부, 플랜 티어, 정책 그룹 등을 기준으로 도구를 분리하는 패턴이 안정적입니다.

4. Sub Agents Routing

4.1. 예제 코드

def build_graph():
    research_graph = build_subgraph(analysis_node, "research")
    action_graph = build_subgraph(action_node, "action")
    answer_graph = build_subgraph(chat_node, "answer")

    g = StateGraph(GraphState)
    g.add_node("route", route_node)
    g.add_node("research_subgraph", research_graph)
    g.add_node("action_subgraph", action_graph)
    g.add_node("answer_subgraph", answer_graph)

    g.set_entry_point("route")
    g.add_conditional_edges(
        "route",
        lambda s: s["route"],
        {
            "analysis": "research_subgraph",
            "action": "action_subgraph",
            "chat": "answer_subgraph",
        },
    )
    g.add_edge("research_subgraph", END)
    g.add_edge("action_subgraph", END)
    g.add_edge("answer_subgraph", END)

    return g.compile()
  • task별 Sub Agent를 구성한 뒤, route node를 이용해 처리하는 방식입니다.
  • 두 가지 node를 병렬적으로 연결한 뒤, aggregation하는 방식으로도 가능합니다.
  • create_agent의 sub_agent는 tool call처럼 host가 sub agent 사용 여부를 판단하는 방식이지만, 이렇게 직접 구현함으로써 sub agent 사용을 강제할 수 있습니다.

Outro

지금까지 LangChain/LangGraph로 에이전트를 개발하는데 활용되는 추상화 모듈들의 종류와 활용법, 작동 원리 등에 대해 알아봤습니다.
사실 LangChain Agent 시리즈를 빨리 끝내고 LangChain에서 새로 출시된 deepagents에 대한 시리즈를 이어서 작성하려고 했는데, 지금까지의 내용만 제대로 이해했다면 포스팅 하나 정도로 충분히 소개할 수 있을 것 같습니다.

따라서 다음 포스팅에서는 번외편 - LangChain Deepagent에 대한 전반적인 내용을 한 번에 소개해보도록 하겠습니다.

0개의 댓글