WellSpring

둘째 마당 : 오픈 AI의 GPT API를 활용한 업무 자동화 본문

[do-it!] LLM 활용한 AI agent개발

둘째 마당 : 오픈 AI의 GPT API를 활용한 업무 자동화

daniel00324 2025. 9. 27. 15:21

 

※ 본 게시글은 이지스퍼블리싱 출판사의 "Do-it!! LLM을 활용한 AI에이전트 개발 입문" 서적을 참고하여 개인 학습 내용을 정리한 글입니다.

 


[ 사전 환경 설정 ]

1. python 3.12.9 버전 설치 ( Download Link : https://www.python.org/downloads/macos/ )

python -V    ## Version Check!!
3.12.9

 

2. 가상화 환경 만들기 & 활성화

## python -m venv [가상화환경 이름]
python -v venv doit
source doit/bin/activate

 

3. 기본 실습 모듈 설치

# 1. OpenAI installation (오픈AI 사용)
(doit) ➜  pip install openai

# 2. python-dotenv 설치 (키 노출 보안)
(doit) ➜  pip install python-dotenv

04장. 문서와 논문을 요약하는 AI 연구원

 

04-1. PDF 문서 전처리 하기

☞ PyMuPDF 패키지를 사용해  PDF 파일에서 텍스트 추출 가능하나 페이지 번호나 헤더, 푸터 등 정보가 섞일 수 있는 제약사항은 있다.

 

Step1. PyMuPDF 설치  ( 가상화 환경 내에서 수행 )

(doit) ➜  pip install PyMuPDF

 

Step2. PDF file 업로드 및 읽어들이기

더보기

1) chap04/data 폴더 생성 후 파일 업로드  ( from.  https://ksae.re.kr/ )

 

2) PDF 에서 txt 파일 추출하기

import pymupdf
import os

#①
pdf_file_path = "chap04/data/과정기반 작물모형을 이용한 웹 기반 밀 재배관리 의사결정 지원시스템 설계 및 구축.pdf"
doc = pymupdf.open(pdf_file_path)

full_text = ''

#②
for page in doc: # 문서 페이지 반복
    text = page.get_text() # 페이지 텍스트 추출
    full_text += text

#③
pdf_file_name = os.path.basename(pdf_file_path)
pdf_file_name = os.path.splitext(pdf_file_name)[0] # 확장자 제거

#④
txt_file_path = f"chap04/output/{pdf_file_name}.txt"
with open(txt_file_path, 'w', encoding='utf-8') as f:
    f.write(full_text)

 

페이지 헤더/ 푸터 작업없이 추출된 결과물 (페이지 구분 내용 포함)

 

3) 헤더, 푸터 제거 후 데이터 추출

import pymupdf
import os

pdf_file_path = "chap04/data/과정기반 작물모형을 이용한 웹 기반 밀 재배관리 의사결정 지원시스템 설계 및 구축.pdf"
doc = pymupdf.open(pdf_file_path)

header_height = 80
footer_height = 80

full_text = ''

for page in doc:
    rect = page.rect # 페이지 크기 가져오기
    
    header = page.get_text(clip=(0, 0, rect.width , header_height))
    footer = page.get_text(clip=(0, rect.height - footer_height, rect.width , rect.height))
    text = page.get_text(clip=(0, header_height, rect.width , rect.height - footer_height))

    full_text += text + '\n------------------------------------\n'

# 파일명만 추출
pdf_file_name = os.path.basename(pdf_file_path)
pdf_file_name = os.path.splitext(pdf_file_name)[0] # 확장자 제거

txt_file_path = f'chap04/output/{pdf_file_name}_with_preprocessing.txt'

with open(txt_file_path, 'w', encoding='utf-8') as f:
    f.write(full_text)

 

[ 결과물 확인 ]

 

04-2 논문을 요약해 주는 AI 연구원 완성하기

☞ 이전 장에서 텍스트를 추출완료 하였으므로, 이 장에서는 문서 내용 요약을 진행해 보자!!

 

Step1. chap04 폴더에 summary.py 파일 생성 후 텍스트 파일 요약하는 코드 작성

더보기

[ 코드 ]

from openai import OpenAI
from dotenv import load_dotenv
import os

load_dotenv()
api_key = os.getenv('OPENAI_API_KEY')

def summarize_txt(file_path: str): # ①
    client = OpenAI(api_key=api_key)

    # ② 주어진 텍스트 파일을 읽어들인다.
    with open(file_path, 'r', encoding='utf-8') as f:
        txt = f.read()

    # ③ 요약을 위한 시스템 프롬프트를 생성한다.
    system_prompt = f'''
    너는 다음 글을 요약하는 봇이다. 아래 글을 읽고, 저자의 문제 인식과 주장을 파악하고, 주요 내용을 요약하라. 

    작성해야 하는 포맷은 다음과 같다. 
    
    # 제목

    ## 저자의 문제 인식 및 주장 (15문장 이내)
    
    ## 저자 소개

    
    =============== 이하 텍스트 ===============

    { txt }
    '''

    print(system_prompt)
    print('=========================================')

    # ④ OpenAI API를 사용하여 요약을 생성한다.
    response = client.chat.completions.create(
        model="gpt-4o",
        temperature=0.1,
        messages=[
            {"role": "system", "content": system_prompt},
        ]
    )

    return response.choices[0].message.content

if __name__ == '__main__':
    file_path = './chap04/output/과정기반 작물모형을 이용한 웹 기반 밀 재배관리 의사결정 지원시스템 설계 및 구축_with_preprocessing.txt'

    summary = summarize_txt(file_path)
    print(summary)

    # ⑤ 요약된 내용을 파일로 저장한다.
    with open('./chap04/output/crop_model_summary.txt', 'w', encoding='utf-8') as f:
        f.write(summary)

 

[ 실행 결과 확인 ]

 

Step2. PDF 내용 요약하여 출력하기

더보기

[ 코드 ]

from openai import OpenAI
from dotenv import load_dotenv
import os
import pymupdf

load_dotenv()
api_key = os.getenv('OPENAI_API_KEY')

def pdf_to_text(pdf_file_path: str):
    doc = pymupdf.open(pdf_file_path)

    header_height = 80
    footer_height = 80

    full_text = ''

    for page in doc:
        rect = page.rect # 페이지 크기 가져오기
        
        header = page.get_text(clip=(0, 0, rect.width , header_height))
        footer = page.get_text(clip=(0, rect.height - footer_height, rect.width , rect.height))
        text = page.get_text(clip=(0, header_height, rect.width , rect.height - footer_height))

        full_text += text + '\n------------------------------------\n'

    # 파일명만 추출
    pdf_file_name = os.path.basename(pdf_file_path)
    pdf_file_name = os.path.splitext(pdf_file_name)[0] # 확장자 제거

    txt_file_path = f'chap04/output/{pdf_file_name}_with_preprocessing.txt'

    with open(txt_file_path, 'w', encoding='utf-8') as f:
        f.write(full_text)

    return txt_file_path


