본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 08. 11:06

나의 정규 표현식(Regex) 기반 파서가 실패한 이유와 LLM Function Calling이 나를 구한 방법

요약

비정형 텍스트에서 데이터를 추출하기 위해 정규 표현식과 규칙 기반 방식의 한계를 경험한 개발자가 OpenAI의 Function Calling을 활용해 구조화된 데이터를 성공적으로 추출한 사례를 다룹니다.

핵심 포인트

  • 정규 표현식은 다양한 텍스트 형식 변화에 취약함
  • LLM의 Function Calling을 통해 JSON 스키마 기반 추출 가능
  • Pydantic과 OpenAI API를 결합하여 구조화된 객체 생성
  • 비정형 데이터(이메일, Slack 등) 처리에 매우 효과적

고백할 것이 있습니다. 저는 한때 서로 다른 제공업체로부터 오는 병원 예약 이메일을 파싱하기 위해 정규 표현식(Regular Expressions)을 작성하는 데 꼬박 사흘을 보냈습니다. 결국 저는 정확히 두 가지 이메일 형식에서만 작동하는 400줄짜리 괴물 같은 코드를 만들었습니다. 세 번째 클리닉이 시스템에 합류했을 때, 저는 다른 접근 방식이 필요하다는 것을 깨달았습니다.

문제점: 도처에 널린 비정형 텍스트 (Unstructured Text)

저는 일반 텍스트 메시지에서 날짜, 시간, 이름, 주소와 같은 구조화된 데이터(Structured Data)를 추출해야 하는 작은 통합 도구를 구축하고 있었습니다. 소스는 이메일, Slack 메시지, 심지어 스캔된 PDF 노트까지 매우 다양했습니다. 각 소스마다 고유한 특징이 있었습니다. 정규 표현식(Regex)은 취약했습니다. HTML이 없는 경우에는 BeautifulSoup이 도움이 되지 않았습니다. spaCy를 사용하여 커스텀 NLP 파이프라인을 시도해 보았지만, 모든 필드에 대해 새로운 엔티티(Entity)를 학습시키는 것은 과했습니다.

저희 팀의 내부 도구는 출시 직전이었지만, 새로운 텍스트 소스가 추가될 때마다 정규 표현식 패턴을 디버깅하는 과정을 반복해야 했습니다.

효과가 없었던 방법들

  • 소스별 정규 표현식 (Regex per source): 알려진 형식에는 작동했지만, 새로운 형식이 나타나면 실패했습니다.
  • 규칙 기반 키워드 매칭 (Rule-based keyword matching): 문맥을 놓쳤습니다. 기준 날짜가 없는 상태에서 "다음 주 화요일"은 모호했습니다.
  • 오프라인 NLP 모델 (Offline NLP models): 각 필드에 대해 라벨링된 데이터(Labeled data)가 필요했지만, 저희에게는 데이터가 없었습니다.
  • 템플릿 매칭 (Template matching): 존재하지 않는 일관된 구조를 가정했습니다.

막막함을 느꼈습니다. 그때 문득 생각났습니다. 대규모 언어 모델(Large Language Models, LLM)은 자연어 지침을 이해하는 데 매우 뛰어나다는 사실을 말입니다. 모델에게 제가 원하는 필드가 무엇인지 정확히 말해주고, 모델이 그것을 추출하게 하면 어떨까요?

접근 방식: 구조화된 추출을 위한 Function Calling

OpenAI의 Function Calling(현재는 Tool Use로 불림)을 사용하면 JSON 스키마(Schema)를 정의하고 모델이 그에 맞는 데이터를 출력하도록 요청할 수 있습니다. 자유 형식의 텍스트를 반환하는 대신, 모델은 직접 파싱할 수 있는 구조화된 객체(Structured Object)를 반환합니다.

제가 이를 설정한 방법은 다음과 같습니다.

1단계: 스키마 정의

import openai
from pydantic import BaseModel

...

2단계: 함수 정의 생성

