logoStephen's 기술블로그

포스트 검색

제목, 태그로 포스트를 검색해보세요

#028 #OPENAI OCR 요금 분석

#028 #OPENAI OCR 요금 분석
Flutter
성훈 김
2024년 11월 19일
목차

👉 주요 OCR서비스 / 라이브러리 비교

  • 네이버 클로바 요금
    • 구분
      서비스
      기본요금
      기본 제공 구간
      추가 과금기준
      추가요금
      영수증
      Standard 플랜
      월 180,000원
      3,000건
      추가 호출 횟수당
      건당 80원
      영수증
      Advanced 플랜
      월 580,000원
      15,000건
      추가 호출 횟수당
      건당 50원
      사업자등록증
      Standard 플랜
      월 180,000원
      3,000건
      추가 호출 횟수당
      건당 80원
      사업자등록증
      Advanced 플랜
      월 580,000원
      15,000건
      추가 호출 횟수당
      건당 50원
      ✅ 높은 정확도
      ⛔️ 커스터 마이징 안됨
      ⛔️ 비싼거 같음
      ⛔️ 절차가 복잡함
       
  • openai 요금
    • 구분
      요금 체계
      토큰 사용량
      한달 사용량
      건당 요금
      gpt-4o-mini
      - 요청 토큰 요금 $0.150 / 1M input tokens - 반환 토큰 요금 $0.600 / 1M output tokens
      - 사업자 등록증 $토큰 사용량 : 대략 37,000 tokens - 영수증 $토큰 사용량 : 대략 17,000 tokens
      건당 과금이지만 네이버 기본 요금제 사용량과 비교시 : 3,000 건 사업자등록증 : 월 24,000원 영수증 : 월 12,000원
      사업자등록증 : 건당 8원 영수증 : 건당 4원
      gpt-4o
      - 요청 토큰 요금 $2.50 / 1M input tokens - 반환 토큰 요금 $10.00 / 1M output tokens
      - 사업자 등록증 $토큰 사용량 : 대략 1,450 tokens - 영수증 $토큰 사용량 : 대략 1,040 tokens
      건당 과금이지만 네이버 기본 요금제 사용량과 비교시 : 3,000 건 사업자등록증 : 월 15,750원 영수증 : 월 10,920원
      사업자등록증 : 건당 5원 영수증 : 건당 4원
      ✅  4o 모델은 정확도 좋음 - 4o모델은 요청 최적화 작업이란게 있어서 요청 토큰 갯수가 되게 적음
      ✅  프롬프트 커스터마이징이 되어서 정확도 발전 시킬수 있음, fine tuning 옵션도 제공함
      ✅  절차가 간편함
      ✅  반환 데이터 형태도 커스터 마이징 가능 (기본주소 + 상세주소 형태 커스터마이징 가능)
      ✅  서버에 먼저 업로드하고 링크로 요청하면 요청 토큰이 1400개에서 1070개로 줄어듬, 3분의 1가량 요금을 줄일 수 있다는 뜻
 
  • tesseract OCR
    • ✅  무료
      ✅  지속적인 업데이트 확인됨
      ⛔️ 정확도 다소 떨어짐
      ⛔️ 커스터마이징 어려움
      ⛔️ 학습 모델이 아님
       
  • paddle OCR
    • ✅  무료
      ✅  정확도는 tesseract보다 약간 좋음
      ⛔️ 최신 라이브러리 업데이트가 3년전
      ⛔️ 커스터마이징 많이 어려움
      ⛔️ 학습 모델이 아님
 

👉 OPENAI api 실제 사용 비교


🔹 계획

JavaScript
1. 캐시에 관한 공식 문서 확인
2. 사업자등록증에서 원하는 데이터를 추출하는 기능으로 테스트
3. 이미지 또는 pdf 파일을 base64변환해서 api 요청 
4. 4o / 4o-mini 모델 토큰과 비용 계산
5. 최적화 방법 (결론)
 

🔹 OpenAIService 구현

JavaScript
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:http/http.dart' as http;
import 'package:logger/logger.dart';
import 'package:image/image.dart' as img;