def summarize_txt(file_path: str): # ①
    client = OpenAI(api_key=api_key)

    # ② 주어진 텍스트 파일을 읽어들인다.
    with open(file_path, 'r', encoding='utf-8') as f:
        txt = f.read()

    # ③ 요약을 위한 시스템 프롬프트를 생성한다.
    system_prompt = f'''
    너는 다음 글을 요약하는 봇이다. 아래 글을 읽고, 저자의 문제 인식과 주장을 파악하고, 주요 내용을 요약하라. 

    작성해야 하는 포맷은 다음과 같다. 
    
    # 제목

    ## 저자의 문제 인식 및 주장 (15문장 이내)
    
    ## 저자 소개

    
    =============== 이하 텍스트 ===============

    { txt }
    '''

    print(system_prompt)
    print('=========================================')

    # ④ OpenAI API를 사용하여 요약을 생성한다.
    response = client.chat.completions.create(
        model="gpt-4o",
        temperature=0.1,
        messages=[
            {"role": "system", "content": system_prompt},
        ]
    )

    return response.choices[0].message.content

def summarize_pdf(pdf_file_path: str, output_file_path: str):
    txt_file_path = pdf_to_text(pdf_file_path)
    summary = summarize_txt(txt_file_path)

    with open(output_file_path, 'w', encoding='utf-8') as f:
        f.write(summary)


if __name__ == '__main__':
    pdf_file_path = "chap04/data/과정기반 작물모형을 이용한 웹 기반 밀 재배관리 의사결정 지원시스템 설계 및 구축.pdf"
    summarize_pdf(pdf_file_path, 'chap04/output/crop_model_summary2.txt')

 

 [ 실행 결과 확인 ]

 


05장. 회의록을 정리하는 AI 서기

05-1. 음성을 텍스트로 변환하기

☞음성을 텍스트로 변환하는 기술을 STT (Speech-To-Text) 라 한다. 이번 실습을 통해 MP3 파일을 사용하여 내용을 요약하는 실습을 해 보자!!

 

Step1. Python + Jupyter 기능 사용하도록 환경 구성  ( Visual Code 에서 수행 )

Step2. 익스텐션 설치 후, 파이썬 환경 선택 → 현재 프로젝트 폴더의 가상환경 [doit] 선택!!

더보기

1) Select Kernel > extension 설치 > Python Environments 선택

 

 

2) 코드 실행하고자 하는 가상화 환경 선택

 

3) 코드 입력 및 수행 결과 확인

코드 블럭 실행하기 : [Shift] + [Enter]

 

수행한 코드블럭 밑에 수행 결과를 확인 할 수 있다!!

05-2. 로컬에서 음성을 텍스트로 변환하기

Step1. huggingface 웹 페이지에서 모델 다운로드 받아 로컬에서 사용하기

더보기

. https://huggingface.co 에 접속하여 모델 선택

 

Step2. 필요한 패키지 설치 ( Jupyter notebook 파일 생성 )

더보기

1. pip 패키지 설치

%pip install --upgrade pip 
%pip install --upgrade transformers datasets\[audio\] accelerate

 

“zsh:1: no matches found: datasets[audio]”는 zsh 셸이 datasets[audio] 라는 패턴을 파일 이름 확장(globbing)으로 해석하려다가 일치하는 파일이 없어서 발생하는 오류임

 

escape 문자를 사용해 주자!!!

 

 

2. FFMPEG 오픈소스 도구 설치 (whisper-large-v3-turbo 모델 사용시 필수 !! )

 - https://www.gyan.dev/ffmpeg/builds/  에 접속하여 다운로드

 - mac 에서는 homebrew 로 인스톨 할 것을 권장함

homebrew install ffmpeg@7

 

 

 3. OS 환경 import

 

