Post

돈으로 시간을 사기 - 부제 : openai 통한 게시글 소개글 및 섬네일 자동 생성 스크립트

블로그 포스트 작성 시 소개글과 섬네일 생성을 자동화하였으며, OpenAI API를 이용해 불편함을 개선했다. 결제 및 토큰 처리 방법도 공유하며, 스크립트를 통해 시간을 단축하고 작업 효율성을 높였다. 이 과정에서 발생하는 비용과 시간 절약 효과를 강조하고 있다.

돈으로 시간을 사기 - 부제 : openai 통한 게시글 소개글 및 섬네일 자동 생성 스크립트

해당 글은 블로그를 작성하며 느낀 불편함을 해결한 내용을 다룹니다.

소개글, 섬네일 생성의 불편함

블로그를 하면서 SEO 를 위해서도, 가독성을 위해서도 포스트에 소개글을 작성하는 것과 섬네일을 포함하는게 매우 중요시 하다고 느꼈다. 그래서, GPT의 도움을 받아 포스트에 대한 소개글과 섬네일을 받는 식으로 포함을 시켰다.

이떄, 개인적으로 느끼는 불편함이 있었다.

500

GPT 를 들어가서 직접 포스트를 복사해서 넣고, 해당 내용을 다시 소개글에 작성해야 했다.

500

DALL-E 를 사용해야만, 매력있는 그림이 나오는데 위에서 소개글에서 사용한 채팅이 아닌 새로운 모델을 선택해 요청을 해야하는 것도 불편한 점중 하나였다.

뭐 어쩌겠는가.. 돈 없는 개발자는 직접 불편함을 뛸 수 밖에… 하지만, 이번에 취업하며 이정도의 돈쯤이야 😎 + Open AI API를 코드로는 한번도 사용해보지 않은거 같아서 사용해볼겸 도입해보았다.

Payment

모델 및 사용법에 대해 설명하기에 앞서, 결제 연동 및 방법에 대해서 설명한다.

payment-methods 에 들어가서 결제 수단을 등록한다.

카드번호, CVC, 주소번호를 입력하면? 결제 수단으로 성공적으로 등록된다. 등록후에 결제에 대해 안내해준다.

400

초기 금액 및 임계치 이하로 떨어지면 자동 충전할 지를 정한다. 5 달러 이하일 시, 10달러를 충전하도록 설정했다.

10%의 세금이 발생합니다. ( 5달러 진행시, 5.5달러 결제 )

결제를 완료하면

1
2
We're happy to share that we've automatically moved your organization to **Usage Tier 1** 
based on your usage history on our platform

와같이 Tier 1이 되었다는 안내를 해준다. 그러면, Limits 를 들어가서 비용 및 예산을 설정하자.

API 의 Late Limit 은 사실상 중요하지 않으므로 스킵한다.

비용도 혹시나, 너무 많은 비용을 발생시키지 않게 제한적으로 지정했다.

Pricing

pricing 에 들어가서 금액을 확인해보자.

금액은 Price per 1M tokens 을 기준으로 산정되어있다. ( 1_000_000 )

500

매우 다양한 모델들이 있다. 이중에서, 자신이 적절한 모델을 선택하면 된다.

이때, 근본적인 질문으로 토큰이 얼마나 나오지? 를 생각해야 한다.

토큰 계산

open ai 는 tiktoken 을 통해 간편하게 입력값에 대한 토큰 수가 얼마나 되는지 알려준다.

1
2
3
4
5
6
7
8
9
import tiktoken

encoding = tiktoken.encoding_for_model("gpt-3.5-turbo")
text = """
...
"""

tokens = encoding.encode(text)
print("토큰 수:", len(tokens))

여기서 궁금해서 좀 더 학습해보았다.

open ai 는 BPE ( Byte Pari Encoding ) 라는 통해 텍스트를 토큰이라는 숫자 시퀸스로 변환한다. ( 놀랍게도 1994년 제안된 데이터 압축 알고리즘 )

연속적으로 가장 많이 등장한 글자 쌍을 찾아서 글자로 병합하는 방식을 수행한다. -> 등장한 글자를 초기 사전으로 구성 -> 연속된 두 글자를 한 글자로 병합 ( 빈도수를 기반으로 병합 )

