티스토리 뷰

 

ToolInputValidator 패턴으로 바라본 실무적 접근

 

LLM 기반 AI Agent를 실제 서비스에 적용하다 보면, 모델의 추론 능력과는 별개로 입력값의 신뢰성 문제를 반복적으로 마주하게 됩니다. 특히 Tool 호출이 포함된 Agent 구조에서는, LLM이 도메인 규칙과 무관한 값을 Tool 파라미터로 생성하는 상황이 비교적 자주 발생합니다. 이 글에서는 이러한 문제를 입력값 Hallucination 관점에서 정리하고, LangChain4j 공식 문서에서 소개하는 에러 처리 방식을 기반으로 ToolInputValidator 패턴이라는 형태로 정리한 실무적인 방어 전략을 공유하고자 합니다.

 

이 글은 LLM의 한계를 지적하기보다는, 해당 특성을 전제로 한 안전한 설계 방식에 초점을 둡니다. 또한 단정적인 결론보다는, 실무에서 겪은 문제를 공식 문서와 구현 경험을 바탕으로 정리하는 개인적인 기록에 가깝습니다.

 


 

Tool 파라미터에서 발생하는 Hallucination의 성격

 

LLM이 생성하는 Hallucination은 흔히 “사실이 아닌 답변”으로만 이해되지만, Agent 구조에서는 조금 다른 양상으로 나타납니다. Tool을 사용하는 Agent의 경우, 문제는 출력 텍스트가 아니라 구조화된 입력값에서 발생합니다.

 

예를 들어 GitHub API를 호출하는 Tool이 있고, owner 파라미터는 반드시 실제 GitHub 계정이어야 한다고 가정해 보겠습니다. 이때 LLM은 자연어 문맥상 그럴듯하다는 이유만으로 owner 값을 "Google"과 같이 생성할 수 있습니다. 문법적으로는 문자열이므로 문제가 없어 보이지만, 실제 API 호출 관점에서는 명백히 잘못된 입력입니다.

 

중요한 점은, 이 값이 “틀렸다”고 해서 LLM 입장에서는 오류가 아니라는 사실입니다. LLM은 외부 시스템의 제약 조건을 내재적으로 보장하지 않으며, Tool의 도메인 규칙을 항상 정확히 반영한다고 기대하기도 어렵습니다. 따라서 이 문제는 모델 품질의 문제가 아니라, 입력값 검증 책임을 어디에 둘 것인가의 설계 문제로 보는 편이 더 현실적입니다.

 


 

ToolInputValidator 패턴의 필요성

 

LangChain4j 공식 문서에서는 Tool 실행 중 오류가 발생했을 때, 해당 오류 메시지를 LLM에게 다시 전달하여 재시도를 유도하는 흐름을 소개합니다. 이 접근은 단순한 예외 처리라기보다는, LLM을 다시 추론 루프에 포함시키는 설계로 이해할 수 있습니다.

 

이 글에서는 해당 방식을 기반으로, Tool 실행 이전에 입력값을 검증하는 역할을 명시적으로 분리하여 ToolInputValidator라는 패턴으로 정리합니다. 이 패턴의 핵심은 다음과 같습니다.

 

Tool은 “검증된 입력만 받는다”는 전제를 갖고, 입력값의 유효성 판단은 Tool 외부에서 수행합니다. 그리고 이 검증 로직은 성공과 실패를 명확히 구분하되, 성공 시에는 추가 정보를 반환하지 않고, 실패 시에는 LLM이 이해할 수 있는 에러 메시지만을 반환합니다.

 

이 구조는 Tool과 검증 로직의 책임을 분리하면서도, LLM이 스스로 입력을 수정할 기회를 제공한다는 점에서 실무적으로 유용하다고 느꼈습니다.

 


 

검증 성공 시 null, 실패 시 에러 메시지를 반환하는 이유

 

ToolInputValidator 패턴에서 인상적이었던 부분은, 검증 결과를 boolean이나 enum으로 표현하지 않고, 성공 시 null, 실패 시 String 메시지로 표현하는 방식입니다. 처음에는 다소 비직관적으로 느껴질 수 있지만, Agent 흐름에서는 오히려 명확한 장점이 있습니다.

 

성공 시 null을 반환한다는 것은, “LLM에게 전달할 추가 맥락이 없다”는 의미입니다. 반면 실패 시에는, 단순히 실패했다는 사실이 아니라 왜 실패했는지, 어떤 조건을 만족해야 하는지를 자연어로 전달할 수 있습니다.

 

이 에러 메시지는 그대로 Tool 실행 오류로 던져지고, LangChain4j의 에러 처리 메커니즘에 의해 다시 LLM의 입력으로 전달됩니다. 결과적으로 LLM은 “이 값이 틀렸다”는 신호가 아니라, “어떤 제약을 위반했는지”를 인지한 상태에서 다음 입력을 생성하게 됩니다.

 