4. whisper 코드 활용 ( https://pytorch.org 에서 mac OS 선택 후 명령어 복사 실행 )

## pip3 install torch torchvision

## Recommand : Specify the detail spec of each part
# export DYLD_LIBRARY_PATH=/opt/homebrew/opt/ffmpeg@7/lib

# pip install torch==2.7.1 torchvision==0.22.1 torchaudio==2.7.1 \
# "transformers==4.42.*" "datasets[audio]==2.20.*" "accelerate==0.30.*"

## Error -> mac 은 ffmpeg@5 사용할 것!!
brew install ffmpeg@5

## 필요 시, 하기 링크 생성
brew link ffmpeg@5 --force

## torch 설치 (현재 ffmpeg 버전에서는 2.8.0 이하만 호환 가능 )
pip install torch==2.7.1 torchvision==0.22.1 torchaudio==2.7.1
pip install "transformers==4.42.*" "datasets[audio]==2.20.*" "accelerate==0.30.*"

 

 

... 중략

 

import torch
from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor, pipeline
# from datasets import load_dataset

device = "cuda:0" if torch.cuda.is_available() else "mps"
torch_dtype = torch.float16 if torch.cuda.is_available() else torch.float32

model_id = "openai/whisper-large-v3-turbo"

model = AutoModelForSpeechSeq2Seq.from_pretrained(
    model_id, torch_dtype=torch_dtype, low_cpu_mem_usage=True, use_safetensors=True
)
model.to(device)

processor = AutoProcessor.from_pretrained(model_id)

pipe = pipeline(
    "automatic-speech-recognition",
    model=model,
    tokenizer=processor.tokenizer,
    feature_extractor=processor.feature_extractor,
    torch_dtype=torch_dtype,
    device=device,
    return_timestamps=True,   # 청크별로 타임스탬프 반환
    chunk_length_s=10,  # 입력 오디오 10초씩 나누기r
    stride_length_s=2,  # 2초씩 겹치도록 청크 나누기
) 

# dataset = load_dataset("distil-whisper/librispeech_long", "clean", split="validation")
# sample = dataset[0]["audio"]
sample = "../audio/lsy_audio_2023_58s.mp3"

result = pipe(sample)
# print(result["text"])

print(result)

 

[ 에러 발생 !! ]

 

[ Trouble shooting ]

☞ mac 은 ffmpeg@5 으로 재설치!!  ( 기존 설치 버전은 삭제할 것 !! )

 

 

또 다른 에러 발생 !!

Reason: no LC_RPATH's found
FFmpeg version 4: dlopen(/Users/daniel0324/Workspace/AI_agent/doit/lib/python3.12/site-packages/torchcodec/libtorchcodec_core4.dylib, 0x0006): Library not loaded: @rpath/libavutil.56.dylib
  Referenced from: <EFBAC5A4-AA8E-3EFE-A5EE-38C7A9BDB08A> /Users/daniel0324/Workspace/AI_agent/doit/lib/python3.12/site-packages/torchcodec/libtorchcodec_core4.dylib
  Reason: no LC_RPATH's found
[end of libtorchcodec loading traceback].

 

Visual Studio Code  > [Problem] 내용 확인

RuntimeError: Could not load libtorchcodec. Likely causes:
          1. FFmpeg is not properly installed in your environment. We support
             versions 4, 5, 6 and 7.
          2. The PyTorch version (2.8.0) is not compatible with
             this version of TorchCodec. Refer to the version compatibility
             table:
             https://github.com/pytorch/torchcodec?tab=readme-ov-file#installing-torchcodec.
          3. Another runtime dependency; see exceptions below.
        The following exceptions were raised as we tried to load libtorchcodec:
        
[start of libtorchcodec loading traceback]
FFmpeg version 7: dlopen(/Users/daniel0324/Workspace/AI_agent/doit/lib/python3.12/site-packages/torchcodec/libtorchcodec_core7.dylib, 0x0006): Library not loaded: @rpath/libavutil.59.dylib
  Referenced from: <5A6534EF-41AA-3E0A-98F4-0637B1BFC89B> /Users/daniel0324/Workspace/AI_agent/doit/lib/python3.12/site-packages/torchcodec/libtorchcodec_core7.dylib
  Reason: no LC_RPATH's found
FFmpeg version 6: dlopen(/Users/daniel0324/Workspace/AI_agent/doit/lib/python3.12/site-packages/torchcodec/libtorchcodec_core6.dylib, 0x0006): Library not loaded: @rpath/libavutil.58.dylib
  Referenced from: <7875BDF2-D0C6-33AC-92E2-4D6C9E828E9F> /Users/daniel0324/Workspace/AI_agent/doit/lib/python3.12/site-packages/torchcodec/libtorchcodec_core6.dylib
  Reason: no LC_RPATH's found
FFmpeg version 5: dlopen(/Users/daniel0324/Workspace/AI_agent/doit/lib/python3.12/site-packages/torchcodec/libtorchcodec_core5.dylib, 0x0006): Library not loaded: @rpath/libavutil.57.dylib
  Referenced from: <9E993DE1-E862-37C4-A572-4AF8D997A1DF> /Users/daniel0324/Workspace/AI_agent/doit/lib/python3.12/site-packages/torchcodec/libtorchcodec_core5.dylib
  Reason: no LC_RPATH's found
FFmpeg version 4: dlopen(/Users/daniel0324/Workspace/AI_agent/doit/lib/python3.12/site-packages/torchcodec/libtorchcodec_core4.dylib, 0x0006): Library not loaded: @rpath/libavutil.56.dylib
  Referenced from: <EFBAC5A4-AA8E-3EFE-A5EE-38C7A9BDB08A> /Users/daniel0324/Workspace/AI_agent/doit/lib/python3.12/site-packages/torchcodec/libtorchcodec_core4.dylib
  Reason: no LC_RPATH's found
[end of libtorchcodec loading traceback].

 

기존 torch 버전 삭제 후, 권고 버전 재설치

## 01. Uninstall Pre-installed Version 
pip uninstall torch torchvision

## 02. Re-install Recommanded-Version
pip install torch==2.7.1 torchvision==0.22.1 torchaudio==2.7.1
pip install "transformers==4.42.*" "datasets[audio]==2.20.*" "accelerate==0.30.*"

 

 

  

[ 성공 !! ]

 

 


05-3. 문장과 화자 구분하기

[ 실습 - 화자 분리 모델로 시간대별 화자 구분하기 ]

 

Step1. 허깅페이스에서 화자 분리 모델 내려받고 사용 준비하기

더보기

1. 토큰 발급

- https://huggingface.co  > 로그인 > 프로필 로고 > [Settings] > [Access Tokens] > [+Create new token]

2. 모델 선택

 - 'speaker-diarization' 검색 > pyannote - speaker-diarization-3.1 

 

3. 사용 필요정보 등록

 - [Company/Personal] 

 - [Website]

 

 

4. ipynb 파일 생성 및 코드 작성

1) pyannote.audio & numpy 설치

 

2) 허깅페이스 토큰 업데이트

 

3) 코드 수정

https://github.com/pytorch/vision#installation

## 01. 이전 버전 삭제
pip uninstall torchvision

## 02. 호환 버전 설치
pip install torchvision==0.23

 

 

 ** pyannote Version 호환성 체크

pip check pyannote

 

** Initiate the pipeline

 

 

Step2. 음성에서 화자 분리하기

Step3. 판다스를 활용해 데이터 프레임 형태로 저장하기

더보기

1. 판다스 import 및 csv file 읽기

 2. 데이터 파일 가공 ( 발화가 끝난시간 = end 값 구하기 )

 

3. Number 열 초기화 및 입력 ( 발언번호 초기화 )

 

4. 화자가 변경 시, 넘버가 변한다.

 

4. 같은 화자끼리 묶기

 

5. 발화시간 추가하고 인덱스 제거

 

 6. CSV파일에 결과 저장

[ 실습 - 판다스로 문장 분석하고 화자 매칭하기 ]

 

☞ 경로가 달라지면 torch 등 재설치 필요

pip install torch==2.7.1 torchvision==0.22.1 torchaudio==2.7.1
pip install "transformers==4.42.*" "datasets[audio]==2.20.*" "accelerate==0.30.*"

 

☞ (주의) 매 sec## 시작하기 전, 가상화 환경 활성화 확인 하고, 파일 호출 위치 "gpt_agent_2025_easyspub" 으로 변경 할 것!!


05-4. 회의록을 정리하는 AI 서기 완성하기

Step1. 전체 회의 요약하기

더보기

1. 쥬피터 노트북 파일 생성 및 오픈

 

2. 화자 이름 지정

 

3. 요약 내용 추출하기

 

4. GPT prompting 및 답신 summary 확인

 

요약 결과 보기

 

 

 

 

Step2. 화자별 발언 내용 순서대로 정리하기 ( json -> dictionary type -> docx 로 변환 )

 


Chap6. GPT-4o 를 이용한 AI 이미지 분석가

☞ 이번 실습은 저작권 문제가 없는 인터넷에 있는 언스플래쉬(unsplash.com)에서 이미지를 가져와 사용합니다.

 

[실습1. 인터넷 이미지 받아오기]

Step1. image_explanation.ipynb 생성

Step2. 코드 실행하여 이미지 설명 결과 확인

더보기

1. 이미지 받아오기

from openai import OpenAI
from dotenv import load_dotenv
import os

load_dotenv()
api_key = os.getenv("OPENAI_API_KEY")  # 환경 변수에서 API 키를 가져오기

client = OpenAI(api_key=api_key)  # 오픈AI 클라이언트의 인스턴스 생성

messages = [
    {
        "role": "user",
        "content": [
            {"type": "text", "text": "이 이미지에 대해 설명해주세요."},
            {
                "type": "image_url",
                "image_url": {
                    "url": "https://images.unsplash.com/photo-1736264335247-8ec5664c8328?q=80&w=1887&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
                },
            },
        ],
    }
]

response = client.chat.completions.create(
    model="gpt-4o",  # 응답 생성에 사용할 모델 지정
    messages=messages # 대화 기록을 입력으로 전달
)

response

 

