개요

세 편을 이어서 왔다. 30편은 모델에 읽을거리를 주는 이야기였고, 31편은 을 달아주는 이야기였다. 남은 게 하나 있다.

에이전트는 무엇을 보고, 어떻게 계획을 세우고, 틀렸을 때 어떻게 되돌아오는가?

도구가 있어도 순서를 모르면 못 쓴다. 순서를 정했어도 중간에 틀리면 그대로 망한다. 이번 편은 그 머리에 해당하는 부분이다.

마지막에는 LangChain을 붙인다. 세 편에서 본 것들 — 검색, 도구, 계획 — 이 실제 코드에서 어디에 앉는지 지도를 그리고 끝낸다.

교육 자료는 대외비라 슬라이드 이미지나 예제 데이터는 싣지 않는다.


1. 환경 — 무엇으로 보여주고, 어떻게 이해시키나

31편에서 에이전트의 다섯 요건을 봤다. 그중 환경 표현환경 이해 두 개를 아직 안 건드렸다.

에이전트가 일하는 환경은 생각보다 종류가 많다. 챗봇의 대화창, 로봇이 움직이는 실제 공간, 게임 화면, 개발자가 만지는 코드베이스까지 전부 환경이다. 문제는 언어모델이 글자만 읽을 수 있다는 것이다.

환경은 글자가 아닌데, 모델은 글자만 읽는다. 번역이 필요하다.

환경을 표현하는 세 방식

환경을 표현하는 세 방식

① 텍스트로 표현하기. 물리 세계의 상황을 아예 문장으로 적어준다. “당신은 방 한가운데 있다. 캐비닛과 싱크대가 보인다” 같은 식이다. 텍스트 기반 임바디드 시뮬레이터가 이 방식을 쓴다. 모델 입장에서 제일 편하지만, 누군가 미리 글로 적어둬야 한다.

② 이미지로 표현하기. 화면이나 실제 풍경을 그대로 보여준다. 거리 사진을 보며 길 안내를 따라가는 데이터셋이 대표적이다. 자연스럽지만 어렵다.

이미지 표현의 함정 무슨 문제인가
글자 읽기(OCR) 화면 속 버튼 이름을 잘못 읽으면 엉뚱한 걸 누른다
복잡한 레이아웃 겹치고 스크롤되는 화면에서 무엇이 무엇인지 헷갈린다
그라운딩 “장바구니 버튼”이라는 말과 화면 속 그 위치를 연결하지 못한다

③ 구조화된 형태로 표현하기. 웹이라면 스크린샷 대신 HTML·DOM·접근성 트리를 준다. 이게 실용적으로 제일 낫다.

접근성 트리가 특히 좋은 이유는, 원래 화면을 못 보는 사람을 위해 만들어진 구조라서다. “이건 버튼이고, 이름은 장바구니에 담기고, 누를 수 있음” 이라는 정보가 이미 텍스트로 정리돼 있다. 시각 정보를 글로 옮기는 일을 웹 표준이 이미 해둔 셈이다.

화면을 픽셀로 주면 모델이 해석해야 하지만, 접근성 트리로 주면 이미 해석돼 있다.

표현했다고 이해하는 건 아니다

보여주는 것과 이해하는 건 다르다. 모델은 자기가 상호작용하는 환경을 다 알지 못한다. 일부는 파라미터 안에 있지만(코딩 지식, 자주 쓰는 사이트 구조), 나머지는 실시간으로 부딪혀서 알아내야 한다.

여기에 접근하는 방식이 순서대로 발전해왔다.

방식 어떻게 한계
환경 특화 프롬프트 그 환경 전용 프롬프트 템플릿을 사람이 만들어 준다 그 환경에서만 잘 된다. 새 환경에서 무너진다
워크플로우 메모리 성공한 작업 흐름을 기억해뒀다가, 그걸로 새 프롬프트를 스스로 만든다 성공 경험이 쌓여야 한다
호기심 기반 탐색 예측이 빗나가는 상태에 들어갈수록 보상을 준다 목표 없이 헤맬 수 있다
탐색 기반 궤적 기억 일단 돌아다녀 보고, 한 모델이 그 궤적에 지시문을 다시 붙인다 탐색 비용이 든다

두 번째가 흥미로웠다. 프롬프트를 사람이 설계하지 않고 에이전트가 경험으로 만들어낸다. 잘 풀린 절차를 기억해뒀다가 비슷한 상황에서 꺼내 쓰는 것이다.

네 번째도 발상이 재밌다. 지시문-궤적 쌍을 사람이 만드는 대신, 먼저 아무렇게나 움직여보고 나중에 “방금 한 게 무슨 작업이었는지” 라벨을 붙인다. 31편에서 본 Toolformer가 스스로 데이터를 만들던 것과 같은 계열이다.