이 방식은 방어적인 예외 처리라기보다는, LLM과 시스템 간의 피드백 루프를 설계하는 관점에서 이해하는 것이 적절하다고 생각합니다.

 


 

자동 교정 로직과 검증 로직의 경계

 

모든 잘못된 입력을 에러로만 처리하는 것이 항상 최선은 아닙니다. 실무에서는 명백히 의도가 보이는 경우도 존재합니다. 예를 들어 "google", "Google Inc.", "google-llc"와 같이 표현은 다르지만, 실제로는 "google"이라는 GitHub owner를 의도한 경우가 이에 해당합니다.

 

이런 경우를 위해, 검증 단계에서 자동 교정 로직을 부분적으로 허용할 수 있습니다. 글의 앞부분에서 언급했던 correctGitHubOwner()와 같은 함수는, 임의의 추론 로직이라기보다는 명시적으로 허용된 보정 규칙에 가깝습니다.

 

중요한 점은, 이 교정이 “LLM을 대신한 추론”이 되어서는 안 된다는 것입니다. 자동 교정은 어디까지나 문자열 정규화, 별칭 매핑 등 결과가 예측 가능한 범위에서만 사용하는 것이 바람직합니다. 교정 이후에도 여전히 유효하지 않다면, 그 시점에서는 에러 메시지를 반환하여 LLM에게 재입력을 요청하는 편이 더 안전합니다.

 


 

Enum 기반 입력값 검증의 의미

 

Enum 값에 대한 검증은 단순해 보이지만, Agent 환경에서는 생각보다 중요한 역할을 합니다. LLM은 종종 Enum 이름과 의미를 혼동하거나, 존재하지 않는 값을 생성하기도 합니다. 예를 들어 provider 값에 "OpenAI" 대신 "ChatGPT"를 넣거나, updateType에 "FULL_UPDATE""INCREMENTAL"이 정의되어 있음에도 "PARTIAL"을 생성하는 경우입니다.

 

이때 validateProviderOptional, validateUpdateTypeOptional과 같은 검증 로직은 단순히 값의 존재 여부를 확인하는 수준을 넘어서, 시스템이 허용하는 선택지를 명확히 LLM에게 다시 알려주는 역할을 합니다.

 

검증 실패 시 반환하는 메시지에 “허용되는 값은 무엇인지”를 함께 포함시키면, 다음 추론 단계에서 LLM이 이를 참고하여 올바른 값을 생성할 가능성이 높아집니다. 이 역시 LLM을 통제 대상으로 보기보다는, 명확한 제약 조건을 가진 협력 대상으로 대하는 설계라고 볼 수 있습니다.

 


 

도메인 특화 검증: ObjectId와 날짜 형식

 

MongoDB ObjectId나 날짜 형식과 같은 도메인 특화 값은, LLM이 특히 취약한 영역 중 하나입니다. 24자리 hex 문자열이라는 ObjectId의 형식적 제약이나, ISO-8601 날짜 포맷과 같은 세부 규칙은 자연어 맥락만으로 정확히 맞추기 어렵습니다.

 

이러한 값들은 ToolInputValidator 단계에서 반드시 명시적으로 검증하는 것이 바람직합니다. 이때도 마찬가지로, 실패 시에는 단순한 “형식 오류”가 아니라, 기대하는 형식을 설명하는 메시지를 반환하는 편이 Agent의 안정성 측면에서 유리합니다.

 

실무적으로 느낀 점은, 이러한 검증 로직이 늘어날수록 Agent가 복잡해진다기보다는, 오히려 문제 발생 지점이 명확해진다는 것입니다. 입력값 오류가 Tool 내부 로직이나 외부 API 호출 실패로 전파되기 전에 차단되기 때문에, 전체 흐름을 이해하고 디버깅하는 비용도 줄어드는 편이었습니다.

 


 

마무리하며

 

AI Agent 환경에서 Hallucination을 완전히 제거하는 것은 현실적인 목표가 아니라고 생각합니다. 대신, Hallucination이 어디까지 허용 가능하고, 어디서부터 시스템적으로 방어해야 하는지를 구분하는 것이 더 중요합니다.

 

ToolInputValidator 패턴은 이러한 관점에서, LLM의 불완전함을 보완하기 위한 하나의 구조적인 접근이라고 느꼈습니다. 모든 입력을 신뢰하지 않되, 무조건 차단하지도 않고, 검증 결과를 다시 추론 과정에 자연스럽게 녹여내는 방식은 실무에서 충분히 검토해볼 만한 가치가 있습니다.

 

이 글에서 정리한 내용은 LangChain4j 공식 문서를 기반으로, 실제 구현 과정에서 느낀 점을 개인적으로 정리한 것입니다. 앞으로 LLM과 Tool의 경계가 더 복잡해질수록, 이러한 입력 검증 패턴은 점점 더 중요해질 것으로 생각합니다.