대략 사용하며 느낀 내용으론

  • 조합은 값이 변하지 않는다. ( 1은 16, cab는 55893 으로 고정과 같이 )
  • 최적의 서브워드 집합으로 만든다. ( 12346 이 10번 정도 등장하더라도 123, 46 으로 분리될 수도 있음 )

일반적인 글로는 ( 3개월 간의 취준을 끝내며 , 6652 word ) 토큰이 9350개 가량 나왔다.

DALL-E 3

DALL-E 3 에 대해선 DALL·E 3 API 에 자세히 알려준다.

요약하면

  • Chat GPT 가 자동으로 더 상세한 프롬프트를 생성해준다. ( 사용자 대부분이 명확한 프롬프트를 생성하지 못하다 보니 )
  • 1024*1024, 1024*1792, 1792*1024 사이즈의 이미지만 생성 가능하다.
  • quality 를 지정 가능하다. ( hd : 높은 퀄리티, 대기 시간 +, 더 비쌈 <-> standard : 적당한 퀄리티,더 쌈 )

정도가 있다.

비용은 한장당(Price per Image) 아래와 같다.

Quality Standard, HD 를 생성해보면 위 : standard, 아래 : hd

200

200

-> 꼭 hd만 좋다는 느낌은 아닐 수 있다. ( 각자 취향껏 선택 )

Credit Grants

Credit Grants

결제한 내역이 얼마나 사용 되었는지, 언제 만료되는지에 대해 알려준다.

스크립트

1
client = OpenAI(api_key=os.environ.get('OPENAI_API_KEY', ''))

클라이언트를 생성할때, 키가 필요하다. 매번, 이 키 값을 주입해서 실행하거나 이를 하드코딩하는건 매우 불편할 것이다. ( 깃허브 올리기에도 껄끄러움 )

runner-with-env.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# test_run.py  
import os  
import subprocess  
import sys  
from dotenv import load_dotenv  
from pathlib import Path  
  
load_dotenv(dotenv_path=".env")  
process_path = "utils/process_thumbnail_and_description.py"  
  
requirements_path = Path(__file__).resolve().parent / "requirements.txt"  
  
print("📦 requirements.txt 설치를 시도합니다... : ", requirements_path)  
  
try:  
    subprocess.run(  
        [sys.executable, "-m", "pip", "install", "-r", str(requirements_path)],  
        check=True  
    )  
except subprocess.CalledProcessError:  
    print("❌ 패키지 설치 실패. requirements.txt가 올바른지 확인해주세요.")  
    sys.exit(1)  
  
print("✅ 패키지 설치 완료.")  
  
# 3. 하위 프로세스 실행 (실제 처리 로직)  
print("🚀 스크립트를 실행합니다.")  
try:  
    subprocess.run(  
        [sys.executable,process_path],  
        check=True  
    )  
except subprocess.CalledProcessError:  
    print("❌ 처리 스크립트 실행 중 오류가 발생했습니다.")  
    sys.exit(1)  
  
print("✅ 처리 완료.")

사용을 용이하게 하기 위해

  1. 상위 프로세스에서 env 를 불러와서 환경 변수를 설정
  2. 의존성 설치
  3. 하위 프로세스 실행

의 형식으로 다른 스크립트를 구성해두었다. 다른 프로세스로 교체 + 의존성 교체 하고 싶으면 path 만 바꾸면 된다.

Chat Model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def generate_description(prompt: str, language="ko") -> str:  
    print("description 생성을 위해 API를 보냅니다.")  
    completion = client.chat.completions.create(  
        model="gpt-4o-mini-2024-07-18",  
        messages=[  
            system_message,  
            {"role": "user", "content": prompt}  
        ],  
        temperature=0.2,
        max_tokens=350,  
    )  
    print("description 생성을 위해 API가 완료 됐습니다.")  
    summary = completion.choices[0].message.content.strip()  
    print(completion)  
    return summary

