오픈소스

[오픈소스 분석 3일차] LangChain Agent Core Components

m00n0107 2025. 10. 26. 18:02

공식문서상 Core Components는 8가지로 구성된다.

Agents, Models, Messages, Tools, Short-term memory, Streaming, Middleware, Structured output 이다.각 components의 의미와 활용에 대해서 자세하게 명시되어있는데, 내용은 다음과 같다. 

 

1. Agents Core components


Agents는 LangChain에서 정의하기로, Task를 추론할 수 있는 시스템을 만드는 Tool이다(LLM을 곁들인)

 

LLM Agent는 Stop 조건이 달성되기 전까지 지속적으로 돌아가는데, Stop 조건은 주로 반복 횟수의 limit에 도달하거나, 최종적인 결과물의 도출이다.

 

Agent 내부에서의 Core components는 Model, Tools, System prompt로 나뉘어져있다.

 

1-1. Model

모델은 Agent 추론 작업의 핵심 부분으로, 정적 모델과 동적 모델로 나뉘어진다. 

from langchain.agents import create_agent

agent = create_agent(
    "openai:gpt-5",
    tools=tools
)

 

위와 같이 일반적으로 create_agent를 정의하고 gpt같은 모델을 가져와서 쓰는게 정적 모델이고

 

from langchain_openai import ChatOpenAI
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse


basic_model = ChatOpenAI(model="gpt-4o-mini")
advanced_model = ChatOpenAI(model="gpt-4o")

@wrap_model_call
def dynamic_model_selection(request: ModelRequest, handler) -> ModelResponse:
    """Choose model based on conversation complexity."""
    message_count = len(request.state["messages"])

    if message_count > 10:
        # Use an advanced model for longer conversations
        model = advanced_model
    else:
        model = basic_model

    request.model = model
    return handler(request)

agent = create_agent(
    model=basic_model,  # Default model
    tools=tools,
    middleware=[dynamic_model_selection]
)

 

이런식으로 Middleware를 걸어서 특정 상황에 맞게 다양한 모델을 상황에 맞게 변동시키는 것이 동적 모델이다.

 

위 동적 모델의 예시 코드 같은 경우, 입력한 Messager의 길이가 10보다 클 경우(복잡한 질문일 경우) gpt-4o 모델을 사용하고, 그렇지않을 경우(가벼운 질문일 경우) gpt-4o-mini 같은 가벼운 모델을 사용하는 내용이다. 아무래도 Agent의 특성상 외부 LLM 모델의 API를 끌어와서 사용하는 경우가 많은데, 과금량을 최소화하기 위해서 이런식으로 동적 모델을 사용하는 것 같다.

 

1-2. Tools

 

Tools는 일반적인 모델이 하는 작업 이외에도 추가적인 작업을 할 수 있게 만드는 도구이다. 이 Tool들은 다양한 Tool들을 연속적으로 가져와서 동작시킬 수도 있고, 병렬적으로 사용할 수 도 있다. 

 

from langchain.tools import tool
from langchain.agents import create_agent


@tool
def search(query: str) -> str:
    """Search for information."""
    return f"Results for: {query}"

@tool
def get_weather(location: str) -> str:
    """Get weather information for a location."""
    return f"Weather in {location}: Sunny, 72°F"

agent = create_agent(model, tools=[search, get_weather])

 

위의 코드는 tool을 import해서, search tool과 get_weather 함수를 정의해서 agent에 집어넣은 것이다. 이외에도 예외처리에서 Message를 넣어서 활용하는 방법도 나와있다. 이는 실제 사용시에 본인의 도메인에 맞게 다양한 tool 들을 연결해서 Agent에 집어넣어서 유연한 Agent를 만들 수 있다는 것을 알 수 있다.

 

1-3. System Prompt

System Prompt는 Agent가 LLM일때 Agent 자체가 가져야할 기본적인 Prompt를 정의하는 것이다. 조금 쉽게 말하면 Agent의 페르소나를 정의하는 과정인데, "너는 개발을 도와주는 시니어 개발자야" 혹은 "너는 데이터 분석을 도와주는 어시스턴트야" 등과 같이 Agent의 답변의 방향성을 지정할 수 있다. 

 

from typing import TypedDict

from langchain.agents import create_agent
from langchain.agents.middleware import dynamic_prompt, ModelRequest


class Context(TypedDict):
    user_role: str

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

    if user_role == "expert":
        return f"{base_prompt} Provide detailed technical responses."
    elif user_role == "beginner":
        return f"{base_prompt} Explain concepts simply and avoid jargon."

    return base_prompt

agent = create_agent(
    model="openai:gpt-4o",
    tools=[web_search],
    middleware=[user_role_prompt],
    context_schema=Context
)

# The system prompt will be set dynamically based on context
result = agent.invoke(
    {"messages": [{"role": "user", "content": "Explain machine learning"}]},
    context={"user_role": "expert"}
)

 

System prompt도 마찬가지로 이런식으로 동적으로 설정해서 A 조건을 충족할 경우 A 페르소나, B 조건을 충족할 경우 B 페르소나라고 정의할 수 있다.

 

2. Invocation

Agents의 최종적인 결과물은 invoke라는 메소드로 호출한다. 

result = agent.invoke(
    {"messages": [{"role": "user", "content": "What's the weather in San Francisco?"}]}
)

 

위 코드와 같이 답변을 구조화해서 invoke 시킬 수 있고, 문서에 따르면 Structured output을 만들기 위해 아래와 같이 Tool을 사용하거나 Provider Strategy를 사용하기도한다

 

#ToolStrategy 사용예시
class ContactInfo(BaseModel):
    name: str
    email: str
    phone: str



#ProviderStrategy 사용예시
from langchain.agents.structured_output import ProviderStrategy

agent = create_agent(
    model="openai:gpt-4o",
    response_format=ProviderStrategy(ContactInfo)
)

 

이외에도 Invocation을 위해서 Memory를 정의하는 방식도 다양한데, 이게 무슨말이냐면 LLM과 했던 대화 기록이 이후의 답변에 얼마나 반영시킬지를 결정하는 것이다. 그 방법에는 첫번째로 middleware를 사용하는 방법과 state_schema를 사용하는 방법이 있는데 state_schema를 사용해서 작업하는 경우는 거의 못봤다. middleware는 체인의 입력/출력 전후에 동적으로 로직을 삽입할수 있지만, state_schema는 메모리 구조를 정적으로 정의하기 때문에, 재사용성과 유연성이 낮기 때문이다.

 

from langchain.agents import AgentState
from langchain.agents.middleware import AgentMiddleware


class CustomState(AgentState):
    user_preferences: dict

class CustomMiddleware(AgentMiddleware):
    state_schema = CustomState
    tools = [tool1, tool2]

    def before_model(self, state: CustomState, runtime) -> dict[str, Any] | None:
        ...

agent = create_agent(
    model,
    tools=tools,
    middleware=[CustomMiddleware()]
)

# The agent can now track additional state beyond messages
result = agent.invoke({
    "messages": [{"role": "user", "content": "I prefer technical explanations"}],
    "user_preferences": {"style": "technical", "verbosity": "detailed"},
})

 

위 코드는 middleware를 설정해서 메모리구조를 정의한 케이스이다. 이런식으로 설정하면 입력/출력을 가공하면서도 여러 체인에서 공통적으로 모듈화하여 사용할 수 있을 것이다.

반응형