[Spring AI] Agentic Workflow — AI가 스스로 생각하고 행동하는 방법

Raha·2026년 4월 11일

Spring AI

목록 보기
8/8

들어가며

지난 글에서는 Spring AI의 Advisor 패턴과 Function Calling을 통해 LLM이 외부 도구를 활용하는 방법을 살펴봤다. 그런데 도구를 쓸 수 있다고 해서 에이전트가 되는 건 아니다.

이번 글에서는 다음 질문들을 중심으로 Agentic Workflow를 다룬다.

  • ChatBot과 Agent는 무엇이 다른가?
  • LLM이 스스로 판단하고 반복 실행하려면 어떤 구조가 필요한가?
  • 복잡한 문제를 더 잘 풀기 위한 패턴에는 어떤 것들이 있는가?

1. ChatBot vs Agent

일반 ChatBot은 입력에 대해 즉각적인 답변을 생성한다. 질문 하나에 답변 하나, 그게 전부다.

반면 Agent는 다르다. 목표가 주어지면 스스로 계획을 세우고, 도구를 선택해 실행하며, 결과를 관찰한 뒤 다음 행동을 결정한다. 이 과정을 목표가 달성될 때까지 반복한다.

항목ChatBotAgent
작동 방식입력 → 즉각 출력목표 → 계획 → 실행 → 관찰 반복
자율성사용자 가이드 의존스스로 다음 단계 결정
도구 활용내부 지식만 사용외부 API, DB 등 자유 활용
적합한 상황단발성 질문다단계 복잡한 워크플로우

핵심 차이는 추론 루프(Reasoning Loop) 의 유무다. 에이전트는 답을 낼 때까지 생각하고 행동하는 과정을 반복한다.


2. ReAct 패턴 — 생각하고, 행동하고, 관찰한다

ReAct(Reasoning + Acting)는 에이전트의 가장 기본적인 사고 방식이다. 네 단계로 이루어진다.

[Thought]     현재 상황을 분석하고 필요한 행동을 결정
[Action]      도구를 선택하고 실행
[Observation] 실행 결과를 확인
[Answer]      모든 정보가 모이면 최종 답변 생성

예를 들어 "서울과 도쿄 날씨를 비교해서 옷차림 추천해줘"라는 요청이 들어오면 이렇게 흘러간다.

[Thought 1]     서울 날씨 데이터가 필요하다
[Action 1]      getWeather("Seoul") 호출
[Observation 1] 서울: 12도, 흐림

[Thought 2]     도쿄 날씨도 필요하다
[Action 2]      getWeather("Tokyo") 호출
[Observation 2] 도쿄: 22도, 맑음

[Thought 3]     두 도시 날씨를 알았으니 옷차림을 추천할 수 있다
[Answer]        서울은 코트, 도쿄는 가디건 추천

이전 Observation이 다음 Thought의 재료가 된다. 이 연결고리가 루프를 만든다.

에이전트가 도구를 선택하는 방법

에이전트는 자바 코드를 직접 읽지 못한다. @Tooldescription을 읽고 판단한다.

@Tool(description = "특정 도시의 현재 날씨를 조회합니다.")
public WeatherResponse getWeather(String city) { ... }

역할 분담은 이렇다.

LLM    → description을 읽고 어떤 도구를 쓸지 판단
Spring → LLM의 판단을 받아 실제 메서드를 실행하고 결과를 반환

description이 구체적일수록 LLM이 올바른 도구를 선택할 확률이 높아진다.

Spring AI 코드

public String solveWithAgent(String goal) {
    return chatClientBuilder.build().prompt()
            .system("Thought → Action → Observation을 반복하여 문제를 해결하세요.")
            .tools("getWeather", "calculator", "getCurrentTime")
            .user(goal)
            .call()
            .content();
}

3. Plan-and-Execute — 계획 먼저, 실행 나중

ReAct는 단순한 요청에 적합하다. 하지만 요청이 복잡해지면 중간에 길을 잃을 수 있다.

"서울과 부산 날씨 비교하고, 평균 기온 계산하고, 여행지 추천까지 해줘"