class OpenAIService {
  Future<String> extractTextFromImage(Uint8List imageBytes) async {
    final image = img.decodeImage(imageBytes);
    final resizedImage = img.copyResize(image!, width: 1000); // 이미지의 크기 조절로 토큰 수를 조절 할 수 있다. 
    final compressedImageBytes = Uint8List.fromList(
        img.encodeJpg(resizedImage, quality: 100)); // 이미지 품질 조절로 토큰 수를 조절 할 수 있다. 

    final base64Image = base64Encode(compressedImageBytes);
    final response = await http.post(
      Uri.parse('https://api.openai.com/v1/chat/completions'),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ${dotenv.env['OPENAI_API_KEY']}',
      },
      body: jsonEncode({
        'model': 'gpt-4o',
        'messages': [
          {
            'role': 'user',
            'content': [
              {
                'type': 'text',
                'text':
                    '너는 뛰어난 세무사야. 이 이미지는 사업자 등록증이고, 여기서 사업자 등록번호, 개업연월일, 사업장 주소, 상호명(법인명,단체명)을 추출해서 json으로만 반환해야해.\n너가 채워야 할 필드는 bizNum, openDate, baseAddress, detailAddress, bizName에 매칭해서 한글로 반환해야해.\n그리고 보통 주소는 기본주소와 상세주소로 나누어 지는데, 주소가 --서울특별시 종로구 종로 6, 5층 스타필드 빌리지(서린동, 광화문우체국)-- 이런식으로 되어있으면 \n기본주소는 --서울특별시 종로구 종로 6--이고 상세주소는 --5층 스타필드 빌리지(서린동, 광화문우체국)-- 이런식으로 나누어져야해 \n그리고 모든 주소는 한국에서 실존하는 지역명이어야 해'
              },
              {
                'type': 'image_url',
                'image_url': {'url': 'data:image/jpeg;base64,$base64Image'}
              }
            ],
            'detail': 'high',
          }
        ],
      }),
    );

    if (response.statusCode == 200) {
      final data = jsonDecode(utf8.decode(response.bodyBytes));
      Logger().i('Full Response: ${jsonEncode(data)}');
      final totalTokens = data['usage']['total_tokens'];
      Logger().i('Total tokens: $totalTokens');
      Logger().i(data['choices'][0]['message']['content']);
      return data['choices'][0]['message']['content'];
    } else {
      throw Exception('Failed to extract text: ${response.body}');
    }
  }
}
💡
  • pdf파일이나 이미지를 변환해 Uint8List 타입을 매개변수로 openai-service를 요청하는 과정이다.
 
  • 요청당 128000 토큰을 넘기면 에러가 발생한다.
    • notion image

      notion image
 
  • 그래서 그냥 이미지를 base64로 변환해서 요청하면 1회 최대 토큰수를 초과하는 경우가 많기 때문에, 이미지 사이즈를 줄이기로 했다.
    • notion image
       
  • 이미지 url로 요청을 할 수도 있다 (주석처리된 부분), 이 경우 base64로 요청하는 것보다 조금 더 저렴하다. 이 부분도 확인해보자.
  • 위와 같은 조건으로 비교를 해보았다.
 

🔹 요청이 캐시되는 원리

요청 구조
OpenAI 공식 문서
OpenAI 공식 문서
💡
캐시를 활용하기 위해서는
  • gpt에게 요청할 때 시작하는 첫 부분을 다른 요청과 동일하게 만들면 캐시가 작동한다.
  • 예를들어 gpt 역할 설정같은 첫 부분을 완전 똑같이 요청하면 캐시가 작동하여 비용을 낮출 수 있다.
 
요청이 작동 되는 원리
OpenAI 공식 문서
OpenAI 공식 문서
💡
  • 요청을 받으면 요청의 앞 부분이 일치하는 요청이 있는 지 조회한다.
  • 만약에 요청을 찾게되면, 그 요청의 응답속도를 현저히 낮출 수 있으며, 요금도 줄일 수 있다.
  • 캐시는 보통 5~10분 보관되지만, 사용량이 낮은 시간대에서는 한 시간동안 보관할 수 있다.
 
캐싱 조건
OpenAI 공식문서
OpenAI 공식문서
💡
  • 토큰 수가 1024 이하인 경우에는 캐시가 작동한지 않는다, 특히 cached_tokens부분이 0을 표시하게 된다.
  • 그리고 만약 1024개를 초과하는 경우에는 캐시가 적용되는 부분은 128토큰씩 증가한다.
    • 예를 들어 1500토큰수의 요청을 했다면 그 아래 128토큰수 증가단위인 1408토큰 까지 캐시적용된다.
 
 