ChatCompletion(id='chatcmpl-CMN4A4Vq2auSbcyd2MH6J138cTewn', choices=[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='이미지에는 한 여성이 주방에서 요리를 준비하는 모습이 담겨 있습니다. 그녀는 검은색 운동복을 입고 있으며, 파란색 그릇에 달걀을 휘젓고 있습니다. 배경에는 후라이팬에 베이컨이 요리되고 있는 모습이 보입니다. 밝고 즐거운 분위기입니다.', refusal=None, role='assistant', annotations=[], audio=None, function_call=None, tool_calls=None))], created=1759447918, model='gpt-4o-2024-08-06', object='chat.completion', service_tier='default', system_fingerprint='fp_cbf1785567', usage=CompletionUsage(completion_tokens=78, prompt_tokens=1119, total_tokens=1197, completion_tokens_details=CompletionTokensDetails(accepted_prediction_tokens=0, audio_tokens=0, reasoning_tokens=0, rejected_prediction_tokens=0), prompt_tokens_details=PromptTokensDetails(audio_tokens=0, cached_tokens=0)))

 

 

[ 실습 2. 내가 가진 이미지로 실습 ]

더보기

Step1. encode_image 함수로 파일경로 받아 바이너리 모드로 열고, base64로 인코딩 후, 데이터를 텍스트로 변환

 

import base64

# Function to encode the image
def encode_image(image_path):
    with open(image_path, "rb") as image_file:
        return base64.b64encode(image_file.read()).decode("utf-8")
    
image_path = "../data/images/mangwon_bakery.jpg"

# 이미지를 base64로 인코딩
base64_image = encode_image(image_path)

print(base64_image)

 

 Step2. base64 로 인코딩된 이미지 전달

messages = [
    {
        "role": "user",
        "content": [
            {"type": "text", "text": "이 이미지에 대해 설명해주세요."},
            {
                "type": "image_url",
                "image_url": {
                    "url": f"data:image/jpeg;base64,{base64_image}",
                },
            },
        ],
    }
]

response = client.chat.completions.create(
    model="gpt-4o",  # 응답 생성에 사용할 모델 지정
    messages=messages # 대화 기록을 입력으로 전달
)

response.choices[0].message.content
'이 이미지는 베이커리의 진열대를 보여주고 있습니다. 유리 케이스 안에는 다양한 빵과 패스트리가 진열되어 있으며, 각각의 제품에는 가격이 적힌 작은 표지판이 붙어 있습니다. 빵은 다양한 종류와 모양으로, 일부는 토핑이나 고명을 얹은 것도 있습니다. 진열대 뒤로는 베이커리 직원들이 일하는 모습이 보입니다. 전체적으로 깔끔하고 따뜻한 분위기의 베이커리 인테리어입니다.'

 

Step3. 여러 이미지 비교 분석하기

seolleung_terrarosa_base64 = encode_image("../data/images/seolleung_terrarosa.jpg")
local_stitch_terrarosa_base64 = encode_image("../data/images/local_stitch_terrarosa.jpg")

messages = [
    {
        "role": "user",
        "content": [
            {"type": "text", "text": "두 카페의 차이점을 설명해주세요."},
            {
                "type": "image_url",
                "image_url": {
                    "url": f"data:image/jpeg;base64,{seolleung_terrarosa_base64}",
                },
            },
            {
                "type": "image_url",
                "image_url": {
                    "url": f"data:image/jpeg;base64,{local_stitch_terrarosa_base64}",
                },
            },
        ],
    }
]

response = client.chat.completions.create(
    model="gpt-4o",  # 응답 생성에 사용할 모델 지정
    messages=messages # 대화 기록을 입력으로 전달
)

response.choices[0].message.content

 

'두 카페의 차이점을 설명해 드릴게요.\n\n1. **조명 및 분위기:**\n   - 첫 번째 카페는 어두운 조명과 목재 바닥을 사용하여 따뜻하고 편안한 분위기를 자아냅니다. 천장의 검은 스트립 조명이 공간을 세련되게 만듭니다.\n   - 두 번째 카페는 밝은 조명과 밝은 색상(노란색과 빨간색)을 사용하여 현대적이고 활기찬 느낌을 줍니다. 바닥의 타일 패턴도 시각적인 포인트가 됩니다.\n\n2. **가구 스타일:**\n   - 첫 번째 카페는 전통적인 나무 의자와 테이블을 사용하고 있습니다. 전체적으로 차분한 색조가 돋보입니다.\n   - 두 번째 카페는 현대적인 디자인의 가구를 사용하며, 금속과 나무의 조합이 강조됩니다. \n\n3. **공간 구성:**\n   - 첫 번째 카페는 넓고 개방된 공간으로, 여러 테이블이 넓게 배치되어 있습니다. 창문이 많아 자연채광이 잘 들어옵니다.\n   - 두 번째 카페는 중앙에 긴 바 테이블이 있고, 테이블과 의자가 깔끔하게 정리되어 있어 보다 체계적인 느낌을 줍니다.\n\n4. **색상 및 디자인 요소:**\n   - 첫 번째 카페는 자연적인 색상과 소재를 사용하여 아늑함을 강조합니다.\n   - 두 번째 카페는 생동감 있는 색상과 금속 소재를 사용하여 현대적이고 세련된 분위기를 만들어 냅니다.\n\n각 카페는 자신의 성격과 스타일에 맞춰 독특한 분위기를 가지고 있어, 방문객이 원하는 경험에 따라 선택할 수 있을 것입니다.'

 

  


06-2. 이미지를 활용해 퀴즈 만들기

[ 실습 1. 문제 생성 함수 만들기 ]

더보기
from glob import glob
import json
from openai import OpenAI
from dotenv import load_dotenv
import os
import base64

load_dotenv()
api_key = os.getenv("OPENAI_API_KEY")  # 환경 변수에서 API 키 가져오기
client = OpenAI(api_key=api_key)  # OpenAI 클라이언트의 인스턴스 생성

def encode_image(image_path):
    with open(image_path, "rb") as image_file:
        return base64.b64encode(image_file.read()).decode("utf-8")
    