2. 계획 — 한 걸음씩 갈 것인가, 통째로 짤 것인가

환경을 봤으면 이제 무엇을 할지 정해야 한다. 컨트롤러가 하는 일이다.

계획 방식은 크게 둘이다.

국소적 계획과 전역적 계획

  국소적 계획 (Local) 전역적 계획 (Global)
방식 한 단계씩 세운다 전체 경로를 한 번에 만든다
도구 선택 매 스텝마다 하나씩 여러 개를 묶어 순서로
장점 단순하고 직관적. 상황에 맞게 유연 효율적. 중간에 흔들리지 않음
단점 장기 의존성에 약하다 — 열 걸음 뒤를 못 본다 복잡한 환경에서 초기 계획이 어긋나면 통째로 실패

ReAct — 생각하고, 하고, 본다

국소적 계획의 대표가 ReAct다. 이름이 Reason(추론) + Act(행동)의 합성이다.

ReAct 루프

한 바퀴가 세 단계다.

  1. Thought(생각) — 지금 상황에서 무엇을 해야 하는지 말로 적는다
  2. Action(행동) — 도구를 하나 고르고 호출한다
  3. Observation(관찰) — 결과를 받아서 읽는다

그리고 다시 1번으로 돌아간다. 답이 나올 때까지 이 바퀴를 돈다.

핵심은 생각을 글로 남긴다는 부분이다. 그냥 도구만 호출하는 것보다, “왜 이걸 부르는지”를 먼저 쓰게 하면 결과가 좋아진다. 그리고 사람이 나중에 어디서 헛다리를 짚었는지 읽을 수 있다.

ReAct의 가치 절반은 성능이고, 나머지 절반은 디버깅 가능해진다는 것이다.

31편에서 본 판별 기준을 여기 대보면 딱 맞는다. 관찰 → 판단 → 행동 → 다시 관찰. 되먹임이 있다. RAG가 에이전트가 아닌 이유가 이 그림에서 확실해진다.

Plan-and-Solve — 먼저 다 짜고 시작한다

전역적 계획 쪽은 반대다. 전체 계획을 먼저 세워두고, 그 계획을 따라 순차적으로 푼다.

한 걸음씩 가는 방식은 중간에 한 번 삐끗하면 그 오류를 안고 계속 간다. 계획을 먼저 세우면 오류가 쌓이는 것을 줄일 수 있다. 대신 처음 세운 계획이 현실과 안 맞으면 그대로 무너진다.

정답은 없고, 작업 성격에 따라 고르는 것이다. 단계가 몇 개 안 되고 상황이 계속 바뀌면 국소적으로, 절차가 정해져 있고 길면 전역적으로.

국소적 계획은 길을 걸으며 갈림길마다 정하는 것, 전역적 계획은 출발 전에 경로를 다 찍어두는 것이다. 처음 가는 동네냐 늘 가던 길이냐로 갈린다.


3. 실패 — 알아채고, 되돌아오기

계획대로 안 되는 게 기본이다. 그래서 에이전트에는 틀린 걸 알아채고 복구하는 장치가 필요하다.

Reflexion — 실패를 말로 정리해서 다시 넣는다

Reflexion의 발상이 좋았다. 실패했을 때 그냥 다시 시도하는 게 아니라, 왜 실패했는지 글로 쓰게 한 다음 그 글을 들고 재시도한다.

Reflexion 루프

  1. 시도 — 작업을 수행하고 궤적(무엇을 했는지 기록)을 남긴다
  2. 평가 — 잘 됐는지 채점한다
  3. 반성 — 궤적을 분석해서 “어디가 잘못됐는지”를 문장으로 만든다
  4. 재시도 — 그 반성문을 컨텍스트에 넣고 다시 한다

여기서 중요한 건 모델 가중치를 안 건드린다는 점이다. 학습이 아니라 컨텍스트에 메모를 남기는 것이다. 그래서 싸고 빠르다.

오류를 탐지하는 데 그치지 않고, 스스로 반성(reflect)한 뒤 고친 행동을 수행한다.

사람이 하는 것과 똑같다. 시험 틀리고 그냥 다시 푸는 것과, 오답노트를 쓰고 다시 푸는 것의 차이다.

계획 자체를 다시 짜기

한 걸음 더 나가면, 계획을 실행 도중에 재검토하는 방식도 있다. 두 개의 에이전트가 역할을 나눠 하나가 큰 계획을 잡고 다른 하나가 세부를 실행하면서, 오류가 나면 위쪽에 알려 계획을 다시 세운다.