캐시가 적용되는 데이터
OpenAI 공식문서
OpenAI 공식문서
💡
  • 여러가지가 있지만 이 블로그에서 중요한 내용은 이미지가 캐시가 되는것인가이다.
  • base64로 된 이미지링크, 그리고 여러개의 파일까지 캐시가 된다
 

👉 model: gpt-4o-mini

요청 형태
JavaScript
'model': 'gpt-4o-mini',
'messages': [
  {
    'role': 'user',
    'content': [
      {
        'type': 'text',
        'text':
            '너는 뛰어난 세무사야. 이 이미지는 사업자 등록증이고, 여기서 사업자 등록번호, 개업연월일, 사업장 주소, 상호명(법인명,단체명)을 추출해서 json으로만 반환해야해.\n너가 채워야 할 필드는 businessNumber, startDate, baseAddress, detailAddress, businessName에 매칭해서 한글로 반환해야해.\n그리고 보통 주소는 기본주소와 상세주소로 나누어 지는데, 주소가 --서울특별시 종로구 종로 6, 5층 스타필드 빌리지(서린동, 광화문우체국)-- 이런식으로 되어있으면 \n baseAddress는 --서울특별시 종로구 종로 6--이고 detailAddress는 --5층 스타필드 빌리지(서린동, 광화문우체국)-- 이런식으로 나누어져야해 \n그리고 모든 주소는 한국에서 실존하는 지역명이어야 해'
      },
      {
        'type': 'image_url',
        'image_url': {'url': 'data:image/jpeg;base64,$base64Image'}
      }
    ],
    'detail': 'high',
  }
],
💡
텍스트 추출의 정확도를 높이기 위해서 ‘detail’: ‘high’ 로 high-resolution 적용
notion image
💡
  • pdf 파일을 base64로 변환해서 요청
  • 이미지 사이즈 width: 1000 설정
  • 이미지 품질 quality: 100 설정
 
응답 형태
notion image
💡
  • 총 사용한 토큰 : 37,153개
  • 정확도 : 오탈자 3개 발견
  • 캐시된 토큰 수 : 0개
 
비용 계산
notion image
💡
  • 건당 요청 비용 : 0.0056 USD
  • 적용 환율 : 1400 원 (약간 비싸게)
  • 건당 비용 : 0.00562USD×1,400KRW/USD=7.861KRW, 대략 8원
  • 네이버 월요금제 3000건 이랑 비교시 : 월 24,000원
 

👉 model: gpt-4o

요청 형태
JavaScript
'model': 'gpt-4o',
    'messages': [
      {
        'role': 'user',
        'content': [
          {
            'type': 'text',
            'text':
                '너는 뛰어난 세무사야. 이 이미지는 사업자 등록증이고, 여기서 사업자 등록번호, 개업연월일, 사업장 주소, 상호명(법인명,단체명)을 추출해서 json으로만 반환해야해.\n너가 채워야 할 필드는 bizNum, openDate, baseAddress, detailAddress, bizName에 매칭해서 한글로 반환해야해.\n그리고 보통 주소는 기본주소와 상세주소로 나누어 지는데, 주소가 --서울특별시 종로구 종로 6, 5층 스타필드 빌리지(서린동, 광화문우체국)-- 이런식으로 되어있으면 \n기본주소는 --서울특별시 종로구 종로 6--이고 상세주소는 --5층 스타필드 빌리지(서린동, 광화문우체국)-- 이런식으로 나누어져야해 \n그리고 모든 주소는 한국에서 실존하는 지역명이어야 해'
          },
          {
            'type': 'image_url',
            'image_url': {'url': 'data:image/jpeg;base64,$base64Image'}
          }
        ],
        'detail': 'high',
      }
    ],
💡
  • 이 요청은 다음날에 진행되었기 때문에 캐시는 적용이 안되었을 것이다. 캐시없이 원요금을 비교하기 위해서!
notion image
💡
  • pdf 파일을 base64로 변환해서 요청
  • 이미지 사이즈 width: 1000 설정
  • 이미지 품질 quality: 100 설정
 
