
LLM의 자유도를 설계하기: 프로덕션 AI 에이전트의 비용·재현성·관측성
요약
프로덕션 환경에서 대량의 데이터를 처리하는 AI 에이전트 설계 시, LLM 주도형 방식의 한계를 지적하고 코드 주도형 설계의 필요성을 설명합니다. LLM을 자율적 주체가 아닌 제한된 추론 컴포넌트로 활용함으로써 비용 절감과 재현성을 확보하는 방법을 다룹니다.
핵심 포인트
- LLM 주도형은 PoC에는 유리하나 대량 실행 시 비용과 회귀 테스트 문제가 발생함
- 코드 주도형 설계는 처리 흐름을 코드로 정의하여 비용을 최대 1/4까지 절감 가능
- LLM을 '제한된 추론 컴포넌트'로 활용하여 워크플로우의 안정성 확보
- 준정형 데이터 처리 시 LLM의 자유도를 제어하는 것이 프로덕션의 핵심
처음 뵙겠습니다! Nowcast의 요타(よーた)입니다.
데이터 & AI 솔루션 사업부에서 AI Full-Cycle Engineer라는 직종으로 AI 에이전트 개발 및 데이터 기반 정비를 수행하고 있습니다.
서론
PoC(Proof of Concept)에서 동작하는 에이전트와, 수천 건·수만 건의 업무 데이터를 안정적으로 처리하고 관측 가능하게 만드는 에이전트는 별개의 것입니다.
예를 들어, 기업 사이트를 크롤링하여 회사 개요나 사업 내용을 추출하는 워크플로우(Workflow)에서는, LLM에게 "필요한 페이지를 찾아 읽어주세요"라고 맡기는 것만으로도 PoC는 동작합니다. 하지만 대량으로 실행하면, Reasoning(추론) 및 고성능 모델 이용에 따른 비용 증가가 발생합니다.
실제로 기업 사이트에서 회사 개요나 주소를 추출하는 태스크(Task)에서, LLM 주도형과 코드 주도형을 비교한 결과, 처리를 분할하고 적재적소에 모델을 배치하는 코드 주도형은 필수 요건을 충족하면서도 워크플로우 전체의 비용을 약 1/4까지 낮출 수 있었습니다.
본 기사에서는 이러한 준정형 워크플로우에서, LLM을 "자유로운 실행 주체"가 아닌 "제한된 추론 컴포넌트 (Inference Component)"로 다루는 설계를 설명합니다. 이 목적을 위해, 본 기사에서는 다음과 같이 용어를 구분합니다.
LLM 주도형: LLM이 현재 상태를 보고 다음에 호출할 Tool(도구), Tool 인자, 계속/종료 판단을 동적으로 결정하는 구성
코드 주도형: 처리 흐름은 코드나 그래프로 정의하고, LLM은 특정 단계 내에서의 판단·선택·추출을 담당하는 구성
참고로, 본 기사에서는 어디까지나 준정형 데이터를 안정적으로 처리하기 위한 워크플로우를 상정하고 있습니다.
1. PoC에서 동작하는 에이전트가 대량 실행 시 어려움을 겪는 이유
LLM 주도형 실행 루프는 PoC에서는 편리합니다.
하지만 대량 실행하는 준정형 워크플로우에서는 다음과 같은 문제가 발생하기 쉽습니다.
| 문제 | 발생하는 현상 | 대량 실행 시의 영향 |
|---|---|---|
| 제어 흐름이 프롬프트(Prompt)에 의존하게 됨 | 분기나 루프, Tool의 입력을 LLM이 매번 판단하여 생성함 | 모델 변경 시 회귀 확인(Regression Test)이 어려워짐 |
| ... |
여기서의 문제는 LLM 주도형 그 자체가 아닙니다. 문제는 수천 건·수만 건의 준정형 데이터를 처리하는 상황에서, 불필요한 처리까지 LLM에 맡기는 것입니다.
따라서 대량 실행하는 워크플로우에서는 LLM에 맡겨야 할 처리와 코드 측에서 고정해야 할 처리를 나누어야 합니다.
2. 워크플로우 분할과 비용 제어는 어떻게 변하는가
본 기사에서 다루는 설계의 효과를 확인하기 위해, 기업 사이트에서 회사 개요 텍스트와 주소를 추출하는 태스크로 검증을 진행했습니다.
검증 대상은 금융기관·SaaS·복수 사업 회사·제조업 그룹·대형 소비자 대상 사이트·지방 기업을 포함한 10개사로 하였으며, 페이지 탐색·회사 개요 추출·노이즈 내성의 난이도가 분산되도록 선정했습니다.
검증에서는 다음과 같은 세 가지 수법을 비교했습니다.
| 수법 | 추론 모델 |
|---|---|
| LLM 주도형 | GPT-5.4-mini |
| 코드 주도형 (동등 모델) | GPT-5.4-mini |
| 코드 주도형 (비용 최적화) | GPT-5.4-mini / Gemini-3.1-Flash-Lite |
각 수법의 처리 흐름은 다음과 같습니다.
LLM 주도형- LLM에게 지정된 URL에서 페이지를 가져오는 툴을 제공하고, 툴의 취득 방법, 페이지 선택 방침, 최종 결과 조건을 부여하여 자유롭게 탐색하게 합니다. OpenAI Agents SDK를 사용합니다.
-
"페이지 탐색", "다음에 볼 링크 판단", "페이지 내 정보 추출", "충분한 정보가 모였는지에 대한 종료 판정"의 모든 것을 AI가 자유롭게 생각하여 자율적으로 반복합니다.
코드 주도형 (동등 모델 / 비용 최적화)- 처리 파이프라인을 코드로 고정하고, 각 단계에서만 LLM을 제한적인 추론 컴포넌트로 호출합니다. 구체적으로는 다음과 같은 순서로 처리를 실행합니다.
-
허브 페이지 탐색: 사이트 내에서 회사 개요나 사업 내용에 대한 링크가 적혀 있을 법한 페이지를 수집한다
-
후보 페이지 선정: 수집한 허브 페이지 중에서 실제로 기업 정보를 추출해야 할 페이지를 선택한다
-
페이지 내 정보 추출: 각 후보 페이지를 읽어 들여 필요한 정보를 개별적으로 추출한다
-
결과 병합: 각 페이지에서 추출한 정보를 최종적인 출력 포맷으로 통합한다
-
"비용 최적화" 수법에서는 1~3단계에 저렴한 Gemini-3.1-Flash-Lite를 적용하고, 문맥 통합이 필요한 4단계에만 GPT-5.4-mini를 적용하고 있습니다.
-
처리 파이프라인을 코드로 고정하고, 각 단계에서만 LLM을 제한적인 추론 컴포넌트 (Inference Component)로 호출합니다. 구체적으로는 다음과 같은 순서로 처리를 실행합니다.
예를 들어, Nowcast 주식회사 (株式会社ナウキャスト)의 사례라면 입출력은 다음과 같습니다.
// 입력
{
"company_name": "株式会社ナウキャスト",
...
// 출력
{
"company_name": "株式会社ナウキャスト",
...
검증의 전제와 평가 축
이번 검증에서는 다음과 같은 3가지 최소한의 정확성 요건을 충족하는 구현을 대상으로 비교했습니다.
주소 정답률: 올바른 주소를 추출할 수 있는가 -
근거 URL 열람률: 추출된 정보의 근거가 되는 URL을 실행 프로세스 내에서 실제로 열람하는가 -
참조 번호 정합률: 최종 출력 텍스트에 포함된 참조 번호와 실제로 액세스한 URL 리스트가 완전히 연결되어 있는가
나아가 참고 지표로서 사람이 직접 수집한 사업 개요 키워드에 대한 망라율(Coverage)도 준비했습니다.
사업 개요 키워드 망라율: 사업 내용을 나타내는 키워드를 얼마나 폭넓게 포착하고 있는가. 광범위한 정보를 수집할 수 있는 능력의 지표로 사용합니다.
실행 비용과 처리량
| 지표 (1건 평균) | LLM 주도형 | 코드 주도형 (동일 모델) | 코드 주도형 (비용 최적화) |
|---|---|---|---|
| 평균 비용 | $0.0427 | $0.0255 | $0.0112 (약 1/4) |
| 평균 레이턴시 (Latency) | 23.2s | 23.1s | 18.8s |
| 평균 LLM 호출 횟수 | 3.4회 | 6.4회 | 5.9회 |
| 평균 페이지 액세스 수 | 4.7회 | 8.4회 | 7.5회 |
| 평균 입력 토큰 수 | 53,549 | 23,384 | 25,215 |
| 평균 출력 토큰 수 | 568 | 1,761 | 1,819 |
| 사업 개요 키워드 망라율 | 57.0% | 55.0% | 63.0% |
참고: 평균 레이턴시는 주요 병목 구간이 페이지 취득이며, 캐시 등에 의해 크게 변동될 수 있으므로 어디까지나 참고치입니다.
실행 결과의 포인트
동일 모델에서의 비교 (아키텍처의 효과)-
LLM 호출 횟수 및 액세스 수는 증가한다: 코드 주도형은 각 페이지를 병행하여 추출하기 때문에, LLM 주도형보다 LLM 호출 횟수가 증가합니다. -
입력 토큰은 급감한다: LLM 주도형은 '지금까지의 도구 실행 이력 및 모든 페이지의 문맥'을 매번 프롬프트에 포함하여 전달하기 때문에 입력 토큰이 불어납니다. 반면, 코드 주도형은 '해당 단계에 필요한 정보'만 전달하므로 호출 횟수가 늘어나더라도 입력 토큰은 대폭 감소합니다. -
출력 토큰은 증가한다: 코드 주도형은 후보 페이지를 폭넓게 읽어 들여 병렬 추출하므로, 각 단계에서의 출력 결과(중간 데이터) 총량은 늘어납니다 (596 → 1,822). 하지만 입력 토큰의 절감 효과가 이를 상회하기 때문에, 동일한 모델을 사용하더라도 전체 비용은 낮아집니다.
비용 최적화 모델에서의 비교 (적재적소의 효과)-
모델 교체를 통한 대폭적인 비용 절감: 각 단계의 책무(추론 컴포넌트)가 한정됨에 따라, '페이지를 선택하기만 하는 단계'나 '적혀 있는 내용을 추출하기만 하는 단계'에는 Gemini-3.1-Flash-Lite와 같은 저렴한 모델을 적용할 수 있게 됩니다. -
품질 유지: 키워드 망라율을 보면, 저렴한 모델을 사용하더라도 63.0%로 LLM 주도형(57.0%)과 동등한 결과를 낼 수 있습니다.
즉, 코드 주도형의 이점은 '처리 횟수를 줄이는 것'이 아닙니다. 오히려 처리 플로우를 분할함으로써 횟수나 출력 토큰은 늘어날 수도 있습니다.
최대의 이점은 각 처리의 책무와 입력을 한정함으로써 불필요한 문맥 전달(입력 토큰)을 줄일 수 있고, 태스크 난이도에 따라 저렴한 모델을 적용할 수 있기 때문에, 비용과 품질을 제어하기 쉬워진다는 것입니다.
보충: 물론 이번 사례에서는 망라하지 못한 패턴이 존재하므로, 실제로는 유연성과 비용의 균형을 고려하며 설계하는 것이 중요합니다.
3. LLM을 컴포넌트로 다루기 위한 3가지 설계 원칙
대량 실행을 견딜 수 있는 준정형 워크플로우를 구축하고, LLM을 '제한된 추론 컴포넌트'로서 적절히 기능하게 하려면 다음과 같은 3가지 설계 원칙이 중요합니다.
원칙 1: LLM에게 실행 대상을 자유롭게 생성하게 하지 마라
대량 실행 시 가장 먼저 재검토해야 할 것은 LLM에게 "무엇을 출력하게 하고 있는가"입니다.
예를 들어, 기업 사이트를 크롤링하는 워크플로우에서 LLM에게 HTML과 액세스용 Tool을 전달하여 다음과 같이 구현했다고 가정해 보겠습니다.
await runAgent({
instructions: `
회사 개요나 사업 내용을 알 수 있는 페이지를 찾아주세요.
...
언뜻 보면 자연스러운 지시사항이지만, 이대로 사용하면 다음과 같은 문제가 발생할 가능성이 있습니다.
- 존재하지 않는 URL을 생성함
- fetch 실패 시 URL을 임의로 보완함
- 세부 지시를 이해할 수 있는 Reasoning 모델을 사용해야 하므로 비용이 증가함
따라서, 예를 들어 처리를 다음과 같이 분할합니다.
type CandidatePage = {
pageId: string;
title: string;
...
이 설계에서 LLM은 URL 문자열을 생성하지 않기 때문에, 적어도 존재하지 않는 URL을 날조하는 유형의 할루시네이션 (Hallucination)은 구조적으로 방지할 수 있습니다. 특히 더욱 엄격한 프랙티스(Practice)로서, pageId를 Structured Output 스키마의 Enum이나 정규 표현식으로 고정하는 방법을 택하면 더욱 안심할 수 있습니다.
또한, LLM이 판단해야 할 내용이 작아지기 때문에 더 저렴한 모델을 선택하기 쉬워집니다. 실제로 이번 검증에서는 저렴한 모델을 채택할 수 있게 됨으로써 비용을 1/4로 억제할 수 있었습니다.
요약하자면 다음과 같습니다.
나쁜 예:
- LLM에게 "다음에 읽어야 할 URL"을 자유롭게 생성하게 함
- LLM에게 검증 불가능한 JSON이나 외부 API 인수를 자유롭게 생성하게 함
- URL 정규화, 검증 도구 실행, 제외 조건까지 프롬프트로 제어함
개선 후:
- 코드 측에서 후보 URL을 수집 및 정규화함
- 각 후보에 ID를 부여하고, LLM에는 ID만 선택하게 함
- ID에서 URL로의 변환, 취득, 검증은 코드 측에서 수행함
원칙 2: LLM 출력은 코드로 검증할 수 있는 형태로 제한한다
LLM의 출력을 pageId나 구조화된 데이터로 제한하면 코드 측에서 검증하기 쉬워집니다.
예를 들어, 기업 정보 추출에서는 다음과 같은 검증을 LLM에게 맡길 필요가 없습니다.
| 검증 | 코드 측에서 확인할 내용 |
|---|---|
| ID 검증 | 선택된 ID가 후보 리스트에 존재하는지 |
| ... |
중요한 것은 검증을 LLM에게 프롬프트로 지시하는 것이 아니라, 워크플로우 상에서 반드시 실행되는 코드로 고정하는 것입니다.
보충: 물론 추가 구현이 필요하므로 구현 공수는 들지만, 더 유연하게 다양한 에러 핸들링 (Error Handling)이나 처리 플로우를 구현할 수 있기 때문에 나중에 도움이 되는 경우가 많다고 생각합니다.
다음은 LLM과 코드로 처리를 나눌 때의 대략적인 판단 기준입니다.
| LLM에 맡김 | 코드에 맡김 |
|---|---|
| 의미 이해 · 분류 · 추출 | schema validation · enum 검증 |
| ... |
단, 추론 · 실행 · 검증 · 에러 핸들링을 세밀하게 분리하면 또 다른 문제가 발생합니다.
그것은 워크플로우 전체의 데이터 흐름이나 분기가 코드로부터 추적하기 어려워진다는 점입니다.
원칙 3: 관측 단위는 "나중에 개선하고 싶은 단위"로 자른다
코드 주도형으로 기울수록 LLM의 출력, 코드 측의 검증, 재시도(Retry), 스킵(Skip) 등이 여러 계층에 분산됩니다.
그렇기 때문에 단순히 LLM 콜(Call)만 로그에 남겨서는 워크플로우 전체의 어디가 느린지, 어디서 실패하고 있는지, 어떤 처리를 교체해야 하는지 알 수 없습니다.
중요한 것은 전략적으로 관측 단위를 "처리가 실행된 단위"가 아니라, "나중에 비교 · 교체 · 개선하고 싶은 단위"로 자르는 것입니다.
여기서는 OpenTelemetry의 주요 개념인 Span을 관측 단위로 다룹니다.
예를 들어, 다음과 같이 모든 처리를 하나의 커다란 Span에 넣으면 나중에 개선하기 어려워집니다.
trace: enterprise_site_crawl
span: run_company_crawl
event: fetch_initial_page
...
반면, 예를 들어 다음과 같이 적절하게 Span을 자름으로써 워크플로우 내의 처리 단계를 추적하기 쉽게 만들 수 있습니다.
trace: enterprise_site_crawl
span: discover_pages
event: fetch_initial_page
...
다음은 실제 Langfuse에서의 표시 예시입니다.

Span을 자를 때는 다음 질문으로 판단하면 좋습니다.
| 질문 | Span으로 나누어야 하는 이유 |
|---|---|
| 이 처리만 모델이나 프롬프트(Prompt)를 교체하고 싶은가 | AB 테스트를 하기 쉬워짐 |
| ... | * |
| 보충: 물론, 이러한 관점은 워크플로(Workflow)형 에이전트 전반에 적용될 수 있다고 생각합니다 |
이렇게 나누어 두면, Trace를 API로 모아서 가져와 집계함으로써, 특정 Span만 별도의 프롬프트, 별도의 모델, 혹은 다른 처리 로직 구현으로 교체했을 때도 해당 Span 단위로 입출력, 비용, 레이턴시(Latency)를 비교할 수 있어, 정밀도 개선과 비용 절감 양쪽을 모두 평가하기 쉬워집니다.
실제로 이번 검증 과정에서도 Span 단위로 입출력이나 데이터의 흐름을 살펴보며, 프롬프트나 파라미터(Parameter)를 세밀하게 튜닝함으로써 정밀도를 보장하면서 비용을 절감하고 있습니다.
또한, 이상의 이유로 Span에서 취득하는 정보는 처리의 입출력이나 비용, 레이턴시 외에도, 취득 URL 수 등의 평가용 통계 정보나 워크플로의 버전 등 비교·집계에 사용할 정보를 추가로 남겨두는 것이 좋습니다.
이처럼 코드 주도형의 관측성(Observability)은 단순한 LLM의 관측이 아니라, LLM을 포함한 처리 부품의 관측이며, 설계상의 중요한 관점입니다.
4. 요약: AI 에이전트 개발의 본질은 LLM의 자유도를 설계하는 것
대량 실행되는 준정형(Semi-structured) AI 에이전트에서는 LLM에 모든 자유도를 부여하면 비용·재현성·관측성의 관리가 어려워집니다. 실제로 이번 검증에서는 코드 주도형과 비용 면에서 4배나 차이가 난다는 점이 나타났습니다.
PoC(Proof of Concept) 단계의 에이전트는 LLM에 자유도를 부여함으로써 빠르게 만들 수 있습니다. 반면, 프로덕션(Production) 단계의 에이전트는 그 자유도를 어디에 남기고, 어디에서 제한하며, 어느 단위로 관측·개선할지를 설계해야 합니다.
중요한 것은 LLM에게 맡길 자유도를 설계하는 것입니다.
- 후보는 코드 측에서 생성한다
- LLM에는 후보 선택·의미 판단·구조화 추출을 맡긴다
- 실행·검증·재시도(Retry)·상태 관리는 코드 측에서 고정한다
- 관측 단위는 나중에 비교·교체·개선하고 싶은 단위로 나눈다
이와 같이 적절하게 LLM의 자유도를 제한해 주면, LLM과 코드의 책임이 분리되어 관측성이 향상되고, 구조적으로 할루시네이션(Hallucination)을 방지할 수 있습니다.
게다가 부분적으로 저비용 모델을 이용할 수 있게 됨으로써, 대폭적인 비용 절감이 가능해질 가능성이 있습니다.
대량 실행되는 준정형 워크플로에서는 LLM을 자유로운 실행 주체가 아니라, 검증 가능한 추론 컴포넌트(Inference Component)로 취급하는 것이 비용·재현성·관측성을 제어하기 위한 출발점이 됩니다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기