LLMOps 06
AI Agent 시스템: 설계 문서, 형식인가 핵심인가?
설계 문서가 형식적인 산출물로 보이고 실제 구현과 분리되어 보인다.
근거 · 교안 p19-p20
1장: 설계의 첫걸음: 무엇을 해결하고 싶은가?
솔라가 태블릿 화면을 손가락으로 툭툭 건드렸다. 화면에는 ‘AI Agent 시스템 설계 절차’라는 제목 아래로 여러 단계가 나열되어 있었다. 그 첫 번째 항목 앞에서 솔라의 손가락이 멈췄다.
1. 문제 정의 (Problem Definition)
- 무엇을 해결해야 하는가?
- 기존 방식의 한계는 무엇인가?
“언니, 이거 좀 봐. AI 에이전트 시스템을 설계한다면서 첫 단계가 ‘문제 정의’래.”
서재에서 책을 고르던 루나가 솔라 쪽으로 고개를 돌렸다.
“너무 당연한 말 아니야? ‘무엇을 해결해야 하는가?’라니. 이걸 모르는 사람이 어디 있겠어. 그냥 빨리 아이디어를 내고 구현부터 시작하면 안 되나? 이런 건 그냥 형식적인 절차처럼 보여.”
솔라의 목소리에는 실질적인 코딩 작업을 시작하기도 전에 발목을 잡힌 듯한 조바심이 묻어났다. 그냥 멋진 AI를 만들고 싶다는 마음이 앞서는데, 문서 작업은 그 길을 가로막는 장애물처럼 느껴졌다.
루나는 들고 있던 책을 내려놓고 솔라의 옆에 와 앉았다. 태블릿 화면을 들여다보는 루나의 표정은 잔잔했다.
“좋은 지적이야. 그럼 우리 둘이서 지금 당장 ‘집안일 돕는 AI 에이전트’를 만든다고 상상해 보자. 문제 정의 같은 건 건너뛰고 바로.”
“좋아! 그럼… 일단 설거지를 시키자! 아니, 청소! 아니면 빨래를 대신해 주는 것도 좋고. 아예 ‘집안일 전부 다 해줘’라고 말하면 알아서 다 해주는 에이전트는 어때?”
솔라의 눈이 반짝였다. 상상만으로도 신나는 일이었다. 하지만 루나는 차분하게 질문을 던졌다.
“‘전부 다’ 좋지. 그런데 지금 당장 에이전트가 세탁기와 청소기 중 하나만 돌릴 수 있다면, 뭘 먼저 해야 할까? 그걸 에이전트는 어떻게 결정하지?”
“음… 그야 더 급한 거부터?”
“‘급하다’의 기준은 뭔데? 예를 들어, 솔라 네가 저녁 약속 때문에 꼭 입어야 할 옷이 더러워진 상황이라면 빨래가 급하겠지. 하지만 집 전체가 먼지투성이라면 청소가 더 급할 수도 있고. 에이전트는 그 상황을 어떻게 알 수 있을까?”
솔라는 잠시 말문이 막혔다. 생각지도 못한 부분이었다. “그럼… 내일 입을 옷이 있는지 옷장을 확인하고, 내 스케줄도 확인해서… 어? 잠깐만. 그럼 에이전트가 내 캘린더랑 옷장 정보까지 다 알아야 하잖아. 복잡해지는데.”
“복잡해지지. 또 다른 문제도 있어. ‘깨끗하게 청소해 줘’라고 했을 때, 솔라 네가 만족하는 ‘깨끗함’과 내가 만족하는 ‘깨끗함’의 기준이 다를 수 있어. 바닥에 머리카락 한 올 없는 상태를 원하는지, 아니면 큰 먼지만 없으면 되는 건지. 이걸 정하지 않으면 에이전트는 일을 끝내고도 계속 ‘이 정도면 됐나요?’라고 물어봐야 할지도 몰라.”
루나의 말은 상상 속에서 신나게 뛰어놀던 솔라를 현실의 바닥으로 끌어내렸다. ‘모든 집안일을 해주는 만능 에이전트’라는 아이디어는 근사했지만, 막상 구현을 상상하니 무엇부터 손대야 할지, 어떤 기능을 만들어야 할지 막막했다. 어디까지가 에이전트의 책임이고, 사용자가 어디까지 정보를 줘야 하는지 경계도 모호했다.
그제야 솔라는 무릎을 쳤다.
“아…! 알겠다. ‘어떤 문제를 해결할 것인가’를 명확히 정하지 않으니까, 뭘 만들어야 할지 갈피를 잡을 수가 없구나. 그냥 ‘멋진 AI’를 만들고 싶다는 생각만으로는 코드를 한 줄도 짤 수 없는 거였어. 방향이 없으니까. 이게 성공인지 실패인지조차 판단할 기준이 없게 돼.”
솔라는 자신이 ‘형식적인 절차’라고 치부했던 ‘문제 정의’가, 사실은 망망대해 같은 개발 과정에서 길을 잃지 않게 해주는 첫 번째 등대였다는 것을 깨달았다.
“맞아. 바로 그거야.” 루나가 부드럽게 말을 이었다. “문제 정의와 목표 정의는 우리가 방금 겪었던 혼란, 즉 ‘어디로 가야 할지 모르는 방향성의 불확실성’을 가장 먼저 제거해 주는 도구야. 코드를 짜다가 ‘아, 이게 아니었네’라며 전부 뒤엎는 재작업을 막아주는, 가장 중요한 첫 번째 안전장치인 셈이지.”
솔라는 고개를 끄덕이며 태블릿 화면을 다시 들여다봤다. ‘문제 정의’라는 글자가 더 이상 공허한 선언으로 보이지 않았다. 그것은 수많은 시행착오를 막아줄 단단한 약속처럼 느껴졌다. 이제 이 첫걸음의 중요성을 확실히 알 것 같았다. 하지만 이내 솔라의 시선은 다음 단계로 옮겨갔다.
“좋아, 그럼 ‘외출 준비를 할 때 입을 옷이 없어 곤란한 문제를 해결한다’라고 문제를 정의했다고 치자. 방향은 잡혔어. 그런데 다음 단계는 ‘시나리오 정의’네? 이건 그냥 사용 설명서를 미리 써보는 거 아니야? ‘사용자가 “옷 추천해줘”라고 말한다. → 에이전트가 옷을 추천한다.’ 이런 흐름을 적는 게, 복잡한 코드를 짜는 데 정말로 직접적인 도움이 될까?”
2장: 사용자 여정 그리기: 시나리오로 구현의 길 찾기
솔라는 태블릿 한쪽에 작은 메모 위젯을 열고 무언가를 끄적였다. 지난번 언니와 나눴던 ‘옷 추천 AI 에이전트’에 대한 생각의 연장선이었다. 방향성을 정하는 ‘문제 정의’의 중요성은 이제 알았으니, 그 다음 단계인 ‘시나리오 정의’를 직접 해보면 감이 올까 싶었다.
그녀는 간단한 흐름도를 그렸다.
<옷 추천 에이전트 시나리오 (초안)>
- 사용자: “오늘 입을 옷 추천해줘.”
- 에이전트: [오늘 날씨]와 [사용자 스케줄]을 확인하여 옷 조합을 추천한다.
- 사용자: “좋아!”
- 종료
솔라는 펜 끝으로 화면을 톡톡 두드렸다. ‘이거 봐. 너무 뻔하잖아.’ 속으로 중얼거렸다. 입력, 처리, 출력. 단순한 기능 명세서와 다를 바 없어 보였다. 이런 간단한 흐름을 적는 것이 수천, 수만 줄의 복잡한 코드를 짜는 데 얼마나 큰 도움이 될지 여전히 의문이었다. 구현하다 보면 얼마든지 바뀔 수 있는, 그저 참고 자료 정도가 아닐까.
“그렇게 간단하면 좋겠지.”
어느새 다가온 루나가 솔라의 태블릿 화면을 들여다보며 말했다. 그녀는 솔라가 그린 흐름도의 3번 항목, ‘좋아!’라는 단어를 손가락으로 가리켰다.
“만약 사용자가 여기서 ‘좋아!’라고 하지 않는다면?”
“응? ‘좋아!’라고 안 하면… ‘다른 거 보여줘’라고 하겠지?”
솔라는 즉답했지만, 질문의 의도를 곱씹으며 말끝을 흐렸다. 루나는 솔라의 초안 아래에 새로운 질문을 적어 내려갔다.
만약 사용자가 "음, 그 청바지는 별로인데." 라고 말한다면?
순간 솔라의 머릿속이 복잡해졌다. 그녀가 그린 깔끔한 일직선 도로에 예상치 못한 갈림길이 나타난 기분이었다.
“그럼… 다른 청바지를 추천해 줘야지.”
“정말? ‘별로’라는 게 무슨 뜻일까? 색깔이 마음에 안 든다는 걸까, 아니면 핏이 별로라는 걸까? 어쩌면 오늘은 청바지 말고 치마를 입고 싶다는 뜻일 수도 있잖아. 에이전트는 이 중에서 어떤 선택을 해야 하지?”
루나의 질문은 꼬리에 꼬리를 물고 이어졌다.
- ‘다른 청바지’를 추천하려면, 방금 추천했던 것과는 다른 스타일의 청바지 목록이 미리 준비되어 있어야 할까?
- 사용자에게 ‘어떤 점이 별로인지’ 되물어야 한다면, 에이전트는 색상, 핏, 스타일 같은 구체적인 속성에 대한 대화를 이끌어갈 능력이 있어야 할까?
- 아예 다른 종류의 옷(치마나 슬랙스)을 제안하려면, 에이전트는 사용자의 숨은 의도를 파악하는 추론 능력이 필요할까?
“잠깐만… 생각보다 훨씬 복잡하네. 그냥 ‘다른 거’ 보여주는 문제가 아니었어.”
솔라는 깨달았다. 사용자의 간단한 불만 한마디에 대응하기 위해, 개발자는 에이전트의 성격(적극적으로 질문하는 타입인지, 아니면 다른 대안을 바로 제시하는 타입인지), 대화 능력의 깊이, 그리고 준비해야 할 데이터의 종류까지 전부 결정해야 했다. 그녀가 쓴 시나리오는 모든 것이 순조롭게 흘러가는 ‘행복한 길’(Happy Path)일 뿐, 실제 사용자가 겪을 수많은 갈림길과 막다른 길을 전혀 보여주지 못했다.
“바로 그거야.” 루나가 말했다. “시나리오 정의는 단순히 사용 설명서를 미리 쓰는 게 아니야. 사용자와 시스템 사이에서 벌어질 수 있는 모든 상호작용을 미리 시뮬레이션해보는 과정이지. 이 과정에서 우리는 ‘사용자는 이렇게 행동할 것이다’라는 막연한 기대를 ‘사용자가 이렇게 행동했을 때, 우리 시스템은 이렇게 반응한다’는 구체적인 약속으로 바꾸게 돼.”
루나는 덧붙였다. “이런 ‘사용자 경험의 불확실성’을 코드를 쓰기 전에 해소하는 거야. 만약 시나리오 없이 구현부터 시작했다면, 개발자는 ‘청바지가 별로’라는 피드백을 받은 후에야 부랴부랴 ‘어, 이제 어떡하지?’ 하고 고민하게 됐을 거야. 그 시점에서는 이미 짜놓은 코드를 대대적으로 수정해야 할지도 모르고.”
솔라는 자신이 처음 그렸던 단순한 흐름도를 다시 보았다. 이제 그것은 불완전한 지도로 보였다. 사용자가 어떤 길로 갈지, 어떤 장애물을 만날지 전혀 예측할 수 없는 지도. 잘 쓰인 시나리오는 바로 그 지도를 상세하게 완성해서, 개발자들이 길을 잃지 않고 헤매지 않도록 안내하는 역할을 하는 것이었다. 구현 과정에서 바뀔 수 있는 참고 자료가 아니라, 구현의 목표와 세부 사항을 결정하는 핵심 설계도였던 것이다.
“알겠다. 시나리오는 사용자의 기대를 구체화하고, 시스템이 그 기대에 어떻게 부응할지 미리 정하는 거구나. 이게 없으면 개발자마다 제각각 상상해서 만들 테니, 결국 누구도 만족 못 하는 결과가 나올 수도 있겠네.”
고개를 끄덕인 솔라의 시선이 다시 태블릿의 설계 절차 목록으로 향했다. ‘문제 정의’로 방향을 잡고, ‘시나리오 정의’로 사용자와의 구체적인 상호작용을 그렸다. 이제 길이 선명해지는 것 같았다.
“좋아, 그럼 이제 어떤 기능을 만들어야 할지 명확해졌어. 그런데 다음 단계는 ‘구조 설계’네. ‘Agent 역할 분리’라… 우리가 만들 옷 추천 에이전트가 날씨도 알려주고, 스케줄도 관리해준다고 치자. 이 기능들을 그냥 하나의 큰 에이전트 안에 다 넣어버리면 안 되는 건가? 굳이 역할을 나누는 이유가 뭐지? 이게 기술적으로 어떤 복잡성을 미리 막아준다는 걸까?”
3장: 큰 그림 그리기: Agent 구조 설계의 기술적 지혜
솔라는 거실 바닥에 앉아 태블릿 화면 위에 커다란 원을 하나 그렸다. 원 안에는 ‘만능 옷 추천 에이전트’라고 큼지막하게 글씨를 썼다. 그리고 그 원을 중심으로 여러 개의 화살표를 뻗어 나가게 했다. ‘날씨 확인’, ‘스케줄 분석’, ‘패션 트렌드 검색’, ‘사용자 스타일 파악’, ‘옷장 아이템 스캔’… 온갖 기능들이 거미줄처럼 ‘만능 에이전트’라는 하나의 중심에 연결되는 그림이었다. 이것이야말로 진정한 AI 에이전트의 모습이라고, 솔라는 생각했다. 모든 것을 하나로 통합한 강력하고 똑똑한 존재.
이전 단계에서 사용자와의 상호작용 시나리오를 그리며 길을 찾았으니, 이제 그 길을 달릴 멋진 자동차를 만들 차례였다. 솔라의 생각에 그 자동차는 모든 기능을 갖춘 만능 장갑차여야 했다. 굳이 여러 대로 나눌 필요가 있을까? 그녀는 태블릿 속 자신의 다이어그램을 보며 만족스럽게 고개를 끄덕였다. 이 정도면 개발자에게 넘겨도 바로 이해할 수 있을 만큼 명쾌한 그림이라고 생각했다.
“재밌는 그림이네.”
소파에서 조용히 책을 읽던 루나가 솔라의 태블릿을 들여다보며 말했다.
“옷 추천 에이전트 구조도야. 어때, 언니? 날씨 확인, 스케줄 분석, 트렌드 검색까지, 필요한 모든 기능을 이 ‘만능 에이전트’ 하나에 다 넣을 거야. 그럼 사용자는 그냥 이 에이전트하고만 대화하면 되니까 편하잖아. 이렇게 큰 그림을 하나로 딱 그려주는 게 ‘구조 설계’ 아니야?”
솔라는 자신의 아이디어가 꽤 그럴듯하다고 생각하며 어깨를 으쓱했다. 기능들을 분리하는 건 오히려 일을 복잡하게 만들 뿐이라고 믿었다.
루나는 잠시 생각에 잠기더니, 솔라의 다이어그램 옆 빈 공간에 작은 식당 주방을 그리기 시작했다. 카운터 하나, 커다란 화구 하나, 싱크대 하나. 그리고 그 안에 분주하게 움직이는 요리사 한 명.
“만약 우리가 식당을 연다고 상상해 보자. 그리고 이 요리사가 솔라 네가 그린 ‘만능 에이전트’야. 손님 주문도 받고, 스테이크도 굽고, 파스타도 만들고, 설거지도 하고, 재료 관리까지 혼자 다 하는 거지.”
“완전 슈퍼 셰프네! 멋진데?”
“처음엔 그렇겠지. 손님이 한두 명일 때는. 그런데 갑자기 점심시간에 손님 스무 명이 몰려온다면 어떨까? 한 손님은 스테이크를, 다른 손님은 파스타를, 또 다른 손님은 샐러드를 주문했어. 이 슈퍼 셰프는 뭘 먼저 해야 할까?”
루나의 질문에 솔라는 잠시 상상에 잠겼다. 머릿속에 그려지는 주방은 순식간에 아수라장이 되었다. 스테이크는 타 들어가고, 파스타 면은 불어 터지고, 주문은 계속 밀려들고…
“혼자서는 감당이 안 되겠네. 스테이크를 굽는 동안에는 파스타를 만들 수 없으니까. 결국 모든 음식이 늦어지거나 퀄리티가 엉망이 될 거야.”
“바로 그거야. 여기서 스테이크를 굽는 일이 너무 오래 걸려서 다른 모든 일이 막히는 현상을 ‘병목 현상’이라고 해. 이제 이걸 다시 네 에이전트 그림으로 가져와 보자.”
루나는 자신의 주방 그림과 솔라의 에이전트 다이어그램을 나란히 놓았다.
“‘패션 트렌드 검색’ 기능이 외부 사이트 문제로 갑자기 느려졌다고 생각해 봐. 그럼 어떻게 될까?”
솔라는 자신의 ‘만능 에이전트’를 다시 보았다. 모든 화살표가 하나의 중심을 향해 있었다. 만약 하나의 화살표가 막히면…
“아…! ‘패션 트렌드 검색’이 끝날 때까지 ‘날씨 확인’이나 ‘스케줄 분석’ 같은 다른 기능들도 전부 멈추겠구나. 그냥 날씨만 물어본 사용자도 아무런 대답을 못 듣고 하염없이 기다려야 하는 상황이 생길 수도 있어.”
“맞아. 더 큰 문제도 있어. 만약 우리가 ‘신발 추천’ 기능을 새로 추가하고 싶다면 어떨까? 이 거대한 ‘만능 에이전트’ 코드 덩어리 어딘가에 새로운 코드를 끼워 넣어야 해. 그러다 실수로 기존의 ‘날씨 확인’ 로직을 건드려서 망가뜨릴 수도 있지. 식당으로 치면, 새로운 디저트 메뉴를 추가하려다 스테이크 굽는 법을 까먹게 되는 셈이야.”
솔라는 그제야 자신의 생각이 얼마나 위험했는지 깨달았다. 하나의 거대한 덩어리로 만드는 것은 단순하고 강력해 보였지만, 실제로는 변화에 취약하고 작은 문제 하나가 시스템 전체를 마비시킬 수 있는 구조였다. 복잡하게 얽힌 실타래 같아서, 한 가닥을 고치려다 전체를 엉망으로 만들 위험이 너무 컸다.
루나는 솔라의 태블릿을 받아 들고 ‘만능 에이전트’ 다이어그램을 수정하기 시작했다. 거대한 원을 지우고, 그 자리에 여러 개의 작은 원들을 그렸다. 그리고 원들을 서로 다른 선으로 연결했다.
- ‘기획자(Planner) 에이전트’: 사용자의 “내일 뭐 입지?” 같은 모호한 요청을 받아서, 어떤 정보가 필요한지 계획을 세운다.
- ‘날씨 정보원(Weather) 에이전트’: 날씨 정보를 가져오는 역할만 전문적으로 수행한다.
- ‘일정 관리사(Calendar) 에이전트’: 사용자의 스케줄만 확인한다.
- ‘실행자(Executor) 에이전트’: 각 정보원에게 받은 정보를 종합해서 최종 옷 조합을 만들어낸다.
“이렇게 역할을 나누는 거야. 각 에이전트는 하나의 전문 분야만 책임져. 식당으로 치면 주문받는 매니저, 스테이크 전문 셰프, 파스타 전문 셰프, 설거지 담당을 따로 두는 거지. 이렇게 하면 ‘패션 트렌드 검색’이 느려져도 다른 기능에는 영향이 없어. ‘신발 추천’ 기능을 추가하고 싶으면 ‘신발 전문가 에이전트’를 새로 만들어서 연결만 하면 되고.”
솔라는 두 개의 다이어그램을 번갈아 보았다. 자신이 그렸던 거대한 원과 루나가 새로 그린 여러 개의 작은 원들의 연결망. 이제야 ‘구조 설계’의 진짜 의미가 보였다. 그것은 단순히 큰 그림을 그리는 것이 아니라, 미래에 닥칠 기술적 혼란과 복잡성을 미리 방지하기 위해 시스템의 뼈대를 세우는 작업이었다. 각 부품을 어떻게 나눌지, 그리고 어떻게 협력하게 할지를 정하는 ‘시스템의 청사진’이었다.
“그렇구나… 구조 설계는 그냥 고수준의 개요가 아니었어. 구현 단계에서 발생할 수 있는 기술적 복잡성, 예를 들면 확장이나 수정의 어려움 같은 불확실성을 미리 제거하는 거였네. 재앙을 막는 설계도였어.”
솔라는 고개를 끄덕였다. 이제 에이전트들을 어떻게 나눠야 할지 큰 그림은 잡혔다. 하지만 그녀의 시선은 루나가 그린 새로운 다이어그램의 ‘기획자 에이전트’와 ‘날씨 정보원 에이전트’를 잇는 선 위에서 멈췄다.
“알겠어. 이렇게 역할을 나누는 건 이해했어. 그런데… ‘기획자 에이전트’가 ‘날씨 정보원’한테 일을 시킬 때, 뭐라고 말하면서 시켜야 하지? ‘내일 날씨 알려줘’라고 그냥 말하나? 그럼 ‘날씨 정보원’은 답을 어떤 형식으로 줘야 해? ‘맑음’이라고 텍스트로 주나, 아니면 온도랑 습도까지 숫자로 줘야 하나? 이 작은 원들 하나하나의 역할과 서로 주고받는 약속은 대체 어떻게 정하는 거야?”
4장: 설계의 정점: Agent 내부 로직 구현의 불확실성 해소
솔라는 태블릿 화면 가득 펼쳐진 다이어그램 위에 복잡한 미로처럼 얽힌 화살표들을 그렸다 지우기를 반복하고 있었다. 지난번 루나가 그려주었던 ‘기획자’, ‘날씨 정보원’, ‘일정 관리사’, ‘실행자’ 에이전트의 구조도였다. 역할이 분리된 작은 원들은 깔끔했지만, 그 원들을 연결하는 선 위에서 솔라의 펜은 길을 잃었다.
그녀는 ‘기획자’와 ‘날씨 정보원’ 사이에 선을 긋고, 그 위에 조그맣게 말풍선을 그렸다.
기획자 → 날씨 정보원: “내일 날씨 알려줘.”
날씨 정보원 → 기획자: “내일은 맑을 거야.”
솔라는 펜을 내려놓고 한숨을 쉬었다. 이건 그냥 연극 대본 같았다. 개발자가 이 대본을 보고 대체 무슨 코드를 짤 수 있을까? ‘날씨 정보원’을 만드는 개발자는 ‘맑을 거야’라는 문장을 반환하는 함수를 만들까? 그럼 ‘기획자’를 만드는 개발자는 받은 문장에서 ‘맑음’이라는 단어를 추출하는 코드를 짜야 하나? 만약 ‘날씨가 화창합니다’라고 대답이 바뀌면 시스템은 바로 오류를 일으킬 것이다. 결국 이건 구현 단계에서 개발자들이 모여 다시 처음부터 약속을 정해야 하는, 번거롭고 불필요한 과정처럼 느껴졌다.
“마치… 외국인 두 명에게 각자 자기 나라 말로 된 설명서 한 쪽씩 던져주고, 거대한 기계를 조립하라고 하는 기분이야.”
솔라의 혼잣말에, 소파에서 다이어그램을 함께 들여다보던 루나가 조용히 입을 열었다.
“그럼 우리가 그 두 명의 개발자가 되어볼까?”
루나는 태블릿에 새로운 페이지를 열고 화면을 둘로 나눴다. 왼쪽에는 ‘개발자 A (기획자 담당)’, 오른쪽에는 ‘개발자 B (날씨 정보원 담당)’라고 썼다. 그리고 솔라가 쓴 대본을 가운데에 띄웠다.
“솔라, 네가 쓴 ‘기획자 → 날씨 정보원: 내일 날씨 알려줘’라는 대본을 보고, 개발자 A는 기획자 에이전트를 만들어. 그리고 나는 그 대본을 보고 개발자 B가 되어서 날씨 정보원 에이전트를 만들게.”
루나는 오른쪽 ‘개발자 B’ 영역에 코드 몇 줄을 흉내 내어 적었다.
# 개발자 B (루나)의 생각:
# "기획자가 날씨를 궁금해하네. 기온 정보도 중요하니까 같이 줘야겠다.
# 친절하게 설명도 덧붙여야지."
def get_weather(date):
weather_data = {
"temperature": 15,
"condition": "맑음",
"comment": "외출하기 좋은 날씨예요."
}
return weather_data
“자, 나는 날씨 정보원이 온도, 상태, 그리고 코멘트까지 담긴 데이터 묶음을 돌려주도록 만들었어. 이제 솔라 네가 개발자 A가 되어서, 날씨 정보원에게 ‘맑을 거야’라는 단순한 문장이 올 거라고 기대하고 코드를 짜 봐.”
솔라는 잠시 고민하다가 왼쪽 ‘개발자 A’ 영역에 자신의 생각을 코드로 표현했다.
# 개발자 A (솔라)의 생각:
# "날씨 정보원은 '맑음'이나 '비' 같은 간단한 단어를 주겠지.
# '맑음'이라는 단어가 포함되어 있으면 옷 추천 로직을 실행하자."
weather_response = get_weather("tomorrow") # 루나의 함수를 호출
if "맑음" in weather_response:
recommend_outfit()
else:
recommend_raincoat()
코드를 다 적고 난 솔라는 고개를 갸웃했다. 뭔가 이상했다. 자신이 짠 코드의 weather_response 변수에는 루나가 만든 복잡한 데이터 묶음 { "temperature": 15, ... }이 들어올 것이다. 하지만 솔라의 코드는 그 변수가 단순한 텍스트일 거라고 철석같이 믿고 if "맑음" in weather_response: 라는 조건문을 실행하려 한다.
“아…!”
솔라의 입에서 짧은 탄식이 터져 나왔다.
“말도 안 돼. 내 코드는 바로 에러가 나겠네. 데이터 묶음(객체)에다가 텍스트(문자열)에나 쓸 수 있는 ‘포함되어 있나(in)’ 명령을 내렸으니까. 두 에이전트는 절대 같이 일할 수 없어. 조립 자체가 불가능해.”
눈앞의 간단한 시뮬레이션은 처참한 실패를 명확히 보여주었다. 서로 다른 기대를 가진 두 개발자가 만든 소프트웨어는 연결되는 순간 부서져 버렸다. ‘구현 전에 쓰는 번거로운 문서’라고 생각했던 것의 부재가 어떤 재앙을 만드는지 순식간에 체감된 것이다.
루나는 솔라가 깨달음을 얻는 것을 지켜본 뒤, 태블릿에 새로운 표를 그리기 시작했다. 더 이상 모호한 말풍선이 아니었다. 표는 ‘Agent 역할 정의서’라는 제목 아래 체계적인 항목들로 채워졌다.
<Agent 역할 정의서: 날씨 정보원>
- 역할(Role): 주어진 날짜와 위치에 대한 날씨 정보를 제공한다. 패션 조언이나 일정 관리는 절대 하지 않는다.
- 입력(Input):
{"date": "YYYY-MM-DD", "location": "도시이름"}형식의 데이터. - 출력(Output):
{"temp_high": 숫자, "temp_low": 숫자, "condition": "맑음|흐림|비"}형식의 데이터.condition은 반드시 세 단어 중 하나여야 한다.
“이게 바로 ‘Agent 역할 정의서’야. 누가, 무엇을, 어떤 형식으로 주고받을지 명확하게 약속하는 거지.”
루나는 이어서 또 다른 문서를 보여주었다. ‘Workflow Spec’은 ‘기획자’가 ‘날씨 정보원’을 호출하고, 그 결과를 받아 ‘실행자’에게 넘기는 순서를 규정했다. ‘State 정의서’는 에이전트들 사이를 오가는 weather_data 같은 데이터의 구조를 명확히 정의했다.
솔라는 방금 전 자신이 겪었던 아찔한 오류의 순간과 루나가 제시한 상세 설계 문서들을 번갈아 보았다. 이제 이 문서들이 더 이상 형식적인 절차로 보이지 않았다. 이것은 서로 다른 방에서 각자의 부품을 만드는 개발자들에게 나눠주는, 단 하나의 통일된 ‘설계도면’이었다. 이 도면이 없다면, 아무리 뛰어난 개발자들이 모여도 결국 합쳐지지 않는 부품들만 잔뜩 만들게 될 터였다.
“알겠다… 이 문서들은 ‘내부 로직의 불확실성’을 제거하는 거였어. ‘알아서 잘 만들겠지’라는 막연한 기대를 ‘이 약속대로 만들어야 한다’는 명확한 규칙으로 바꾸는 거구나. 코드를 짜기 전에 미리 코드가 어떻게 상호작용할지 결정하는 거였네.”
솔라는 자신이 ‘번거로운 과정’이라고 불렀던 것이, 사실은 시스템 전체의 붕괴를 막는 가장 중요한 안전장치였음을 깨달았다. 이제 각 에이전트의 내부가 어떻게 움직여야 할지 선명하게 그려졌다. 하지만 동시에 새로운 의문이 고개를 들었다.
“좋아, 이제 에이전트들끼리는 어떻게 대화해야 할지 알겠어. 약속된 형식대로 데이터를 주고받으면 되니까. 그런데 ‘기획자 에이전트’는 맨 처음에 사용자의 ‘내일 추울까?’ 같은 애매한 말을 듣고 ‘아, 날씨 정보를 확인해야겠다’라고 결정하잖아. 그리고 날씨 정보를 받은 후에는 ‘15도니까 자켓을 추천해야겠다’라고 또 결정하고. AI가 스스로 판단하고 결정하는 이 부분은 어떻게 설계하는 거지? 이건 약속된 데이터를 주고받는 것과는 차원이 다른 문제 같은데.”
5장: AI의 지휘봉: 예측 가능한 Agent 행동을 위한 문서
솔라는 태블릿 화면의 ‘기획자 에이전트’라는 동그라미를 꾹 눌렀다. 그 주위로는 ‘날씨 정보원’, ‘일정 관리사’ 같은 다른 에이전트들이 위성처럼 떠 있었다. 각 에이전트가 어떤 데이터를 어떤 형식으로 주고받아야 하는지에 대한 약속, 즉 ‘상세 설계’의 중요성은 이제 뼈저리게 알았다. 개발자들 사이의 오해로 시스템 전체가 멈춰버리는 끔찍한 상황을 막아줄 테니까.
하지만 솔라의 생각은 ‘기획자 에이전트’의 가장 핵심적인 역할에서 멈춰 섰다. 이 에이전트는 사용자의 모호한 말을 듣고, 스스로 상황을 판단해서 다른 에이전트에게 일을 시켜야 한다. 예를 들어, 사용자가 “내일 약속 있는데, 뭐 입지?”라고 말했을 때, ‘기획자’는 ‘날씨 정보원’과 ‘일정 관리사’ 둘 다에게 정보를 요청해야 한다고 결정해야 한다.
솔라는 태블릿에 새로운 메모를 열고 ‘기획자 에이전트의 두뇌’라는 제목을 적었다. 그리고 그 아래에 의사 코드처럼 써 내려갔다.
만약, 사용자의 요청이 ‘옷’과 ‘날씨’에 대한 것이면 → 날씨 정보원 호출
만약, 사용자의 요청이 ‘옷’과 ‘일정’에 대한 것이면 → 일정 관리사 호출
만약, 기온이 15도 이하면 → ‘자켓’ 추천 목록에 추가
여기까지는 그럴듯했다. 하지만 곧바로 막막한 질문들이 떠올랐다. ‘기온이 15도 이하’는 명확하지만, 사용자의 요청이 ‘옷과 날씨에 대한 것’인지는 AI가 어떻게 판단할까? “내일 좀 쌀쌀할까?”라는 질문은 명백히 날씨에 대한 것이지만, “내일 데이트하는데 뭐 입지?”는 날씨에 대한 언급이 전혀 없다. 하지만 당연히 날씨를 고려해야 한다. AI의 ‘자율적인 판단’에 맡겨야 하는 영역. 이 부분은 약속된 데이터를 주고받는 것과는 차원이 다른, 예측 불가능한 영역처럼 보였다.
“언니, 이거 봐봐.”
솔라가 화면을 들고 루나에게 다가갔다.
“AI 에이전트의 진짜 핵심은 이 ‘결정’을 내리는 부분 같아. 그런데 이건 미리 설계하기가 너무 어려워 보여. 결국 AI 모델의 성능에 기대는 수밖에 없는 거 아냐? 똑똑한 LLM을 쓰면 알아서 잘 판단해 주겠지. 그럼 이런 ‘결정 기준’이나 ‘프롬프트’ 같은 건, 일단 구현해보고 나서 결과가 이상하면 조금씩 고쳐나가는, 그런 튜닝 작업에 가까운 거 아닐까?”
루나는 솔라의 메모를 잠시 들여다보았다. 그러고는 고개를 저으며 말했다.
“그 ‘똑똑한 LLM’을 아주 의욕 넘치지만 세상 물정 모르는 신입사원이라고 생각해 보자. 내가 그 신입사원 역할을 해볼게. 솔라 네가 팀장이고, 나한테 일을 시키는 거야. 우리 목표는 ‘사용자의 질문 의도를 파악해서 필요한 정보를 가져오는 것’이야. 자, 시작해 봐.”
솔라는 잠시 어리둥절했지만, 이내 상황극에 몰입했다.
“좋아, 신입. 사용자가 ‘내일 추울까?’라고 물었어. 의도를 파악해서 처리해 봐.”
루나는 눈을 반짝이며 대답했다. 마치 거대한 언어 모델의 지식에 막 접속한 듯한 표정이었다.
“알겠습니다, 팀장님! ‘추울까’라는 단어는 ‘추리’라는 단어와 유사성이 높습니다. 사용자는 추리 소설을 추천받고 싶어 하는 것 같습니다. 최신 추리 소설 목록을 검색하겠습니다!”
“아니, 아니! 그게 아니잖아! 춥냐고, 날씨!”
솔라가 기가 막힌다는 듯 소리쳤다. 루나는 태연하게, 하지만 기계적인 톤으로 대답했다.
“피드백 감사합니다. ‘날씨’라는 키워드를 학습했습니다. 재시도하겠습니다. ‘춥다’는 것은 겨울과 관련이 깊습니다. 사용자의 기분을 전환하기 위해 신나는 겨울 캐럴을 재생하겠습니다.”
“그만!”
솔라는 두 손으로 머리를 감쌌다. 명확한 지시가 없자, 똑똑하지만 방향 없는 AI가 얼마나 터무니없는 결과를 내놓을 수 있는지 단 두 번의 대화만으로 실감했다. 의도는 좋았을지 몰라도, 결과는 재앙에 가까웠다. 만약 이게 실제 서비스였다면 사용자는 앱을 바로 삭제했을 것이다.
“이제 알겠어? ‘알아서 잘 하겠지’라는 기대는 얼마나 위험한지. 우리는 이 신입사원, 즉 AI에게 명확한 ‘의사결정 정책(Decision Policy)’을 줘야 해.”
루나는 솔라의 태블릿에 적혀 있던 만약...으로 시작하는 의사 코드를 가리켰다. 그리고 그 옆에 새로운 규칙을 추가했다.
<Decision Policy: 의도 파악>
- 사용자 요청이 들어오면, 먼저 날씨 관련 키워드(
춥다,덥다,날씨,기온…)가 있는지 확인한다. - 만약 있다면: 즉시
날씨 정보원에이전트를 호출한다. - 만약 없다면: 다음 단계로 넘어간다.
- 일정 관련 키워드(
약속,일정,회의…)가 있는지 확인한다. - …
“이렇게 AI가 어떤 상황에서 어떤 판단을 내려야 하는지, 그 기준과 절차를 명확하게 문서로 정의하는 거야. 개발자는 이 정책을 보고 AI의 판단 로직을 구현하게 돼. ‘알아서’가 아니라, 이 ‘정책’에 따라서 움직이도록.”
솔라의 눈이 커졌다. “AI의 행동을 예측 가능하게 만드는 거구나.”
“맞아. 그리고 또 하나. ‘날씨 정보원’을 호출하기로 결정했어. 그럼 뭐라고 말하면서 호출해야 할까? ‘날씨 알려줘’라고 그냥 던져주면, 아까처럼 또 엉뚱한 대답이 돌아올 수 있잖아.”
루나는 이어서 ‘Prompt Spec’이라는 제목의 문서를 보여주었다. 그 안에는 단순한 대화가 아닌, 잘 짜인 지시문이 있었다.
<Prompt Spec: 의도 분석용>
너는 사용자의 말을 분석하여 핵심 의도를 찾는 AI 에이전트다.
사용자의 질문: "{user_input}"
위 질문의 핵심 의도는 다음 중 무엇에 가장 가까운가?
A. 날씨 정보
B. 일정 확인
C. 옷 추천
선택지와 그 이유를 JSON 형식으로 답하라.
“이게 바로 ‘프롬프트 명세(Prompt Spec)’야. AI에게 일을 시킬 때 사용하는 ‘업무 지시서’지. 어떤 역할을 맡아야 하는지, 어떤 맥락을 알아야 하는지, 어떤 형식으로 답해야 하는지 아주 구체적으로 알려주는 거야. 이 지시서가 명확할수록 우리가 원하는 결과를 안정적으로 얻을 수 있어. 절대 구현 후에나 하는 튜닝 작업이 아니야. 구현 전에 AI의 행동 품질을 결정하는 가장 중요한 설계 과정이지.”
솔라는 자신이 ‘튜닝’이라고 생각했던 것들이, 사실은 AI라는 강력하지만 예측 불가능한 존재를 통제하고 지휘하기 위한 핵심적인 ‘지휘봉’이었음을 깨달았다. 이 설계가 없다면, 수많은 에이전트들은 각자 똑똑할지는 몰라도 결코 하나의 목표를 향해 움직이는 오케스트라가 될 수 없었다. 그저 소란스러운 소음만 만들어낼 뿐이었다.
“그렇구나… 설계 문서들은 그냥 형식이 아니었어. 처음부터 끝까지, ‘불확실성’이라는 안개를 걷어내는 과정이었네.”
솔라는 중얼거리며 태블릿의 첫 페이지로 돌아갔다. ‘AI Agent 시스템 설계 절차’라는 제목 아래, 이제는 낯설지 않은 항목들이 순서대로 놓여 있었다. 그녀는 빈 페이지를 새로 열었다. 그리고 “내가 만약 ‘오늘 저녁 메뉴를 추천해주는 AI 에이전트’를 만든다면?”이라고 혼잣말하며, 망설임 없이 목록을 써 내려가기 시작했다.
- 문제 정의: “무엇을 먹을지 결정하는 데 드는 시간과 정신적 소모를 줄인다.” → 이걸로 우리는 ‘맛집 광고’가 아니라 ‘결정 보조’라는 방향성의 불확실성을 잡았고.
- 시나리오 정의: 사용자가 “배고파”라고 말하면, “한식, 중식, 양식 중에 뭐가 좋아요?”라고 되묻는다. → 이걸로 개발자 마음대로가 아닌, 사용자 경험의 불확실성을 잡았고.
- 구조 설계: ‘레시피 검색 에이전트’와 ‘냉장고 재료 파악 에이전트’를 나눈다. → 이걸로 한 기능이 막혔을 때 전체가 멈추는 기술적 복잡도의 불확실성을 잡았고.
- 상세 설계 (Workflow, State): ‘냉장고 재료 파악’ 에이전트는 반드시
["돼지고기", "양파", "김치"]같은 문자열 리스트 형식으로 결과를 반환한다. → 이걸로 에이전트끼리 말이 통하지 않는 내부 로직의 불확실성을 잡았어. - 상세 설계 (Decision Policy, Prompt Spec): 만약 사용자가 ‘매운 거’라고 하면, 레시피 검색 시 ‘spicy’ 태그를 추가하도록 결정한다. → 이걸로 AI가 제멋대로 ‘매운 라면’만 추천하는 AI 행동의 불확실성을 잡는 거야.
자신이 쓴 글을 가만히 내려다보는 솔라의 얼굴에 희미한 미소가 번졌다. 딱딱한 문서 목록이 아니라, 하나의 잘 짜인 이야기처럼 느껴졌다. 코드를 한 줄도 짜지 않았지만, 이미 머릿속에는 견고하고 안정적으로 동작하는 AI 에이전트가 그려지고 있었다. 설계는 구현과 동떨어진 형식적인 절차가 아니었다. 성공적인 구현으로 가는 가장 확실한 길 그 자체였다.