에이전트가 날씨 조회나 문서 검색 같은 도구를 호출하다가 JSON을 깨뜨리거나, 존재하지 않는 인자를 보내거나, 엉뚱한 도구를 고르면 팀은 대개 모델 품질부터 의심한다. 그러나 vLLM 환경의 도구 호출 실패는 모델 하나가 만든 결과가 아니다. 도구 정의를 모델 입력으로 바꾸는 채팅 템플릿, 모델별 도구 호출 파서, 구조화 디코딩, 애플리케이션의 스키마 검증과 실행 실패 처리까지 이어지는 파이프라인이 함께 결과를 만든다.
따라서 결정 질문은 “이 모델의 도구 호출 정확도가 낮은가”가 아니라 다음처럼 바꾸는 편이 낫다.
현재 실패 중 어디까지가 출력 형식 문제이고, 어디부터가 도구 선택과 인자 판단 문제인가? 형식 문제를 제약과 검증으로 줄인 뒤에도 남는 실패가 모델 교체 비용을 정당화하는가?
vLLM은 이름을 지정한 함수 호출에서 구조화 출력 기능을 사용해 JSON 스키마에 맞는 파싱 가능한 호출을 만들 수 있다. 다만 파싱 가능한 호출과 업무적으로 올바른 호출은 다르다. 올바른 형식의 JSON이라도 잘못된 함수를 고르거나, 사용자의 의도와 다른 값을 넣거나, 필요한 문맥을 놓칠 수 있다. 이 경계를 먼저 정해야 “스키마 정확도 100%”라는 결과를 과도하게 해석하지 않는다.
1. 요구사항에서 먼저 나눌 것: 형식 준수와 실행 성공
구축 초기에 CTO나 개발 리드는 도구 호출 성공을 하나의 지표로 묶지 말고 세 층으로 나누어 정의해야 한다.
| 구분 | 확인할 질문 | 요구사항 산출물 |
|---|---|---|
| 형식 준수 | 응답을 파싱할 수 있는가, JSON과 필수 필드가 스키마에 맞는가 | 도구별 JSON Schema와 호출 응답 계약 |
| 의미 적합성 | 사용자의 요청에 맞는 도구와 인자를 선택했는가 | 대표 요청, 기대 도구, 허용·금지 인자 목록 |
| 실행 결과 | 권한, 대상 상태, 외부 도구 응답을 고려해 작업이 완료됐는가 | 실행 결과 코드, 사용자 재질문 규칙, 폴백 정책 |
이 구분은 책임도 분명하게 한다. 형식 준수는 모델 출력 형식, 템플릿, 구조화 디코딩, 파서가 함께 책임질 영역이다. 의미 적합성은 도구 설명, 대화 문맥, 모델의 추론 품질과 관련이 크다. 실행 성공에는 외부 API 상태, 인증, 권한, 데이터 최신성처럼 모델 밖의 조건도 들어온다.
예를 들어 location과 unit이 필요한 날씨 도구가 있다고 하자. JSON이 깨져 파싱하지 못하면 형식 실패다. location에 도시가 아닌 사용자의 질문 전체를 넣으면 의미 실패다. 올바른 도시와 단위를 보냈지만 외부 날씨 서비스가 오류를 반환하면 실행 실패다. 세 실패를 모두 “모델이 틀렸다”고 기록하면 교체 판단도 흐려진다.
2. 템플릿과 파서를 배포 단위로 고정한다
도구 선언은 일반 텍스트 프롬프트에 JSON만 붙이는 과정이 아니다. 모델은 학습 시 익힌 특정 토큰 형식으로 도구 목록과 도구 호출 구간을 해석할 수 있다. vLLM도 모델별 도구 호출 파서와 채팅 템플릿을 설정해 도구 호출을 처리한다.
여기서 첫 번째 검증 산출물은 모델, vLLM 버전, 채팅 템플릿, 도구 호출 파서, 구조화 출력 백엔드의 조합표다. 운영 환경에서 이 조합이 바뀌면 같은 애플리케이션 요청도 다른 출력 경로를 탈 수 있다.
배포 전에는 다음을 확인한다.
- 현재 모델에 맞는 채팅 템플릿과 도구 호출 파서를 선택했는가
- 서버가 도구 호출 자동 선택을 허용하는 설정인지, 특정 함수를 지정하는 호출인지 구분했는가
- 애플리케이션이 기대하는
tool_calls응답 형태와 서버 파서의 반환 형태가 맞는가 - 도구 설명, 함수명, 인자명, JSON Schema가 요청 생성 코드와 같은 버전인가
- 모델 또는 템플릿을 바꾼 뒤 이전 회귀 테스트를 다시 실행했는가
특히 함수명이나 인자명을 애플리케이션에서는 변경했는데 프롬프트 예시나 테스트 fixture에는 이전 이름이 남아 있으면, 모델 문제처럼 보이는 혼합 실패가 생긴다. 이 문제는 더 큰 모델로도 해결되지 않을 수 있다.
3. 구조화 디코딩은 형식 오류를 줄이는 수단으로 쓴다
vLLM의 구조화 출력은 JSON Schema, 선택지, 정규식, 문법 같은 제약을 생성에 적용할 수 있다. 이름을 지정한 함수 호출에서는 도구 파라미터 객체의 스키마에 맞도록 구조화 출력을 사용할 수 있다. 형식이 깨지는 호출이 주된 문제라면 이 기능은 모델 교체보다 먼저 검토할 보강 수단이다.
다만 구조화 디코딩을 켠다고 모든 도구 호출 문제가 사라지지는 않는다. 제약은 허용된 토큰과 출력 형태를 제한할 수 있지만, 사용자의 의도를 올바르게 해석했다는 보증은 아니다. search_customer와 delete_customer 중 무엇을 선택해야 하는지, 고객 식별자가 어느 사람을 뜻하는지는 별도 검증 대상이다.
구조화 디코딩 적용 단계의 산출물은 다음 세 가지면 충분하다.
- 축소된 스키마: 실제 실행에 필요하지 않은 선택 인자와 모호한 자유 텍스트 필드를 걷어낸 버전
- 제약 전후 비교 세트: 같은 요청에서 파싱 실패, 스키마 실패, 의미 실패를 따로 기록하는 테스트
- 백엔드 변경 검증 기록: 구조화 출력 백엔드를 바꿀 때 어떤 스키마와 출력이 달라졌는지 남긴 결과
vLLM 문서는 구조화 출력 백엔드에 따라 지원 방식과 허용 토큰이 달라질 수 있다고 설명한다. 따라서 백엔드 교체를 성능 튜닝처럼 취급하면 안 된다. 스키마, 정규식, 생성 결과를 다시 검증하는 호환성 변경으로 다뤄야 한다.
4. 검증은 “호출했는가”보다 “어디서 틀렸는가”를 남긴다
도구 호출 테스트는 성공 예시 몇 개로 끝내기 쉽다. 하지만 운영 판단에 필요한 것은 성공률 하나가 아니라 실패 분포다. 제품 요구사항을 확정할 때 도구별 테스트 묶음을 만든다.
각 도구에는 최소한 다음 유형을 포함한다.
- 호출해야 하는 요청: 어떤 함수와 필수 인자가 나와야 하는가
- 호출하면 안 되는 요청: 일반 답변으로 끝나야 하는가
- 둘 이상의 도구가 가능한 요청: 우선순위나 사용자 확인이 필요한가
- 필수 정보가 부족한 요청: 추측 호출 대신 질문을 되돌려야 하는가
- 유효하지만 위험한 요청: 실행 전에 애플리케이션 승인 절차를 거쳐야 하는가
- 여러 턴 요청: 이전 도구 결과를 다음 호출의 근거로 제대로 쓰는가
테스트 결과는 최소한 파싱 실패, 스키마 불일치, 잘못된 도구 선택, 잘못된 인자, 도구 실행 오류, 정상 완료로 분류한다. 이 분류가 있어야 모델 교체 후보를 평가할 때도 공정해진다. 한 모델이 JSON 형식은 안정적이지만 도구 선택에서 약한지, 다른 모델은 형식 제약이 없으면 자주 깨지는지 비교할 수 있기 때문이다.
5. 재시도와 폴백은 실패 유형별로 설계한다
파싱 실패와 외부 도구 장애를 같은 재시도로 처리하면 불필요한 호출이 늘고 원인을 숨길 수 있다. 애플리케이션은 모델 응답을 받은 뒤 스키마 검증, 업무 규칙 검증, 도구 실행을 분리해 처리하는 편이 안전하다.
권장할 수 있는 흐름은 다음과 같다.
- 모델 응답에서 도구 호출을 추출한다.
- JSON 파싱과 스키마 검증을 통과하지 못하면 실행하지 않는다.
- 스키마는 맞지만 업무 규칙상 실행할 수 없으면, 누락 정보를 묻거나 사람이 확인할 경로로 보낸다.
- 실행 가능한 호출만 외부 도구에 전달한다.
- 외부 도구 실패는 모델의 재생성 실패와 구분해 기록한다.
- 재시도 뒤에도 해결되지 않으면 제한된 일반 응답, 사용자 재입력 요청, 승인 대기 중 하나로 종료한다.
여기서 폴백의 목표는 “어떤 상황에서도 도구를 실행한다”가 아니다. 잘못된 호출을 실행하지 않고, 사용자가 다음 행동을 알 수 있게 만드는 데 있다. 특히 상태를 변경하는 도구는 파싱 가능 여부만으로 실행 권한을 주지 않는 편이 낫다.
모델을 바꿀 시점은 제약 적용 뒤에 판단한다
모델 교체는 다음 순서로 판단할 수 있다. 먼저 템플릿과 파서를 모델 조합에 맞게 고정하고, 스키마를 검토한 뒤, 구조화 디코딩을 적용한다. 그 다음 대표 작업 세트에서 형식 실패가 줄었는지 본다.
형식 실패가 크게 줄고도 잘못된 도구 선택과 잘못된 인자가 남는다면, 그때는 프롬프트와 도구 설명을 고칠지, 더 적합한 모델을 비교할지 결정할 근거가 생긴다. 반대로 구조화 제약을 적용하지 않은 상태의 오류율만 보고 모델을 바꾸면, 새 모델에서도 같은 템플릿·파서·검증 공백이 반복될 수 있다.
다음 배포 회의에서는 모델 후보 목록보다 먼저 이 질문을 확정해 보자. 우리 서비스가 허용하지 않는 실패는 JSON이 깨지는 일인가, 잘못된 대상을 실행하는 일인가, 아니면 외부 도구 장애를 복구하지 못하는 일인가? 답에 따라 투자해야 할 곳은 모델, 제약 디코딩, 애플리케이션 검증, 운영 폴백 중 서로 달라진다.
