많은 에이전트 시스템이 프로덕션에서 실패하는 이유와 상태 머신, 명시적 도구 계약, 재시도 패턴, 깊은 관찰성을 통해 안정적인 에이전트를 설계하는 방법.

에이전트 시스템은 LLM이 단순히 프롬프트에 답하는 것을 넘어 다음에 무엇을 할지 결정하는 애플리케이션입니다: 어떤 도구를 호출할지, 어떤 데이터를 가져올지, 어떤 단계를 실행할지, 언제 "완료"로 간주할지. 이들은 모델, 도구 집합(API, DB, 서비스), 계획/실행 루프, 그리고 이를 연결하는 인프라를 결합합니다.
데모에서는 마법처럼 보입니다: 에이전트가 계획을 세우고, 몇 개의 도구를 호출해 완벽한 결과를 반환하죠. 해피 패스는 짧고, 지연은 낮으며, 동시에 실패가 발생하지 않습니다.
실제 워크로드에서는 같은 에이전트가 데모가 보지 못한 방식으로 스트레스받습니다:
결과는 재현하기 어려운 불안정한 동작, 조용한 데이터 손상, 가끔씩 멈추거나 무한 루프에 빠지는 사용자 흐름입니다.
불안정한 에이전트는 단순한 "즐거움"을 해치지 않습니다. 이들은:
이 글은 "더 나은 프롬프트"가 아니라 엔지니어링 패턴에 관한 것입니다. 우리는 상태 머신, 명시적 도구 계약, 재시도 및 실패 처리 전략, 메모리와 동시성 제어, 그리고 로드 시 에이전트 시스템을 예측 가능하게 만드는 관찰성 패턴을 살펴봅니다. 무대 위에서만 인상적인 것이 아니라 실무에서 신뢰할 수 있는 설계를 목표로 합니다.
대다수 에이전트 시스템은 단일 해피 패스 데모에서는 괜찮아 보입니다. 트래픽, 도구, 엣지 케이스가 함께 오면 실패합니다.
순진한 오케스트레이션은 모델이 한두 번의 호출에 "올바른 것"을 할 것이라고 가정합니다. 실제 사용에서는 다음 패턴이 반복됩니다:
명시적 상태와 종료 조건이 없으면 이러한 동작은 불가피합니다.
LLM 샘플링, 지연 변동성, 도구 타이밍은 숨겨진 비결정성을 만듭니다. 같은 입력이 다른 분기를 지나거나, 다른 도구를 호출하거나, 도구 결과를 다르게 해석할 수 있습니다.
스케일에서 도구 문제는 지배적입니다:
이들 각각은 스푸리어스 루프, 재시도, 또는 잘못된 최종 답으로 전환됩니다.
10 RPS에서는 드물게 일어나는 문제가 1,000 RPS에서는 지속적으로 발생합니다. 동시성은 다음을 드러냅니다:
제품팀은 종종 결정론적 워크플로, 명확한 SLA, 감사 가능성을 기대합니다. 제약 없이 둔 에이전트는 확률적, 최선의 노력 행동을 제공하며 보장이 약합니다.
이 불일치를 무시하고 에이전트를 전통적 서비스처럼 취급하면, 신뢰성이 가장 중요할 때 예측 불가능해집니다.
프로덕션에 적합한 에이전트는 "똑똑한 프롬프트"에 관한 것이 아니라 규율 있는 시스템 설계에 관한 것입니다. 이를 이해하기 쉬운 방식은: 에이전트를 가끔 LLM을 호출하는 작은 예측 가능한 머신으로 생각하는 것입니다. 신비한 LLM 덩어리가 당신의 시스템을 가끔 건드리는 것이 아닙니다.
네 가지 속성이 가장 중요합니다:
이 속성들은 프롬프트만으로 얻을 수 없습니다. 구조에서 얻습니다.
많은 팀이 시작하는 기본 패턴은: "while not done, call model, let it think, maybe call a tool, repeat" 입니다. 프로토타입은 쉽지만 운영하기 어렵습니다.
더 안전한 패턴은 에이전트를 명시적 워크플로로 표현하는 것입니다:
COLLECTING_INPUT, PLANNING, EXECUTING_STEP, WAITING_ON_HUMAN, DONE).이렇게 하면 에이전트는 검사 가능하고, 테스트 가능하며, 재생 가능한 상태 머신이 됩니다. 자유형 루프는 유연하게 느껴지지만, 인시던트를 디버깅 가능하게 하고 동작을 감사 가능하게 하는 것은 명시적 워크플로입니다.
모든 것을 하는 모놀리식 에이전트는 매력적이지만 관련 없는 책임들이 강하게 결합되어 문제를 만듭니다: 계획, 검색, 비즈니스 로직, UI 오케스트레이션 등.
대신 작고 잘 범위가 정해진 에이전트나 스킬을 조합하세요:
각 스킬은 자체 상태 머신, 도구, 안전 규칙을 가질 수 있습니다. 조합 로직은 상위 수준의 워크플로가 되고, 단일 에이전트 내부의 프롬프트가 점점 커지는 상황을 피할 수 있습니다.
이 모듈화는 각 에이전트를 이해하기 쉽게 만들고, 한 기능을 진화시키더라도 다른 부분을 불안정하게 만들지 않습니다.
유용한 사고 모델은 에이전트를 세 개의 계층으로 분리하는 것입니다:
결정 정책(LLM 프롬프트 + 모델)
에이전트가 다음 행동을 어떻게 선택하는지를 캡슐화합니다. 엄격한 제약 하에서 해석됩니다. 모델을 교체하거나 온도를 조정하거나 프롬프트를 정제할 수 있어도 시스템 배선은 건드리지 않아야 합니다.
상태 머신 / 워크플로 엔진
프로세스상 어디에 있는지, 어떤 전이가 가능한지, 진행을 어떻게 영속화할지를 소유합니다. 정책은 이동을 제안하고 상태 머신이 검증해 적용합니다.
도구 계층
실제로 세상에서 무엇이 일어나는지를 구현합니다: API, DB, 큐, 외부 서비스. 도구는 좁고 타입이 정해진 계약을 노출하고 인가, 레이트 리밋, 입력 검증을 시행합니다.
이 분리를 강제하면 비즈니스 로직을 프롬프트나 도구 설명에 숨기는 함정을 피할 수 있습니다. LLM은 결정 구성 요소로서 명확하고 결정론적인 외피 안에서 동작합니다.
가장 신뢰할 수 있는 에이전트 시스템은 가장 인상적인 데모가 아니라, 화이트보드에서 동작을 설명할 수 있는 시스템입니다.
구체적으로:
이러한 작은, 조립 가능한, 잘 구조화된 에이전트에 대한 편향이 시스템이 범위를 확장해도 복잡성에 무너지지 않게 합니다.
대부분의 에이전트 구현은 LLM 호출을 둘러싼 "생각, 행동, 관찰" 루프로 시작합니다. 데모에는 괜찮지만 곧 불투명하고 취약해집니다. 더 나은 접근법은 에이전트를 명시적 상태 머신으로 다루는 것입니다: 유한한 상태 집합과 이벤트로 촉발되는 명확한 전이.
모델이 다음에 뭘 할지 암묵적으로 결정하게 두지 말고 작은 상태 다이어그램을 정의하세요:
이 상태들 사이의 전이는 UserRequestReceived, ToolCallSucceeded, ToolValidationFailed, TimeoutExceeded, HumanOverride 같은 타입화된 이벤트에 의해 촉발됩니다. 각 이벤트와 현재 상태가 다음 상태와 행동을 결정합니다.
이렇게 하면 재시도와 타임아웃을 간단하게 만들 수 있습니다: 개별 상태에 정책을 붙이면 됩니다(예: CALL_TOOL은 지수적 백오프로 3번 재시도, PLAN은 재시도하지 않음). 분산된 코드베이스 전반에 재시도 로직을 흩어놓는 일을 피할 수 있습니다.
현재 상태와 최소한의 컨텍스트를 외부 저장소(DB, 큐, 워크플로 엔진)에 영속화하세요. 에이전트는 이제 순수 함수가 됩니다:
next_state, actions = transition(current_state, event, context)
이로 인해 얻을 수 있는 것들:
상태 머신을 사용하면 에이전트 동작의 모든 단계가 명시적입니다: 어떤 상태였는지, 어떤 이벤트가 발생했는지, 어떤 전이가 발동했는지, 어떤 부작용이 생성되었는지. 이 명확성은 디버깅을 빠르게 하고 인시던트 조사와 컴플라이언스 리뷰를 단순화합니다. 로그와 상태 기록으로 특정 위험한 행동이 특정 상태 및 정의된 조건에서만 수행되었음을 증명할 수 있습니다.
도구가 "프롬프트 안에 숨어있는 API"처럼 보이지 않고 명확한 보장(guarantee)을 가진 인터페이스처럼 보일 때 에이전트는 훨씬 예측 가능해집니다.
각 도구는 다음을 포함하는 계약을 가져야 합니다:
InvalidInput, NotFound, RateLimited, TransientFailure 같은 타입 오류와 명확한 의미이 계약을 모델에 구조화된 문서로 노출하세요. 에이전트 플래너는 어떤 오류가 재시도 가능한지, 사용자 개입이 필요한지, 워크플로를 중단해야 하는지를 알아야 합니다.
도구 입출력을 다른 프로덕션 API처럼 다루세요:
이렇게 하면 프롬프트를 장황하게 만드는 대신 스키마 기반 안내에 의존할 수 있습니다. 명확한 제약은 환각(hallucination)된 인수와 말이 안 되는 도구 시퀀스를 줄입니다.
도구는 진화합니다; 에이전트가 도구 변경 때문에 깨지면 안 됩니다.
v1, v1.1, v2)을 붙이고 에이전트를 특정 버전에 고정하세요.플래닝 로직은 서로 다른 성숙도 수준의 에이전트와 도구를 안전하게 섞을 수 있습니다.
도구 계약을 부분 실패를 염두에 두고 설계하세요:
에이전트는 그에 따라 적응할 수 있습니다: 기능을 축소해 워크플로를 계속하거나 사용자에게 확인을 요청하거나 대체 도구로 전환합니다.
도구 계약은 보안 제한을 인코딩하기에 자연스러운 장소입니다:
confirm: true)를 요구하세요.모델이 "잘 행동"할 것이라고만 믿지 말고 서버 사이드 검사와 결합하세요.
도구가 명확하고 검증 가능하며 버전 관리된 계약을 가지면 프롬프트는 짧아지고 오케스트레이션 로직은 단순해지며 디버깅이 훨씬 쉬워집니다. 복잡성을 취약한 자연어 지시에서 결정론적 스키마와 정책으로 옮기면 환각된 도구 호출과 예기치 않은 부작용이 줄어듭니다.
신뢰할 수 있는 에이전트 시스템은 모든 것이 결국 실패할 것이라고 가정합니다: 모델, 도구, 네트워크, 심지어 자체 조정 계층까지. 목표는 실패를 피하는 것이 아니라 실패를 저렴하고 안전하게 만드는 것입니다.
멱등성은: 같은 요청을 반복하면 한 번 실행한 것과 동일한 외부 가시적 효과를 가진다는 뜻입니다. 이는 부분 실패나 모호한 응답 이후에 도구 호출을 자주 다시 수행하는 LLM 에이전트에서 매우 중요합니다.
도구를 멱등하도록 설계하세요:
request_id를 포함하세요. 도구는 이를 저장하고 다시 같은 ID를 보면 동일한 결과를 반환합니다.일시적 실패(타임아웃, 레이트 리밋, 5xx)에 대해서는 구조화된 재시도(지수 백오프, 지터, 엄격한 최대 시도 수)를 사용하세요. 에이전트 동작을 추적할 수 있게 상관 ID로 모든 시도를 로깅하세요.
영구 실패(4xx, 검증 오류, 비즈니스 규칙 위반)에 대해서는 재시도하지 마세요. 에이전트 정책에 구조화된 오류를 노출해 재계획, 사용자 문의, 다른 도구 선택 등을 하게 하세요.
에이전트와 도구 계층 모두에 회로 차단기를 구현하세요: 반복 실패 이후 해당 도구에 대한 호출을 일시 차단하고 빠르게 실패하게 만드세요. 이를 저하 모드(캐시, 근사값, 대체 도구)와 페어링하세요.
에이전트 루프에서 무작정 재시도하지 마세요. 도구가 멱등하지 않고 실패 유형이 명확하지 않으면, 단지 부작용, 지연, 비용만 증폭시킬 뿐입니다.
신뢰할 수 있는 에이전트는 무엇이 상태인지와 어디에 존재하는지에 대해 명확하게 생각하는 것에서 시작합니다.
에이전트를 요청을 처리하는 서비스처럼 다루세요:
이 둘을 섞으면 혼란과 버그가 생깁니다. 예를 들어 일시적인 도구 결과를 메모리에 넣으면 이후 대화에서 오래된 컨텍스트를 재사용하게 됩니다.
세 가지 주요 옵션이 있습니다:
좋은 규칙: LLM은 명시적 상태 객체에 대한 무상태 함수입니다. 그 상태 객체를 모델 밖에 보관하고 프롬프트는 그로부터 재생성하세요.
대화 로그, 트레이스, 원시 프롬프트를 사실상의 메모리로 사용하는 것은 흔한 실패 패턴입니다.
문제점:
대신 user_profile, project, task_history 같은 구조화된 메모리 스키마를 정의하세요. 로그는 상태에서 파생되어야지 그 반대가 되어서는 안 됩니다.
여러 도구나 에이전트가 동일 엔터티(예: CRM 레코드, 티켓, 문서)를 업데이트할 때는 기본적인 일관성 제어가 필요합니다:
중요한 작업에는 대화 로그와 별개로 무엇이 변경되었는지, 왜 변경되었는지, 어떤 입력을 근거로 했는지 기록한 결정 로그를 남기세요.
크래시, 배포, 레이트 리밋을 견디려면 워크플로는 재개 가능해야 합니다:
이것은 타임 트래블 디버깅도 가능하게 합니다: 문제가 된 정확한 상태를 검사하고 재생할 수 있습니다.
메모리는 자산인 동시에 책임입니다. 프로덕션 에이전트를 위해:
메모리를 설계되고 버전 관리되며 거버넌스되는 제품 표면으로 취급하세요. 단순히 늘어나는 텍스트 덤프로 두지 마세요.
에이전트는 화이트보드상에서 순차적으로 보일지 몰라도 실제 부하 아래서는 분산 시스템처럼 동작합니다. 많은 동시 사용자, 도구, 백그라운드 작업이 생기면 레이스 컨디션, 중복 작업, 정렬 문제를 처리해야 합니다.
일반적인 실패 모드:
이를 완화하려면 멱등성 도구 계약, 명시적 워크플로 상태, 데이터 레이어의 낙관적/비관적 잠금을 사용하세요.
동기적인 요청–응답 흐름은 단순하지만 취약합니다: 모든 종속성이 작동하고 레이트 리밋 내에 있으며 빠르게 응답해야 합니다. 에이전트가 많은 도구나 병렬 하위 작업으로 팬아웃하면 장기 실행이나 부작용 스텝을 큐 뒤로 옮기세요.
큐 기반 오케스트레이션은 다음을 가능하게 합니다:
에이전트는 일반적으로 세 가지 클래스의 리밋에 걸립니다:
명시적 레이트-리밋 계층을 도입해 사용자별, 테넌트별, 글로벌 스로틀을 적용하세요. 토큰 버킷이나 리키 버킷을 사용하고 RATE_LIMIT_SOFT, RATE_LIMIT_HARD와 같은 명확한 오류 타입을 노출해 에이전트가 점진적으로 백오프하게 하세요.
백프레셔는 시스템이 스트레스 아래 스스로를 보호하는 방식입니다. 전략으로는:
큐 깊이, 워커 활용률, 모델/도구 오류율, 레이턴시 퍼센타일을 모니터해 포화 신호를 감지하세요. 큐가 쌓이고 레이턴시나 429/503 오류가 증가하면 에이전트가 환경을 넘기려 한다는 초기 경고입니다.
무엇을 해야 하는지, 왜 그렇게 했는지를 빠르게 대답할 수 없다면 에이전트를 신뢰할 수 없습니다. 에이전트 관찰성은 그 답을 쉽고 정밀하게 하는 것입니다.
단일 작업에 대해 다음을 하나의 트레이스로 연결하세요:
트레이스 안에 핵심 결정(라우팅 선택, 계획 수정, 가드레일 발동)을 위한 구조화된 로그와 볼륨 및 상태를 위한 메트릭을 붙이세요.
유용한 트레이스에는 보통 다음이 포함됩니다:
프롬프트, 도구 입력, 출력을 구조화된 형태로 로깅하되 레다션 레이어를 통과시키세요:
로우 콘텐츠는 하위 환경에서 기능 플래그로 접근 가능하게 하고, 프로덕션은 기본적으로 레다션된 뷰를 사용하세요.
최소한 다음을 추적하세요:
인시던트 시 좋은 트레이스와 메트릭은 "에이전트가 불안정하게 느껴진다"에서 "P95 작업이 ToolSelection에서 2번의 재시도 이후 실패, 원인은 billing_service의 새로운 스키마" 같은 정밀한 진술로 이동하게 해 진단 시간을 수시간에서 수분으로 줄입니다.
에이전트를 테스트한다는 것은 에이전트가 호출하는 도구와 모든 것을 이어 붙이는 흐름을 모두 테스트하는 것입니다. 프롬프트 튜닝이 아니라 분산 시스템 테스트로 접근하세요.
도구 경계에서 시작하세요:
이 테스트들은 LLM에 의존하지 않습니다. 합성 입력으로 도구를 직접 호출해 정확한 출력이나 오류 계약을 검증합니다.
통합 테스트는 에이전트 워크플로(LLM + 도구 + 오케스트레이션)를 종단 간으로 검증합니다.
시나리오 기반 테스트를 만드세요:
이 테스트들은 LLM의 모든 토큰을 검증하지 않고 상태 전이와 도구 호출을 assert 하세요: 어떤 도구가 어떤 인수로 어떤 순서로 호출되었는지, 최종 상태/결과가 무엇인지.
테스트를 반복 가능하게 유지하려면 LLM 응답과 도구 출력을 픽스처로 고정하세요:
전형적인 패턴:
with mocked_llm(fixtures_dir="fixtures/llm"), mocked_tools():
result = run_agent_scenario(input_case)
assert result.state == "COMPLETED"
모든 프롬프트나 스키마 변경은 비협상적인 회귀 실행을 트리거해야 합니다:
스키마 진화(필드 추가, 타입 강화)는 별도의 회귀 케이스로 관리해 여전히 옛 계약을 가정하는 에이전트나 도구를 잡아냅니다.
새 모델, 정책, 라우팅 전략을 바로 프로덕션에 배포하지 마세요.
대신:
오프라인 게이트를 통과한 경우에만 새 변형을 프로덕션에 투입하세요. 이상적으로는 피처 플래그와 점진적 롤아웃을 사용합니다.
에이전트 로그는 종종 민감한 사용자 데이터를 포함합니다. 테스트는 이를 존중해야 합니다:
CI 파이프라인에서 이러한 규칙을 코드화해 익명화 검증 없이 테스트 아티팩트를 생성하거나 저장하지 못하게 하세요.
에이전트를 운영하는 것은 정적 모델을 배포하는 것보다 분산 시스템을 운영하는 것에 가깝습니다. 롤아웃 제어, 명확한 신뢰성 목표, 엄격한 변경 관리가 필요합니다.
새 에이전트나 동작을 점진적으로 도입하세요:
모든 변경은 피처 플래그와 설정 기반 정책(라우팅 규칙, 사용 도구, 온도, 안전 설정)으로 제어하세요. 코드가 아니라 설정으로 배포되고 즉시 되돌릴 수 있어야 합니다.
시스템 건강과 사용자 가치를 반영하는 SLO를 정의하세요:
이를 알람에 연결하고 일반적인 프로덕션 서비스처럼 인시던트를 처리하세요: 명확한 소유권, 런북, 완화(플래그 롤백, 트래픽 드레인, 안전 모드) 단계 포함.
로그, 트레이스, 대화 기록을 사용해 프롬프트, 도구, 정책을 정교화하세요. 각 변경은 버전화된 아티팩트로 검토, 승인, 롤백 가능해야 합니다.
무단 프롬프트나 도구 변경은 피하세요. 변경 통제가 없으면 회귀를 특정 편집과 연결할 수 없고 인시던트 대응은 추측에 의존하게 됩니다.
프로덕션 준비된 에이전트 시스템은 책임 분리가 명확한 구조의 이점을 누립니다. 목표는 에이전트를 결정에서는 "똑똑하게" 유지하되 인프라에서는 "단순하게" 만드는 것입니다.
1. 게이트웨이 / API 엣지
클라이언트(앱, 서비스, UI)를 위한 단일 진입점입니다. 여기서:
2. 오케스트레이터
오케스트레이터는 "뇌간"이지 뇌는 아닙니다. 다음을 조정합니다:
LLM은 오케스트레이터 뒤에 위치하며 플래너와 특정 도구에서 언어 이해용으로 사용됩니다.
3. 도구 및 스토리지 계층
비즈니스 로직은 기존 마이크로서비스, 큐, 데이터 시스템에 남겨두세요. 도구는 다음을 감싸는 얇은 래퍼입니다:
오케스트레이터는 엄격한 계약을 통해 도구를 호출하고 스토리지 시스템은 진실의 원천입니다.
게이트웨이에서 인증과 쿼터를 시행하고, 오케스트레이터에서 안전성, 데이터 접근, 정책을 시행하세요. 모든 호출(LLM과 도구)은 구조화된 텔레메트리를 방출해 다음에 공급됩니다:
더 단순한 아키텍처(게이트웨이 → 단일 오케스트레이터 → 도구)가 운영하기 쉽습니다. 플래너, 정책 엔진, 모델 게이트웨이를 분리하면 유연성은 커지지만 조정, 지연, 운영 복잡성이 증가합니다.
이제 로드에서 예측 가능하게 동작하는 에이전트를 만드는 핵심 재료를 가지고 있습니다: 명시적 상태 머신, 명확한 도구 계약, 규율된 재시도, 그리고 깊은 관찰성. 마지막 단계는 이 아이디어들을 팀 내에서 반복 가능한 실천으로 전환하는 것입니다.
각 에이전트를 상태가 있는 워크플로로 생각하세요:
이 요소들이 정렬되면 엣지 케이스에서 우아하게 저하되는 시스템을 얻을 수 있습니다. 무너지지 않습니다.
프로토타입 에이전트를 실제 사용자에게 배포하기 전에 다음을 확인하세요:
어느 항목이라도 없다면 아직 프로토타입 단계입니다.
지속 가능한 구조는 보통 다음과 같이 분리됩니다:
이렇게 하면 제품팀은 빠르게 움직일 수 있고 플랫폼팀은 신뢰성, 보안, 비용 제어를 강제할 수 있습니다.
기반이 안정화되면 다음을 탐구하세요:
이런 진전은 점진적으로 도입하세요: 피처 플래그 뒤에서, 오프라인 평가와 강력한 가드레일로 보호하세요.
일관된 주제는 같습니
에이전트 시스템은 LLM이 단순히 한 번의 프롬프트에 답하는 것을 넘어 다음에 무엇을 할지 결정하는 애플리케이션입니다: 어떤 도구를 호출할지, 어떤 데이터를 가져올지, 워크플로의 어떤 단계를 실행할지, 언제 종료할지를 판단합니다.
단순한 채팅 완성(chat completion)과 달리 에이전트 시스템은 다음을 결합합니다:
프로덕션에서는 LLM이 시스템 전체가 아니라 명확한 결정 구성 요소 중 하나로 동작합니다.
데모는 대개 단 하나의 해피 패스를 가정합니다: 한 명의 사용자, 이상적인 도구 동작, 타임아웃 없음, 스키마 변화 없음, 짧은 대화. 프로덕션 부하에서는 에이전트가 다음과 같은 문제에 직면합니다:
명시적 워크플로, 계약, 실패 처리 없이 이 요인들은 데모 환경에서는 드러나지 않는 루프, 정지, 부분 완료, 조용한 오류를 만들어냅니다.
LLM을 자유형 루프 대신 명확한 구조 안에서 운용하세요:
이렇게 하면 각 단계별로 동작을 설명하고 테스트하며 디버그할 수 있습니다. 불투명한 “에이전트의 생각” 루프를 쫓을 필요가 줄어듭니다.
에이전트를 while not done: call LLM 같은 자유 루프 대신 명명된 상태와 타입화된 이벤트를 가진 워크플로로 모델링하세요.
일반적인 상태 예시는 다음과 같습니다:
도구를 프롬프트 안의 설명처럼 두지 말고, 프로덕션 API처럼 명확한 계약을 정의하세요. 각 도구는 다음을 가져야 합니다:
모든 것은 결국 실패한다고 가정하고 설계하세요(모델, 네트워크, 도구, 조정 계층 등). 목표는 실패를 회피하는 것이 아니라 실패를 저렴하고 안전하게 만드는 것입니다.
핵심 패턴:
request_id나 비즈니스 키를 포함시키세요.**단기 상태(짧은 시간)**와 장기 메모리를 분리하고, LLM 자체는 상태를 갖지 않는다고 생각하세요.
LLM은 명시적 상태 객체에 대한 무상태 함수로 취급하세요: 권한 있는 상태를 외부에서 로드하고 프롬프트를 생성해 모델을 호출한 뒤, 갱신된 상태를 저장합니다.
대화 로그나 트레이스를 그대로 메모리로 사용하는 안티패턴을 피하고, 구조화된 메모리 스키마(user_profile, project, task_history 등)를 정의하세요.
에이전트는 화이트보드상에 순차적으로 보이지만 실제 부하에서는 분산 시스템처럼 동작합니다. 동시성이 생기면 레이스, 중복 작업, 순서 문제를 처리해야 합니다.
권장 방법:
어떤 작업이 어떤 행동을 했고 왜 했는지를 빠르게 답할 수 있어야 합니다. 에이전트 관찰성은 그 답변을 쉽고 정확하게 하는 것입니다.
필요한 요소:
에이전트를 진화하는 서비스로 취급하고 다른 프로덕션 서비스처럼 엄격하게 관리하세요.
권장 관행:
PLAN – 요청을 해석해 단계별 계획을 생성CALL_TOOL – 특정 도구(또는 배치)를 호출VERIFY – 출력물을 간단한 불변식이나 추가 모델 검사로 확인RECOVER – 재시도, 폴백, 에스컬레이션으로 오류 처리DONE / FAILED – 종료 결과이벤트(예: ToolCallSucceeded, TimeoutExceeded)와 현재 상태가 합쳐져 다음 상태를 결정합니다. 이렇게 하면 재시도와 타임아웃 처리가 프롬프트나 흩어진 코드에 숨겨지지 않고 명시적으로 관리됩니다.
InvalidInput, NotFound, RateLimited, TransientFailure와 같은 타입화된 오류모델에 계약을 구조화된 문서로 노출하고, 어떤 오류가 재시도 가능한지, 사용자 개입이 필요한지, 워크플로를 중단해야 하는지를 플래너가 알게 하세요.
큐 깊이, 레이턴시 퍼센타일, 429/503 비율을 모니터해 과부하 징후를 조기에 포착하세요.
이렇게 하면 사건 대응 시 “에이전트가 불안정하게 느껴진다”는 모호한 상태에서 벗어나, 문제를 일으킨 상태와 도구, 변경점을 정확히 지목할 수 있습니다.
제품팀은 동작과 도메인 도구, 평가 데이터셋을 소유하고 플랫폼팀은 상태 머신 프레임워크, 공통 도구 SDK, 관찰성, 정책 집행을 소유하는 방식으로 책임을 분리하면 지속 가능성이 높아집니다.