def image_quiz(image_path, n_trial=0, max_trial=3):
    if n_trial >= max_trial: # 최대 시도 회수에 도달하면 포기
        raise Exception("Failed to generate a quiz.")
    
    base64_image = encode_image(image_path) # 이미지를 base64로 인코딩

    quiz_prompt = """
    제공된 이미지를 바탕으로, 다음과 같은 양식으로 퀴즈를 만들어주세요. 
    정답은 1~4 중 하나만 해당하도록 출제하세요.
    토익 리스닝 문제 스타일로 문제를 만들어주세요.
    아래는 예시입니다. 
    ----- 예시 -----

    Q: 다음 이미지에 대한 설명 중 옳지 않은 것은 무엇인가요?
    - (1) 베이커리에서 사람들이 빵을 사고 있는 모습이 담겨 있습니다.
    - (2) 맨 앞에 서 있는 사람은 빨간색 셔츠를 입고 있습니다.
    - (3) 기차를 타기 위해 줄을 서 있는 사람들이 있습니다.
    - (4) 점원은 노란색 티셔츠를 입고 있습니다.

    Listening: Which of the following descriptions of the image is incorrect?
    - (1) It shows people buying bread at a bakery.
    - (2) The person standing at the front is wearing a red shirt.
    - (3) There are people lining up to take a train.
    - (4) The clerk is wearing a yellow T-shirt.
        
    정답: (4) 점원은 노란색 티셔츠가 아닌 파란색 티셔츠를 입고 있습니다.
    (주의: 정답은 1~4 중 하나만 선택되도록 출제하세요.)
    ======
    """

    messages = [
        {
            "role": "user",
            "content": [
                {"type": "text", "text": quiz_prompt},
                {
                    "type": "image_url",
                    "image_url": {
                        "url": f"data:image/jpeg;base64,{base64_image}",
                    },
                },
            ],
        }
    ]

    try: 
        response = client.chat.completions.create(
            model="gpt-4o",  # 응답 생성에 사용할 모델 지정
            messages=messages # 대화 기록을 입력으로 전달
        )
    except Exception as e:
        print("failed\n" + e)
        return image_quiz(image_path, n_trial+1)
    
    content = response.choices[0].message.content

    if "Listening:" in content:
        return content, True
    else:
        return image_quiz(image_path, n_trial+1)


q = image_quiz("./chap06/data/images/busan_dive.jpg")
print(q)



txt = '' # 문제들을 계속 붙여 나가기 위해 빈 문자열 선언
eng_dict = []
no = 1 # 문제 번호를 위해 선언
for g in glob('./chap06/data/images/*.jpg'):  # ②
    q, is_suceed = image_quiz(g)

    if not is_suceed:
        continue


    divider = f'## 문제 {no}\n\n'
    print(divider)
    
    txt += divider
    # 파일명 추출해 이미지 링크 만들기
    filename = os.path.basename(g) # 마크다운에 표시할 이미지 파일 경로 설정   
    txt += f'![image]({filename})\n\n' 

    # 문제 추가
    print(q)
    txt += q + '\n\n---------------------\n\n'
    # 마크다운 파일로 저장
    with open('./chap06/data/images/image_quiz_eng.md', 'w', encoding='utf-8') as f:
        f.write(txt)

    # 영어 문제만 추출
    eng = q.split('Listening: ')[1].split('정답:')[0].strip()

    eng_dict.append({
        'no': no,
        'eng': eng,
        'img': filename
    })

    # json 파일로 저장
    with open('./chap06/data/images/image_quiz_eng.json', 'w', encoding='utf-8') as f:
        json.dump(eng_dict, f, ensure_ascii=False, indent=4)
    
    
    no += 1 # 문제 번호 증가

 

 

 

[ 실습 2. TTS로 영어 듣기 평가 문제 만들기 ]

더보기

Step1. image_quiz.md 에서 영어 문제 추출 ( 'Listening :' ~ '정답' 사이 발췌 )

  ☞ 이전 실습에서 수행

 

Step2. OpenAI 의 TTS  기능 사용

1. OpenAI API 사용하기

from openai import OpenAI
from dotenv import load_dotenv
import os

load_dotenv()
api_key = os.getenv("OPENAI_API_KEY")  # 환경 변수에서 API 키를 가져옵니다.
client = OpenAI(api_key=api_key)  # OpenAI 클라이언트의 인스턴스를 생성합니다.

 

2. "alloy" 활용한  TTS  기능 활용 - mp3 파일 생성

response = client.audio.speech.create(
    model="tts-1-hd",
    voice="alloy",
    input="Hello world! This is a TTS test.",
)

response.write_to_file("hello_world.mp3")

# 재생
import IPython.display as ipd

ipd.Audio("hello_world.mp3")

 

3. 다른 목소리로 바꾸어 보자!!  ( 'alloy', 'ash', 'coral', 'echo', 'fable', 'onyx', 'nove', 'sage', 'shimmer' etc )

# 다른 목소리
voice = "ash"
mp3_file = f"hello_world_{voice}.mp3"

response = client.audio.speech.create(
    model="tts-1-hd",
    voice=voice,
    input=f"Hello world! I'm {voice}. This is a TTS test.",
)

response.write_to_file(mp3_file)

# 재생
import IPython.display as ipd

ipd.Audio(mp3_file)

 

4. json 파일 읽기 ( 사전에 만들어 둔 '영어 퀴즈' 파일 읽어오기 )

import json

# json 파일 열기
with open('../data/images/image_quiz_eng.json', 'r', encoding='utf-8') as f:
    eng_dict = json.load(f)

eng_dict

 

 5. for 문으로 딕셔너리에 저장된 내용을 하나씩 읽어온다.

  ☞ (1) -> one. 으로 바꾸어 주어야 mp3파일 재생 시 숫자를 건너 띄는 경우를 방지할 수 있다!!

voices = ['alloy', 'ash', 'coral', 'echo', 'fable', 'onyx', 'nova', 'sage' , 'shimmer']

for q in eng_dict:
    no = q['no']
    quiz = q['eng']
    quiz = quiz.replace("- (1)", "- One.\t")
    quiz = quiz.replace("- (2)", "- Two.\t")
    quiz = quiz.replace("- (3)", "- Three.\t")
    quiz = quiz.replace("- (4)", "- Four.\t")    

    print(no, quiz)
    
    voice = voices[no % len(voices)] # 문제 개수를 목소리 개수로 나눈 나머지 값으로 선택  

    response = client.audio.speech.create(
        model="tts-1-hd",
        voice=voice,
        input=f'#{no}. {quiz}',
    )

    response.write_to_file(f"../data/audio/{no}.mp3")

 

 

 6. 최종 생성된 mp3파일을 플레이 해 보자!!

ipd.Audio(f"../data/audio/1.mp3")

 

 


Chap7.  최신 주식 정보를 알려주는 AI 투자자

07-1. 펑션 콜링의 기초

☞ Function Calling 이란?

: GPT와 유사한 언어 모델이 단순히 텍스트 응답을 생성하는 것이 아니라, 미리 정의된 외부 함수(API, 내부 로직 등)를 호출해야 할지를 판단하고, 호출 시 필요한 인자를 구조화된 형태(예: JSON)로 반환하여 시스템이 그 함수를 실제로 실행하게 하는 메커니즘을 일컫는다.

 

[ 순서 설명 ]

1 개발자가 사용할 함수들의 정의 (스펙) 을 모델에게 전달 (name, description, parameters 등)
2 사용자 메시지를 모델에 전달
3 모델은 메시지를 보고, 함수를 호출할지 / 호출하지 않을지 / 어느 함수를 호출할지 판단
4 만약 호출을 결정했다면, 모델은 tool_calls 또는 function_call 필드로 함수 이름 + 인자 (JSON 형태) 를 응답
5 애플리케이션은 이 응답을 해석하여 실제 함수를 호출하고 그 결과를 획득
6 함수 호출 결과를 다시 모델에게 메시지로 넘겨주고, 모델은 이 결과를 반영한 최종 응답을 생성
7 최종 응답을 사용자에게 전달

 

[ 예시 ]

