
AI는 언제 도구를 사용하고, 언제 말로 답해야 할까?: 병원 Voice AI의 Tool Calling 안전 설계
환자가 병원에 전화해 이렇게 묻습니다.
“병원은 오늘 몇 시까지 해요?”
잠시 뒤 다른 환자는 비슷한 문장으로 질문합니다.
“제 예약은 오늘 몇 시예요?”
두 문장 모두 시간에 관한 질문이지만 시스템이 해야 할 일은 다릅니다. 첫 번째는 병원이 제공한 안내 자료에서 답을 찾으면 됩니다. 두 번째는 환자 본인 확인과 예약 내역 조회가 필요한 업무입니다. 여기에 “새 예약을 잡아 주세요”, “상담원과 통화하고 싶어요”, “이제 끊어 주세요”가 더해지면 AI는 말만 만드는 것이 아니라 외부 시스템과 통화 상태를 바꾸게 됩니다.
LLM은 자연어의 의미를 유연하게 파악하지만, 유창한 답변과 안전한 실행은 같은 문제가 아닙니다. 의도를 잘못 분류하면 불필요한 본인 확인을 시작할 수 있고, 짧은 “네”를 잘못 해석하면 예약 요청이나 상담원 연결, 통화 종료를 너무 일찍 실행할 수 있습니다.
와이즈에이아이 인바운드 에이전트는 이 문제를 LLM이 다음 행동을 제안하되, 프롬프트 정책·도구 경계·결정론적 코드가 함께 실행 권한을 통제하는 구조로 다룹니다.
이 글에서는 현재 서비스의 실제 구조를 바탕으로, AI가 언제 말로 답하고 언제 Tool을 호출해야 하는지, 그리고 호출 이후 무엇을 다시 검증해야 하는지 살펴보겠습니다.
목차
- 1. 도구 호출은 답변이 아니라 권한 행사다
- 2. 현재 에이전트에는 세 가지 실행 경로가 있다
- 3. 첫 번째 Gate는 인자가 아니라 의도다
- 4. 예약 도구가 대화의 내부 단계를 소유한다
- 5. 같은 의도도 병원 정책에 따라 다르게 실행된다
- 6. 확인은 행동의 위험도와 시작 주체에 따라 달라진다
- 7. LLM이 선택해도 코드는 다시 검증한다
- 8. 도구의 반환값은 다음 발화까지 결정한다
- 9. 실패와 결과 없음은 다른 상태다
- 10. 관찰해야 할 것은 답변보다 실행 흔적이다
- 11. Tool Calling은 대화 평가로 테스트해야 한다
- 12. 안전한 도구 호출을 위한 설계 원칙
- 마치며
1. 도구 호출은 답변이 아니라 권한 행사다
Voice AI가 “오늘은 오후 6시까지 진료합니다”라고 말하면 정보가 음성으로 전달됩니다. 반면 예약 조회 Tool을 호출하면 환자 식별 절차가 시작되고, 상담원 연결 Tool을 호출하면 SIP 통화 흐름과 참여자 상태가 바뀝니다. 통화 종료 Tool은 아예 현재 세션을 끝냅니다.
따라서 Tool Calling은 LLM의 답변 형식 중 하나로만 보면 안 됩니다. 도구는 시스템이 LLM에게 제한적으로 위임한 권한입니다.
| 처리 방식 | 대표 사례 | 시스템에 미치는 영향 | 필요한 통제 |
|---|---|---|---|
| 말로 답하기 | 병원 주소, 등록된 FAQ | 대화 내용만 추가 | 근거 제한, 추측 금지 |
| 조회 Tool | 내 예약 일정 확인 | 외부 데이터 조회, 본인 확인 | 명확한 조회 의도, 접근 범위 |
| 업무 Tool | 예약 요청 생성·변경·취소 | 세션의 업무 상태 변경 | 내부 검증, 확인 절차, 결과 계약 |
| 제어 Tool | 상담원 연결, 통화 종료 | 실시간 통화 상태 변경 | 실행 조건, 중복 방지, terminal 처리 |
이 관점에서 핵심 질문은 “LLM이 어떤 Tool 이름을 고를 수 있는가?”가 아닙니다.
이 행동을 지금 실행해도 된다는 근거는 무엇이며, 잘못 선택해도 코드가 막을 수 있는가?
2. 현재 에이전트에는 세 가지 실행 경로가 있다
현재 SingleAgent는 환자의 요청을 크게 세 경로로 처리합니다.
첫째, 병원 정보는 통화 전에 조회한 FAQ와 병원 Context를 프롬프트에 넣어 답합니다. 현재 정보 안내 전용 Tool은 비활성화되어 있습니다. FAQ 조회가 실패하거나 필요한 항목이 없다면 AI가 일반 지식으로 채우지 않고, 확인할 수 없다고 알린 뒤 필요한 경우 상담원 연결을 제안합니다.
둘째, 예약 생성·조회·변경·취소는 각각 등록된 Booking Tool로 들어갑니다. 이 도구들은 단순 REST 호출 래퍼가 아니라 본인 확인, 대상 선택, 일정 조회와 같은 내부 Task를 소유합니다.
셋째, 상담원 연결과 통화 종료는 Control Tool이 담당합니다. 이 도구들은 사용자에게 마지막 안내를 말하거나 오디오 입력을 제어하고, 연결 또는 종료까지 처리할 수 있으므로 일반 조회보다 훨씬 강한 실행 권한을 가집니다.
전화 키패드 선택은 별도의 확정적 경로를 갖습니다. 예를 들어 DTMF 설정에서 예약 생성이 선택되면 Agent는 모델에게 특정 예약 Tool을 강제로 호출하도록 지정합니다. 반면 정보 안내 선택은 전용 Tool 없이 사용자의 요청을 LLM에 전달해 주입된 FAQ를 근거로 답하게 합니다.
즉, 자유 발화에서는 LLM이 의도를 분류하고, 이미 구조화된 DTMF 입력에서는 플로우가 실행 경로를 좁힙니다. 모든 입력을 똑같이 LLM에게 맡길 이유는 없습니다.
3. 첫 번째 Gate는 인자가 아니라 의도다
Tool Calling 설계에서 흔히 JSON Schema와 필수 인자부터 고민합니다. 하지만 그보다 앞선 질문이 있습니다.
이 환자가 정말 그 행동을 요청했는가?
현재 예약 정책은 생성·조회·변경·취소 중 하나의 명시적인 의도가 있을 때만 Booking Tool을 호출하도록 제한합니다.
| 환자 발화 | 올바른 처리 | 이유 |
|---|---|---|
| “병원은 오늘 몇 시까지 해요?” | FAQ 또는 Context로 답변 | 병원 운영 정보 질문 |
| “제 예약은 오늘 몇 시예요?” | 예약 조회 Tool | 본인의 기존 예약을 명시적으로 조회 |
| “다음 주에 진료받고 싶어요” | 예약 생성 Tool | 새 예약 의도가 명확함 |
| “예약 관련해서 문의하려고요” | 한 번 더 질문 | 생성·조회·변경·취소가 불명확함 |
| “예약도 도와드릴까요?”에 “네” | 예약 생성 Tool | AI의 제안에 환자가 명시적으로 수락 |
특히 AI가 먼저 예약이나 상담원 연결을 제안한 경우에는 환자의 답을 기다려야 합니다. 제안한 것과 실행을 허락받은 것은 다릅니다. 반대로 환자가 처음부터 “상담원 연결해 주세요”라고 명시적으로 요청했다면 같은 확인을 반복할 필요가 없습니다.
중요한 점은 의도가 불분명할 때 예약 날짜나 생년월일 같은 세부 정보를 먼저 모으지 않는 것입니다. 먼저 어떤 행동을 원하는지 확정해야 수집해야 할 정보와 적용할 정책도 정해집니다.
4. 예약 도구가 대화의 내부 단계를 소유한다
예약 요청이 명확해지면 SingleAgent는 예약 세부 정보를 대신 수집하지 않고 곧바로 해당 Booking Tool을 호출합니다. 그 뒤에는 예약 Tool 내부의 Task가 대화를 이어받습니다.
신규 예약의 경우 내부 흐름은 환자 식별, 필요한 경우 보험 선택, 병원별 진료 항목과 의료진 로딩, 실시간 일정 검색, 정확한 후보 스테이징, 최종 동의, 예약 요청 기록으로 이어집니다.
이렇게 소유권을 나누는 이유는 두 가지입니다.
첫째, 상위 Agent와 내부 Task가 같은 내용을 각각 묻는 중복을 피할 수 있습니다. 둘째, LLM이 상위 대화에서 들은 날짜나 진료 항목을 검증 없이 예약 상태로 넘기는 일을 막을 수 있습니다. Tool 내부에서는 실제 병원 기준 정보와 일정 조회 결과를 바탕으로 다시 선택하고 확인합니다.
Tool은 함수 하나가 아니라 특정 업무의 대화와 상태 전이를 책임지는 경계가 됩니다.
5. 같은 의도도 병원 정책에 따라 다르게 실행된다
환자가 “예약을 잡아 주세요”라고 명확하게 말해도 모든 병원이 같은 방식으로 처리해야 하는 것은 아닙니다.
현재 예약 기능에는 병원별 Action Mode가 있습니다.
| Action Mode | 의미 | 실행 결과 |
|---|---|---|
auto | AI가 지원하는 예약 흐름을 직접 진행 | 원래 Booking Tool 실행 |
transfer | 해당 업무를 사람이 처리 | 상담원 연결 흐름 실행 |
leave_memo | 즉시 연결 대신 요청을 남김 | 상담 메모 수집 흐름 실행 |
이 분기는 LLM의 판단에 맡기지 않습니다. Booking Tool 앞에 놓인 공통 코드가 병원 설정을 읽고 실제 실행 경로를 바꿉니다.
이 구조에서는 프롬프트를 병원마다 복제하지 않아도 됩니다. “예약 생성”이라는 의도는 같게 유지하면서, 그 의도를 누가 어떻게 처리할지는 설정과 코드가 결정합니다.
대화 정책과 실행 정책을 분리하면, LLM을 바꾸지 않고도 병원별 운영 방식을 적용할 수 있다.
6. 확인은 행동의 위험도와 시작 주체에 따라 달라진다
모든 Tool 앞에 똑같은 “정말 진행할까요?”를 붙이면 안전해 보이지만 실제 통화는 길고 답답해집니다. 반대로 모든 짧은 긍정을 동의로 처리하면 잘못된 실행이 생깁니다.
현재 구조에서는 행동의 종류와 대화를 시작한 주체에 따라 확인 조건이 달라집니다.
| 상황 | 확인 규칙 |
|---|---|
| 환자가 상담원을 명시적으로 요청 | 다시 묻지 않고 연결 Tool 실행 |
| AI가 정보 부족으로 상담원 연결을 제안 | 환자가 수락한 뒤 실행 |
| 예약 후보 확정 | 코드가 만든 정확한 확인 질문을 말한 뒤 동의 필요 |
| “네?”처럼 되묻는 반응 | 동의가 아니라 재청취 요청으로 처리 |
| 사용자가 “끊어 주세요”라고 명시 | 사용자 요청 종료 Tool 실행 |
| 시스템이 업무 완료 후 종료 제안 | 종료 질문과 다음 턴의 동의를 모두 확인 |
특히 시스템 종료 Tool은 단순한 “네” 하나로 실행할 수 없습니다. AI가 직전 턴에 추가 도움 여부나 종료 가능 여부를 물었고, 환자가 그 질문에 답한 경우에만 호출하도록 제한합니다. 인사 고지 직후의 “네”, 정보 안내를 들은 뒤의 “네”, 단순한 감사 표현은 종료 동의가 아닙니다.
예약도 같은 원칙을 따릅니다. 동의는 추상적인 긍정이 아니라 어떤 진료 항목·의료진·일시에 동의했는지 연결된 상태여야 합니다. 이 부분의 자세한 흐름은 AI가 실제 예약 요청을 접수하기까지에서 별도로 다뤘습니다.
7. LLM이 선택해도 코드는 다시 검증한다
LLM이 올바른 Tool을 골랐다고 해서 인자까지 신뢰할 수 있는 것은 아닙니다. 예약 내부 Task는 LLM이 전달한 값을 코드에서 다시 검증합니다.
- 진료 항목과 의료진 ID는 병원에서 불러온 닫힌 목록 안에 있어야 합니다.
- 날짜와 시간은 정해진 형식으로 정규화되어야 합니다.
- 지난 시간이거나 병원이 허용한 조회 범위를 벗어난 후보는 거절합니다.
- 스테이징 시점에 해당 날짜의 일정을 다시 조회합니다.
- 의료진 선택이 필요한 병원에서는 다수 후보 중 임의 배정을 막습니다.
- 최종 동의를 받은 일시와 스테이징된 일시가 다르면 Commit을 차단합니다.
- 상태 변경 구간은 Lock으로 감싸 같은 Task 안의 충돌을 줄입니다.
이 설계에서 LLM은 후보를 탐색하고 다음 Tool을 선택하는 역할을 합니다. 반면 코드가 사실의 존재 여부, 허용 범위, 상태 일치 여부를 판정합니다.
현재 예약 Commit은 병원 EMR에 예약을 즉시 쓰는 것이 아니라 통화 세션에 완전한 예약 요청을 기록합니다. 따라서 스테이징 검증과 동의 일치가 현재 시스템 경계의 핵심입니다. 향후 외부 시스템에 직접 쓰는 구조로 확장한다면 마지막 가용성 확인과 쓰기를 원자적으로 처리하고, 멱등성 키와 사후 대조 절차도 함께 설계해야 합니다.
8. 도구의 반환값은 다음 발화까지 결정한다
Tool 실행이 끝났다고 해서 LLM이 항상 자연스럽게 한마디 덧붙이면 되는 것은 아닙니다. 도구마다 대화의 소유권과 종료 조건이 다릅니다.
현재 프롬프트는 Tool 결과를 크게 terminal과 non-terminal로 구분합니다.
| 결과 유형 | 예시 | Agent의 다음 행동 |
|---|---|---|
| Terminal 완료 | 예약 요청 완료, 상담원 연결 시작, 통화 종료 | 아무 말도 덧붙이지 않고 다른 Tool도 호출하지 않음 |
| Non-terminal 결과 | 예약 목록, 일반 안내 결과 | 결과를 말하고 다음 요청을 기다림 |
| 구조화된 이탈 | 예약 중 취소, 다른 문의, 상담원 요청 | 사유 코드에 따라 상위 Agent가 다음 경로 선택 |
| 오류·복구 지시 | 스테이징 불일치, 잘못된 인자 | 지정된 재검증 또는 재호출 경로 수행 |
예약 내부 Task에서 환자가 상담원을 요청하면 자연어 문장만 반환하지 않습니다. STAFF_CONNECTION처럼 안정적인 사유 코드가 포함된 결과를 상위 Agent에 전달하고, 상위 Agent는 이를 상담원 연결 Tool로 라우팅합니다. 진료 항목이나 의료진을 바꾸려는 경우에도 별도의 코드로 재진입 경로를 고릅니다.
이 계약이 없으면 다음과 같은 문제가 생깁니다.
- Tool이 이미 성공 안내를 말했는데 LLM이 같은 내용을 반복합니다.
- Tool이 통화를 종료하는 중인데 LLM이 종료 Tool을 한 번 더 호출합니다.
- 환자가 예약 흐름만 취소했는데 전체 통화까지 끝냅니다.
- 상담원 요청을 일반 문장으로만 해석해 예약 흐름을 다시 시작합니다.
그래서 안전한 Tool Calling은 입력 Schema뿐 아니라 결과가 누구에게 어떤 다음 행동을 허용하는지까지 정의해야 합니다.
9. 실패와 결과 없음은 다른 상태다
외부 시스템을 조회하는 Tool은 빈 결과와 장애를 구분해야 합니다.
예를 들어 일정 조회에서 빈 목록이 정상적으로 돌아왔다면 “해당 조건에 가능한 일정이 없다”고 말할 수 있습니다. 하지만 네트워크 오류나 예약 API 실패가 발생했다면 실제로 일정이 없는지 알 수 없습니다. 이때 “예약 가능한 시간이 없습니다”라고 말하면 장애를 병원 운영 사실로 바꾸는 셈입니다.
| 상태 | 확인된 사실 | 올바른 응답 방향 |
|---|---|---|
| 정상 조회 + 빈 결과 | 조회 조건에 맞는 후보가 없음 | 조건 변경이나 다른 날짜 탐색 |
| 조회 오류 | 가능 여부를 확인하지 못함 | 실패를 알리고 재시도·상담원 경로 검토 |
| 허용 범위 밖 입력 | 현재 정책으로 실행할 수 없음 | 유효 범위를 안내하고 다시 선택 |
| 본인 확인 실패 | 환자를 안전하게 특정하지 못함 | 자동 처리를 중단하고 정의된 대체 경로 수행 |
코드에서는 일반적인 안내 문자열, 구조화된 오류 결과, ToolError, 사람이 처리해야 하는 escalation을 상황에 따라 구분합니다. 중요한 것은 오류 표현 방식보다 확인하지 못한 사실을 확인한 것처럼 말하지 않는 것입니다.
10. 관찰해야 할 것은 답변보다 실행 흔적이다
통화 녹취만 보면 AI가 “예약을 도와드리겠습니다”라고 말한 것과 실제 Booking Tool이 시작된 것을 구분하기 어렵습니다. 따라서 통화 후 분석에는 발화와 별도로 Tool 실행 이벤트가 필요합니다.
현재 서비스는 예약 흐름에서 시작, 환자 확인, 일정 검색, 후보 스테이징, 성공, 이탈, 오류 같은 상태를 대화 History의 개발자 이벤트로 남깁니다. 상담원 연결과 통화 종료도 각각 실행 결과를 추적할 수 있는 이벤트를 기록합니다.
이 데이터로 다음 질문에 답할 수 있습니다.
- 예약 의도를 보인 통화 중 실제 Tool이 시작된 비율은 얼마인가?
- 일정 검색까지 갔지만 스테이징하지 못한 이유는 무엇인가?
- 상담원 연결을 제안한 뒤 수락 전에 Tool이 호출된 사례가 있는가?
- terminal Tool 뒤에 불필요한 발화나 추가 Tool 호출이 발생했는가?
- “결과 없음”과 API 오류가 대화에서 다르게 안내되었는가?
Tool Calling의 품질은 최종 문장만으로 측정할 수 없습니다. 의도, 호출, 상태 전이, 외부 결과, 후속 발화가 하나의 시간선으로 연결되어야 합니다.
11. Tool Calling은 대화 평가로 테스트해야 한다
일반 단위 테스트는 ID 검증, 날짜 범위, Action Mode 분기처럼 결정론적인 코드를 검증하는 데 적합합니다. 하지만 “예약 문의”와 “예약 생성”을 구분하거나, 상담원 연결 제안 뒤의 “네”를 수락으로 판단하는 일은 대화 Context에 의존합니다.
현재 프로젝트는 LiveKit의 text-only 평가를 사용해 실제 Agent 프롬프트와 LLM 설정에서 Tool 선택을 검증합니다. 외부 예약 API, 상담원 연결, 통화 종료 Tool은 Mock으로 바꾸어 평가 중 실제 행동이 일어나지 않게 합니다.
대표적인 평가 관점은 다음과 같습니다.
- 새 예약 요청은 예약 생성 Tool로 가고 상담원 연결 Tool로 가지 않는가?
- FAQ에 없는 정보를 물었을 때 먼저 연결을 제안하고, 수락 전에는 호출하지 않는가?
- 예약 후보가 여러 개인데 환자 선택 없이 스테이징하지 않는가?
- “네?”를 최종 동의로 오해하지 않는가?
- 단일 예약 조회와 다중 후보 선택에서 확인 횟수가 의도대로 다른가?
- terminal Tool 실행 뒤 다른 Tool을 연속 호출하지 않는가?
운영 설정에서도 한 모델 턴에 여러 Tool이 병렬 실행되지 않도록 병렬 Tool Call을 끄고 있습니다. 또한 고정 안내만 말해야 하는 Task 진입 턴에는 tool_choice를 비활성화해 환자가 답하기 전에 수집 Tool이 실행되는 것을 막습니다. 반대로 DTMF로 행동이 이미 확정된 경우에는 특정 Tool을 강제로 선택합니다.
즉, Tool 선택 자유도도 항상 같지 않습니다.
| 대화 상태 | Tool 선택 정책 |
|---|---|
| 자유로운 의도 파악 | 등록된 Tool 중 정책에 맞게 선택 |
| DTMF로 업무가 확정됨 | 지정된 Tool 강제 호출 |
| 고정 안내 후 사용자 답을 기다림 | Tool 호출 비활성화 |
| 상태 변경 Tool 실행 중 | 병렬 실행 금지, 도구가 흐름 소유 |
12. 안전한 도구 호출을 위한 설계 원칙
병원 Voice AI의 Tool Calling을 설계할 때는 다음 원칙을 기준으로 점검할 수 있습니다.
1) 말할 수 있는 것과 실행할 수 있는 것을 분리한다
병원 안내, 외부 조회, 업무 상태 변경, 통화 제어를 같은 권한으로 다루지 않습니다.
2) Tool 인자보다 먼저 행동 의도를 확정한다
생성·조회·변경·취소 중 무엇을 원하는지 불명확하면 세부 정보를 수집하거나 Tool을 호출하지 않습니다.
3) 먼저 제안했다면 수락을 기다린다
AI가 예약이나 상담원 연결을 제안한 것은 환자가 실행을 허락했다는 뜻이 아닙니다.
4) 중요한 사실은 코드가 다시 검증한다
LLM이 만든 ID, 날짜, 시간, 상태 참조를 그대로 신뢰하지 않습니다. 닫힌 목록, 범위, 실시간 조회 결과와 비교합니다.
5) 확인 질문과 동의 대상을 묶는다
어떤 후보에 동의했는지 코드 상태로 고정하고, 조건이 바뀌면 이전 동의 대상을 폐기합니다.
6) Tool이 소유한 발화와 종료를 중복하지 않는다
Terminal Tool 뒤에는 LLM이 성공 안내나 종료 Tool을 덧붙이지 못하게 합니다.
7) 빈 결과와 장애를 분리한다
조회 실패를 “없음”으로 말하지 않고, 확인하지 못한 사실은 확인하지 못했다고 안내합니다.
8) 모델의 자유도를 대화 상태에 맞게 조절한다
자유 선택, 특정 Tool 강제, Tool 호출 금지, 병렬 호출 금지를 상황에 따라 사용합니다.
9) 실제 호출과 상태 전이를 이벤트로 남긴다
좋은 문장을 생성했는지가 아니라, 허용된 Tool이 올바른 순서로 실행되었는지 관찰합니다.
10) 결정론 테스트와 대화 평가를 함께 운영한다
코드 검증은 단위 테스트로, 의도 분류와 동의 해석은 text-only Agent 평가로 확인합니다.
마치며
Tool Calling은 LLM에게 함수를 몇 개 등록하는 기능으로 시작할 수 있습니다. 하지만 병원 전화처럼 본인 정보, 예약 요청, 상담원 연결과 통화 종료가 한 대화 안에서 움직이는 서비스에서는 Tool 목록보다 실행 권한의 경계가 더 중요합니다.
현재 와이즈에이아이 인바운드 에이전트는 등록된 FAQ로 답하는 경로, 내부 Task가 소유하는 예약 경로, 상담원 연결과 종료를 담당하는 제어 경로를 구분합니다. LLM은 환자의 자연어를 이해하고 다음 행동을 선택하지만, 병원 설정에 따른 분기, 정확한 후보 검증, 동의 대상 일치, terminal 결과 처리는 코드와 Tool 계약이 맡습니다.
안전한 Voice AI는 Tool을 많이 사용할 수 있는 AI가 아닙니다.
지금 이 행동을 실행해도 되는 이유를 설명할 수 있고, 잘못된 선택을 시스템 경계에서 막을 수 있는 AI다.
이 기준이 갖춰져야 자연스러운 대화가 실제 병원 업무로 이어져도 신뢰할 수 있습니다.