서론
2023년 초, 한 스타트업의 CTO가 필자에게 절박한 전화를 걸어왔다. “우리는 GPT-4로 고객 응대 챗봇을 만들었어요. 프롬프트도 정성스럽게 작성했고, few-shot 예시도 20개나 넣었죠. 그런데 배포 후 일주일 만에 환각으로 인해 잘못된 정보를 제공하는 사례가 30%나 되었어요.”
이것은 드문 일이 아니다. 수많은 기업이 LLM을 프로덕션에 도입하려다 비슷한 벽에 부딪혔다. 프롬프트만 잘 쓰면 된다는 초기의 낙관론은 곧 한계에 봉착했고, 더 정교한 접근이 필요해졌다.
AI 에이전틱 패러다임의 진화는 단순한 유행이 아니다. 이것은 “언어 모델에게 무엇을 원하는지 말하기"에서 “어떻게 원하는 것을 얻을지 시스템화하기"로의 근본적 전환이다. 각 패러다임 전환은 이전 방식이 현장에서 약속을 지키지 못한 실패로부터 비롯되었다.
이 글에서는 2022년부터 2026년까지의 세 번의 패러다임 전환을 기술적으로 분석하고, 왜 각 단계가 필요했는지, 그리고 실무에서 어떻게 적용해야 하는지를 다룬다.
본론
1. 패러다임 진화 개요
1
2
3
4
5
6
7
8
9
| graph LR
A[Prompt Engineering<br/>2022-2023] --> B[Context Engineering<br/>2023-2024]
B --> C[Harness Engineering<br/>2024-2026]
A --> D[실패: 일관성 부족]
D --> B
B --> E[실패: 상태 관리 한계]
E --> C
|
위 다이어그램은 AI 개발 패러다임이 어떻게 진화했는지를 보여준다. 각 전환은 명확한 실패 지점에서 출발했다.
2. 제1막: Prompt Engineering (2022-2023)
2.1 등장 배경과 약속
GPT-3가 등장했을 때, 개발자들은 “자연어로 프로그래밍한다"는 꿈을 꿨다. 복잡한 코드 대신 영어로 원하는 것을 설명하면 AI가 알아서 처리해 줄 것이라 믿었다.
핵심 철학: “모든 것은 프롬프트 안에 있다”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # 전형적인 Prompt Engineering 접근 (2022년 스타일)
SYSTEM_PROMPT = """
당신은 전문 고객 서비스 에이전트입니다.
다음 원칙을 따르세요:
1. 항상 정중하게 응답하세요
2. 확실하지 않은 정보는 제공하지 마세요
3. 고객의 문제를 먼저 이해하세요
예시:
고객: "환불하고 싶어요"
응답: "불편을 드려 죄송합니다. 어떤 상품에 대한 환불을 원하시나요?"
"""
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_query}
]
)
|
2.2 기술적 원리: In-Context Learning
Prompt Engineering의 핵심은 **In-Context Learning (ICL)**이다. 모델 가중치를 업데이트하지 않고, 컨텍스트 윈도우 내에서 예시와 지시사항을 통해 원하는 동작을 유도한다.
2020년 GPT-3 논문(Brown et al.)에서 제시된 ICL은 다음 세 가지 형태로 발전했다:
| 기법 | 설명 | 예시 개수 | 효과 | | :— | :— | :— | :— | | Zero-shot | 예시 없이 지시만 | 0개 | 단순 작업에 적합 | | Few-shot | 몇 개의 예시 제공 | 1-10개 | 복잡한 패턴 인식 | | Chain-of-Thought | 추론 과정 명시 | 3-5개 | 논리적 추론 향상 |
2.3 왜 실패했는가
Prompt Engineering의 근본적 한계는 결정론적 통제의 부재였다.
1
2
3
4
5
6
7
8
9
| # 같은 입력, 다른 출력 - 비결정적 동작
for _ in range(5):
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": "파리의 수도는?"}],
temperature=0.0 # 낮춰도 완전한 결정성 보장 안 됨
)
print(response.choices[0].message.content)
# 출력이 5번 모두 같다는 보장이 없음
|
주요 실패 요인:
- Temperature ≠ 0도 완전한 결정성을 보장하지 않음 2. 프롬프트 버전 관리의 어려움 - “v1.2_final_real_final.txt” 3. 프롬프트 인젝션 취약점 - 사용자 입력이 시스템 지시를 덮어씀 4. 환각(Hallucination) 통제 불가 - 모델이 그럴듯한 거짓말을 생성
Wei et al. (2022)의 연구에 따르면, Chain-of-Thought 프롬프팅도 수학적 추론에서 40% 이상의 오류율을 보였다.
3. 제2막: Context Engineering (2023-2024)
3.1 패러다임 전환의 계기
“프롬프트를 더 잘 쓰는 것"이 아니라 “프롬프트에 더 많은 정확한 정보를 담는 것"으로 초점이 이동했다.
1
2
3
4
5
6
7
| graph TD
A[User Query] --> B[Embedding]
B --> C[Vector DB 검색]
C --> D[관련 문서 추출]
D --> E[Context 구성]
E --> F[LLM 추론]
F --> G[응답]
|
3.2 RAG: Retrieval-Augmented Generation
RAG는 Context Engineering의 핵심 기술이다. Lewis et al. (2020)가 제안했고, 2023년에 폭발적으로 채택되었다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| # RAG 파이프라인 구현 (LangChain 스타일)
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA
# 1. 지식 베이스 구축
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(
documents=knowledge_base_docs,
embedding=embeddings
)
# 2. RAG 체인 구성
llm = ChatOpenAI(model="gpt-4", temperature=0)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # 모든 문서를 컨텍스트에 포함
retriever=vectorstore.as_retriever(
search_kwargs={"k": 5} # 상위 5개 문서 검색
)
)
# 3. 질의
response = qa_chain.run("우리 회사의 환불 정책이 어떻게 되나요?")
|
3.3 기술적 발전: RAG의 고도화
RAG는 빠르게 진화했다:
| RAG 세대 | 특징 | 기술적 한계 | | :— | :— | :— | | Naive RAG | 단순 유사도 검색 | 관련성 낮은 문서 검색 | | Advanced RAG | 재순위화, 쿼리 재작성 | 검색 품질 의존 | | Modular RAG | 검색-생성 모듈 분리 | 파이프라인 복잡도 증가 | | Graph RAG | 지식 그래프 활용 | 그래프 구축 비용 |
3.4 왜 또 실패했는가
Context Engineering은 프롬프트의 “정보 품질"을 개선했지만, 상태 관리와 행동 제어 문제는 해결하지 못했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # Context Engineering의 한계 예시
# 대화가 길어질수록 컨텍스트가 폭발
conversation_history = [] # 계속 쌓임
def chat_with_memory(user_input):
conversation_history.append({"role": "user", "content": user_input})
# 컨텍스트 윈도우 한계 도달
total_tokens = count_tokens(conversation_history)
if total_tokens > 128000: # GPT-4 Turbo 한계
# 어떤 대화를 버릴 것인가? -> 정보 손실
conversation_history = truncate_middle(conversation_history)
response = llm.chat(conversation_history)
conversation_history.append({"role": "assistant", "content": response})
return response
|
핵심 실패 요인:
- 컨텍스트 윈도우 한계 - 긴 대화에서 정보 손실 2. 상태 일관성 - 에이전트가 이전 결정을 “기억"하지 못함 3. 도구 사용의 불안정성 - API 호출 실패 시 복구 불가 4. 평가 기준 부재 - “좋은 컨텍스트"가 무엇인지 정의 안 됨
4. 제3막: Harness Engineering (2024-2026)
4.1 새로운 패러다임의 정의
Harness Engineering은 AI 에이전트를 “감싸는(harness)” 구조적 프레임워크를 설계하는 것이다. 이것은 단순한 프롬프트가 아닌, 아키텍처 레벨의 엔지니어링이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| graph TD
A[User Input] --> B[Input Guardrail]
B --> C[Intent Classifier]
C --> D[Action Planner]
D --> E[Tool Executor]
E --> F[State Manager]
F --> G[Output Validator]
G --> H[Response]
E --> I[Memory Store]
I --> F
G -->|실패| J[Fallback Handler]
J --> H
|
4.2 핵심 구성 요소
Harness Engineering의 5가지 기둥:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
| # Harness Engineering 아키텍처 예시 (Pydantic 기반)
from pydantic import BaseModel, Field
from typing import List, Optional, Literal
from enum import Enum
class AgentState(str, Enum):
IDLE = "idle"
PLANNING = "planning"
EXECUTING = "executing"
WAITING_FOR_INPUT = "waiting_for_input"
COMPLETED = "completed"
FAILED = "failed"
class ToolCall(BaseModel):
tool_name: str
arguments: dict
expected_output: str
class AgentMemory(BaseModel):
short_term: List[dict] = Field(default_factory=list)
long_term: List[dict] = Field(default_factory=list)
working_memory: dict = Field(default_factory=dict)
class HarnessAgent:
def __init__(self):
self.state = AgentState.IDLE
self.memory = AgentMemory()
self.tools = self._register_tools()
self.guardrails = self._setup_guardrails()
def execute_with_harness(self, user_input: str) -> dict:
"""Harness로 감싸진 실행 파이프라인"""
# 1. Input Guardrail
if not self.guardrails.validate_input(user_input):
return self._fallback("invalid_input")
# 2. Intent Classification (구조화)
intent = self._classify_intent(user_input)
# 3. Action Planning (상태 기반)
plan = self._plan_actions(intent)
# 4. Constrained Execution
for step in plan:
if not self._can_execute(step):
return self._fallback("constraint_violation")
result = self._execute_step(step)
self._update_state(result)
if self.state == AgentState.FAILED:
return self._recover_or_fail()
# 5. Output Validation
response = self._generate_response()
if not self.guardrails.validate_output(response):
return self._fallback("output_validation_failed")
return response
|
4.3 상태 기반 설계 (State Machine)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| # 유한 상태 머신 기반 에이전트 제어
class StateMachine:
transitions = {
AgentState.IDLE: [AgentState.PLANNING],
AgentState.PLANNING: [AgentState.EXECUTING, AgentState.FAILED],
AgentState.EXECUTING: [
AgentState.COMPLETED,
AgentState.WAITING_FOR_INPUT,
AgentState.FAILED
],
AgentState.WAITING_FOR_INPUT: [AgentState.EXECUTING],
AgentState.COMPLETED: [AgentState.IDLE],
AgentState.FAILED: [AgentState.IDLE]
}
def transition(self, from_state: AgentState, to_state: AgentState) -> bool:
if to_state in self.transitions[from_state]:
self.current_state = to_state
return True
raise InvalidStateTransition(
f"Cannot transition from {from_state} to {to_state}"
)
|
4.4 패러다임 비교
| 차원 | Prompt Engineering | Context Engineering | Harness Engineering | | :— | :— | :— | :— | | **핵심 가
출처: https://news.hada.io/topic?id=28301