개요
세 편을 이어서 왔다. 30편은 모델에 읽을거리를 주는 이야기였고, 31편은 손을 달아주는 이야기였다. 남은 게 하나 있다.
에이전트는 무엇을 보고, 어떻게 계획을 세우고, 틀렸을 때 어떻게 되돌아오는가?
도구가 있어도 순서를 모르면 못 쓴다. 순서를 정했어도 중간에 틀리면 그대로 망한다. 이번 편은 그 머리에 해당하는 부분이다.
마지막에는 LangChain을 붙인다. 세 편에서 본 것들 — 검색, 도구, 계획 — 이 실제 코드에서 어디에 앉는지 지도를 그리고 끝낸다.
교육 자료는 대외비라 슬라이드 이미지나 예제 데이터는 싣지 않는다.
1. 환경 — 무엇으로 보여주고, 어떻게 이해시키나
31편에서 에이전트의 다섯 요건을 봤다. 그중 환경 표현과 환경 이해 두 개를 아직 안 건드렸다.
에이전트가 일하는 환경은 생각보다 종류가 많다. 챗봇의 대화창, 로봇이 움직이는 실제 공간, 게임 화면, 개발자가 만지는 코드베이스까지 전부 환경이다. 문제는 언어모델이 글자만 읽을 수 있다는 것이다.
환경은 글자가 아닌데, 모델은 글자만 읽는다. 번역이 필요하다.
환경을 표현하는 세 방식
① 텍스트로 표현하기. 물리 세계의 상황을 아예 문장으로 적어준다. “당신은 방 한가운데 있다. 캐비닛과 싱크대가 보인다” 같은 식이다. 텍스트 기반 임바디드 시뮬레이터가 이 방식을 쓴다. 모델 입장에서 제일 편하지만, 누군가 미리 글로 적어둬야 한다.
② 이미지로 표현하기. 화면이나 실제 풍경을 그대로 보여준다. 거리 사진을 보며 길 안내를 따라가는 데이터셋이 대표적이다. 자연스럽지만 어렵다.
| 이미지 표현의 함정 | 무슨 문제인가 |
|---|---|
| 글자 읽기(OCR) | 화면 속 버튼 이름을 잘못 읽으면 엉뚱한 걸 누른다 |
| 복잡한 레이아웃 | 겹치고 스크롤되는 화면에서 무엇이 무엇인지 헷갈린다 |
| 그라운딩 | “장바구니 버튼”이라는 말과 화면 속 그 위치를 연결하지 못한다 |
③ 구조화된 형태로 표현하기. 웹이라면 스크린샷 대신 HTML·DOM·접근성 트리를 준다. 이게 실용적으로 제일 낫다.
접근성 트리가 특히 좋은 이유는, 원래 화면을 못 보는 사람을 위해 만들어진 구조라서다. “이건 버튼이고, 이름은 장바구니에 담기고, 누를 수 있음” 이라는 정보가 이미 텍스트로 정리돼 있다. 시각 정보를 글로 옮기는 일을 웹 표준이 이미 해둔 셈이다.
화면을 픽셀로 주면 모델이 해석해야 하지만, 접근성 트리로 주면 이미 해석돼 있다.
표현했다고 이해하는 건 아니다
보여주는 것과 이해하는 건 다르다. 모델은 자기가 상호작용하는 환경을 다 알지 못한다. 일부는 파라미터 안에 있지만(코딩 지식, 자주 쓰는 사이트 구조), 나머지는 실시간으로 부딪혀서 알아내야 한다.
여기에 접근하는 방식이 순서대로 발전해왔다.
| 방식 | 어떻게 | 한계 |
|---|---|---|
| 환경 특화 프롬프트 | 그 환경 전용 프롬프트 템플릿을 사람이 만들어 준다 | 그 환경에서만 잘 된다. 새 환경에서 무너진다 |
| 워크플로우 메모리 | 성공한 작업 흐름을 기억해뒀다가, 그걸로 새 프롬프트를 스스로 만든다 | 성공 경험이 쌓여야 한다 |
| 호기심 기반 탐색 | 예측이 빗나가는 상태에 들어갈수록 보상을 준다 | 목표 없이 헤맬 수 있다 |
| 탐색 기반 궤적 기억 | 일단 돌아다녀 보고, 한 모델이 그 궤적에 지시문을 다시 붙인다 | 탐색 비용이 든다 |
두 번째가 흥미로웠다. 프롬프트를 사람이 설계하지 않고 에이전트가 경험으로 만들어낸다. 잘 풀린 절차를 기억해뒀다가 비슷한 상황에서 꺼내 쓰는 것이다.
네 번째도 발상이 재밌다. 지시문-궤적 쌍을 사람이 만드는 대신, 먼저 아무렇게나 움직여보고 나중에 “방금 한 게 무슨 작업이었는지” 라벨을 붙인다. 31편에서 본 Toolformer가 스스로 데이터를 만들던 것과 같은 계열이다.
2. 계획 — 한 걸음씩 갈 것인가, 통째로 짤 것인가
환경을 봤으면 이제 무엇을 할지 정해야 한다. 컨트롤러가 하는 일이다.
계획 방식은 크게 둘이다.
| 국소적 계획 (Local) | 전역적 계획 (Global) | |
|---|---|---|
| 방식 | 한 단계씩 세운다 | 전체 경로를 한 번에 만든다 |
| 도구 선택 | 매 스텝마다 하나씩 | 여러 개를 묶어 순서로 |
| 장점 | 단순하고 직관적. 상황에 맞게 유연 | 효율적. 중간에 흔들리지 않음 |
| 단점 | 장기 의존성에 약하다 — 열 걸음 뒤를 못 본다 | 복잡한 환경에서 초기 계획이 어긋나면 통째로 실패 |
ReAct — 생각하고, 하고, 본다
국소적 계획의 대표가 ReAct다. 이름이 Reason(추론) + Act(행동)의 합성이다.
한 바퀴가 세 단계다.
- Thought(생각) — 지금 상황에서 무엇을 해야 하는지 말로 적는다
- Action(행동) — 도구를 하나 고르고 호출한다
- Observation(관찰) — 결과를 받아서 읽는다
그리고 다시 1번으로 돌아간다. 답이 나올 때까지 이 바퀴를 돈다.
핵심은 생각을 글로 남긴다는 부분이다. 그냥 도구만 호출하는 것보다, “왜 이걸 부르는지”를 먼저 쓰게 하면 결과가 좋아진다. 그리고 사람이 나중에 어디서 헛다리를 짚었는지 읽을 수 있다.
ReAct의 가치 절반은 성능이고, 나머지 절반은 디버깅 가능해진다는 것이다.
31편에서 본 판별 기준을 여기 대보면 딱 맞는다. 관찰 → 판단 → 행동 → 다시 관찰. 되먹임이 있다. RAG가 에이전트가 아닌 이유가 이 그림에서 확실해진다.
Plan-and-Solve — 먼저 다 짜고 시작한다
전역적 계획 쪽은 반대다. 전체 계획을 먼저 세워두고, 그 계획을 따라 순차적으로 푼다.
한 걸음씩 가는 방식은 중간에 한 번 삐끗하면 그 오류를 안고 계속 간다. 계획을 먼저 세우면 오류가 쌓이는 것을 줄일 수 있다. 대신 처음 세운 계획이 현실과 안 맞으면 그대로 무너진다.
정답은 없고, 작업 성격에 따라 고르는 것이다. 단계가 몇 개 안 되고 상황이 계속 바뀌면 국소적으로, 절차가 정해져 있고 길면 전역적으로.
국소적 계획은 길을 걸으며 갈림길마다 정하는 것, 전역적 계획은 출발 전에 경로를 다 찍어두는 것이다. 처음 가는 동네냐 늘 가던 길이냐로 갈린다.
3. 실패 — 알아채고, 되돌아오기
계획대로 안 되는 게 기본이다. 그래서 에이전트에는 틀린 걸 알아채고 복구하는 장치가 필요하다.
Reflexion — 실패를 말로 정리해서 다시 넣는다
Reflexion의 발상이 좋았다. 실패했을 때 그냥 다시 시도하는 게 아니라, 왜 실패했는지 글로 쓰게 한 다음 그 글을 들고 재시도한다.
- 시도 — 작업을 수행하고 궤적(무엇을 했는지 기록)을 남긴다
- 평가 — 잘 됐는지 채점한다
- 반성 — 궤적을 분석해서 “어디가 잘못됐는지”를 문장으로 만든다
- 재시도 — 그 반성문을 컨텍스트에 넣고 다시 한다
여기서 중요한 건 모델 가중치를 안 건드린다는 점이다. 학습이 아니라 컨텍스트에 메모를 남기는 것이다. 그래서 싸고 빠르다.
오류를 탐지하는 데 그치지 않고, 스스로 반성(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 |
대응표를 만들다 두 군데에서 걸렸는데, 어설프게 짝지으면 오해가 될 것 같아 따로 적어둔다.
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편
참고 자료
- ReAct: Synergizing Reasoning and Acting in Language Models (ICLR 2023)
- Plan-and-Solve Prompting (ACL 2023)
- Reflexion: Language Agents with Verbal Reinforcement Learning (NeurIPS 2023)
- WebArena: A Realistic Web Environment for Autonomous Agents
- ALFWorld: Aligning Text and Embodied Environments
- Agent Workflow Memory
- LangChain 문서
한줄 평
- 세 편을 다 쓰고 나서야 검색·도구·계획이 따로 배운 게 아니라 한 물건의 부품이었다는 게 보였는데, 예전에 Express 대응표 만들어놓고 Spring Boot에 붙었던 때랑 똑같은 감각이었다!