응답 형태
notion image
💡
  • 총 사용한 토큰 : 1,423개
  • 정확도 : 오탈자 0개 발견
  • 캐시된 토큰 수 : 0개
 
비용 계산
notion image
💡
  • 건당 요청 비용 : 0.0043 USD
  • 적용 환율 : 1400 원 (약간 비싸게)
  • 건당 비용 : 0.00427USD×1,400KRW/USD=5.978KRW, 대략 6원
  • 네이버 월요금제 3000건 이랑 비교시 : 월 18,000원
 

👉 비용을 조금더 아낄 수 있는 방법?

🔹 url로 요청

JavaScript
  'model': 'gpt-4o',
  'messages': [
    {
      'role': 'user',
      'content': [
        {
          'type': 'text',
          'text':
              '?너는 뛰어난 세무사야. 이 이미지는 사업자 등록증이고, 여기서 사업자 등록번호, 개업연월일, 사업장 주소, 상호명(법인명,단체명)을 추출해서 json으로만 반환해야해.\n너가 채워야 할 필드는 bizNum, openDate, baseAddress, detailAddress, bizName에 매칭해서 한글로 반환해야해.\n그리고 보통 주소는 기본주소와 상세주소로 나누어 지는데, 주소가 --서울특별시 종로구 종로 6, 5층 스타필드 빌리지(서린동, 광화문우체국)-- 이런식으로 되어있으면 \n기본주소는 --서울특별시 종로구 종로 6--이고 상세주소는 --5층 스타필드 빌리지(서린동, 광화문우체국)-- 이런식으로 나누어져야해 \n그리고 모든 주소는 한국에서 실존하는 지역명이어야 해'
        },
        {
          'type': 'image_url',
          'image_url': {
            'url':
                'https://ims365.co.kr/file_data/ims365s/2020/09/23/584537f7305734571cbaf170666715cc.jpg'
          },
        }
      ],
      'detail': 'high',
    },
  ],
💡
  • 요청 원문이 비슷하기 때문에, 제일 앞에 ? 를 붙여 변경하였다. 이렇게 하면 캐시 적용이 안된다.
  • 인터넷에 공개된 사업자 등록증 사진 링크로 테스트 해보자.
 
응답형태
notion image
💡
  • 총 사용한 토큰 : 1,073개
  • 정확도 : 오탈자 0개 발견
  • 캐시된 토큰 수 : 0개
 
비용계산
notion image
💡
  • 건당 요청 비용 : 0.0033 USD
  • 적용 환율 : 1400 원 (약간 비싸게)
  • 건당 비용 : 0.00332USD×1,400KRW/USD=4.648KRW, 대략 5원
  • 네이버 월요금제 3000건 이랑 비교시 : 월 15,000원
 

👉 결론

정확하게 왜 그런지는 알 수 없지만, 4o모델로 요청을 하면 토큰 수가 작게 계산이 되었다. 4o모델이 요청을 최적화한다고 gpt가 이야기 했지만, 다른 tokenizer 방식을 쓰는 건가 싶기도 하다. 캐시토큰도 0으로 찍히니 캐시때문은 아닌 것 같다. 그래서 정확도 높은 최고 효율을 내기 위해서는, 요청전 사업자 등록증을 스토리지 서버에 업로드를 먼저 진행하고 링크로 요청하면 1400개 정도로 요청되던 것이 1000개정도로 요청이 된다. 총 비용을 조금 더 절약 할 수 있다.
 
그리고 요청 링크는 변경하되 본문을 변경하지 않고 그대로 사용하며, 5~10분 이내에 재요청이 들어온다면 매 요청당 1024 토큰을 더 아낄수 있게 된다. ( 내 생각에는 완벽히 일치하는 구간이 1024토큰이 되어야 되지 않을 까 생각한다. 그렇게 따지면 현재 링크 방식은 응답토큰이 85개라서 정확히 일치하는 본문 구간이 1024토큰이 되지 않아 캐시가 적용이 되지 않을 것이라 생각한다. )
 
결론을 이렇게 내보겠다.
  • gpt-4o 모델 사용
  • 요청 본문을 정확히 일치 시켜야된다. (최소 1024 토큰 구간까지는)
  • 파일 변환 방식도 괜찮고, 링크로 요청한다면 요청 본문을 좀 늘리는 것도 괜찮을 것 같다.