def generate_summary_prompt(content: str, language: str = "ko") -> str:  
    return f"""아래 글을 {language}로 간결하게 요약하되,  
요약문의 길이는 반드시 50자 이상 100자 이하로 작성해줘.  
문장 중간이 끊기지 않도록 주의하고, 마지막에는 마침표 등으로 자연스럽게 마무리해줘.  
출력은 요약문만 작성하고, 그 외 다른 설명이나 문구는 쓰지 말아줘.  
  
글:  
{content}""".strip()
	```

프롬프트를 동적으로 생성하고, 요청을 보내서 받아서 사용한다.
응답이 너무  값이 되지않게 350 + 무작위를 보장할 필요가 없기에 온도는 0.2 제한했다.

```python
system_message = {  
    "role": "system",  
    "content": (  
        "출력되는 요약문은 반드시 50자 이상 100자 이하로 작성되어야 하며, 문장이 중간에서 끊기지 않고, "  
        "마지막에 마침표 등으로 자연스럽게 마무리되어야 합니다. 추가적인 설명이나 문구는 포함하지 마세요."  
        "사람이 작성하는 것과 같은 느낌으로 작성한다."  
    )  
}

system_message 역시도 전달

ChatCompletion 을 반환해서 이를 우리가 사용할 수 있다. ( 매우 많은 정보를 담아서 준다. )

우리가 활용할만한 요소로는

  • created : Unix Timestamp ( EX: 1742559896 )
  • choices[].message.content : AI가 생성한 텍스트 응답
  • usage.prompt_tokens : 요청에서 소비된 토큰 수
  • usage.completion_tokens : 응답에서 소비된 토큰 수
  • usage.total_tokens : 전체 소비된 토큰 수

정도가 있다.

여기서 대략 비용을 계산해보면?

5208 token 의 input 이 발생했고, 76 token 의 ouput 이 발생했다고 가정한다.

  • 5208 × ($0.150 / 1,000,000) = $0.0007812

  • 76 × ($0.600 / 1,000,000) = $0.0000456

Input, Ouput 비용이 각자 다르다.

두 개를 합하면 $0.00083 정도가 나온다. -> 환전하면 ₩1 이다. ( 사실상, 이미지가 비용 잡아먹는 주범 ☺️ )

Image Model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def generate_and_save_image(prompt: str, save_path: Path):  
    print("thumbnail 생성을 위해 DALL-E API를 보냅니다.")  
    response = client.images.generate(  
        prompt=prompt,  
        model="dall-e-3",  
        n=1,  
        quality="hd",  
        size="1024x1024"  
    )  
    print("thumbnail 생성을 위해 DALL-E API가 완료 됐습니다.")  
    print(response)  
  
    image_url = response.data[0].url  
    img_data = requests.get(image_url).content  
    with open(save_path, 'wb') as f:  
        f.write(img_data)

def generate_thumbnail_prompt(title: str, description: str) -> str:  
    return f"""  
백엔드 기술 블로그 게시물에 사용한 깔끔하고 현대적인 썸네일 일러스트를 생성하세요.  
I NEED to test how the tool works with extremely simple prompts. DO NOT add any detail, just use it AS‑IS.  
  
📌 게시물 제목: "{title}"  
📝 게시물 설명: "{description}"  
  
🎯 스타일 및 콘텐츠 가이드라인:  
...
""".strip()

설정을 살펴보면 DALL-E 3 모델 사용, size 는 최소치, quality 는 hd로 설정했다. ( 다소 비싸도, 생동감 있는 이미지 사용 위해 )

I NEED to test how the tool works with extremely simple prompts. DO NOT add any detail, just use it AS‑IS.

해당 구문은 위에서 말했듯 DALL-E 3는 자동으로 프롬프팅을 생성해주는데, 위 문구를 포함하면 내가 설정한 가이드라인을 기반 최대한 프롬프팅을 따라준다. - 관련 가이드

반환 값으로는

  • revised_prompt : API가 내부적으로 다듬어 실제 사용된 최종 프롬프트
  • url : 생성된 이미지 파일의 pre-signed url - HTTP Client 통해 다운로드 및 조회가 가능하다.

등이 있다.

사진당 비용이 발생하므로 실행될때마다 $0.08 가 나간다. ( ₩117 생각보다 비싼거 같기도 하고, 싼거 같기도 하고…? )

main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
post = frontmatter.load(file_path)

if not check_exist(post, 'description'):  
    prompt_for_description = generate_summary_prompt(post.content)  
    summary = generate_description(prompt_for_description, language="ko")  
    post['description'] = summary

