2. 에이전트 구축 필수 개념
1장에서 첫 번째 LLM 호출을 만들고 기본적인 요청-응답 흐름을 보았습니다. 이제 우리가 실제로 무엇을 만들고 있는지 이해해야 합니다: 에이전틱 AI 시스템입니다. 이 장에서는 책 전반에 걸쳐 사용할 기반 개념을 정리합니다.
이 장을 마치면 다음을 이해하게 됩니다:
- AI 시스템을 "에이전틱"하게 만드는 요소(그리고 왜 중요한지)
- LangChain과 LangGraph 같은 프레임워크가 존재하는 이유
- LLM이 내부적으로 실제로 어떻게 동작하는지(그리고 이것이 에이전트 설계에 왜 영향을 주는지)
- LLM 사용의 경제성(토큰, 비용, 모델 선택)
- 에이전트 시스템을 위한 효과적인 프롬프트를 작성하는 방법
이 장은 개념 설명 장입니다. 3장에서 다시 실습 코딩으로 돌아갈 것입니다. 하지만 이 개념들은 에이전트를 구축할 때 내리게 될 설계 결정들을 이해하는 데 핵심입니다.
2.1) 에이전틱 AI란? (챗봇 vs 에이전트)
대부분의 사람들이 "AI 애플리케이션"을 떠올리면 챗봇을 상상합니다. 질문하면 AI가 응답하고, 대화가 끝납니다. 하지만 에이전틱 AI는 근본적으로 다릅니다. 에이전트는 단지 응답하는 것이 아니라, 목표를 달성하기 위해 자율적으로 행동합니다.
핵심 구분
명확한 정의부터 시작해 보겠습니다:
챗봇(Chatbot): 사용자 입력에 대해 생성된 텍스트로 응답하는 시스템입니다. 상호작용은 순수하게 대화적입니다. 챗봇은 다음 메시지를 생성하는 것 외에 세상에서 행동을 취할 능력이 없습니다.
에이전트(Agent): 다음을 할 수 있는 시스템입니다:
- 인지: 환경을 인지합니다(예: 주문 상태 읽기, 재고 확인).
- 결정: 어떤 행동을 할지 결정합니다(예: 티켓 생성, 이메일 전송, 또는 에스컬레이션 중 선택).
- 행동: 그 결정에 따라 행동합니다(예: API 호출, 데이터베이스 업데이트).
- 반복: 목표를 달성할 때까지 반복합니다(예: 주문 확인 → 티켓 생성 → 확인 → 완료).
핵심 차이는 자율성과 행동입니다. 챗봇은 반응적이고, 에이전트는 능동적입니다.
구체적 예시: 고객 지원
이 차이를 실제로 살펴보겠습니다.
챗봇 접근:
사용자: "주문 #12345에 대한 환불을 원합니다"
챗봇: "환불을 원하신다는 것을 이해했습니다. 처리를 위해 다음을 진행해 주세요:
1. example.com/refunds의 환불 포털을 방문하세요
2. 주문 번호를 입력하세요
3. 사유를 선택하세요
4. 양식을 제출하세요
5. 수동 검토를 위해 24-48시간 기다리세요
다른 도움 드릴 일이 있을까요?"챗봇은 정보를 제공하지만 어떤 행동도 하지 않습니다. 사용자가 모든 작업을 해야 합니다.
에이전트 접근:
사용자: "주문 #12345에 대한 환불을 원합니다"
에이전트(내부 추론):
1. 사용자는 주문 #12345의 환불을 원함
2. 이 주문이 존재하는지 확인해야 함
3. [get_order_details(order_id="12345") 호출]
4. 주문을 찾았고, 환불 대상임
5. [create_refund_ticket(order_id="12345", reason="customer_request") 호출]
6. 티켓 생성됨: TICKET-789
에이전트: "주문 #12345에 대해 환불 티켓 TICKET-789를 생성했습니다.
환불 팀에서 3-5영업일 내에 처리할 예정입니다.
곧 이메일로 확인 안내를 받으실 겁니다."에이전트는 행동을 취했습니다. 주문을 확인하고, 티켓을 생성하고, 결과를 확인해 주었습니다. 사용자는 수동 단계 없이 문제를 해결했습니다.
개발에서 왜 중요한가
이 구분을 이해하면 시스템을 설계하는 방식이 달라집니다:
챗봇 개발:
- 응답 품질과 대화 흐름에 집중
- 주요 관심사: 도움이 되고 정확한 텍스트 생성
- 단순한 아키텍처: 프롬프트 → LLM → 응답
- 외부 통합이 필요 없음
에이전트 개발:
- 의사결정과 행동 실행에 집중
- 주요 관심사: 올바른 행동 선택, 오류 처리, 상태(state) 유지
- 복잡한 아키텍처: 인지 → 추론 → 행동 선택 → 실행 → 검증
- 도구(tool) 통합, 오류 처리, 상태 관리가 필요
자율성의 스펙트럼
모든 에이전트가 동일하게 자율적이지는 않습니다. 스펙트럼이 있습니다:
레벨 1: 보조된 행동
- 에이전트가 행동을 제안하고, 사용자가 매번 승인
- 예: "환불 티켓을 만들 수 있습니다. 진행할까요?"
- 고위험 작업에 가장 안전한 접근
레벨 2: 제한된 자율성
- 에이전트가 사전에 정의된 제약 내에서 행동
- 예: 티켓 생성과 이메일 전송은 가능하지만, $500 초과 환불을 처리하거나 결제 시스템에 직접 접근할 수는 없음
- 프로덕션 시스템에서 가장 일반적(효율성과 안전의 균형)
레벨 3: 완전 자율성
- 에이전트가 목표 달성을 위해 독립적으로 행동
- 예: 사람 개입 없이 전체 환불 워크플로를 처리
- 견고한 가드레일과 모니터링이 필요
에이전트의 핵심 특성
정리하면, 에이전틱 AI 시스템은 다음의 핵심 속성을 가집니다:
- 도구 사용: 함수, API, 외부 서비스를 호출할 수 있음(이게 없으면 챗봇일 뿐)
- 목표 지향: 단지 응답하는 것이 아니라 특정 결과를 향해 작업함
- 다단계: 복잡한 작업을 행동 시퀀스로 분해함
- 적응형: 중간 결과에 따라 행동을 조정함
- 상태 유지: 여러 상호작용에 걸쳐 컨텍스트를 유지함
처음 두 가지가 필수입니다. 도구와 목표가 없으면 에이전트가 아닙니다. 나머지는 좋은 에이전트와 훌륭한 에이전트가 갈리는 품질 요소입니다.
이제 에이전트가 무엇인지와 왜 강력한지 이해했습니다.
2.2) 왜 LangChain과 LangGraph인가?
"왜 프레임워크가 필요하죠? OpenAI API를 직접 호출하면 안 되나요?"라고 궁금할 수 있습니다. 프레임워크가 존재하는 이유와 어떤 문제를 해결하는지 살펴보겠습니다.
에이전트 개발의 복잡성
API를 직접 호출해서 간단한 챗봇을 만드는 것은 쉽습니다:
import openai
response = openai.chat.completions.create(
model="gpt-5-mini",
messages=[{"role": "user", "content": "안녕하세요!"}]
)
print(response.choices[0].message.content)이 방식은 기본적인 사용 사례에는 잘 동작합니다. 하지만 에이전트를 만들기 시작하면, 복잡성이 폭발합니다:
도전 과제 1: 다단계 워크플로 에이전트는 다음을 수행해야 합니다:
- 지식 베이스에서 관련 문서를 가져오기
- 사용자 의도에 따라 어떤 도구를 호출할지 결정하기
- 도구를 실행하고 오류를 처리하기
- 결과를 포맷팅하고 사용자에게 응답하기
각 단계에는 세심한 오케스트레이션, 오류 처리, 상태 관리가 필요합니다.
도전 과제 2: 프로바이더 추상화 다음과 같은 요구가 생기면 어떻게 될까요?
- OpenAI에서 Anthropic이나 Google로 전환하기
- 작업별로 서로 다른 모델 사용하기
- 기본 모델이 실패하면 더 저렴한 모델로 폴백하기
원시 API 호출을 쓰면, 프로바이더마다 상당한 코드를 다시 작성해야 합니다.
도전 과제 3: 대화 메모리 에이전트는 컨텍스트를 기억해야 합니다:
- 대화의 이전 메시지
- 이전 질의에서 가져온 문서
- 도구 호출의 중간 결과
이 상태를 수동으로 관리하면 오류가 나기 쉽고 번거롭습니다.
도전 과제 4: 도구 통합 에이전트는 다음이 필요합니다:
- 스키마와 함께 사용 가능한 도구를 정의하기
- LLM이 어떤 도구를 호출할지 선택하게 하기
- LLM 출력에서 도구 인자를 파싱하기
- 검증과 함께 안전하게 도구를 실행하기
- 도구 오류와 재시도 로직을 처리하기
이는 많은 보일러플레이트 코드가 필요하고 모든 단계에서 보안 고려사항이 동반됩니다.
도전 과제 5: 복잡한 라우팅 실제 에이전트는 조건부 로직이 필요합니다:
- "사용자가 환불을 물어보면 정책 문서를 가져와라"
- "사용자가 환불을 원하면 티켓을 만들어라"
- "질문이 주제에서 벗어나면 정중히 거절해라"
이걸 if-else로 구현하면 금방 유지보수 불가능해집니다.
LangChain이 제공하는 것
LangChain은 LLM 애플리케이션을 구축하기 위한 프레임워크입니다. 다음을 제공합니다:
1. 모델 추상화
서로 다른 LLM 프로바이더(OpenAI, Anthropic, Google 등)를 위한 통합 인터페이스입니다.
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
# 동일한 인터페이스, 다른 프로바이더
openai_llm = ChatOpenAI(model="gpt-5-mini")
anthropic_llm = ChatAnthropic(model="claude-4-5-sonnet")
# 둘 다 동일한 메시지 포맷으로 .invoke()를 사용합니다
response = openai_llm.invoke([{"role": "user", "content": "안녕하세요"}])애플리케이션 로직을 다시 작성하지 않고도 프로바이더를 바꿀 수 있습니다.
2. 조합 가능한 체인(LCEL)
LangChain Expression Language - 컴포넌트를 파이프라인으로 연결하는 문법입니다.
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# | 연산자로 파이프라인을 조합합니다(Unix 파이프처럼)
chain = prompt | llm | output_parser
# 한 번의 호출로 전체 파이프라인을 실행합니다
result = chain.invoke({"input": "사용자 질문"})단계 사이에서 데이터를 수동으로 전달하지 않고도 복잡한 워크플로를 만들 수 있습니다. LCEL은 6장에서 배웁니다.
3. 대화 메모리
상태가 있는 대화를 위한 메시지 히스토리 관리입니다.
from langchain_core.chat_history import InMemoryChatMessageHistory
# 메시지 히스토리 헬퍼
history = InMemoryChatMessageHistory()
history.add_user_message("안녕하세요")
history.add_ai_message("안녕하세요!")
# 필요할 때 메시지를 가져옵니다
messages = history.messages
response = llm.invoke(messages)리스트를 수동으로 관리하는 대신 헬퍼 클래스로 메시지 저장을 추상화합니다. 이 단계에서는 여전히 히스토리를 명시적으로 모델에 전달합니다. 8장에서 이를 체인에 통합할 것입니다. LangGraph(15장+)는 내장 상태 관리로 이를 더 간단하게 만듭니다.
4. 도구 통합
Python 함수를 LLM에 노출하기 위한 데코레이터 기반 시스템입니다.
from langchain_core.tools import tool
@tool
def create_ticket(order_id: str, reason: str) -> str:
"""주문에 대한 지원 티켓을 생성합니다."""
# 여기에 구현
return f"{order_id}에 대한 티켓을 생성했습니다"
# LangChain이 스키마 생성과 LLM 통합을 처리합니다어떤 Python 함수든 도구로 바꿔서 도구 지원 LLM이나 에이전트가 발견하고 호출할 수 있게 만들 수 있습니다 — JSON 스키마를 수동으로 작성할 필요가 없습니다.
5. 문서 로더와 벡터 스토어
다양한 소스에서 문서를 로드하고, 검색 가능한 임베딩으로 저장하기 위한 즉시 사용 가능한 컴포넌트입니다.
from langchain_community.document_loaders import TextLoader
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
# 문서를 로드합니다
docs = TextLoader("support_docs.txt").load()
# 검색 가능한 인덱스를 만듭니다
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(docs, embeddings)
# 관련 문서를 검색합니다
results = vectorstore.similarity_search("환불 정책")파싱, 임베딩, 검색을 처음부터 구현하는 대신, 미리 만들어진 로더와 벡터 스토어(vector store)를 사용해 RAG 시스템을 구축합니다.
LangGraph가 제공하는 것
LangGraph는 복잡한 에이전트 워크플로를 위해 LangChain을 확장합니다. 다음을 제공합니다:
1. 명시적 상태 관리
변수들에 흩어져 있는 대신, 모든 에이전트 데이터를 하나의 타입 지정 스키마에 정의합니다.
from typing import TypedDict, Optional
from langchain_core.messages import BaseMessage
from langchain_core.documents import Document
class AgentState(TypedDict):
messages: list[BaseMessage]
retrieved_docs: list[Document]
current_step: Optional[str]
# 모든 에이전트 데이터가 여기에 존재합니다 - 디버깅할 때 한 곳에서 확인 가능데이터가 어디에 있고 단계 사이에서 어떻게 흐르는지 더 이상 찾아 헤매지 않아도 됩니다. 상태 변화는 명시적입니다: 노드(node)는 state에서 읽고 업데이트를 반환합니다. IDE가 필드명을 자동완성해 주고, 타입 체커가 런타임 전에 오류를 잡아줍니다.
2. 그래프 기반 워크플로
오케스트레이션 코드를 작성하는 대신, 단계(노드)와 연결(엣지)을 선언하여 워크플로를 구성합니다.
graph = StateGraph(AgentState)
# 노드(워크플로의 단계)를 정의합니다
graph.add_node("retrieve", retrieve_docs)
graph.add_node("answer", generate_answer)
# 엣지(단계 간 전이)를 정의합니다
graph.add_edge("retrieve", "answer")"retrieve가 실행된 다음 answer가 실행된다"라는 구조를 정의하면 LangGraph가 실행을 처리합니다. 단계 사이에서 상태를 전달하기 위한 오케스트레이션 코드를 작성할 필요가 없습니다. 시퀀싱이나 분기 같은 제어 흐름은 그래프 구조 자체에 선언됩니다.
3. 조건부 라우팅
워크플로에서 어떤 경로로 갈지에 대한 런타임 의사결정입니다.
from langchain_core.messages import BaseMessage
def route_request(state):
last_message = state["messages"][-1].content
if "refund" in last_message:
return "create_ticket"
else:
return "answer_question"
graph.add_conditional_edges(
"classify",
route_request,
{
"create_ticket": "create_ticket",
"answer_question": "answer_question",
}
)핵심 인사이트: 라우팅 함수는 다음 노드의 이름("create_ticket" 또는 "answer_question")을 반환합니다.
왜 중요할까요: 결정 로직은 실행과 분리됩니다. 라우팅 규칙을 바꾸고 싶나요? 함수 하나만 수정하면 됩니다. 가능한 모든 경로를 보고 싶나요? 그래프 정의를 보면 됩니다. 어떤 경로가 선택되었는지 디버깅하고 싶나요? 실행 트레이스를 확인하면 됩니다—중첩된 함수 호출을 파고들 필요가 없습니다.
4. 체크포인팅과 영속성
각 단계 이후 상태가 자동으로 체크포인트되어, 크래시 복구와 일시정지-재개 워크플로가 가능해집니다.
from langgraph.checkpoint.sqlite import SqliteSaver
checkpointer = SqliteSaver("agent_state.db")
graph = graph.compile(checkpointer=checkpointer)
# 각 단계에서 상태가 자동으로 저장됩니다
result = graph.invoke(
input_state,
config={"configurable": {"thread_id": "user-123"}}
)각 단계 이후 LangGraph는 현재 상태를 영속 저장소에 체크포인트합니다. 프로세스에 크래시가 발생하면, 동일한 thread ID에 연결된 최신 체크포인트부터 실행을 재개할 수 있습니다.
체크포인팅은 조건부 라우팅이나 인터럽트와 결합하면, 사람의 승인을 기다리는 것 같은 일시정지-재개 워크플로를 가능하게 합니다.
각 프레임워크를 언제 사용할까
LangChain을 사용할 때:
- 간단한 체인(프롬프트 → LLM → 파서)을 구축할 때
- RAG 시스템을 구현할 때
- 메시지 기반 컨텍스트(채팅 히스토리를 명시적으로 전달)를 처리할 때
- LLM 프로바이더 간 추상화가 필요할 때
LangGraph를 사용할 때:
- 다단계 에이전트 워크플로를 구축할 때
- 조건부 라우팅 로직을 구현할 때
- 단계 사이에서 복잡한 상태를 관리할 때
- 체크포인팅과 크래시 복구가 필요할 때
2.3) LLM은 어떻게 동작하는가 (에이전트 구축자를 위해)
효과적인 에이전트를 만들려면 LLM이 실제로 어떻게 동작하는지 이해해야 합니다. 이건 트랜스포머의 수학이 아니라, 에이전트 시스템을 설계하는 방식에 영향을 주는 멘탈 모델에 관한 것입니다.
핵심 메커니즘: 토큰 예측
근본적인 인사이트는 이것입니다: LLM은 데이터베이스처럼 사실을 "알고" 있는 것이 아니라, 다음 토큰을 예측할 뿐입니다.
구체적인 예로 풀어봅시다.
입력: "The capital of France is"
당신이 일어날 거라고 생각할 수 있는 과정:
- LLM이 지식 베이스에서 프랑스의 수도를 "조회"한다
- LLM이 답을 "검색"한다: Paris
- LLM이 "Paris"를 반환한다
실제로 일어나는 과정:
- LLM이 입력을 토큰으로 변환한다:
["The", "capital", "of", "France", "is"] - LLM이 가능한 모든 다음 토큰에 대한 확률 분포를 계산한다
- 가장 가능성이 높은 다음 토큰:
"Paris"(가장 높은 확률) - LLM이 분포에서 샘플링한다(보통 가장 높은 확률을 선택)
- LLM이 "Paris"를 반환한다
LLM은 Paris가 수도라는 것을 "아는" 것이 아닙니다. 입력 패턴이 주어졌을 때 "Paris"가 다음 토큰으로 가장 그럴듯하다고 예측하는 것입니다.
(참고: 토크나이제이션과 확률은 개념적 설명이며, 모델과 토크나이저에 따라 달라집니다.)
이것이 에이전트에 왜 중요한가
이 토큰 예측 모델은 에이전트 설계에 매우 큰 함의를 가집니다:
함의 1: LLM은 환각을 일으킬 수 있습니다
LLM은 사실을 검색하는 것이 아니라 토큰을 예측하기 때문에, 그럴듯하지만 틀린 정보를 생성할 수 있습니다.
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke("아틀란티스의 수도는 무엇인가요?")
print(response.content)
# 참고: 실제 출력은 달라질 수 있습니다.
# 최신 모델은 아틀란티스를 허구로 인식하고 답변을 거부할 수도 있습니다.
# 핵심 포인트:
# 명시적인 사실 검증이 없으면, LLM은 그럴듯하지만 틀린 정보를 생성할 수 있습니다.가능한(과거 모델/제약이 덜한 설정의) 출력 예시:
아틀란티스의 수도는 에테리아(Etheria)입니다. 플라톤의 기록에 따르면, 에테리아는 섬의 최동단 끝에 위치한 항구 도시였으며, 그 이름은 하늘에 닿을 만큼 높이 솟아 있다는 믿음에서 유래했습니다.아틀란티스는 허구인데도 LLM은 그럴듯한 답을 만들어냅니다. 에이전트에서는 이것이 다음을 의미합니다:
- LLM 출력을 맹목적으로 신뢰하지 마세요
- 권위 있는 출처를 통해 사실을 검증하세요
- 가드레일을 구현하세요
함의 2: 컨텍스트가 핵심입니다
LLM은 당신이 제공한 토큰만 봅니다. 이전 대화를 명시적으로 포함하지 않는 한, 이전 대화에 대한 메모리가 없습니다.
# 첫 번째 호출
response1 = llm.invoke("내 이름은 Alice야")
print(response1.content) # "만나서 반가워요, Alice!"
# 두 번째 호출(별개의 invocation)
response2 = llm.invoke("내 이름이 뭐였지?")
print(response2.content) # "저는 당신의 이름에 접근할 수 없습니다..."두 번째 호출에는 첫 번째 호출의 컨텍스트가 없습니다. 에이전트에서는 이것이 다음을 의미합니다:
- 대화 히스토리를 관리해야 합니다
- 컨텍스트 윈도우 제한이 중요합니다
- 상태 관리는 핵심입니다
함의 3: 프롬프트는 질의가 아니라 지시문이다
LLM은 토큰을 예측하기 때문에, 프롬프트를 어떻게 표현하느냐가 출력 품질에 큰 영향을 줍니다.
# 약한 프롬프트(질의 형태)
response = llm.invoke("환불 정책")
# 출력: "환불 정책에 대해 무엇을 알고 싶으신가요?"
# 강한 프롬프트(지시 형태)
response = llm.invoke(
"당신은 고객 지원 담당자입니다. 환불 정책을 명확하고 간결하게 설명하세요."
)
# 출력: "환불 정책은 30일 이내 반품을 허용합니다..."에이전트에서는 이것이 다음을 의미합니다:
- 프롬프트가 주요 제어 메커니즘입니다
- 프롬프트 엔지니어링은 핵심 역량입니다
- 시스템 메시지는 에이전트 행동을 설정합니다
함의 4: 구조화된 출력은 가이드를 필요로 한다
LLM은 본래 자유 형식 텍스트를 생성합니다. 구조화된 출력(JSON, 특정 포맷)을 얻으려면 명시적인 지시가 필요합니다.
# 구조 가이드 없이
response = llm.invoke("다음에서 주문 ID를 추출해: '주문 #12345에 대한 환불을 원합니다'")
print(response.content)
# 출력: "주문 ID는 12345입니다"(일반 텍스트, 포맷이 일관되지 않음)
# 구조 가이드 포함
response = llm.invoke(
'주문 ID를 추출하고 다음 포맷의 JSON 객체만 반환하세요: {"order_id": "..."}\n\n'
"텍스트: '주문 #12345에 대한 환불을 원합니다'"
)
print(response.content)
# 출력: {"order_id": "12345"} (구조화되고 파싱 가능)에이전트에서는 이것이 다음을 의미합니다:
- 스키마로 출력 포맷을 제약하세요
- 출력 포맷을 명시적으로 지정하세요
- LLM 응답을 검증하고 파싱하세요
자유 형식 텍스트는 사람을 위해 최적화되어 있습니다. 에이전트는 기계가 읽을 수 있도록 명시적인 포맷팅이 필요합니다.
결정성, 확률성, 그리고 현대 LLM
LLM은 근본적으로 확률적 시스템입니다. 결정적인 규칙을 실행하는 것이 아니라, 가장 가능성이 높은 다음 토큰을 예측하여 텍스트를 생성합니다. 따라서 같은 입력이 항상 같은 출력을 보장하지는 않습니다.
초기 모델에서는 개발자가 temperature 같은 파라미터로 이 랜덤성을 명시적으로 제어했습니다. 낮은 값은 예측 가능한 출력을 만들고, 높은 값은 변형과 창의성을 장려했습니다.
많은 현대의 추론 지향(reasoning-oriented) 모델은 temperature나 top_p 같은 파라미터를 더 이상 노출하지 않습니다. 대신 안정적이고 구조화된 추론을 우선하도록 디코딩과 샘플링 전략을 내부적으로 관리합니다. 하지만 그렇다고 해서 이 모델들이 완전히 결정적인 것은 아닙니다.
이런 모델들조차 동일한 출력을 보장하지 않습니다. 출력은 다음 때문에 달라질 수 있습니다:
- 내부 샘플링: 모델이 서로 다른 추론 경로를 따라가며, 구조/디테일/표현이 달라질 수 있습니다.
- 모델 업데이트: 프로바이더는 공지 없이 모델을 지속적으로 업데이트하므로, 같은 프롬프트가 시간이 지나면 다른 응답을 낼 수 있습니다.
- 안전 필터: 콘텐츠 모더레이션 때문에 어떤 경우에는 직접 답하지만 다른 경우에는 완곡하게 말하거나 거절하거나 재구성할 수 있습니다.
- 도구 정책: 에이전트 시스템에서 모델은 같은 입력에 대해 다른 도구를 호출하거나—혹은 아무 도구도 호출하지 않아—실행 경로가 바뀔 수 있습니다.
이것이 에이전트 구축자에게 의미하는 바:
핵심 관심사는 파라미터 튜닝이 아니라 예측 가능성(predictability)입니다. 에이전트 시스템은 명시적으로 제약하지 않는 한 LLM 출력이 단어 선택, 구조, 심지어 결론까지도 달라질 수 있다고 가정하고 설계해야 합니다.
이는 에이전트 시스템을 위한 몇 가지 구체적인 설계 원칙으로 이어집니다:
- 로직 결정에서 특정 단어나 문구에 의존하지 마세요 — 제어 흐름은 모델 출력의 특정 문구 매칭이 아니라 구조화된 신호(스키마, enum, 플래그)에 의존해야 합니다.
- LLM 출력을 항상 구조화하세요 — LLM 출력이 코드에 의해 소비될 때마다 스키마, 밸리데이터, 엄격한 포맷으로 제약해 프로그램이 자유 텍스트를 "해석"하지 않도록 하세요.
- 중요한 것은 무엇이든 검증하세요 — 돈, 권한, 되돌릴 수 없는 행동에 영향을 주는 사실은 도구나 외부 시스템으로 확인해야 하며, 모델만 믿어서는 안 됩니다.
- LLM 출력은 결정이 아니라 제안으로 취급하세요 — 모델은 무엇을 할지 제안하지만, 시스템이 실제로 행동할지/언제/어떻게 할지를 결정합니다.
현대 에이전트 시스템에서 신뢰성은 파라미터 튜닝이 아니라 시스템 설계에서 나옵니다. 작업이 중요할수록 모델이 가질 수 있는 자유는 줄어야 하고, 에이전트가 강제하는 구조는 더 많아야 합니다.
핵심 요약: 일관된 LLM 출력을 기대하기보다, 스키마/검증/도구 통합으로 신뢰할 수 있는 에이전트를 구축하세요.
다음 섹션에서는 토큰 기반 처리의 경제적 함의를 살펴보겠습니다.
2.4) 토큰: 기본 자원
토큰(tokens)은 LLM이 처리하는 기본 단위입니다. 토큰을 이해하는 것은 필수인데, 에이전트 시스템에서 토큰이 무엇이 가능한지(제약)와 무엇이 비용이 드는지(비용)를 모두 정의하기 때문입니다.
토큰이란?
토큰(token)은 LLM이 추론하고 생성하는 텍스트의 가장 작은 단위입니다.
언어와 컨텍스트에 따라 토큰은 다음을 나타낼 수 있습니다:
- 단어(
agent) - 단어의 일부(
calculat,ion) - 숫자 또는 기호(
#,123) - 문장부호 또는 공백
토큰은 문자도 아니고 단어도 아닙니다 — 토큰은 토크나이저가 만들어내는 모델별 단위입니다.
모델에 보내거나 모델이 생성하는 모든 정보는 토큰으로 측정됩니다:
- 시스템 지시문
- 사용자 메시지
- 검색된 문서
- 도구 설명
- 모델 출력
토큰은 LLM 상호작용의 기본 화폐(currency)입니다.
시스템 제약으로서의 토큰
토큰은 비용일 뿐만 아니라, 에이전트가 한 번의 요청에서 할 수 있는 일을 제한하는 하드 리밋이기도 합니다.
모든 LLM에는 컨텍스트 윈도우(context window)가 있습니다. 한 번에 처리할 수 있는 최대 토큰 수가 고정되어 있습니다.
예시 시나리오: 고객 지원 에이전트에 필요한 것:
- 시스템 지시문: 200 토큰
- 최근 10개 메시지: ~2,000 토큰
- 검색된 도움말 글 3개: ~1,500 토큰
- 생성 응답: ~200 토큰
- 총합: 3,900 토큰
모델의 컨텍스트 윈도우가 4,000 토큰이면, 97.5%를 사용 중입니다. 긴 메시지 하나만 더 들어오면 시스템이 동작을 멈춥니다.
(현대 모델은 보통 128K+ 토큰 컨텍스트 윈도우를 제공하지만, 원리는 같습니다: 컨텍스트는 유한하며 이 제한을 고려해 설계해야 합니다.)
한도를 초과하면 발생하는 일:
- 오래된 메시지가 삭제됨 → 에이전트가 이전 컨텍스트를 잊어 대화 연속성이 깨짐
- 검색된 문서가 잘림 → 중요한 정보가 사라져 오답으로 이어짐
- 요청 자체가 실패함 → 시스템이 아예 응답하지 못함
더 많은 공간을 돈으로 살 수 없습니다. 컨텍스트 윈도우는 하드 리밋입니다—1리터 병에 2리터를 담으려는 것과 같습니다.
그래서 장시간 동작하는 에이전트는 컨텍스트에 무엇을 남기고 무엇을 남기지 않을지 적극적으로 관리해야 합니다. 토큰 관리는 최적화 디테일이 아니라 핵심 아키텍처 관심사입니다.
토큰이 직접 영향을 주는 것
즉각적인 컨텍스트 윈도우 한도 외에도, 토큰은 두 가지 중요한 설계 결정을 좌우합니다:
1. 메모리 전략: 전체 히스토리 vs 요약
전체 대화 히스토리를 유지하면 디테일은 보존되지만 토큰 사용량이 계속 증가합니다.
일반적인 대안은 메모리 요약(memory summarization)입니다:
- 오래된 메시지를 간결한 요약으로 대체
- 토큰 비용을 줄이면서 의도를 보존
이 트레이드오프는 다음에 영향을 줍니다:
- 비용
- 정확도
- 장기적인 에이전트 일관성
따라서 메모리 설계는 토큰 관리 문제입니다.
2. RAG 청크 크기와 검색 전략
검색 증강 생성(RAG)에서는 검색 전에 문서를 청크로 분할합니다.
-
큰 청크
- 검색 호출 횟수 적음
- 요청당 토큰 비용 높음
- 관련 없는 컨텍스트 많음
-
작은 청크
- 토큰 비용 낮음
- 정밀도 높음
- 핵심 정보 누락 위험
청크 크기는 중요한 설계 결정입니다 — 비용과 답변 품질에 직접 영향을 줍니다.
토큰 경제학
토큰 비용을 이해하면 비용 효율적인 시스템을 구축할 수 있습니다.
일반적인 가격(2026):
| 모델 | 입력(토큰 100만 개당) | 출력(토큰 100만 개당) |
|---|---|---|
| GPT-5 | $1.25 | $10.00 |
| GPT-5-mini | $0.25 | $2.00 |
| Claude 4.5 Sonnet | $3.00 | $15.00 |
| Gemini 3 Pro | $2.00 | $12.00 |
| Gemini 3 Flash | $0.50 | $3.00 |
핵심 인사이트: 출력 토큰은 입력 토큰보다 4–8배 비싸므로, 프로덕션 시스템에서는 통제되지 않은 생성이 가장 큰 비용 요인이 되는 경우가 많습니다.
빠른 비용 추정:
GPT-5-mini로 입력 500 토큰과 출력 50 토큰을 사용하는 일반적인 요청의 경우:
- 입력: (500 / 1,000,000) × $0.25 = $0.000125
- 출력: (50 / 1,000,000) × $2.00 = $0.0001
- 총합: 요청당 ~$0.000225
하루 10,000건 요청이면: 월 ~$67.5
실전 비용 최적화
전략 1: 작업 복잡도에 맞는 모델 매칭
단순한 작업에는 더 작고 저렴한 모델을 사용하세요:
from langchain_openai import ChatOpenAI
# 복잡한 추론을 위한 비싼 모델
complex_llm = ChatOpenAI(model="gpt-5")
# 단순한 작업을 위한 저렴한 모델
simple_llm = ChatOpenAI(model="gpt-5-nano")
def get_llm_for_task(task_type):
if task_type == "complex_reasoning":
return complex_llm
else:
return simple_llm전략 2: 컨텍스트 크기와 품질의 균형
필요한 컨텍스트만 포함하세요:
# 효율적: 관련 청크만 포함
relevant_chunks = retrieve_top_k(user_question, k=3) # 약 500 토큰
prompt = f"Context: {relevant_chunks}\n\nQuestion: {user_question}"중요: 컨텍스트를 과하게 줄이면 답변 정확도가 떨어질 수 있습니다. 비용 절감과 품질의 균형을 맞추세요.
전략 3: 출력 길이 제어
모델이 생성하는 양을 제한하세요:
# 비용 통제
llm = ChatOpenAI(model="gpt-5-mini", max_tokens=100)
response = llm.invoke(messages)
# 최대 100개 출력 토큰핵심 요약: 토큰 관리는 비용 절감만의 문제가 아니라, 하드한 자원 제약 내에서 신뢰할 수 있고 확장 가능한 에이전트 시스템을 설계하는 문제입니다.
다음 섹션에서는 모델 지형을 살펴보고 각 작업에 적합한 모델을 고르는 방법을 배웁니다.
2.5) 모델 지형 이해하기
에이전트에 적합한 LLM을 고르려면 다음의 균형을 맞춰야 합니다:
- 컨텍스트 윈도우: 모델이 처리할 수 있는 텍스트의 양은?
- 비용: 요청당 비용은?
- 지연 시간: 모델이 얼마나 빠르게 응답하는가?
- 성능: 모델이 얼마나 잘 추론하는가?
2026년 모델 지형을 살펴보겠습니다.
주요 모델 계열
OpenAI GPT 모델
| 모델 | 컨텍스트 윈도우 | 입력 비용 | 출력 비용 | 지연 시간 | 적합한 용도 |
|---|---|---|---|---|---|
| GPT-5 | 400K tokens | $1.25 / 1M | $10.00 / 1M | ~2–4s | 복잡한 추론, 고급 코드 |
| GPT-5-mini | 400K tokens | $0.25 / 1M | $2.00 / 1M | ~1.5–3s | 일반 작업, 채팅, 요약 |
| GPT-5-nano | 400K tokens | $0.05 / 1M | $0.40 / 1M | ~1–2s | 분류, 추출 |
Anthropic Claude 모델
| 모델 | 컨텍스트 윈도우 | 입력 비용 | 출력 비용 | 지연 시간 | 적합한 용도 |
|---|---|---|---|---|---|
| Claude Opus 4.5 | 200K tokens | $5.00 / 1M | $25.00 / 1M | ~2–4s | 깊은 추론, 분석 |
| Claude Sonnet 4.5 | 200K tokens | $3.00 / 1M | $15.00 / 1M | ~1.5–3s | 균형 잡힌 성능, 코딩 |
Google Gemini 모델
| 모델 | 컨텍스트 윈도우 | 입력 비용 | 출력 비용 | 지연 시간 | 적합한 용도 |
|---|---|---|---|---|---|
| Gemini 3.0 Pro | 1M tokens | $2.00 / 1M | $12.00 / 1M | ~3–5s | 초대형 컨텍스트, 리서치 |
| Gemini 3.0 Flash | 1M tokens | $0.50 / 1M | $3.00 / 1M | ~1–2s | 대용량 처리 애플리케이션 |
프로바이더별 특징
OpenAI:
- 적합한 용도: 범용 에이전트, 광범위한 멀티도메인 워크플로, 함수 호출(function calling)
- 강점: 범용적인 추론, 강력한 개발자 도구 및 생태계, 잦은 모델 업데이트
Anthropic:
- 적합한 용도: 안전 민감 워크플로, 구조화되고 확장된 추론
- 강점: 깊은 분석, 체계적인 출력, 강한 정렬(alignment)을 바탕으로 한 확장된 사고
Google:
- 적합한 용도: 초대형 컨텍스트 입력과 멀티모달 작업
- 강점: 대규모 문서 분석, 멀티모달 이해, 높은 처리량 프로세싱
작업에 모델 맞추기
작업 복잡도, 컨텍스트 크기, 지연 시간 요구에 따라 선택하세요:
작업 복잡도별
단순 작업 → 비용 최적화 모델(GPT-5-nano, GPT-5-mini):
- 의도 분류, 감성 분석, 키워드 추출, 간단한 포맷팅
- 선택 기준: 비용이 최우선일 때
중간 작업 → 균형형 모델(GPT-5-mini):
- 질의응답, 요약, 도구 선택
- 선택 기준: 비용과 품질의 균형이 필요할 때
복잡 작업 → 성능 중심 모델(GPT-5, Claude Sonnet, Gemini Pro):
- 다단계 추론, 코드 생성, 상세 분석
- 선택 기준: 추론 성능이 중요하고 비용이 감내 가능할 때
최고 난이도 → 프리미엄 모델(Claude Opus):
- 매우 복잡한 추론, 미션 크리티컬 결정
- 선택 기준: 정확도가 최우선이고 비용은 부차적일 때
지연 시간 요구별
실시간(인지 지연 ~1초 이하) → 빠른 모델(GPT-5-nano, Gemini Flash):
- 사용자 대면 채팅
- 인터랙티브 애플리케이션
준실시간(1-3초) → 대부분의 모델:
- 표준 에이전트 작업
배치(>3초) → 성능 좋은 모델:
- 백그라운드 분석
컨텍스트 윈도우 고려사항
표준 작업: 주요 모델은 모두 200K+ 토큰을 지원하며, 대부분의 에이전트 워크플로에 충분합니다.
특수 케이스:
- 400K 토큰이 필요: GPT-5 계열(전체 문서 분석, 대규모 코드베이스)
- 1M 토큰이 필요: Gemini 모델(전체 책, 방대한 문서 세트)
실용적 조언: 큰 컨텍스트 윈도우가 있어도 선택적 검색(RAG)이 보통 더 나은 결과를 냅니다.
핵심 요약
- 단 하나의 "최고" 모델은 없다 → 모델마다 강점이 다름
- 작업 복잡도에 맞게 모델을 선택하세요 → 간단한 작업에 과다 지불하지 마세요
- 컨텍스트 윈도우 ≠ 더 낫다 → 선택적 검색을 사용하세요
- 지연 시간은 사용자 경험에 영향을 줍니다 → 대화형 작업에 응답 시간을 고려하세요
실전에서는: 대부분의 에이전트가 여러 모델을 사용합니다—단순 작업에는 저렴한 모델, 복잡한 추론에는 성능 좋은 모델을 사용합니다. 이는 이후 장에서 구현할 것입니다.
다음 섹션에서는 효과적인 프롬프팅으로 모델 행동을 제어하는 방법을 배웁니다.
2.6) 에이전트 시스템을 위한 프롬프팅 기초
프롬프트는 LLM 동작을 제어하는 주요 인터페이스입니다. 에이전트에서는 효과적인 프롬프팅이 핵심입니다. 에이전트가 올바른 결정을 내리고, 올바른 도구를 호출하며, 신뢰할 수 있는 출력을 만들 수 있는지를 좌우합니다.
프롬프트의 구성 요소
완전한 프롬프트는 세 가지 구성 요소를 가집니다:
1. 시스템 메시지(System Message: 역할과 제약) 에이전트의 페르소나, 기능, 경계를 정의합니다.
2. 컨텍스트(Context: 관련 정보) 작업을 완료하는 데 필요한 정보를 제공합니다.
3. 지시(Instruction: 구체적인 작업) 에이전트가 정확히 무엇을 해야 하는지 알려줍니다.
실제로 보면 다음과 같습니다:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-5-mini")
response = llm.invoke([
# 시스템 메시지: 역할과 제약을 정의합니다
{
"role": "system",
"content": """당신은 TechCorp의 고객 지원 담당자입니다.
당신의 기능:
- 환불 정책에 대한 질문에 답변하기
- 지원 티켓 생성하기
- 문제 해결 가이드 제공하기
당신의 제약:
- TechCorp 제품에 대한 질문에만 답변하기
- 환불 처리 일정에 대해 약속하지 않기
- 항상 정중하고 전문적으로 응대하기"""
},
# 사용자 메시지: 컨텍스트 + 지시
{
"role": "user",
"content": """컨텍스트: 고객은 2026-01-15에 노트북 모델 X500을 구매했습니다.
오늘은 2026-02-20입니다. 환불 정책은 30일 이내 반품을 허용합니다.
지시: 고객이 환불을 원합니다. 제가 무엇을 말해야 하나요?"""
}
])
print(response.content)출력:
노트북 모델 X500에 대해 환불을 원하신다는 점 이해했습니다. 안타깝게도
구매일이 1월 15일이고 오늘이 2월 20일이므로,
30일 반품 가능 기간을 초과했습니다. 다만 보증 서비스나 교환 같은
다른 옵션을 검토할 수 있도록 지원 티켓을 생성해 드릴 수 있습니다.
그렇게 진행해 드릴까요?시스템 메시지: 에이전트 행동 설정하기
시스템 메시지는 에이전트의 성격과 기능을 정의하는 곳입니다. 에이전트 프롬프팅에서 가장 중요한 부분입니다.
약한 시스템 메시지:
system_message = "당신은 도움이 되는 어시스턴트입니다."강한 시스템 메시지:
system_message = """당신은 TechCorp의 고객 지원 담당자입니다.
ROLE:
당신은 고객의 환불 요청, 제품 질문, 기술 문제를 돕습니다.
CAPABILITIES:
- 제공된 문서를 사용해 질문에 답변하기
- 필요 시 지원 티켓 생성하기
- 단계별 문제 해결 안내 제공하기
CONSTRAINTS:
- TechCorp 제품에 대한 질문에만 답변하기
- 모르는 것은 모른다고 말하기 - 절대 추측하지 않기
- 문서를 사용할 때는 항상 출처를 인용하기
- 구체적인 일정이나 결과를 약속하지 않기
TONE:
전문적이고, 공감적이며, 해결 중심.
"""강한 버전이 더 잘 작동하는 이유:
- 명시적 기능 → 에이전트가 무엇을 할 수 있는지 앎
- 명확한 제약 → 무엇을 피해야 하는지 앎
- 정의된 톤 → 일관된 페르소나
- 구체적 지시 → 모호성 감소
지시의 명확성: 구체적으로 작성하기
LLM은 지시를 문자 그대로 따릅니다. 모호한 지시는 신뢰할 수 없는 결과를 만듭니다.
모호한 지시:
instruction = "고객의 환불을 도와줘."구체적인 지시:
instruction = """고객의 요청을 분석하고 다음을 결정하세요:
1. 주문이 환불 대상인가요? (구매일 vs 환불 정책 확인)
2. 대상이라면: 환불 절차를 설명하세요
3. 대상이 아니라면: 이유를 설명하고 대안을 제시하세요
응답을 다음 포맷으로 작성하세요:
- Eligibility: [YES/NO]
- Reason: [간단한 설명]
- Next Steps: [고객이 해야 할 일]
"""컨텍스트 관리: 필요한 것만 제공하기
에이전트는 결정을 내리기 위해 컨텍스트가 필요하지만, 컨텍스트가 너무 많으면 토큰을 낭비하고 모델을 혼란스럽게 합니다.
과도한 컨텍스트(낭비적):
context = f"""
회사 연혁: TechCorp는 1995년에 설립...
제품 카탈로그: 500개 이상의 제품을 판매...
환불 정책: {refund_policy_text}
배송 정책: {shipping_policy_text}
보증 정책: {warranty_policy_text}
고객 이력: {full_customer_history}
"""
# 5000+ 토큰, 대부분 관련 없음선택적 컨텍스트(효율적):
context = f"""
관련 정책: {refund_policy_text}
주문 상세: {order_details}
"""
# 200 tokens, 모두 관련 있음출력 포맷팅: 응답을 구조화하기
에이전트에서는 자유 형식 텍스트가 아니라 구조화된 출력(JSON, 특정 포맷)이 필요한 경우가 많습니다.
비구조화 출력(파싱이 어려움):
response = llm.invoke([
{"role": "system", "content": "당신은 지원 담당자입니다."},
{"role": "user", "content": "이 환불 요청에 대해 티켓을 만들어야 하나요?"}
])
print(response.content)
# 출력: "네, 티켓을 만들어야 할 것 같습니다. 왜냐하면..."
# 문제: 파싱이 어렵고 포맷이 일관되지 않음구조화 출력(파싱이 쉬움):
response = llm.invoke([
{"role": "system", "content": """당신은 지원 담당자입니다.
항상 다음 JSON 포맷으로 응답하세요:
{
"action": "CREATE_TICKET" or "ANSWER_QUESTION" or "ESCALATE",
"reason": "간단한 설명",
"response_text": "고객에게 전달할 문구"
}"""},
{"role": "user", "content": "고객이 주문 #12345에 대한 환불을 원합니다. 구매일은 40일 전입니다"}
])
print(response.content)출력:
{
"action": "CREATE_TICKET",
"reason": "주문이 30일 환불 가능 기간을 초과하여 수동 검토가 필요함",
"response_text": "환불 요청을 검토할 수 있도록 지원 티켓을 생성했습니다. 저희 팀에서 24시간 내에 연락드리겠습니다."
}7장에서는 강건한 구조화 출력을 위해 Pydantic 스키마를 사용할 것입니다.
퓨샷 예시(Few-Shot Examples): 말만 하지 말고 보여주기
복잡한 작업에서는 긴 지시문보다 예시를 제공하는 것이 더 효과적입니다.
제로샷(지시만):
prompt = """고객 메시지에서 주문 ID, 제품명, 문제를 추출하세요.
고객 메시지: "내 노트북 X500 주문 #12345가 켜지지 않아요"
"""
# 모델이 포맷에서 어려움을 겪을 수 있음퓨샷(예시 포함):
prompt = """고객 메시지에서 주문 ID, 제품명, 문제를 추출하세요.
예시 1:
입력: "내 노트북 X500 주문 #12345가 켜지지 않아요"
출력: {"order_id": "12345", "product": "노트북 X500", "issue": "켜지지 않아요"}
예시 2:
입력: "주문 67890 - 휴대폰이 충전되지 않아요"
출력: {"order_id": "67890", "product": "휴대폰", "issue": "충전되지 않아요"}
이제 다음 메시지에서 추출하세요:
입력: "내 태블릿 주문 #11111 화면이 깨졌어요"
출력:
"""모델은 예시에서 패턴을 학습하고 이를 일관되게 적용합니다.
에이전트를 위한 프롬프트 엔지니어링 패턴
패턴 1: Chain of Thought(추론)
복잡한 결정에는 모델에게 "단계별로 생각하라"고 요청하세요:
prompt = """지원 티켓을 생성할지 결정해야 합니다.
다음 순서대로 단계별로 생각하세요:
1. 고객은 무엇을 요청하고 있나요?
2. 기존 문서로 답변할 수 있나요?
3. 수동 개입이 필요한가요?
4. 어떤 행동을 취해야 하나요?
고객 메시지: "주문 #12345에 대해 환불을 원하지만 영수증을 잃어버렸어요"
추론:
"""모델이 명시적으로 추론을 보여 주어 결정이 더 투명하고 신뢰할 수 있게 됩니다.
패턴 2: 제약된 생성(Constrained Generation, 안전)
모델의 가능한 출력 범위를 제한하세요:
prompt = """고객의 의도를 분류하세요. 다음 옵션 중 정확히 하나로만 응답하세요:
- REFUND_REQUEST
- PRODUCT_QUESTION
- TECHNICAL_ISSUE
- OFF_TOPIC
고객 메시지: "비밀번호를 어떻게 재설정하나요?"
분류:
"""이렇게 하면 모델이 예상치 못한 출력을 생성하는 것을 막을 수 있습니다. 출력 옵션을 명시적으로 제한하면:
- 파싱 오류를 방지합니다(항상 네 가지 옵션 중 하나)
- 의도치 않은 행동을 차단합니다(정의되지 않은 행동 실행 방지)
- 디버깅을 단순화합니다(출력 공간이 제한되어 문제 추적이 쉬움)
패턴 3: 자기 비평(Self-Critique, 품질)
모델이 자신의 출력을 검증하고 개선하도록 요청하세요:
prompt = """고객에게 응답을 생성한 다음, 그것을 비평하세요.
고객 메시지: "환불을 원합니다"
1단계 - 응답 생성:
[여기에 응답]
2단계 - 비평:
- 이 응답은 정확한가요?
- 도움이 되나요?
- 회사 정책을 준수하나요?
- 무엇을 개선할 수 있나요?
3단계 - 최종 응답(비평을 반영):
[개선된 응답]
"""이 다단계 접근은 종종 더 높은 품질의 출력을 만드는데, 그 이유는 다음과 같습니다:
- 초기에 오류를 포착: 모델이 최종 확정 전에 자신의 추론을 검토
- 톤과 명확성 개선: 자기 성찰을 통해 불명확하거나 부적절한 표현 식별
- 정책 준수 보장: 비평 단계에서 가이드라인 준수 여부 확인
흔한 프롬프팅 실수
실수 1: 모델이 뭔가를 "알고 있을 것"이라고 가정하기
LLM은 실시간 정보나 암묵적 컨텍스트에 접근할 수 없습니다. 필요한 데이터는 빠짐없이 명시적으로 제공하세요.
# 나쁨: 모델이 현재 날짜를 안다고 가정
prompt = "이 주문은 환불 대상인가요? 주문 #12345"
# 좋음: 필요한 정보 모두 제공
prompt = f"""이 주문은 환불 대상인가요?
주문 #12345
구매일 {purchase_date}
오늘: {current_date}
정책: 30일 이내 반품
"""실수 2: 모호한 지시
"처리해", "진행해", "다뤄줘"와 같은 모호한 동사는 해석의 여지를 너무 많이 남깁니다. 필요한 정확한 행동을 명시적으로 작성하세요.
# 나쁨: "처리"가 무슨 의미인지 불명확
prompt = "이 환불 요청을 처리해"
# 좋음: 명시적 행동
prompt = "이 환불 요청이 대상인지 판단하세요. 대상이면 티켓을 생성하세요. 대상이 아니면 이유를 설명하세요."실수 3: 컨텍스트 과부하
무관한 정보를 포함하면 토큰을 낭비하고 지연 시간을 늘리며 모델을 혼란스럽게 할 수 있습니다. 특정 작업에 필요한 것만 가져오세요.
# 나쁨: 10,000 tokens 컨텍스트, 대부분 무관
prompt = f"""
{entire_knowledge_base}
질문: 환불 정책이 뭐예요?
"""
# 좋음: 관련 섹션만 검색
prompt = f"""
{refund_policy_section}
질문: 환불 정책이 뭐예요?
"""실수 4: 일관되지 않은 포맷
출력 형식이 달라지면 후속 코드가 중단됩니다. 특히 구조화된 데이터에서는 기대하는 정확한 포맷을 항상 지정하세요.
# 나쁨: 어떤 때는 JSON, 어떤 때는 일반 텍스트
prompt = "결정에 대해 응답해"
# 좋음: 항상 포맷 지정
prompt = 'JSON 포맷으로 응답하세요: {"decision": "...", "reason": "..."}'핵심 요약
에이전트를 위한 효과적인 프롬프팅에는 다음이 필요합니다:
- 명확한 시스템 메시지 → 역할, 기능, 제약 정의
- 구체적인 지시 → 모델이 정확히 무엇을 해야 하는지 명시
- 선택적 컨텍스트 → 관련 정보만 제공
- 구조화된 출력 → 포맷을 명시적으로 지정
- 퓨샷 예시 → 원하는 패턴을 보여주기
- 추론 프롬프트 → 단계별 사고 요청
프롬프팅은 연습으로 향상되는 기술입니다. 이 책 전반에서 실제 에이전트 시스템에 적용되는 이 패턴들을 보게 될 것입니다.
장 요약
이제 에이전틱 AI 시스템을 구축하기 위한 기반 개념을 이해했습니다:
에이전틱 AI vs 챗봇:
- 에이전트는 목표 달성을 위해 자율적으로 행동합니다
- 에이전트는 도구를 사용하고 다단계 결정을 내립니다
- 에이전트는 상태를 유지하고 결과에 따라 적응합니다
프레임워크가 중요한 이유:
- LangChain은 모델 추상화, 체인(chain), 메모리, 도구, RAG를 제공합니다
- LangGraph는 상태 관리, 라우팅, 체크포인팅을 추가합니다
- 프레임워크는 보일러플레이트를 줄이고 복잡한 워크플로를 가능하게 합니다
LLM 메커니즘:
- LLM은 사실을 검색하지 않고 토큰을 예측합니다
- 컨텍스트는 암묵적이 아니라 명시적입니다
- 프롬프트는 질의가 아니라 지시문입니다
- 구조화된 출력은 가이드가 필요합니다
- 컨텍스트 윈도우는 유한합니다
토큰 경제학:
- 출력 토큰은 입력 토큰보다 4–8배 비쌉니다
- 모델 선택은 10-100배의 비용 영향을 가집니다
- 선택적 컨텍스트는 비용을 극적으로 줄입니다
- 모니터링과 예산은 폭주하는 지출을 방지합니다
모델 선택:
- 작업 복잡도에 맞게 모델을 선택하세요
- 컨텍스트 요구사항과 지연 시간 제약을 고려하세요
- 비용 최적화를 위해 멀티 모델 아키텍처를 사용하세요
- 저렴한 모델로 시작하고, 필요할 때만 업그레이드하세요
프롬프팅 기본:
- 시스템 메시지는 에이전트 행동을 정의합니다
- 구체적인 지시는 신뢰할 수 있는 결과를 만듭니다
- 선택적 컨텍스트는 품질을 높이고 비용을 줄입니다
- 구조화된 출력은 파싱과 검증을 가능하게 합니다
- 퓨샷 예시는 패턴을 효과적으로 가르칩니다