초기 계획이 불완전해도 실행 과정에서 보완이 된다. 오래 걸리는 작업일수록 이게 필요하다. 열 단계짜리 작업에서 3단계가 틀렸는데 계획을 못 고치면 나머지 일곱 단계가 다 헛수고다.

장치 무엇을 고치나
Reflexion 행동을 고친다 — 같은 계획, 다른 실행
계획 재검토 계획을 고친다 — 애초에 방향이 틀렸을 때

4. LangChain — 이걸 전부 조립해주는 것

여기까지가 개념이다. 이제 실제로 만들려면 도구가 필요한데, 그게 LangChain이다.

한 줄로 말하면 LLM 기반 애플리케이션을 빠르게 만들도록 도와주는 오픈소스 프레임워크다. 특징은 셋이다.

  • 여러 LLM 제공사(OpenAI, Anthropic, Google 등)의 API 차이를 공통 인터페이스로 덮는다
  • 프롬프트, 메모리, 도구 같은 부품이 모듈로 나뉘어 있어 갈아끼울 수 있다
  • LangGraph로 복잡한 워크플로우를 그래프처럼 설계하고 관리한다

첫 번째가 실질적으로 제일 크다. 모델을 바꿀 때 코드를 다시 안 짜도 된다. 31편의 MCP가 모델과 도구 사이를 표준화했다면, LangChain은 내 코드와 모델 사이를 덮어주는 셈이다.

컴포넌트 대응표

예전에 Spring Boot를 처음 볼 때 Express와의 개념 대응표를 만들어놓고 봤더니 훨씬 빨리 붙었다. 이번에도 같은 걸 만들었다. 세 편에서 본 개념이 어느 부품에 앉는지로 정리하는 게 나한테는 제일 빨랐다.

LangChain 부품 하는 일 어느 편에서 본 개념인가
Prompt Templates 프롬프트를 틀로 만들어 값만 갈아끼운다 18편 프롬프트 엔지니어링
Chains 여러 단계를 순서대로 잇는다 이번 편 계획 — 단, 아래 단서
Agents 상황을 보고 도구를 골라 실행한다 이번 편 ReAct
Memory 대화 기록과 상태를 유지한다 이번 편 메모리 — 단, 아래 단서
Tools 외부 API·DB·계산기를 연결한다 31편 도구와 MCP
Retrievers / Vector Stores 문서를 색인하고 검색해 온다 30편 RAG

세 편의 내용이 LangChain 어디에 앉는가

대응표를 만들다 두 군데에서 걸렸는데, 어설프게 짝지으면 오해가 될 것 같아 따로 적어둔다.

Chains를 전역적 계획이라고 부르면 안 된다. 둘 다 “여러 단계를 순서대로 잇는다”는 점은 같지만, 순서를 누가 정하느냐가 정반대다.

  순서를 정하는 주체 실행 중 바뀌나
Chains 사람이 코드로 고정한다 안 바뀐다
전역적 계획 모델이 상황을 보고 만든다 재검토하면 바뀐다

Chains는 계획이라기보다 정해진 배관에 가깝다. 모델이 계획을 세우게 하려면 Chains가 아니라 Agents 쪽으로 가야 한다.

Memory도 마찬가지다. LangChain의 Memory는 대화 기록을 들고 다니는 것이지, 1절에서 본 워크플로우 메모리처럼 성공한 절차를 학습해 재사용하는 것이 아니다. 이름만 같고 하는 일이 다르다.

이 둘만 빼면 나머지는 그대로 맞아떨어진다. 표를 다 채우고 나니 세 편이 하나로 붙었다. 따로 배운 게 아니라 한 물건의 부품들이었다.

문서는 링크로만 남긴다

LangChain은 공식 튜토리얼이 잘 되어 있다. RAG, 에이전트, MCP 어댑터까지 이번 강의에서 다룬 게 대부분 문서에 있다.

화면 캡처는 일부러 안 넣었다. 확인해보니 강의 자료가 가리키던 튜토리얼 페이지가 이미 다른 구조로 바뀌어 있었다. 스크린샷을 실으면 몇 달 뒤 이 글이 거짓말이 된다.

빨리 바뀌는 프레임워크는 화면이 아니라 개념을 적어둬야 오래 간다.


5. 실습으로 가는 길

강의 마지막에 실습 미리보기가 붙었다. 만들 것은 RAG 기반 고객 응대 에이전트다. 세 편의 내용이 그대로 순서가 된다.

