Agent_3

coticoger·2026년 4월 22일

Agent

목록 보기
3/5

with_structured_output(클래스명)

from typing import Literal 
from pydantic import BaseModel,  Field
from langchain_openai import ChatOpenAI

class Feedback(BaseModel): 
    grade : Literal["funny","not funny"] = Field(description="농담이 재미있는지 평가") 
    feedback : str = Field(description="재미없다면 개선 방안 제시")

llm = ChatOpenAI(model="gpt-4o-mini",temperature=0)
 
evaluator = llm.with_structured_output(Feedback)
라이브러리역할
Literal아무 문자열이나 받는게 아니라 여기서 지정한 값들 중 하나만 허용하도록함
BaseModelBaseModel 상속을 통해서 어떤 필드를 가지고, 각 필드는 무슨 타입인지를 선언할 수 있음
Field기본값, 설명, 검증 관련 옵션등을 지정

프롬프트 강제 vs with_structure_output()

항목프롬프트만 사용하는 방식with_structured_output() 방식
출력 형식 제어 방식자연어 지시로 형식 요청JSON schema 기반 구조 강제 + 파싱
형식 안정성낮음 (모델이 종종 어김)높음 (스키마 기반 검증 포함)
타입 검증없음있음 (Pydantic validation)
enum 값 제한 (Literal)불가능가능
필드 누락 감지직접 구현 필요자동 감지
JSON 파싱 안정성직접 처리 필요자동 처리
실패 처리직접 예외 처리 필요validation error 발생 가능
코드 복잡도단순 (초기 작성 쉬움)약간 증가
유지보수성낮음 (프롬프트 의존)높음 (schema 중심 구조)
확장성 (필드 증가 시)급격히 불안정해짐안정적으로 유지
체인 연결 (pipeline)불편매우 적합
agent workflow 연결제한적적합
structured DB 저장추가 처리 필요바로 가능
tool 호출 결과 처리불편자연스럽게 연결 가능
multi-step reasoning pipeline불안정안정적
production 환경 적합성낮음높음
prototype / 실험용매우 적합약간 과함
nested 구조 처리어려움자연스럽게 가능
리스트/객체 출력불안정안정적
자동 문서화(schema 역할)없음있음
IDE 타입 지원없음있음

State 정의가 꼭 필요할까?

1) LangChain 기본 Agent에서는 State 정의가 필수가 아님

from langchain.agents import initialize_agent
같은 전통적인 LangChain Agent는 내부적으로 상태를 자동 관리함.
즉, 메시지 히스토리, intermediate steps, tool 호출 결과들이 내부적으로 처리됨

2) LangGraph 기반 Agent에서는 사실상 필수적!

LangGraph에서는 Agent가 "그래프 위에서 상태를 전달하며 실행되는 프로그램"이므로 반드시 State 구조가 필요함

여기서 State는 다음과 같은 역할을 함

  • 노드 간 데이터 전달
  • tool 실행 결과 전달
  • LLM 입력 구성
  • 실행 흐름 제어
  • memory 역할 수행

즉 LangGraph에서는 Agent = state transition system이다.

add_conditional_edges

LangGraph에서 조건 분기를 정의하는 부분. 특정 노드 실행 후 다음에 어떤 노드로 이동할지 조건에 따라 결정함

add_conditional_edges(
    source_node,
    routing_function,
    routing_map
)
  • source_node
    이 노드 실행이 끝난 뒤 어디로 갈지 조건 분기하겠다는 의미
  • routing_function
    현재 state를 보고 다음 노드 결정에 사용할 label 반환
  • routing_map
    routing function의 반환값을 기반으로 다음 노드를 결정함

Messages?

종류설명
HumanMessage사용자 입력을 나타내는 메시지
LLM 입장에서는 role = '
user'와 동일하다
사용자 질문 전달, agent 입력, chain 시작 입력
SystmeMessage모델의 행동 규칙 또는 컨텍스트 설정
LLM 입장에서는 role = 'system'에 해당
역할 설정, 출력 형식 제한, 스타일 지정, 정책 설정, persona 정의
AIMessage모델의 응답을 나타내는 메시지
LLM 입장에서는 role = "assistant"에 해당
이전 응답 저장, 대화 history 구성, agent intermediate reasoing 기록
ToolMessagetool 실행 결과를 나타내는 메시지
tool execution 결과 전달, agent-tool loop 연결, function calling 결과 전달
ChatMessage사용자 정의 role 메시지 생성
multi-agent system, evaluator 역할 분리, palnner/critic/reviewer 구조
RemoveMessagestate에서 특정 메시지 제거, LangGraph에서 message pruning 할 때 사용
memory trimming, context window 관리, 오래된 메시지 제거