{
  "name": "get_weather",
  "description": "주어진 지역의 현재 날씨를 조회합니다.",
  "parameters": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "예: 'Seoul, Korea'"
      },
      "units": {
        "type": "string",
        "enum": ["metric", "imperial"],
        "description": "온도 단위"
      }
    },
    "required": ["location"]
  }
}

 

[ 실습 1. 펑션 콜링 적용하기 ]

더보기

1. GPT에서 사용할 함수 정의

 - def get_current_time()

 

2. tools 를 통해서 함수가 펑션 콜링을 사용하기 위한 설명을 추가한다.

from datetime import datetime
import pytz

def get_current_time(timezone: str = 'Asia/Seoul'):
    tz = pytz.timezone(timezone) # 타임존 설정
    now = datetime.now(tz).strftime("%Y-%m-%d %H:%M:%S")
    now_timezone = f'{now} {timezone}'
    print(now_timezone)
    return now_timezone


tools = [
    {
        "type": "function",
        "function": {
            "name": "get_current_time",
            "description": "해당 타임존의 날짜와 시간을 반환합니다.",
            "parameters": {
                "type": "object",
                "properties": {
                    'timezone': {
                        'type': 'string',
                        'description': '현재 날짜와 시간을 반환할 타임존을 입력하세요. (예: Asia/Seoul)',
                    },
                },
                "required": ['timezone'],
            },        
        }
    },
]


if __name__ == '__main__':
    get_current_time('America/New_York')

 

 

3. GPT에 tools 정보 포함하기

☞ 반복 대화를 위해서 "While True : " 이하에서 함수를 호출해 준다!!

from gpt_functions import get_current_time, tools 
from openai import OpenAI
from dotenv import load_dotenv
import os
import json

load_dotenv()
api_key = os.getenv("OPENAI_API_KEY")  # 환경 변수에서 API 키 가져오기

client = OpenAI(api_key=api_key)  # 오픈AI 클라이언트의 인스턴스 생성

def get_ai_response(messages, tools=None):
    response = client.chat.completions.create(
        model="gpt-4o",  # 응답 생성에 사용할 모델 지정
        messages=messages,  # 대화 기록을 입력으로 전달
        tools=tools,  # 사용 가능한 도구 목록 전달
    )
    return response  # 생성된 응답 내용 반환



messages = [
    {"role": "system", "content": "너는 사용자를 도와주는 상담사야."},  # 초기 시스템 메시지
]

while True:
    user_input = input("사용자\t: ")  # 사용자 입력 받기

    if user_input == "exit":  # 사용자가 대화를 종료하려는지 확인
        break
    
    messages.append({"role": "user", "content": user_input})  # 사용자 메시지 대화 기록에 추가
    
    ai_response = get_ai_response(messages, tools=tools)
    ai_message = ai_response.choices[0].message
    print(ai_message)  # ③ gpt에서 반환되는 값을 파악하기 위해 임시로 추가

    tool_calls = ai_message.tool_calls  # AI 응답에 포함된 tool_calls를 가져옵니다.
    if tool_calls:  # tool_calls가 있는 경우
        for tool_call in tool_calls:
            tool_name = tool_call.function.name # 실행해야한다고 판단한 함수명 받기
            tool_call_id = tool_call.id         # tool_call 아이디 받기    
            arguments = json.loads(tool_call.function.arguments) # (1) 문자열을 딕셔너리로 변환    
            
            if tool_name == "get_current_time":  # ⑤ 만약 tool_name이 "get_current_time"이라면
                messages.append({
                    "role": "function",  # role을 "function"으로 설정
                    "tool_call_id": tool_call_id,
                    "name": tool_name,
                    "content": get_current_time(timezone=arguments['timezone']),  # 타임존 추가
                })
        messages.append({"role": "system", "content": "이제 주어진 결과를 바탕으로 답변할 차례다."})  # 함수 실행 완료 메시지 추가
        ai_response = get_ai_response(messages, tools=tools) # 다시 GPT 응답 받기
        ai_message = ai_response.choices[0].message

    messages.append(ai_message)  # AI 응답을 대화 기록에 추가하기

    print("AI\t: " + ai_message.content)  # AI 응답 출력

 

 

 

복수 질문에 대한 답변 확인하기

 

[ 실습2. 스트림릿에서 펑션 콜링 사용하기 ]

☞ 스트림릿 기능 활용을 위해 먼저 설치해 주자!!

더보기
## 01. 모듈 설치
pip install streamlit

## 02. 테스트  ( 데모 웹 페이지 확인 가능!! )
streamlit hello

 

☞ 스트림 릿 활용한 코드 살펴보기

더보기

[ 코드 ]

from gpt_functions import get_current_time, tools 
from openai import OpenAI
from dotenv import load_dotenv
import os
import json
import streamlit as st

load_dotenv()
api_key = os.getenv("OPENAI_API_KEY")  # 환경 변수에서 API 키 가져오기

client = OpenAI(api_key=api_key)  # 오픈AI 클라이언트의 인스턴스 생성

def get_ai_response(messages, tools=None):
    response = client.chat.completions.create(
        model="gpt-4o",  # 응답 생성에 사용할 모델 지정
        messages=messages,  # 대화 기록을 입력으로 전달
        tools=tools,  # 사용 가능한 도구 목록 전달
    )
    return response  # 생성된 응답 내용 반환

st.title("💬 Chatbot")   

if "messages" not in st.session_state:
    st.session_state["messages"] = [
        {"role": "system", "content": "너는 사용자를 도와주는 상담사야."},  # 초기 시스템 메시지
    ] 

for msg in st.session_state.messages:
    if msg["role"] == "assistant" or msg["role"] == "user": # assistant 혹은 user 메시지인 경우만
        st.chat_message(msg["role"]).write(msg["content"])


if user_input := st.chat_input():    # ① 사용자 입력 받기
    st.session_state.messages.append({"role": "user", "content": user_input})  # ① 사용자 메시지를 대화 기록에 추가
    st.chat_message("user").write(user_input)  # ① 사용자 메시지를 브라우저에서도 출력
    
    ai_response = get_ai_response(st.session_state.messages, tools=tools)
    ai_message = ai_response.choices[0].message
    print(ai_message)  # ③ gpt에서 반환되는 값을 파악하기 위해 임시로 추가

    tool_calls = ai_message.tool_calls  # AI 응답에 포함된 tool_calls를 가져옵니다.
    if tool_calls:  # tool_calls가 있는 경우
        for tool_call in tool_calls:
            tool_name = tool_call.function.name # 실행해야한다고 판단한 함수명 받기
            tool_call_id = tool_call.id         # tool_call 아이디 받기    
            arguments = json.loads(tool_call.function.arguments) # (1) 문자열을 딕셔너리로 변환    
            
            if tool_name == "get_current_time":  # ⑤ 만약 tool_name이 "get_current_time"이라면
                st.session_state.messages.append({
                    "role": "function",  # role을 "function"으로 설정
                    "tool_call_id": tool_call_id,
                    "name": tool_name,
                    "content": get_current_time(timezone=arguments['timezone']),  # 타임존 추가
                })
        st.session_state.messages.append({"role": "system", "content": "이제 주어진 결과를 바탕으로 답변할 차례다."}) 
        ai_response = get_ai_response(st.session_state.messages, tools=tools) # 다시 GPT 응답 받기
        ai_message = ai_response.choices[0].message

    st.session_state.messages.append({
        "role": "assistant",
        "content": ai_message.content
    })  # ③ AI 응답을 대화 기록에 추가합니다.

    print("AI\t: " + ai_message.content)  # AI 응답 출력
    st.chat_message("assistant").write(ai_message.content)  # 브라우저에 메시지 출력

 