단계 하는 일 어느 편
1. 청킹(Chunking) 긴 문서를 모델이 한 번에 볼 수 있는 크기로, 의미가 이어지는 덩어리로 자른다 30편에서 미뤄둔 것
2. 임베딩(Embedding) 각 덩어리를 의미를 담은 숫자 벡터로 바꾼다 30편 밀집 검색
3. 벡터 스토어 벡터를 저장하고 유사도로 찾을 수 있게 색인한다 30편 Index
4. 리트리버 질문이 오면 관련 덩어리를 꺼내 온다 30편 검색기
5. 도구화 그 리트리버를 에이전트가 부를 수 있는 도구로 감싼다 31편 도구
6. 에이전트 ReAct로 도구를 골라 쓰며 답을 만든다 이번 편

5번이 다리 역할이다. 같은 검색기인데 어떻게 쓰느냐가 다르다.

파이프라인에 박아 넣으면 RAG, 도구로 감싸서 쥐여주면 에이전트가 된다.

30편에서 “RAG는 에이전트가 아니다”라고 선을 그었는데, 코드 수준에서 보니 그 선이 생각보다 얇았다. 검색기를 함수로 감싸느냐 마느냐의 차이다. 개념은 멀어 보였는데 구현은 한 겹이었다.

코드로는 이 정도 모양이 된다.

from langchain.tools import tool

@tool
def search_manual(query: str) -> str:
    """고객 응대 매뉴얼에서 관련 내용을 찾아 온다"""
    return retriever.invoke(query)

31편에서 본 그대로다. 데코레이터를 붙이면 함수가 도구가 되고, 독스트링이 모델이 읽는 설명이 된다.

청킹에서 한 가지만 기억하려 한다. 그냥 글자 수로 자르면 문장이 중간에서 끊긴다. 의미가 이어지는 단위로 잘라야 검색해 온 덩어리가 말이 된다.

너무 잘게 자르면 맥락이 날아가고, 너무 크게 자르면 관계없는 내용이 딸려 들어와 30편에서 본 검색 노이즈가 된다. 자르는 크기와 겹치는 정도를 조절하는 게 실습의 첫 관문이 될 것 같다.

청킹은 사소해 보이는데, 여기서 잘못 자르면 뒤의 임베딩·검색·생성이 전부 그 위에서 돌아간다.


정리

  • 환경 표현 세 가지 — 텍스트(미리 적어둬야 함) / 이미지(자연스럽지만 OCR·레이아웃·그라운딩에서 깨짐) / 구조화된 웹(HTML·DOM·접근성 트리)
    • 접근성 트리가 유리한 이유 — 시각 정보를 글로 옮기는 일을 웹 표준이 이미 해뒀다
  • 환경 이해 — 사람이 만든 특화 프롬프트는 새 환경에서 무너진다 → 워크플로우 메모리(성공 절차를 기억해 스스로 프롬프트 생성), 호기심 기반 탐색, 탐색 후 라벨 붙이기
  • 계획 두 방식
    • 국소적 — 한 단계씩. 유연하지만 장기 의존성에 약하다. 대표가 ReAct
    • 전역적 — 전체 경로를 먼저. 오류 축적이 줄지만 초기 계획이 어긋나면 통째로 실패
  • ReAct — Thought → Action → Observation 반복. 생각을 글로 남기는 것이 성능과 디버깅 둘 다 살린다
  • Reflexion — 실패 궤적을 문장으로 반성해 컨텍스트에 넣고 재시도. 가중치를 안 건드려서 싸다
    • Reflexion은 행동을 고치고, 계획 재검토는 계획을 고친다
  • LangChain — 제공사별 API 차이를 덮고, 부품을 모듈로 나누고, LangGraph로 워크플로우를 설계
    • Prompt / Chains / Agents / Memory / Tools(31편) / Retrievers·Vector Stores(30편)
    • 헷갈리기 쉬운 두 개 — Chains는 사람이 순서를 고정하는 것이라 모델이 짜는 전역적 계획이 아니고, Memory는 대화 기록이라 학습된 워크플로우 메모리가 아니다
  • 실습 순서 — 청킹 → 임베딩 → 벡터 스토어 → 리트리버 → 도구화 → 에이전트
    • 검색기를 파이프라인에 박으면 RAG, 도구로 감싸 쥐여주면 에이전트
  • 겹치는 글 — 검색은 30편, 도구와 MCP는 31편, 프롬프트 설계는 18편

참고 자료

한줄 평

  • 세 편을 다 쓰고 나서야 검색·도구·계획이 따로 배운 게 아니라 한 물건의 부품이었다는 게 보였는데, 예전에 Express 대응표 만들어놓고 Spring Boot에 붙었던 때랑 똑같은 감각이었다!