이런 요청을 계획 없이 바로 실행하면 순서가 꼬이거나 일부 작업을 빠뜨릴 수 있다. Plan-and-Execute는 전체 로드맵을 먼저 작성한 뒤 실행한다.

Planner  → 전체 계획 수립 (도구 없음, 생각만)
Executor → 계획을 순서대로 실행 (도구 사용)

Planner에 .tools()가 없는 이유가 여기 있다. 계획 단계에서는 무엇을 해야 할지 판단만 하면 되기 때문이다.

// 1단계: 계획 수립 (도구 없음)
String plan = chatClientBuilder.build().prompt()
        .system("복잡한 목표를 위한 단계별 계획을 수립하는 전략가입니다.")
        .user(goal)
        .call()
        .content();

// 2단계: 계획 실행 (도구 사용)
return chatClientBuilder.build().prompt()
        .system("주어진 계획을 정확히 이행하는 실행 전문가입니다.")
        .tools("getWeather", "calculator", "getCurrentTime")
        .user("수립된 계획: " + plan + "\n원래 목표: " + goal)
        .call()
        .content();

4. Self-Reflection — 실행 후 스스로 검증한다

LLM은 틀린 답을 자신 있게 내놓는 환각(Hallucination) 문제가 있다. Self-Reflection은 답변을 생성한 뒤 스스로 검토하고 수정하는 과정을 반복한다.

답변 생성 → 검증 → APPROVED? → 종료
                 → 미흡?    → 피드백 반영 후 재시도

검토자도 LLM이다. LLM이 자신의 답변을 스스로 비판하는 구조다.

for (int i = 0; i < maxIterations; i++) {
    // 1. 답변 생성
    currentResponse = client.prompt()
            .system("도구를 사용하여 문제를 해결하세요. 이전 피드백을 반영하세요.")
            .user("문제: " + problem + "\n이전 피드백: " + feedback)
            .call().content();

    // 2. 검증
    feedback = client.prompt()
            .system("답변의 결함을 검토하세요. 완벽하면 'APPROVED'라고 답하세요.")
            .user("검토 대상: " + currentResponse)
            .call().content();

    if (feedback.contains("APPROVED")) break;
}

한 번에 완벽한 답을 요구하는 게 아니라, 틀려도 괜찮으니 스스로 고쳐나가는 구조다.


5. Multi-Agent — 전문화된 역할 분리

하나의 에이전트가 모든 걸 처리하면 각 역할이 어중간해진다. Multi-Agent는 전문화된 페르소나를 가진 여러 에이전트가 협력한다.

Researcher → 데이터 수집 전문
Analyst    → 인사이트 도출 전문
Writer     → 보고서 작성 전문

각 에이전트의 출력이 다음 에이전트의 입력이 된다.

더 나아가 Dynamic Orchestration 패턴은 문제 유형에 따라 필요한 에이전트만 소집한다.

List<AgentRole> pipeline = switch (problemType) {
    case "CALCULATION"     -> List.of(AgentRole.ANALYST, AgentRole.WRITER);
    case "DATA_COLLECTION" -> List.of(AgentRole.RESEARCHER, AgentRole.WRITER);
    default                -> List.of(AgentRole.RESEARCHER, AgentRole.ANALYST, AgentRole.WRITER);
};

LLM 호출은 곧 비용과 시간이다. 불필요한 에이전트를 호출하지 않는 것만으로도 효율이 크게 달라진다.


마치며

네 가지 패턴 모두 공통된 목표를 가진다. LLM의 환각을 줄이고 더 나은 답변 품질을 얻는 것이다.

ReAct            → Thought → Action → Observation 반복
Plan-and-Execute → 계획 먼저, 실행 나중 (복잡한 요청에 유리)
Self-Reflection  → 실행 후 스스로 검증 (환각 최소화)
Multi-Agent      → 전문화된 역할 분리 + 동적 조합 (품질 + 비용 최적화)

단순히 LLM을 호출하는 것과 에이전트를 설계하는 것은 다른 차원의 문제다. 어떤 패턴을 선택하느냐는 결국 풀려는 문제의 복잡도에 달려 있다.

profile
Backend Developer | Aspiring Full-Stack Enthusiast

0개의 댓글