☞ 터미널에서 streamlist run [ File name ].py 명령어를 실행해 보자!!

 

  


07-2. GPT와 미국 주식 이야기하기

[ 실습1. yfinance 사용하기 ]

더보기

1. 모듈 설치하기. ( .ipynb 실행 )

%pip install yfinance

 

2. yfinance 연습하기 

( * "MSFT" : Microsoft 를 의미하는 티커로, 주식, ETF, 펀드 등 다양한 금융 상품을 식별하는 고유 코드를 의미 한다. )

import yfinance as yf

# Microsoft (MSFT)에 대한 Ticker 객체 생성
msft = yf.Ticker("MSFT")

# Ticker 객체에 대한 정보 출력 (.py에서 실행할 때는 print(msft.info)로 사용)
display(msft.info)

 

3. 최근 5일간 주가 정보 확인하기

hist = msft.history(period="5d") # 5일간의 주가 데이터를 가져옴
display(hist) # 데이터 출력

 

 

4. 추천 종목의 추천 여부 확인하기

msft.recommendations # 추천 정보 출력

 

 

 

[ 실습2. GPT에서 사용한 yfinance 관련 함수 만들기 ]

더보기

[ 코드 - 종목 가져오는 함수 포함 ]

from datetime import datetime
import pytz
import yfinance as yf

def get_current_time(timezone: str = 'Asia/Seoul'):
    tz = pytz.timezone(timezone) # 타임존 설정
    now = datetime.now(tz).strftime("%Y-%m-%d %H:%M:%S")
    now_timezone = f'{now} {timezone}'
    print(now_timezone)
    return now_timezone

def get_yf_stock_info(ticker: str):
    stock = yf.Ticker(ticker)
    info = stock.info
    print(info)
    return str(info)

def get_yf_stock_history(ticker: str, period: str):
    stock = yf.Ticker(ticker)
    history = stock.history(period=period)
    history_md = history.to_markdown() # 데이터프레임을 마크다운 형식으로 변환
    print(history_md)
    return history_md

def get_yf_stock_recommendations(ticker: str):
    stock = yf.Ticker(ticker)
    recommendations = stock.recommendations
    recommendations_md = recommendations.to_markdown() # 데이터프레임을 마크다운 형식으로 변환
    print(recommendations_md)
    return recommendations_md


tools = [
    {
        "type": "function",
        "function": {
            "name": "get_current_time",
            "description": "해당 타임존의 날짜와 시간을 반환합니다.",
            "parameters": {
                "type": "object",
                "properties": {
                    'timezone': {
                        'type': 'string',
                        'description': '현재 날짜와 시간을 반환할 타임존을 입력하세요. (예: Asia/Seoul)',
                    },
                },
                "required": ['timezone'],
            },        
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_yf_stock_info",
            "description": "해당 종목의 Yahoo Finance 정보를 반환합니다.",
            "parameters": {
                "type": "object",
                "properties": {
                    'ticker': {
                        'type': 'string',
                        'description': 'Yahoo Finance 정보를 반환할 종목의 티커를 입력하세요. (예: AAPL)',
                    },
                },
                "required": ['ticker'],
            },        
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_yf_stock_history",
            "description": "해당 종목의 Yahoo Finance 주가 정보를 반환합니다.",
            "parameters": {
                "type": "object",
                "properties": {
                    'ticker': {
                        'type': 'string',
                        'description': 'Yahoo Finance 주가 정보를 반환할 종목의 티커를 입력하세요. (예: AAPL)',
                    },
                    'period': {
                        'type': 'string',
                        'description': '주가 정보를 조회할 기간을 입력하세요. (예: 1d, 5d, 1mo, 1y, 5y)',
                    },
                },
                "required": ['ticker', 'period'],
            },        
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_yf_stock_recommendations",
            "description": "해당 종목의 Yahoo Finance 추천 정보를 반환합니다.",
            "parameters": {
                "type": "object",
                "properties": {
                    'ticker': {
                        'type': 'string',
                        'description': 'Yahoo Finance 추천 정보를 반환할 종목의 티커를 입력하세요. (예: AAPL)',
                    },
                },
                "required": ['ticker'],
            },        
        }
    },
]


if __name__ == '__main__':
    # get_current_time('America/New_York')
    # info = get_yf_stock_info('AAPL')  

    get_yf_stock_history('AAPL', '5d')
    print('----')
    get_yf_stock_recommendations('AAPL')

 

 [ 실행 결과 ]

 

 

[ Streamlit 코드 활용한 주식정보 가져오기 ]

from gpt_functions import get_current_time, tools, get_yf_stock_info, get_yf_stock_history, get_yf_stock_recommendations
from openai import OpenAI
from dotenv import load_dotenv
import os
import json
import streamlit as st

load_dotenv()
api_key = os.getenv("OPENAI_API_KEY")  # 환경 변수에서 API 키 가져오기

client = OpenAI(api_key=api_key)  # 오픈AI 클라이언트의 인스턴스 생성

def get_ai_response(messages, tools=None):
    response = client.chat.completions.create(
        model="gpt-4o",  # 응답 생성에 사용할 모델 지정
        messages=messages,  # 대화 기록을 입력으로 전달
        tools=tools,  # 사용 가능한 도구 목록 전달
    )
    return response  # 생성된 응답 내용 반환

st.title("💬 Chatbot")   

if "messages" not in st.session_state:
    st.session_state["messages"] = [
        {"role": "system", "content": "너는 사용자를 도와주는 상담사야."},  # 초기 시스템 메시지
    ] 

for msg in st.session_state.messages:
    if msg["role"] == "assistant" or msg["role"] == "user": # assistant 혹은 user 메시지인 경우만
        st.chat_message(msg["role"]).write(msg["content"])