ChatModel vs Agent

HumanMessage()와 {"role":"user","content":...}의 차이점

구분ChatModelAgent
역할한 번 답변 생성여러 단계 문제 해결
입력messagesstate(dict)
내부 구조단일 LLM 호출LLM + tool + routing + memory
실행 흐름1-stepmulti-step
예시llm.invoke()agent.invoke()
입력 형식HumanMessage(...) 또는 "텍스트"{"messages": [...]} (state dict)

ChatModel은 모델에게 대화 기록을 전달
Agent 호출은 워크플로우 전체 상태를 전달 ( {"messages" : [...]}구조로 state의 messages필드에 메시지를 넣어서 agent 실행을 시작함)

문자열 vs Message 객체 vs state(dict)

방식언제 쓰는가입력 예시의미
문자열단일 LLM 호출"요약해줘"prompt 전달
Message 객체대화/역할 포함 호출[HumanMessage(...)]구조화된 대화 전달
state(dict)Agent / LangGraph 실행{"messages": [...]}실행 상태 전달
  1. 문자열
    result = llm.invoke("다음 주제로 짧은 이야기 써줘: 용과 마법사")

요약, 번역, 제목 생성, 간단한 질문, 단일 응답 생성

  1. Message 객체
from langchain_core.messages import HumanMessage, SystemMessage

result = llm.invoke([
    SystemMessage(content="너는 동화 작가다."),
    HumanMessage(content="용과 마법사 이야기 써줘")
])

SystemMesasge 필요, multi-turn 대화, role 구분 필요, structured output, chain 구성

  1. state(dict) Agent/LangGraph 실행
research_agent.invoke(
    {"messages": [{"role": "user", "content": "Transformer 발전 과정 조사"}]}
)

prompt 전달이 아닌 state 전달

state 전달
↓
LLM 판단
↓
tool 호출 가능
↓
다시 LLM
↓
state 업데이트
↓
결과 반환

Reducer

Reducer란?

LangGraph에서 여러 노드가 같은 state 필드를 업데이트할 수 있음
이 경우 기존 값과 새 값을 어떻게 합칠지를 결정해야 하는데 이때 사용하는 것이 reducer이다

즉, 기존 값 + 새 값 -> 최종값을 정의하는 함수

Reducer 함수의 입력 의미

override_reducer(current, new)

current는 기존 state 값, new는 새로 추가되는 값이다

override 조건 부분

def override_reducer(current, new):
    """override 타입 지원 reducer"""
    if isinstance(new, dict) and new.get("type") == "override":
        return new.get("value", new)
    return operator.add(current, new)

type이 override인 경우 기존 값을 무시하고 새값을 덮어씌움
override가 아닌 경우 기존 값 + 새값으로

MessageState

class AgentState(MessagesState):
    """메인 에이전트 상태"""
    research_brief: Optional[str] = None
    supervisor_messages: Annotated[List, override_reducer] = []
    notes: Annotated[List[str], override_reducer] = []
    final_report: str = ""
    # Human-in-the-Loop: 최대 1회 질문 추적
    clarification_asked: bool = False

MessageState : LangGraph에서 기본적으로 제공하는 state 클래스로 messages 필드를 기본으로 포함하는 state 구조를 제공함

라이브러리설명
Optional[str] = None문자열일 수도 있고 없을 수도 있음이라는 뜻
Annotated[List, override_reducer]이 필드를 state 업데이트할 때 어떤 reducer를 사용할지 지정
이 필드는 List 타입이고 state 병합 시 override_reducer 사용

State 종류 개요

종류언제 사용특징
TypedDict 기반 State가장 일반적가볍고 빠름
MessageStateAgent / 대화형 workflowmessages 필드 자동 포함
Pydantic BaseModel 기반 Statevalidation 필요타입 안정성 높음
dataclass 기반 State간단한 구조Pythonic 스타일
Custom reducer 포함 State병합 전략 제어 필요필드별 update 방식 지정

Command

-> Command[Literal["research_tools"]]
이 노드는 실행 후 "research_tools" 노드로 이동한다는 의미

실제 Command 객체 구조는 다음과 같다

Command(
    update={...},
    goto="next_node"
)
필드의미
updatestate 변경
goto다음 실행 노드 지정

일반 노드 vs Command node

일반 노드는 node 실행 -> state 업데이트 -> 그래프 edge 따라 이동

Command 노드는 node 실행 -> 다음 실행 노드 직접 지정

conditional_edges와 차이

conditional edges는 그래프 밖에서 정의 한다

builder.add_conditional_edges(...)

하지만 Command는 노드 내부에서 정의함
return Command(goto="...")

0개의 댓글