소프트웨어 엔지니어에서 AI 엔지니어로 - 파트 1: 완전히 새로운 세상
요약
소프트웨어 엔지니어가 AI 엔지니어로 전환하기 위한 여정을 소개하는 시리즈의 첫 번째 파트입니다. LLM을 활용한 비결정론적 애플리케이션 구축의 개념을 다루며, 결제 어시스턴트인 PayIQ를 엔드 투 엔드로 구현하는 과정을 안내합니다.
핵심 포인트
- AI 엔지니어링은 LLM이라는 비결정론적 요소를 다루는 소프트웨어 엔지니어링임
- 모델(LLM)과 애플리케이션/에이전트/하네스의 개념적 구분
- PayIQ 프로젝트를 통해 구조화된 출력, 에이전트 루프, 오케스트레이션 등을 학습
- 기존 소프트웨어 엔지니어링 개념과 AI 패턴의 연결 강조
당신은 소프트웨어 엔지니어입니다. 수년간의 세심한 연습을 통해 당신의 기술을 연마해 왔습니다. 그러던 어느 날 갑자기, 챗봇(Chatbots)과 에이전트(Agents)가 등장했습니다. 하룻밤 사이에 동료들의 LinkedIn 직함이 "AI 엔지니어"로 바뀌었습니다. 어떤 이들은 이미 시니어(SENIOR) AI 엔지니어가 되었습니다. 당신은 이 새로운 세상이 궁금하며, 흐름을 따라잡아 스스로 그 일원이 되고 싶을지도 모릅니다.
만약 당신이 이런 상황이라면, AI 엔지니어링(AI engineering) 분야를 구성하는 개념과 패턴들을 살펴보는 이 여정에 저와 함께 참여하십시오. 우리는 AI 애플리케이션(AI application) 개발이 대부분, 진정으로 기묘하고 새로운 비결정론적(non-deterministic) 구성 요소인 LLM(Large Language Model, 대규모 언어 모델)에 적용된 '단순한' 소프트웨어 엔지니어링(software engineering)이라는 것을 발견하게 될 것입니다.
이 여정 동안, 우리는 실제 애플리케이션을 엔드 투 엔드(end to end)로 구축할 것입니다. 매 기사마다 새로운 레이어(layer)가 추가됩니다. 우리는 새로운 패턴과 용어들을 여러분이 이미 알고 있는 기존의 소프트웨어 엔지니어링 개념들과 연결할 것입니다.
이륙하기 전에, 전체 과정에서 사용될 용어 규칙 하나를 정하고자 합니다. "모델(the model)"은 LLM 자체(GPT나 Claude와 같은 대규모 언어 모델)를 의미하며, AI 엔지니어들이 그 주변에 구축하는 것은 "애플리케이션(the application)", "에이전트(the agent)" 또는 "하네스(the harness)"라고 부를 것입니다.
우리가 만드는 것
저 자신이 결제 기업에서 근무하고 있기 때문에, 제 전문 분야를 유지하기로 했습니다. 우리가 구축할 애플리케이션인 PayIQ는 가맹점이 결제 작업(환불 처리, 차지백(chargebacks) 방어, 결제 수수료 계산 등)을 수행할 수 있도록 돕는 어시스턴트입니다. 결제 금액과 결제 수단을 입력하면, 실제 환불 비용이 얼마인지 계산해 줍니다(스포일러: 환불 금액보다 더 많이 듭니다). 차지백에 대응할 가치가 있는지 물어보면, 당신의 지식 베이스(knowledge base)를 사용하여 기대 가치(expected-value) 수학 계산을 수행합니다. 책임감 있게 답변할 수 없는 질문을 하면, 부족한 정보가 무엇인지 되묻습니다. 추측도, 환각(hallucinations)도 없습니다.
결과적으로 PayIQ는 다른 시스템에서 소비할 수 있는 구조화된 출력 (structured outputs), 금융 계산기 도구 모음 (tool belt of financial calculators), 지식 베이스에 대한 검색 (retrieval over a knowledge base), 지속적인 메모리를 가진 에이전트 루프 (agent loop with persistent memory), 모델이 건너뛸 수 없는 단계들로 구성된 오케스트레이션 그래프 (orchestration graph), FastAPI 서비스 뒤의 토큰 스트리밍 (token streaming), 회귀 평가 스위트 (regression eval suite), 그리고 계층화된 인젝션 방어 (layered injection defense)를 갖추게 될 것입니다. 만약 이 단어들 중 일부가 전혀 이해되지 않더라도 걱정하지 마세요. 그것은 단지 시간문제일 뿐입니다.
각 기사는 10~15분 정도의 읽기 분량이며, 의도적으로 이전 기사들을 바탕으로 구축됩니다 (파트 2의 구조화된 출력이 파트 3의 도구 사용 (tool usage)에 사용됩니다). 따라서 순차적으로 읽어보시는 것을 권장합니다. 이 첫 번째 기사는 기초를 다지고 모델과 간단한 상호작용을 수행합니다.
동반 저장소 (https://github.com/BjornvdLaan/ai-engineering-articles-code-samples)에는 모든 코드 샘플이 포함되어 있어 여러분도 직접 시도해 볼 수 있습니다. 예제들은 Anthropic의 모델들을 사용합니다. 만약 다른 제공업체(또는 로컬에서 호스팅되는 모델)를 선호한다면, 여러분이 좋아하는 챗봇이 설정을 조정하는 데 도움을 줄 수 있을 것입니다. 걱정하지 마세요, LangChain의 라이브러리들은 (대체로) 모델 불가지론적 (model-agnostic)입니다.
모델을 생각하기 위한 멘탈 모델 (The mental model for thinking about models)
코드에 들어가기 전에, 모델이 실제로 무엇인지에 대한 정확한 멘탈 모델 (mental model)을 구축해 봅시다. 신경망 (neural networks)이나 트랜스포머 (transformers)가 내부적으로 어떻게 작동하는지가 아니라 — 그 부분은 똑똑한 과학자들에게 맡겨둡시다. AI 엔지니어를 지망하는 여러분에게 필요한 것은 운영적 (operational) 멘탈 모델입니다: 이 대상이 무엇을 소비하는지, 비용이 얼마나 드는지, 그리고 여러분이 무엇을 제어할 수 있는지에 대한 모델입니다. 가장 단순한 형태로, 모델은 단순히 다음과 같은 인터페이스입니다:
f(입력 메시지 목록) -> 출력 메시지
그게 전부입니다. 중요한 세부 사항은 입력이 단순히 당신이 준 마지막 프롬프트(prompt)가 아니라, 메시지의 _리스트(list)_라는 점입니다. 모델에 대한 각 호출은 일반적으로 시스템 메시지("애플리케이션 설정"), 지금까지의 전체 대화 내용("현재 상태"), 그리고 가장 최근의 프롬프트를 포함합니다. 이러한 모든 이전 메시지들을 바탕으로, 모델은 대화의 다음 메시지를 생성합니다.
만약 Codex, Claude Code 또는 다른 "하네스(harnesses)"를 사용해 보았다면 이 말이 이상하게 들릴 수도 있습니다. 당신은 모델이 인터넷을 검색하고, 이전 세션의 사실들을 기억하며, 할 일 목록(todo list)을 유지하고, 당신이 제공한 문서를 읽으며, 코드베이스를 작성하고 편집하는 것을 보게 됩니다. 그 모든 마법은 AI 엔지니어들이 모델을 중심에 두고(around) 구축한 애플리케이션 로직(application logic)이지만, 모델이 보는 것은 오직 입력 메시지의 리스트뿐입니다.
토큰(Tokens): 모든 것의 단위
메시지는 문자(character)나 단어(word)가 아닌 토큰(token) 단위로 모델에 전달됩니다. 토크나이저(tokenizer)라고 불리는 구성 요소가 먼저 텍스트를 토큰으로 분할하고 각 토큰에 숫자 형태의 토큰 ID를 할당합니다. 흔히 쓰이는 단어는 종종 하나의 토큰이 되지만, 흔치 않은 단어는 여러 개로 분할될 수 있습니다. 모델은 원문 텍스트를 절대 직접 보지 않습니다. 오직 이 토큰 ID들만을 처리합니다.
토큰은 계산의 단위로서 역할을 할 뿐만 아니라, 과금의 단위이기 때문에 매우 중요합니다. 당신은 입력 및 출력 토큰당 비용을 지불합니다. 토큰을 클라우드 청구서의 컴퓨팅 초(compute-seconds) 단위처럼 취급하세요. AI 기능은 단일 요청의 비용이 신경 쓰일 정도로 큰 첫 번째 종류의 백엔드 엔드포인트(backend endpoint)입니다. 사용자 요청당 5번의 모델 호출을 수행하는 에이전트(agent)는 실제 운영 트래픽 규모에서 상당한 비용을 발생시킵니다. 이는 또한 새로운 보안 리스크이기도 합니다. 전통적으로 DDoS 공격은 주로 인프라 자원(CPU, 메모리, 대역폭 및 오토스케일링된 인스턴스)을 소모했습니다. 이제는 모든 요청이 과금 가능한 토큰을 소모할 수 있어, 잠재적으로 훨씬 더 큰 두 번째 비용 구성 요소를 추가하게 됩니다. 이 공격에는 별도의 이름도 있습니다: 디나이얼 오브 월렛(denial of wallet, 지갑 거부 공격).
컨텍스트 윈도우(The context window): 모델의 입력 제한
각 모델은 처리할 수 있는 입력 토큰(input tokens)의 최대 개수를 가지고 있습니다. 이를 컨텍스트 윈도우(context window)라고 부르며, 종종 수십만 개의 토큰을 수용할 수 있습니다. 위에서 논의한 바와 같이, 모델이 요청에 응답하는 데 필요한 모든 것—시스템 프롬프트(system prompt), 지금까지의 대화 내용, 검색된 문서(retrieved documents), 도구 결과(tool results), 그리고 당신이 제공하는 기타 모든 컨텍스트(context)—이 그 안에 들어맞아야 합니다. 이를 모델의 작업 기억(working memory)이라고 생각하세요. 컨텍스트 윈도우가 클수록 더 많은 정보를 제공할 수 있지만, 더 많은 컨텍스트가 항상 더 좋은 것은 아닙니다. 무관한 정보가 쌓일수록 모델은 더 많은 방해 요소를 갖게 되며, 중요한 세부 사항을 놓치기 쉬워집니다. 이를 _컨텍스트 부패(context rot)_라고 부르는 것을 보게 될 것입니다. 또한 입력 토큰이 많아질수록 비용도 더 많이 발생합니다! 이것이 바로 "그냥 전부 다 붙여넣기"가 확장 가능하지 않은(scale) 이유입니다. 컨텍스트 엔지니어링(context engineering)은 양보다 질에 관한 것입니다.
설정 (Setup)
이제 배경 지식을 얻었으니, 모델이 실제로 작동하는 모습을 볼 차례입니다. 이 설정은 이 시리즈 전체에 걸친 모든 코드 샘플에 대해 한 번만 수행하면 됩니다.
사전 요구 사항: Python 3.11 이상, 선호하는 제공업체의 API 키, 그리고 이 시리즈 전체를 통틀어 5유로(five euros) 미만의 크레딧.
새 프로젝트 디렉토리를 생성하거나(또는 동반 리포지토리(companion repo)를 클론하거나) Python 가상 환경(virtual environment)을 활성화하세요:
python3 -m venv .venv
source .venv/bin/activate
requirements.txt를 생성하세요:
langchain>=1.3,<2.0
langchain-core>=1.4,<2.0
langchain-text-splitters>=1.1,<2.0
...
이 패키지들 대부분은 이후 단계(검색(retrieval), MCP, 웹 레이어(web layer))를 위한 것이지만, 지금 설치해 두면 이 파일을 다시는 건드릴 필요가 없습니다.
pip install -r requirements.txt를 실행한 다음, 프로젝트 루트에 .env 파일을 생성하세요. 여기에 API 키(및 선택적으로 기타 설정)를 저장하게 됩니다:
ANTHROPIC_API_KEY=sk-ant-...
Anthropic의 API 키는 platform.claude.com에서 얻을 수 있습니다.
첫 번째 호출 (Your first call)
01_first_call.py를 생성하세요:
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
...
실행해 보세요: python3 01_first_call.py.
코드 샘플에서 주목할 두 가지 세부 사항이 있습니다. 첫째, init_chat_model은 LangChain의 공급자 독립적(provider-agnostic) 생성자입니다. 문자열 `
첫 번째는 **비결정론 (non-determinism)**입니다. 모델은 매번 단순히 가장 확률이 높은 토큰을 선택하는 것이 아닙니다. 대신, 분포(distribution)로부터 샘플링(sampling)을 수행하기 때문에 동일한 입력이라도 다음 호출 시에는 다른 출력을 생성할 수 있습니다. 샘플링이 얼마나 자유로운지, 그리고 사용자가 이를 제어할 수 있는지 여부는 모델마다 다릅니다. 일부 모델은 temperature 설정을 제공합니다. 이러한 비결정론을 다루는 것은 '일반적인' 소프트웨어 엔지니어링과의 핵심적인 차이점입니다.
두 번째는 **환각 (hallucination)**으로, 모델이 사실이 아닌 것을 지어낸다는 이야기를 아마 들어보셨을 것입니다. 이는 모델이 구축된 방식에서 기인합니다. 모델은 무엇이 실제로 사실인지 구별하도록 설계된 것이 아니라, 다음 토큰을 예측하도록 설계되었습니다. 모델은 사실을 검색(retrieving)하는 것이 아니라, 그럴듯한 텍스트를 생성하는 것입니다. 질문에 정답이 있지만 모델에 필요한 정보가 부족한 경우, 모델은 확신에 찬 어조로 설득력 있지만 틀린 답을 내놓을 수 있습니다. 모델 제작자들은 모델이 지시사항을 따르고, 특정 형식을 사용하며, 일부 요청을 거부하고, 불확실성을 더 자주 인정하도록 가르치기 위해 '사후 학습 (post-training)'을 적용합니다. 하지만 그렇다고 해서 모델에게 내부적인 사실 확인기 (fact checker)가 생기는 것은 아닙니다.
이 두 가지가 동전의 양면이라는 점에 주목하십시오. 즉, 모델은 정확한 사실을 검색하는 대신 그럴듯한 토큰을 샘플링한다는 것입니다. 해결책은 대개 더 나은 프롬프트(prompt)를 사용하는 것이 아닙니다. 정확성이 중요할 때 모델의 출력을 제한하고 적절한 정보를 제공할 수 있도록 모델 주변에 시스템을 구축하는 것입니다. 그 시스템이 바로 이 시리즈의 나머지 부분에서 구축할 내용이며, AI 엔지니어가 하는 일입니다.
해내셨습니다!
잘하셨습니다! 방금 AI 엔지니어링을 향한 첫걸음을 내디뎠습니다. 우리는 몇 가지 이론을 배웠고 첫 번째 모델 호출을 수행했습니다. 분명히, 이것만으로는 아직 매우 유용하지는 않습니다. 만약 당신의 백엔드(backend)가 이 모델을 호출한다면, 어떤 종류의 출력을 기대할 수 있을지 전혀 알 수 없기 때문입니다. 출력 메시지는 비구조화(unstructured)되어 있으며, 따라서 우리가 JSON 응답에서 하는 것처럼 객체로 쉽게 파싱(parse)할 수 없습니다. 다음 기사에서는 바로 그 작업, 즉 모델의 출력이 당신의 코드가 신뢰할 수 있는 구조를 따르도록 만드는 방법을 다룰 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기