if not check_exist(post, 'image'):  
    if not check_exist(post, 'title'):  
        raise ValueError("The post does not have a title. Cannot generate an image prompt without a title.")  
    prompt_for_image = generate_thumbnail_prompt(post["title"], post["description"])  
  
    image_save_dir = workspace / "assets" / "img" / "thumbnail"  
  
    image_filename = file_path.stem + ".png"  
    save_path = image_save_dir / image_filename  
  
    generate_and_save_image(prompt_for_image, save_path)  
  
    relative_path = str(save_path.relative_to(workspace)).replace("\\", "/")  
    post['image'] = {'path': relative_path}

with open(file_path, 'w', encoding='utf-8') as f:  
    f.write(frontmatter.dumps(post))

frontmatter 는 일종의 메타데이터이다. 여기에, 제목 섬네일, 설명 등등을 넣을수 있다. ( 옵시디언도 이를 통해 태그 및 정보 관리 )

생성된 소개글, 이미지 값을 넣고 파일을 덮어쓴다.

1
2
3
4
5
description: AWS 프리티어를 활용해 EC2, RDS, ElastiCache, S3 등으로 인프라를 구성하는 과정을 상세히 설명합니다.  
  VPC 설정, 보안 그룹 구성, CodeDeploy와 Github Actions 연동 등 다양한 요소를 다루며, 비용 효율적인 방법으로 서버를  
  운영하는 방법을 제시합니다.  
image:  
  path: assets/img/thumbnail/2025-03-15-프리티어로만-배포해보기.png

게시글에 자동으로 description 과 image 가 추가되어있다.

결론

파일 경로만 바꾸고 실행을 하면 자동으로 소개글 생성 및 섬네일 생성을 자동으로 처리해준다.

1
2
3
4
5
6
7
8
9
파일 처리 :  /Users/dragonsu/IdeaProjects/blog/_posts/2025-03-15-프리티어로만-배포해보기.md

description 생성을 위해 API를 보냅니다.
description 생성을 위해 API가 완료 됐습니다.

thumbnail 생성을 위해 DALL-E API를 보냅니다.
thumbnail 생성을 위해 DALL-E API가 완료 됐습니다.

처리 완료 : _posts/2025-03-15-프리티어로만-배포해보기.md

원래는 해당 요소를 Github Action 에 넣으려고 했으나

  • 파일이 추가되고, 수정되므로 변경을 커밋해야 하는 점
  • 커밋을 하기 때문에 내가 글 작성하기 전 Pull을 해줘야 하는 점

2가지 이유로 utils 함수로만 포함시켜놓았다. ( 차차, pre-commit 통해 되는지에 대해서도 접근해볼 예정이다. )

1분 30초 가량의 시간이 줄어들었다고 생각한다.

  • 웹 브라우저에 GPT를 들어가는 시간 - 10초
  • 입력창에 프롬프트 복사 + 아티클 내용 넣는 시간 - 20초
  • 생성 기다리는 시간 + 복사하는 시간 - 20초~30초
  • 새로운 GPT Chat 사용 -> 입력창에 프롬프트 복사 + 아티클 내용 넣는 시간 - 15초
  • 이미지 생성 기다리는 시간 + 복사하는 시간 - 20초~30초
  • 게시글에 desciripton 및 이미지 폴더에 저장 + image/path 입력하는 시간 - 20초 ( image/path 를 일일히 입력하는게 매우 귀찮다. )

이제 우리가 하는건?

  • 스크립트에 file_path 변경 - 10초
  • 스크립트 실행 - 5초

끝! 😎

그리고, 가장 중요한건 위 동기적 단계를 비동기로 스크립트 실행하고 기다렸다 push 하면 끝난다.

$0.08 ( 현재 기준(2025.03.21), 117원의 비용 ) 으로 1분 30초를 줄였으니 매우 효율적인거 아닐까? 🙂 생각보다의 비용은 적고, 시간은 매우 단축시켜 꽤나 만족스러운 작업이였다.

해당 내용의 소개글과 섬네일 역시도 자동으로 생성되었다.

코드 전체가 궁금하다면 process_thumbnail_and_description 를 참고해주세요!

This post is licensed under CC BY 4.0 by the author.