AI 에이전트를 만드는 일은 그냥 “모델을 호출하고 도구를 실행하는 루프”처럼 보입니다. 하지만 실제로 구축해보면 SDK 선택부터 캐싱 전략, 실패 처리까지 예상치 못한 복잡함과 마주하게 되죠.

Flask 프레임워크를 만든 개발자 Armin Ronacher가 수개월간 에이전트를 구축하며 얻은 교훈을 정리한 글을 발표했습니다. 이론이 아닌 실전에서 작동하는 설계 원칙과 예상치 못한 함정들을 솔직하게 공유하고 있어요.
출처: Agent Design Is Still Hard – Armin Ronacher’s Blog
SDK 추상화의 역설: 더 낮은 레벨이 더 나을 때
Vercel AI SDK나 Pydantic 같은 고수준 추상화 라이브러리들이 있지만, Armin의 팀은 결국 OpenAI나 Anthropic의 원본 SDK를 직접 사용하는 쪽으로 회귀했습니다.
이유는 간단합니다. 모델마다 차이가 너무 크고, 추상화 레이어가 오히려 방해가 된다는 거예요. 예를 들어 Anthropic의 웹 검색 도구를 Vercel SDK로 사용하면 메시지 히스토리가 자주 깨지는데, 원인조차 파악하기 어려웠다고 합니다. 캐시 관리나 에러 메시지도 원본 SDK를 쓸 때 훨씬 명확하죠.
“에이전트 설계는 기본적으론 그냥 루프인데, 제공하는 도구에 따라 미묘한 차이들이 생깁니다. 올바른 추상화가 아직 명확하지 않은 상황에서 원본 SDK를 쓰면 완전한 통제권을 유지할 수 있어요.”
아직 이 분야가 안정화되지 않았다는 뜻입니다. 당분간은 낮은 레벨에서 직접 제어하는 편이 더 나을 수 있어요.
명시적 캐싱: 불편해 보이지만 실은 더 좋다
Anthropic은 캐시 포인트를 직접 지정하고 비용도 따로 내야 합니다. 처음엔 “왜 플랫폼이 자동으로 안 해주지?”라고 생각했지만, 지금은 오히려 이 방식을 선호한다고 해요.
명시적 캐싱의 장점은 예측 가능성입니다. 비용과 캐시 활용률을 정확히 파악할 수 있고, 대화를 분기시켜 동시에 두 방향으로 진행하거나 컨텍스트를 수정하는 것도 가능해집니다. 다른 플랫폼의 자동 캐싱은 “될 때도 있고 안 될 때도 있어서” 예측이 어렵다고 하네요.
실제 구현은 간단합니다. 시스템 프롬프트 뒤에 캐시 포인트 하나, 대화 시작 부분에 두 개를 두고, 마지막 포인트는 대화가 진행되면서 함께 이동시킵니다. 현재 시간 같은 동적 정보는 나중에 별도 메시지로 넣어 캐시를 유지하죠.
강화(Reinforcement): 에이전트에게 계속 상기시키기
Simon Willison이 강조한 “강화(reinforcement)”는 에이전트가 도구를 실행할 때마다 추가 정보를 주입하는 기법입니다. 단순히 도구 결과만 돌려주는 게 아니라, 전체 목표를 다시 상기시키거나 작업 상태를 알려주거나 실패 시 힌트를 제공하는 거예요.
흥미로운 예가 Claude Code의 TODO 도구입니다. 이 도구는 에이전트가 “할 일 목록”을 입력하면 그대로 되돌려주기만 합니다. 말 그대로 에코 도구죠. 하지만 이것만으로도 에이전트가 길을 잃지 않고 앞으로 나아가게 하는 데 충분하다고 합니다.
“에이전트가 자기 자신을 강화하는 겁니다. 작업 목록을 명시적으로 말하고 확인받는 것만으로도, 컨텍스트에서 초기 목표가 너무 멀어진 상황을 방지할 수 있어요.”
강화는 환경 변화를 알리는 데도 씁니다. 에이전트가 실패 후 재시도하는데 잘못된 데이터로 작업하고 있다면, 몇 단계 뒤로 가서 다시 시작하라는 메시지를 주입할 수 있죠.
실패를 숨기고, 상태를 공유하고
에이전트가 시행착오를 많이 거친다면, 그 실패들을 컨텍스트에서 숨길 수 있습니다. 서브 에이전트를 돌려서 성공할 때까지 반복시키고, 메인 에이전트에게는 최종 성공 결과와 “이런 접근은 안 됐다”는 간략한 요약만 전달하는 거예요. 실패 기록도 학습에 도움이 되지만, 모든 세부사항이 필요한 건 아니니까요.
도구들 사이에 데이터를 공유하려면 공통 저장소가 필요합니다. Armin의 팀은 가상 파일 시스템을 선택했어요. 이미지 생성 도구가 결과를 파일로 저장하면, 코드 실행 도구가 그걸 읽어서 zip으로 묶는 식입니다. 파일 경로를 주고받으면서 작업이 끊기지 않고 이어지죠.
출력 도구 설계도 까다롭습니다. 에이전트가 사용자에게 최종 메시지를 보내는 전용 도구를 만들었는데, 말투와 톤을 조정하는 게 예상보다 어렵더라고 합니다. 메인 루프의 텍스트 출력을 그대로 쓰는 것보다 품질이 떨어지는 경우도 있었어요. 때로는 출력 도구를 아예 호출하지 않아서, 루프가 끝날 때 “출력 도구를 사용하세요”라고 강화 메시지를 넣어야 했죠.
아직 해결되지 않은 문제: 테스트
Armin이 가장 어렵다고 꼽은 문제는 테스팅입니다. 일반 프롬프트는 외부 시스템에서 평가할 수 있지만, 에이전트는 너무 많은 것을 입력해야 해서 실제 테스트 실행이나 관찰 데이터를 기반으로 평가해야 합니다. 하지만 지금까지 시도한 솔루션 중 만족스러운 게 없었다고 해요.
“에이전트의 특성상 평가가 훨씬 더 어렵습니다. 이건 점점 더 좌절스러운 부분이 되고 있어요.”
아직 정답이 없는 영역
모델 선택은 크게 바뀌지 않았습니다. Anthropic의 Haiku와 Sonnet이 여전히 최고의 도구 호출 성능을 보이고, Gemini 2.5는 대용량 문서 요약이나 PDF 작업에 유용하다고 합니다.
중요한 건 토큰 비용만으로 에이전트 비용을 판단할 수 없다는 점입니다. 더 나은 도구 호출 성능을 가진 모델이 더 적은 토큰으로 작업을 끝내니까요. 겉보기엔 저렴해 보이는 모델이 루프 안에서는 오히려 비쌀 수 있습니다.
에이전트 설계는 여전히 빠르게 진화하는 영역입니다. 정답은 아직 없고, 각자 실험하며 배워가는 단계죠. Armin의 경험담이 가치 있는 이유는, 실제로 작동하는 것과 그렇지 않은 것을 솔직하게 공유했기 때문입니다.
참고자료:
- Agent design is still hard – Simon Willison
- LLM APIs are a Synchronization Problem – Armin Ronacher’s Blog

답글 남기기