if user_input := st.chat_input():    # 사용자 입력 받기
    st.session_state.messages.append({"role": "user", "content": user_input})  # 사용자 메시지를 대화 기록에 추가
    st.chat_message("user").write(user_input)  # 사용자 메시지를 브라우저에서도 출력
    
    ai_response = get_ai_response(st.session_state.messages, tools=tools)
    ai_message = ai_response.choices[0].message
    print(ai_message)  # gpt에서 반환되는 값을 파악하기 위해 임시로 추가

    tool_calls = ai_message.tool_calls  # AI 응답에 포함된 tool_calls를 가져옵니다.
    if tool_calls:  # tool_calls가 있는 경우
        for tool_call in tool_calls:
            tool_name = tool_call.function.name # 실행해야한다고 판단한 함수명 받기
            tool_call_id = tool_call.id         # tool_call 아이디 받기    
            arguments = json.loads(tool_call.function.arguments) # 문자열을 딕셔너리로 변환    
            
            if tool_name == "get_current_time":  
                func_result = get_current_time(timezone=arguments['timezone'])
            elif tool_name == "get_yf_stock_info":
                func_result = get_yf_stock_info(ticker=arguments['ticker'])
            elif tool_name == "get_yf_stock_history":  # get_yf_stock_history 함수 호출
                func_result = get_yf_stock_history(
                    ticker=arguments['ticker'], 
                    period=arguments['period']
                )
            elif tool_name == "get_yf_stock_recommendations":  # get_yf_stock_recommendations 함수 호출
                func_result = get_yf_stock_recommendations(
                    ticker=arguments['ticker']
                )

            st.session_state.messages.append({
                "role": "function",
                "tool_call_id": tool_call_id,
                "name": tool_name,
                "content": func_result,
            })


        st.session_state.messages.append({"role": "system", "content": "이제 주어진 결과를 바탕으로 답변할 차례다."}) 
        ai_response = get_ai_response(st.session_state.messages, tools=tools) # 다시 GPT 응답 받기
        ai_message = ai_response.choices[0].message

    st.session_state.messages.append({
        "role": "assistant",
        "content": ai_message.content
    })  # ③ AI 응답을 대화 기록에 추가합니다.

    print("AI\t: " + ai_message.content)  # AI 응답 출력
    st.chat_message("assistant").write(ai_message.content)  # 브라우저에 메시지 출력

 

[ 실행 결과 ]


07-3. 스트림 출력하기

 

[ 실습1. 터미널 창에서 스트림 방식으로 출력하기 ]

더보기

[ 코드 - 스트림 방식으로 변환하기 ]

 

from gpt_functions import get_current_time, tools, get_yf_stock_info, get_yf_stock_history, get_yf_stock_recommendations
from openai import OpenAI
from dotenv import load_dotenv
import os
import json
import streamlit as st

load_dotenv()
api_key = os.getenv("OPENAI_API_KEY")  # 환경 변수에서 API 키 가져오기

client = OpenAI(api_key=api_key)  # 오픈AI 클라이언트의 인스턴스 생성

def get_ai_response(messages, tools=None, stream=True):
    response = client.chat.completions.create(
        model="gpt-4o",  # 응답 생성에 사용할 모델을 지정합니다.
        stream=stream, # (1) 스트리밍 출력을 위해 설정
        messages=messages,  # 대화 기록을 입력으로 전달합니다.
        tools=tools,  # 사용 가능한 도구 목록을 전달합니다.
    )

    if stream: 
        for chunk in response:
            yield chunk  # 생성된 응답의 내용을 yield로 순차적으로 반환합니다.
    else:
        return response  # 생성된 응답의 내용을 반환합니다.


st.title("💬 Chatbot")   

if "messages" not in st.session_state:
    st.session_state["messages"] = [
        {"role": "system", "content": "너는 사용자를 도와주는 상담사야."},  # 초기 시스템 메시지
    ] 

for msg in st.session_state.messages:
    if msg["role"] == "assistant" or msg["role"] == "user": # assistant 혹은 user 메시지인 경우만
        st.chat_message(msg["role"]).write(msg["content"])


if user_input := st.chat_input():    # 사용자 입력 받기
    st.session_state.messages.append({"role": "user", "content": user_input})  # 사용자 메시지를 대화 기록에 추가
    st.chat_message("user").write(user_input)  # 사용자 메시지를 브라우저에서도 출력
    
    ai_response = get_ai_response(st.session_state.messages, tools=tools)
    # print(ai_message) 

    content = ''
    for chunk in ai_response:
        content_chunk = chunk.choices[0].delta.content # ② 청크 속 content 추출
        if content_chunk: # ③ 만약 content_chunk가 있다면, 
            print(content_chunk, end="")	 # ④ 터미널에 줄바꿈 없이 이어서 출력
            content += content_chunk # ⑤ content에 덧붙이기
        
    print('\n===========')
    print(content)

    ai_message = ai_response.choices[0].message
    tool_calls = ai_message.tool_calls  # AI 응답에 포함된 tool_calls를 가져옵니다.
    if tool_calls:  # tool_calls가 있는 경우
        for tool_call in tool_calls:
            tool_name = tool_call.function.name # 실행해야한다고 판단한 함수명 받기
            tool_call_id = tool_call.id         # tool_call 아이디 받기    
            arguments = json.loads(tool_call.function.arguments) # 문자열을 딕셔너리로 변환    
            
            if tool_name == "get_current_time":  
                func_result = get_current_time(timezone=arguments['timezone'])
            elif tool_name == "get_yf_stock_info":
                func_result = get_yf_stock_info(ticker=arguments['ticker'])
            elif tool_name == "get_yf_stock_history":  # get_yf_stock_history 함수 호출
                func_result = get_yf_stock_history(
                    ticker=arguments['ticker'], 
                    period=arguments['period']
                )
            elif tool_name == "get_yf_stock_recommendations":  # get_yf_stock_recommendations 함수 호출
                func_result = get_yf_stock_recommendations(
                    ticker=arguments['ticker']
                )

            st.session_state.messages.append({
                "role": "function",
                "tool_call_id": tool_call_id,
                "name": tool_name,
                "content": func_result,
            })


        st.session_state.messages.append({"role": "system", "content": "이제 주어진 결과를 바탕으로 답변할 차례다."}) 
        ai_response = get_ai_response(st.session_state.messages, tools=tools) # 다시 GPT 응답 받기
        ai_message = ai_response.choices[0].message

    st.session_state.messages.append({
        "role": "assistant",
        "content": ai_message.content
    })  # ③ AI 응답을 대화 기록에 추가합니다.

    print("AI\t: " + ai_message.content)  # AI 응답 출력
    st.chat_message("assistant").write(ai_message.content)  # 브라우저에 메시지 출력

 

 

yield 함수는 GPT가 답변을 완성하는 데 오래 걸리므로, 중간에 순차적으로 끊어서 처리할 때 사용하는 유용한 함수이다!!

 

[ 어려운 질문으로 청크 처리 방식 확인 ]

 - 청크가 있을 경우, 로직에 따라 연달아서 타이핑 하듯이 화면에 결과물을 출력해 준다!! ( 사용자 대기 시간 최소화 )

 

 


[ 참조 링크 모음 ]

더보기

1) PuMuPDF 가이드 문서 웹 페이지 : https://pypi.org/project/PyMuPDF/

2) 허깅페이스 : https://huggingface.co/

3) FFMPEG 설치 참조

  - 블로그 : https://www.lainyzine.com/ko/article/how-to-install-ffmpeg-on-mac/

  - 공식 사이트 : https://trac.ffmpeg.org/wiki/CompilationGuide/macOS

4) torch / torchvision 호환성 : https://github.com/pytorch/vision#installation