extraction_function = {
    "name": "extract_appointment",
    "description": "비정형 텍스트에서 예약 상세 정보를 추출합니다.",
...

3단계: 모델 호출 (Call the model)

def extract_appointment(text: str) -> Appointment:
    response = openai.chat.completions.create(
        model="gpt-4o-mini",  # 저렴하고 충분히 빠름
...

이것으로 끝입니다. extract_appointment("Dr. Smith appointment for John Doe on March 12 at 10 AM at 123 Main St")를 실행하면 깔끔한 Appointment 객체가 반환됩니다.

실제 결과 (Real-World Results)

다양한 클리닉에서 온 50개의 이메일을 대상으로 테스트했습니다. 다음과 같은 항목들을 처리할 수 있었습니다:

  • 날짜 형식 (Date formats): "March 12", "3/12/2025", "next Tuesday" (문맥 포함)
  • 시간 형식 (Time formats): "10:00 AM", "10:00", "10AM"
  • 누락된 필드 (Missing fields): 빈 문자열 반환
  • 위치 변형 (Location variations): 전체 주소, 건물 이름, 화상 회의 링크

모든 필드에 대한 정확도는 약 92%였습니다. 8%의 실패 사례는 주로 상대적 날짜 (예: 기준 날짜를 모르는 상태에서의 "next Monday")를 잘못 해석한 경우였습니다. 이를 해결하기 위해 프롬프트 수정 (prompt tweak)을 추가했습니다: 현재 날짜를 문맥 (context)으로 전달하는 방식입니다.

예외 케이스 처리 (Handling Edge Cases)

import datetime
context = f"Today is {datetime.date.today().isoformat()}"
message = [
...

이 방법으로 상대적 날짜 문제를 완전히 해결했습니다.

트레이드오프 (Trade-offs) 및 사용하지 말아야 할 경우

이 방식은 공짜가 아닙니다 (말 그대로). gpt-4o-mini를 사용할 경우 추출 한 번당 약 0.1¢의 비용이 발생합니다. 처리량이 많은 파이프라인 (하루에 수천 건 이상)의 경우 비용이 누적됩니다. 또한, 호출당 지연 시간 (latency)이 약 1~2초 정도 소요되므로 실시간 타이핑 제안 (real-time typing suggestions)에는 적합하지 않습니다.

고려했던 대안들:

  • 정규 표현식 (Regex): 무료이고 빠르지만, 유지보수가 악몽 수준입니다.
  • 특화된 추출 API (Specialized extraction APIs): 일부 서비스는 호스팅된 추출 기능을 제공하지만 (제 설정의 https://ai.interwestinfo.com/와 같이), 저는 스키마 (schema)에 대한 제어권을 원했습니다.
  • 미세 조정 모델 (Fine-tuned models): 고정된 스키마에는 훌륭하지만, 라벨링된 데이터 (labeled data)가 필요합니다.

저의 사용 사례—하루 수백 건 정도의 중간 규모 볼륨과 빈번하게 변경되는 소스—를 고려했을 때, LLM 함수 호출 (Function Calling)이 가장 적합한 지점이었습니다.

배운 점 (Lessons Learned)

  1. 가능한 한 가장 엄격한 스키마 (Schema)로 시작하세요. 모델은 모든 필드를 채우려고 시도할 것입니다. 누락된 데이터에 대해 환각 (Hallucination)된 값을 내놓는 것보다 빈 문자열 (Empty strings)을 반환하는 것이 더 낫습니다.
  2. 모델이 일관된 오류를 범한다면 시스템 프롬프트 (System prompt)에 예시를 포함하세요. 저는 까다로운 날짜 형식을 위해 한두 개의 퓨샷 (Few-shot) 예시를 추가했습니다.
  3. 사용하기 전에 출력을 검증하세요. Pydantic 모델이 타입 체크 (Type checking)를 처리하지만, 전화번호나 우편번호에 대해서는 추가적인 정규 표현식 (Regex) 검증이 필요할 수 있습니다.
  4. 상대적인 표현을 위해 오늘 날짜와 같은 컨텍스트 (Context)를 항상 전달하세요.

다음에 다시 한다면 다르게 할 점

저는 작은 검증 루프 (Validation loop)를 구축할 것입니다. 예를 들어, 추출된 날짜가 과거인데 텍스트가 미래를 암시한다면, 힌트와 함께 모델에게 다시 프롬프트를 보냅니다. 그렇게 하면 환각 (Hallucination)을 잡아낼 수 있을 것입니다. 또한, 체감 지연 시간 (Perceived latency)을 줄이기 위해 백그라운드 태스크 (Background task)에서 함수 호출을 스트리밍 (Streaming)하는 방안도 고려할 것입니다.

더 큰 그림 (The Bigger Picture)

이 기술은 예약에만 국한되지 않습니다. 이후 저는 이 기술을 사용하여 송장 품목 (Invoice line items), 회의록, 그리고 고객 피드백에서 감성 점수 (Sentiment scores)를 추출하는 데에도 사용했습니다. 지저분한 인간의 텍스트를 구조화된 데이터 (Structured data)로 변환해야 하는 모든 문제는 이 기술의 적용 대상입니다.

만약 여러분이 이번 주에 벌써 다섯 번째로 새로운 형식에 맞춰 정규 표현식 (Regex) 패턴을 디버깅하고 있다면, 이 방법을 시도해 보세요. 미래의 여러분이 고마워할 것입니다.

비구조화된 텍스트 (Unstructured text)에서 구조화된 데이터를 추출하기 위해 여러분이 주로 사용하는 방법은 무엇인가요? 여러분에게 효과적이었던 방법이 무엇인지 정말 듣고 싶습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0