팀장님이 QA 100번 시키길래 만들었습니다.
비결정적 AI 워크플로우를 자동으로 검증하고 개선하는 에이전트 개발기
Mango • AX Team / FDE
- AI
안녕하세요, 채널톡 FDE(Forward Deployed Engineer) 망고입니다.
제가 맡은 일은 Cafe24와 같은 커머스 플랫폼에 바로 연동해 쓰는 "앱 태스크"를 만드는 거예요.
"주문 조회", "반품 접수" 처럼 고객사가 앱을 설치만 하면 작동하는 시나리오들이죠.
채널톡에서 제공하는 대표 예시인만큼 한 번 잘 도는 걸로는 부족해요.
수십 가지 사용자 상황에서 흔들리지 않아야 비로소 "대표 태스크"라 부를 수 있거든요.
그래서 팀장님이 말씀하셨죠. "앱 태스크 하나당 100번씩 돌려봐."
문제는 제가 이걸 주에 3개씩 만들어야 한다는 거예요. 주문 있는 사용자, 없는 사용자, 환불 조건 안 맞는 사용자, 중간에 갑자기 딴소리하는 사용자… 그 모든 경우를 일일이 타이핑하다 보면
저 집에 갈 수 없어요.
그러다 "이걸 자동으로 할 순 없을까?" 이 질문 하나로 시작해, 태스크를 자동으로 QA해주는 에이전트를 만들었어요. 하나의 태스크를 QA하는 과정을 따라가 볼게요.
먼저, 태스크가 뭐냐면
태스크는 고객이 자주 요청하는 업무를, 안내가 아니라 실제 처리까지 해주는 기능이에요.
고객이 "내 주문 어떻게 됐어요?"라고 물으면, 일반 봇은 "마이페이지에서 확인하실 수 있어요"라고 안내만 하죠. 태스크는 달라요 — 직접 주문을 조회해서, 상태까지 답해줍니다.
그래서 태스크는 하나의 트리거(예: "주문 조회하고 싶어요")로 시작해, 여러 개의 노드가 순서대로 이어지는 흐름으로 생겼어요.
이 글에서는 두 개의 노드만 알면 돼요
agent노드 — 고객의 발화에 따라 응대하는 AI 노드에요.code노드 — 실제 처리를 하는 단계. 우리 예시에선 주문조회 API를 호출하죠.
막상 자동으로 테스트하는 에이전트를 만드려니 막막했어요.
사람마다 발화가 다르고, 들고 오는 데이터도 제각각인데, 그 흐름엔 매번 다르게 반응하는 비결정적 agent 노드까지 끼어 있어요. 이걸 대체 어떻게 자동으로 검증하죠?
시나리오 생성하기
모양을 조금 바꿔볼까요? 텍스트와 아이콘을 걷어내면,
결국 시작점에서 종점으로 향하는 그래프 하나가 남아요.
그러면 테스트할 시나리오가 또렷해져요. 시작점에서 종점까지 도달 가능한 모든 경로,
그걸 빠짐없이 방문하면 되는 거죠. 익숙하지 않나요? 네, DFS입니다.
비회원 주문을 찾는 시나리오 A → B → C → D → E → F → G → N,
OTP 발송이 실패하는 시나리오 A → B → C → 상담사연결
이런 경로들을 빠짐없이 훑는 거죠.
이제 "어떤 시나리오를 테스트할지"는 손에 쥐었어요.
고민했던 지점은 그다음이었습니다.
AI와 대화 진행하기
태스크는 사용자 발화에 따라 경로를 결정하고 종점에 도달하게 됩니다.
"그냥 대본을 미리 짜두면 되지 않나요?" 저도 그렇게 시작했어요.
그러나 agent 노드에겐 통하지 않습니다. 봇 응답이 사용자의 발화에 따라 매번 달라지거든요.
전화번호를 주면 "이 번호 맞나요?"라 되묻고, 확실한 입력값을 받기 위해 다시 요청하고…
멀티턴으로 몇 차례씩 오갑니다. 봇이 예상 못 한 되물음을 하는 순간, 대본이 무너져요.
단순한 생각이 떠올랐습니다. 봇이 AI면, 사용자도 AI로 두면 되잖아?
대본 대신 LLM에게 "목표"(시나리오)만 주고 사용자 역할을 맡기는 거예요.
사용자를 흉내내는 AI가 읽고 다음 발화를 스스로 만듭니다.
테스트 대상이 AI이면, 테스터도 AI여야 한다 — 봇끼리 대화시킨 거죠. 제가 흉내 내던 "딴소리하는 사용자"까지 이제 AI가 연기합니다.
가짜 데이터 가져오기
사용자를 흉내내는 AI와 봇을 대화시키자마자 문제가 터졌어요.
봇이 대화 중 code 노드에서 Cafe24 주문 조회 API를 호출합니다.
데이터를 가져오고, 실제로 반품 접수를 합니다.
게다가 결과가 데이터에 따라 매번 달라지니 테스트가 재현이 안 됩니다.
"주문목록 있는 사용자" 시나리오를 돌리려면 매번 그런 주문이 실제로 존재해야 하는데,
그걸 어떻게 보장하죠? 시나리오마다 필요한 주문 상태가 다 달라 비현실적이었어요.
그래서 실제 호출은 건너뛰고, API 호출이 성공했다면 memory에 넣었을 값을 대신 주입했어요.
이 기대값은 DFS가 뽑아낸 경로를 보고 시나리오 생성 단계에서 미리 만들어 둡니다
("이 경로를 가려면 주문 목록에 이런 게 있어야 한다").
덕분에 외부 환경 종속없이, 각 시나리오가 필요로 하는 값을 결정론적으로 제공한 채로
태스크의 검증만 순수하게 테스트할 수 있게 됐어요.
실시간 봇 응답 가져오기
사용자를 흉내 내는 AI가 다음 말을 하려면, 먼저 봇이 뭐라고 답했는지를 받아와야 해요.
문제는 봇 응답이 언제 올지 모른다는 거였어요.
폴링
가장 단순한 방법은 폴링(polling)이에요. 응답이 왔나 안 왔나 DB를 짧은 간격으로 계속 캐묻는 거죠. 한 대화만 돌리면 별 문제 없어요. 그런데 이 QA의 핵심은 경로 수십 개를 병렬로 돌리는 거라, 하나의 시나리오당 수백 개의 호출이 쌓입니다.
그래서 이벤트 기반으로
봇이 말할 때 알려주게 바꿨어요. 봇이 메시지를 만들면 webhook으로 push해 주고, 로컬 QA 서버는 cloudflared 터널로 그 콜백을 받습니다.
사용자를 흉내 내는 AI는 이제 봇이 신호를 줄 때만 깨어나 말을 잇습니다.
여기서 두 가지를 더 다듬었어요
과금 — 테스트용 대화가 실제 과금으로 잡히면 안 됐어요. 그래서 QA 전용 세션을 따로 열어, 봇과 똑같이 주고받으면서도 과금에는 잡히지 않습니다.
프롬프트 캐싱 — AI 사용자는 LLM이 이전 대화를 기억하지 못하니, 매 턴 시스템 프롬프트부터 대화 기록까지 통째로 다시 보냅니다. 턴이 쌓일수록 보내는 양이 눈덩이처럼 불죠. 그래서 프롬프트 캐싱으로 반복되는 앞부분을 저장해 재사용했어요.
결과 평가하기
대화가 끝나면, 성공과 실패를 판정해야 해요. 처음엔 단순하게 갔어요. AI 사용자가 실제로 밟은 노드(visited)를, DFS가 뽑아둔 정답 경로와 비교한 거죠.
그런데 금세 어긋났습니다.
봇이 엉뚱한 답을 해도 종점만 시나리오의 기대와 맞으면 통과되고,
반대로 봇이 응대를 잘했는데 에이전트 노드가 응답 대기 상태로 끝나면 실패로 찍혔어요.
"고객의 문의를 해결했나"와 다른 얘기였죠.
그래서 핵심 의도와 이상적 단계를 주고, 실제 대화가 고객 문의를 해결했는지 판정하게 했어요.
목표: {goal}
핵심 의도: {intent} 예) 비회원 OTP 인증 → 주문 조회 → 반품 신청 완료
실제 대화: {전체 turn}
→ {"pass": true|false, "reason": "..."}진단하고, 고치기 (Evolving)
지금까지가 "테스트"과정 이였다면, 마지막은 그 결과로 무엇을 하느냐입니다.
QA를 한 바퀴 돌리면 경로별 통과/실패와 이유가 담긴 결과지가 나와요. 예전 같으면 제가 그걸 읽고, 문제점을 판단하고, 태스크를 손으로 고쳤겠죠.
이걸 매번 제가 할 필요는 없겠더라고요.
그래서 에이전트들을 하나의 하네스(harness)로 묶었습니다.
시나리오 생성 — 그래프(DFS)에서 경로를 뽑아 만든 시나리오
유저 — 그 목표로 봇과 대화 (AI 사용자)
평가 — 의도 기준으로 통과/실패 판정 (Judge)
진화(Evolving) — 결과지 기반 → 태스크 개선
FDE로 일하며 채널톡 주요 고객사의 태스크를 직접 만들다 보니, 좋은 태스크에 대한 암묵지가 쌓였거든요. 핵심은 바로 그 암묵지를 프롬프트로 옮겨 담은 것입니다.
덕분에 "LLM이 알아서"가 아니라, 좋은 설계 원칙에 기대요.
이제 테스트 → 평가 → 개선이 한 바퀴로 돕니다. 제가 손으로 돌리던 데서 시작해, 이제는 사람 손을 점점 덜 타는 — 스스로 개선되는 루프(self-evolving)에 가까워지고 있어요.
느낀점 - 에이전트의 손익분기점
AI와 수십 턴을 떠들고 채점까지 하니까, 하나를 테스트하는 데 LLM 호출이 수백 번씩 나가요.
QA 버튼 클릭 한 번이 곧 돈입니다. 그래서 비용을 진지하게 따져봤어요.
비용은 두 군데에 있습니다.
돌릴 때마다 나가는 비용(LLM 호출),
도구를 만드느라 한 번에 부어 넣은 선불 비용(제 시간)입니다.
발생시킨 호출을 수집해서, 실제로 몇 번이나 쓰였는지 추적해봤습니다.
수동으로 할 때 비용선과 자동화했을 때 비용선을 같이 그리면,
두 선이 교차하는 지점이 손익분기점이고, 거기를 지나야 비로소 본전입니다.
다행히 태스크 QA는 라이브 전에 반드시 거치는 관문이라, 쓰일 횟수가 보장돼 있었어요.
그래서 손익분기점을 넘길 수 있었어요.
자동화는 공짜가 아니에요. 앞으로 뭔가를 자동화하고 싶어질 때 이걸 스스로 묻게 될 것 같아요.
"이 도구가 손익분기점을 넘길 만큼 사용될까?"
마무리하며
'좋은 태스크는 이래야 한다'는 제 암묵지가 에이전트 안으로 옮겨가, 이제는 제가 없는 자리에서도 태스크를 진단하고 고치고 있어요. 손으로 돌리던 일이, 스스로 한 바퀴씩 도는 루프가 된 거죠.
새 앱 태스크를 만들 때도, 기존 Cafe24 앱 태스크를 고도화할 때도 이 도구를 거치고 있고,
덕분에 앱 태스크의 해결율은 이전보다 눈에 띄게 올랐어요.
아직은 PoC라 수정안은 사람이 한 번씩 확인하고 반영해요. 하지만 그래서 더 재밌습니다.
다음 한 바퀴는, 사람 손이 닿는 마지막 자리까지 줄이는 일일 테니까요.
