본문 바로가기
Book+ACT/Skill up

day3_openAI

by Qookoo 2025. 5. 28.
반응형
def dalle_prompt_generator(song_title, artist):
    response = client.chat.completions.create(
        model='gpt-4o',
        temperature=0.2,
        messages=[
            {
                "role": "system",
                "content": """
                    You are an AI assistant designed to generate prompts for Dalle-3. When a user provides information about a song, envision an image that represents the song's lyrics and mood.
                    Based on the image you've envisioned, generate a Dalle-3 prompt in a couple of sentences, avoiding crime-related words such as gangs or drugs.
                    If the prompt contains any violent or sexual expressions that are not suitable for a 15-year-old child to hear, present them in a more subdued manner.
                    Refrain from mentioning any famous person's name or the artist of the song.
                    """
            },
            {"role": "user", "content": f'Black or White - Michael Jackson'},
            {"role": "assistant",
             "content": "A world of contrasts and contradictions, where darkness and light collide in a never-ending struggle. The beat pulses with the rhythm of life, as voices rise up in a chorus of hope and defiance. The message is clear: no matter the color of our skin, we are all one people, united in our humanity."},
            {"role": "user", "content": f'Attention - Charlie Puth'},
            {"role": "assistant",
             "content": " A person standing alone in a crowded room, feeling disconnected and unheard. He realizes that his ex is only doing it for her own benefit and not because she truly cares about him"},
            {"role":"user", "content":f"{song_title} - {artist}"}
        ]
    )
    return response.choices[0].message.content

def generate_dalle_image(song_title, artist):
    prompt = dalle_prompt_generator(song_title, artist)
    print(prompt)

    DATA_DIR = Path.cwd() / 'make_prompt_img_result'
    DATA_DIR.mkdir(exist_ok=True)

    response = client.images.generate(
        prompt=prompt,
        n=1,
        size='512x512',
        response_format='b64_json'
    )

    file_name = DATA_DIR / f'{song_title}_{artist}.png'
    b64_data = response.data[0].b64_json
    image_data = b64decode(b64_data)

    image_file = DATA_DIR / f'{file_name}'

    with open(image_file, mode='wb') as png:
        png.write(image_data)

    return image_file

song_title = 'When I was your man'
artist = 'Bruno Mars'
generate_dalle_image(song_title, artist)

 

 

함수별 상세 분석

dalle_prompt_generator 함수

 
python
def dalle_prompt_generator(song_title, artist):

이 함수는 GPT-4o를 사용해 DALL-E 3용 프롬프트를 생성합니다.

주요 특징:

  • Temperature 0.2: 창의성보다는 일관성 있는 응답을 위한 낮은 설정
  • 시스템 프롬프트: DALL-E 3 프롬프트 생성 전문가 역할 정의
  • 안전 가이드라인:
    • 범죄 관련 단어(갱, 마약 등) 제외
    • 15세 미만에게 부적절한 폭력적/성적 표현 완화
    • 유명인 이름이나 아티스트명 언급 금지

Few-shot Learning 예시:

 
python
{"role": "user", "content": f'Black or White - Michael Jackson'},
{"role": "assistant", "content": "A world of contrasts..."}
  • 실제 예시를 통해 원하는 응답 형태를 학습시킴

generate_dalle_image 함수

 
python
def generate_dalle_image(song_title, artist):

이 함수는 생성된 프롬프트로 실제 이미지를 만들고 저장합니다.

처리 과정:

  1. 프롬프트 생성:
  2.  
  3.  
    python
    prompt = dalle_prompt_generator(song_title, artist)
  4. 디렉토리 생성:
  5.  
    python
    DATA_DIR = Path.cwd() / 'make_prompt_img_result'
    DATA_DIR.mkdir(exist_ok=True)
  6. DALL-E 이미지 생성:
  7.  
    python
    response = client.images.generate( prompt=prompt,
    n=1, # 1개 이미지 생성
    size='512x512', # 정사각형 크기
    response_format='b64_json' # Base64 JSON 형태로 응답 )
  8. 이미지 저장:
    • Base64 인코딩된 데이터를 디코딩하여 PNG 파일로 저장
  9.  
    python
    b64_data = response.data[0].b64_json
    image_data = b64decode(b64_data)

실행 예시

 
python
song_title = 'When I was your man'
artist = 'Bruno Mars'
generate_dalle_image(song_title, artist)

이 코드는 Bruno Mars의 "When I was your man"에 대한:

  1. 감정적 프롬프트 생성
  2. 해당 프롬프트 기반 이미지 생성
  3. make_prompt_img_result 폴더에 PNG 파일 저장

활용 분야

  • 음악 시각화: 노래의 감정과 분위기를 이미지로 표현
  • 앨범 커버 아트: 자동 앨범 커버 생성
  • 음악 추천 시스템: 시각적 요소를 포함한 추천
  • 창작 도구: 뮤지션들의 영감 제공

이 시스템은 AI의 언어 이해 능력과 이미지 생성 능력을 결합하여 음악을 시각적으로 해석하는 혁신적인 도구입니다.

 

 

오후 강의 :  Function Calling의 핵심 개념과 각 단계별 처리 과정학습

 

 

# 전체 프로세스:
# 사용자 질문 → GPT 분석 → 함수 호출 결정 → 함수 실행 → 결과 통합 → 최종 응답

# 1단계: 질문 분석 및 함수 호출 결정
# 2단계: 함수 실행 및 데이터 수집  
# 3단계: 결과를 자연어로 변환하여 최종 응답

 

from openai import OpenAI
import json

# OpenAI 클라이언트 초기화 (실제 코드에서는 client = OpenAI() 필요)

# 1. 사용자 메시지 정의 - 날씨 정보를 요청하는 질문
messages = [
    {
        'role':'user',
        'content': '오늘 서울 날씨 어때?'
    }
]

# 2. 함수 스키마 정의 - GPT가 호출할 수 있는 함수의 명세서
functions = [
    {
        "name": "get_current_weather",  # 함수명
        "description":"주어진 지역의 현재 날씨를 알려줍니다.",  # 함수 기능 설명
        "parameters":{  # 함수 매개변수 스키마 (JSON Schema 형식)
            "type":"object",
            "properties":{
                "location":{  # 필수 매개변수: 지역명
                    "type":"string",
                    "description":"지역, e.g, 서울, 부산, 대구, 제주도"
                },
                "unit":{  # 선택 매개변수: 온도 단위
                    "type":"string",
                    "enum":["섭씨", "화씨"]  # 허용되는 값 제한
                }
            },
            "required":["location"]  # 필수 매개변수 지정
        }
    }
]

# 3. 실제 함수 구현 - GPT가 호출할 날씨 정보 함수
def get_current_weather(location, unit="섭씨"):
    # 실제 환경에서는 날씨 API를 호출하지만, 여기서는 예시 데이터 반환
    weather_info = {
        "location": location,
        "temperature": "30",
        "unit":unit,
        "forecast":["sunny", "windy"]
    }
    # JSON 문자열로 변환하여 반환 (OpenAI API 요구사항)
    return json.dumps(weather_info)

# 4. 첫 번째 API 호출 - GPT가 함수 호출 필요성을 판단
response = client.chat.completions.create(
    model='gpt-4o',
    messages=messages,
    functions=functions,  # 사용 가능한 함수 목록 전달
    function_call="auto"  # GPT가 자동으로 함수 호출 여부 결정
)

# 5. 응답 확인 - GPT의 함수 호출 결정 및 매개변수 출력
print(response)
print(response.choices[0].message.function_call.arguments)  # 추출된 매개변수 확인

# 6. 함수 호출 처리 시작
response_message = response.choices[0].message

if response_message.function_call:  # GPT가 함수 호출을 결정했는지 확인
    # 7. 사용 가능한 함수 매핑 - 함수명과 실제 함수 연결
    available_functions = {
        "get_current_weather" : get_current_weather
    }

    # 8. 함수 호출 정보 추출
    get_current_weather = response_message.function_call.name  # 호출할 함수명
    function_to_call = available_functions[get_current_weather]  # 실제 함수 객체
    function_args = json.loads(response_message.function_call.arguments)  # 매개변수 파싱
    
    # 9. 실제 함수 실행 - 날씨 정보 조회
    function_response = function_to_call(
        location=function_args.get("location"),  # 안전한 매개변수 추출
        unit=function_args.get("unit")
    )

    # 10. 대화 기록 업데이트 - 함수 호출과 결과를 대화에 추가
    messages.append(response_message)  # GPT의 함수 호출 메시지 추가
    messages.append(
        {
            'role':'function',  # 함수 실행 결과임을 명시
            'name': 'get_current_weather',
            'content': function_response  # 함수 실행 결과 (JSON 문자열)
        }
    )

    # 11. 최종 응답 생성 - 함수 결과를 바탕으로 자연어 응답 생성
    second_response = client.chat.completions.create(
        model='gpt-4o',
        messages=messages  # 함수 호출 기록이 포함된 전체 대화
    )
    
    # 12. 최종 결과 출력 - 사용자에게 친화적인 날씨 정보 제공
    print(second_response.choices[0].message.content)

 

오후 2번째 피자 챗봇

 

 

# 1. 함수 스키마 정의 - GPT가 호출할 수 있는 피자 가격 조회 함수 명세
functions = [
    {
        "name": "pizza_price_info_func",  # 함수명
        "description": "피자의 가격을 알아봅니다.",  # 함수 기능 설명
        "parameters":{  # 함수 매개변수 스키마 (JSON Schema 형식)
            "type":"object",
            "properties":{
                "pizza_name":{  # 피자 이름 매개변수
                    "type":"string",
                    "description": "The name of the pizza, e.g. Salami"
                }
            },
            "required":["pizza_name"]  # 필수 매개변수 지정
        }
    }
]

# 2. 실제 피자 가격 조회 함수 구현
def pizza_price_info_func(pizza_name):
    # 간단한 가격 결정 로직: 치즈가 포함되면 30,000원, 아니면 20,000원
    if '치즈' in pizza_name:
        pizza_price = {
            "name":pizza_name,
            "price":"30,000원"  # 치즈 피자는 더 비쌈
        }
    else:
        pizza_price = {
            "name": pizza_name,
            "price": "20,000원"  # 일반 피자 가격
        }
    
    # JSON 문자열로 변환하여 반환 (OpenAI API 요구사항)
    return json.dumps(pizza_price)

# 3. 기본 채팅 함수 - 첫 번째 API 호출만 처리
def chat(query):
    # GPT에게 사용자 질문과 함수 정보를 전달
    response = client.chat.completions.create(
        model='gpt-4o',
        messages=[
            {
                'role':'user',
                'content':query  # 사용자 질문
            }
        ],
        functions=functions,  # 사용 가능한 함수 목록
        function_call='auto'  # GPT가 자동으로 함수 호출 여부 결정
    )
    # 첫 번째 응답의 메시지만 반환 (함수 호출 정보 포함)
    return response.choices[0].message

# 4. 테스트 실행 - 첫 번째 단계만 확인
query = '감자치즈피자 가격은 얼마야?'
message = chat(query)
print(message)  # 함수 호출 정보가 포함된 메시지 출력

# 5. 완전한 피자 가격 챗봇 함수 - 전체 프로세스 처리
def pizza_price_chatbot_func(query):
    # 5-1. 첫 번째 API 호출 - 함수 호출 필요성 판단
    message = chat(query)

    # 5-2. 함수 호출이 필요한지 확인
    if message.function_call:
        # 5-3. 함수 호출 정보 추출
        function_name = message.function_call.name  # 호출할 함수명
        pizza_name = json.loads(message.function_call.arguments).get('pizza_name')  # 피자명 추출

        # 5-4. 실제 함수 실행 - 피자 가격 조회
        function_response = pizza_price_info_func(pizza_name=pizza_name)

        # 5-5. 두 번째 API 호출 - 함수 결과를 바탕으로 최종 응답 생성
        second_response = client.chat.completions.create(
            model='gpt-4o',
            messages=[
                {
                    'role':'user',
                    'content':query  # 원래 사용자 질문
                },
                message,  # GPT의 함수 호출 메시지
                {
                    'role':'function',  # 함수 실행 결과
                    'name':function_name,
                    'content':function_response  # 피자 가격 정보 (JSON)
                }
            ]
        )
    # 5-6. 최종 자연어 응답 반환
    return second_response.choices[0].message.content

# 6. 실제 테스트 실행
result = pizza_price_chatbot_func(query)
print(result)  # "감자치즈피자는 30,000원입니다." 형태의 응답

result = pizza_price_chatbot_func("감자피자 가격은 얼마야?")
print(result)  # "감자피자는 20,000원입니다." 형태의 응답

 

핵심 처리 과정

  1. 질문 분석: GPT가 피자 이름을 추출하고 가격 조회 필요성 판단
  2. 함수 매핑: 문자열 함수명을 실제 Python 함수와 연결
  3. 데이터 변환: JSON 문자열 ↔ Python 딕셔너리 변환
  4. 컨텍스트 유지: 전체 대화 기록을 GPT에 전달하여 자연스러운 응답 생성
  5. 최종 응답: 구조화된 데이터를 사용자 친화적인 자연어로 변환

이 시스템은 단순한 규칙 기반 가격 책정을 GPT의 자연어 처리 능력과 결합한 실용적인 예시

 

결과물 처리 상세 설명

1단계: 함수 호출 감지

 
python
# message 객체의 구조: ChatCompletionMessage( content=None, # 일반 텍스트 응답 없음 role='assistant', function_call=FunctionCall( arguments='{"pizza_name": "감자치즈피자"}', # JSON 형태의 매개변수 name='pizza_price_info_func' # 호출할 함수명 ) )

2단계: 함수 실행 결과

 
python
# pizza_price_info_func("감자치즈피자") 실행 결과: '{"name": "감자치즈피자", "price": "30,000원"}' # JSON 문자열

3단계: 최종 API 호출 메시지 구조

 
python
messages = [ {'role': 'user', 'content': '감자치즈피자 가격은 얼마야?'}, # 원래 질문 ChatCompletionMessage(..., function_call=...), # GPT의 함수 호출 { 'role': 'function', 'name': 'pizza_price_info_func', 'content': '{"name": "감자치즈피자", "price": "30,000원"}' # 함수 결과 } ]

4단계: 최종 출력 예시

 
text
감자치즈피자의 가격은 30,000원입니다. 감자피자의 가격은 20,000원입니다.
 
 

 

오후 3교시 

 

 

# ===== 라이브러리 임포트 섹션 =====
from PIL.FontFile import puti16  # PIL 폰트 관련 (현재 코드에서 미사용)
from click import argument  # CLI 인터페이스 관련 (현재 코드에서 미사용)
from openai import OpenAI  # OpenAI API 클라이언트
import json  # JSON 데이터 처리

from pyparsing.diagram import template  # 파싱 다이어그램 관련 (현재 코드에서 미사용)

# 다른 모듈에서 임포트 (프로젝트 내부 모듈들)
from day1.codeUseEx2 import result  # 이전 예제 결과 (현재 코드에서 미사용)
from day3.dallePart.functionCallPart.functionCallPart_1 import function_call  # 함수 호출 관련 (현재 코드에서 미사용)

import requests  # HTTP 요청을 위한 라이브러리
from bs4 import BeautifulSoup  # HTML 파싱을 위한 라이브러리

# ===== 웹 크롤링 함수 정의 =====
def crawling_google_news(keyword: str, limit=5):
    """
    Google 뉴스에서 특정 키워드로 검색한 뉴스 기사들을 크롤링하는 함수

    Args:
        keyword (str): 검색할 키워드
        limit (int): 가져올 뉴스 개수 (기본값: 5개)

    Returns:
        list: 뉴스 정보가 담긴 딕셔너리들의 리스트
    """

    # Google 검색 기본 URL 설정
    google_search_url = 'https://www.google.com/search'

    # URL 파라미터 설정 - 뉴스 검색을 위한 쿼리 구성
    params = {
        'q': keyword,  # 검색할 키워드
        'tbm': 'nws',  # 뉴스 탭 검색 모드 (tbm=news)
        'num': limit  # 검색 결과 개수 제한
    }

    # HTTP 헤더 설정 - 봇 차단을 피하기 위해 실제 브라우저처럼 위장
    headers = {
        "User-Agent":
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36"
    }

    # Google 뉴스 검색 페이지에 HTTP GET 요청 전송
    res = requests.get(google_search_url, params=params, headers=headers)

    # 받아온 HTML 응답을 BeautifulSoup으로 파싱하여 DOM 구조 분석
    soup = BeautifulSoup(res.content, "html.parser")

    # 뉴스 결과를 저장할 빈 리스트 초기화
    news_results = []

    # Google 뉴스 검색 결과에서 각 뉴스 항목을 찾아 반복 처리
    # "div.SoaBEf"는 Google 뉴스 검색 결과의 각 뉴스 항목을 감싸는 CSS 선택자
    for el in soup.select("div.SoaBEf"):
        # 각 뉴스 항목에서 필요한 정보 추출하여 딕셔너리로 구성
        news_results.append(
            {
                # 뉴스 기사 링크 URL 추출 - 첫 번째 a 태그의 href 속성
                "link": el.find("a")["href"],

                # 뉴스 제목 추출 - "div.MBeuO" 클래스에서 텍스트 가져오기
                "title": el.select_one("div.MBeuO").get_text(),

                # 뉴스 요약/스니펫 추출 - ".GI74Re" 클래스에서 텍스트 가져오기
                "snippet": el.select_one(".GI74Re").get_text()
            }
        )

    # 추출된 뉴스 정보 리스트 반환
    return news_results

# ===== 크롤링 함수 테스트 실행 =====
# '프롬프트 엔지니어링' 키워드로 뉴스 크롤링 테스트
results = crawling_google_news('프롬프트 엔지니어링')
# 결과를 JSON 형태로 예쁘게 출력 (한글 깨짐 방지, 들여쓰기 적용)
print(json.dumps(results, ensure_ascii=False, indent=2))

# ===== GPT Function Calling 함수 정의 =====
def news_call_func(messages, temperature=0, max_tokens=2048):
    """
    GPT가 뉴스 크롤링 함수를 호출할 수 있도록 하는 Function Calling 래퍼 함수
    
    Args:
        messages: GPT와의 대화 메시지 리스트
        temperature: GPT 응답의 창의성 조절 (0=일관성, 1=창의성)
        max_tokens: 최대 응답 토큰 수 제한
    """
    
    # GPT가 호출할 수 있는 함수 스키마 정의 (JSON Schema 형식)
    functions = [
        {
            "name": "crawling_google_news",  # 실제 Python 함수명과 정확히 일치해야 함
            "description": "google news searching.",  # GPT가 함수 용도를 이해할 수 있는 설명
            "parameters": {  # 함수 매개변수 스키마 정의
                "type": "object",
                "properties": {
                    "keyword": {  # 검색 키워드 매개변수
                        "type": "string",
                        "description": "검색 키워드..."  # GPT가 키워드 추출 시 참고할 설명
                    }
                },
                "required": ["keyword"]  # 필수 매개변수 지정
            }
        }
    ]

    # OpenAI API 호출 - Function Calling 기능 활성화
    response = client.chat.completions.create(
        model='gpt-4o',  # 사용할 GPT 모델
        messages=messages,  # 대화 메시지 리스트
        functions=functions,  # 사용 가능한 함수 목록 전달
        function_call='auto',  # GPT가 자동으로 함수 호출 여부 결정
        temperature=temperature,  # 응답 일관성 조절
        max_tokens=max_tokens  # 응답 길이 제한
    )
    
    # GPT의 첫 번째 응답 반환 (함수 호출 정보 포함 가능)
    return response.choices[0].message

# ===== 1단계: GPT가 뉴스 검색 필요성 판단 및 키워드 추출 =====
results = news_call_func(
    [
        {
            'role':'user',
            'content':'한국 경제에 대한 뉴스를 요약해줘'  # 사용자의 뉴스 요약 요청
        }
    ]
)
# GPT의 함수 호출 결정 및 추출된 키워드 확인
print(results)

# ===== 2단계: GPT가 추출한 키워드로 실제 뉴스 크롤링 실행 =====
# GPT가 제안한 함수 매개변수를 JSON으로 파싱
arguments = json.loads(results.function_call.arguments)
# 추출된 키워드로 실제 Google 뉴스 크롤링 실행
results = crawling_google_news(arguments['keyword'])
# 크롤링 결과를 JSON 형태로 예쁘게 출력
print(json.dumps(results, ensure_ascii=False, indent=2))

# ===== 3단계: 크롤링된 뉴스 데이터를 GPT에게 전달하여 종합 요약 생성 =====
result = news_call_func([
    {
        'role':'user',
        'content': json.dumps(results, ensure_ascii=False)  # 크롤링된 뉴스 데이터를 GPT에게 전달
    },
    {
        'role':'user',
        'content': '한국 경제에 대한 뉴스를 요약해줘. 뉴스를 종합하여 제목과 본문이 있는 새로운 글로 작성해줘'
        # 여러 뉴스를 종합하여 하나의 완성된 기사로 재구성 요청
    }
])

# ===== 최종 결과 출력 =====
# GPT가 생성한 종합 뉴스 요약 글 출력
print(result.content)

 

코드 동작 과정 요약

1단계: 요청 준비

  • Google 뉴스 검색 URL 구성
  • 검색 파라미터 설정 (키워드, 뉴스 탭, 결과 개수)
  • 브라우저 헤더로 위장하여 차단 방지

2단계: 웹 요청 및 응답

  • requests.get()으로 Google 뉴스 페이지 요청
  • HTML 응답 데이터 수신

3단계: HTML 파싱

  • BeautifulSoup으로 HTML 구조 분석
  • CSS 선택자를 사용하여 뉴스 항목들 식별

4단계: 데이터 추출

  • 각 뉴스 항목에서 링크, 제목, 요약 추출
  • 구조화된 딕셔너리 형태로 데이터 정리

5단계: 결과 반환

  • 모든 뉴스 정보를 리스트로 반환

이 함수는 Google 뉴스의 HTML 구조를 분석하여 실시간 뉴스 데이터를 자동으로 수집하는 웹 크롤링 도구입니다.

 

 

실행 단계별 코드 순서 설명

0단계: 초기 설정 및 함수 정의

# 라이브러리 임포트
from openai import OpenAI
import json
import requests
from bs4 import BeautifulSoup

# 크롤링 함수 정의
def crawling_google_news(keyword: str, limit=5):
    # ... 함수 구현 ...

# Function Calling 래퍼 함수 정의 
def news_call_func(messages, temperature=0, max_tokens=2048):
    # ... 함수 구현 ...

1단계: 크롤링 함수 테스트

# 직접 크롤링 테스트 실행
results = crawling_google_news('프롬프트 엔지니어링')
print(json.dumps(results, ensure_ascii=False, indent=2))

목적: 크롤링 함수가 정상 작동하는지 확인

2단계: GPT 키워드 추출

# GPT에게 뉴스 요약 요청 → 키워드 추출
results = news_call_func(
    [
        {
            'role':'user',
            'content':'한국 경제에 대한 뉴스를 요약해줘'
        }
    ]
)
print(results)

결과: GPT가 "한국 경제" 키워드를 추출하고 함수 호출 결정

 

3단계: 실제 뉴스 크롤링

# GPT가 추출한 키워드로 뉴스 크롤링 실행
arguments = json.loads(results.function_call.arguments)
results = crawling_google_news(arguments['keyword'])
print(json.dumps(results, ensure_ascii=False, indent=2))

결과: Google 뉴스에서 "한국 경제" 관련 뉴스 데이터 수집

 

4단계: 종합 요약 생성

# 크롤링된 데이터를 GPT에게 전달하여 종합 요약 생성
result = news_call_func([
    {
        'role':'user',
        'content': json.dumps(results, ensure_ascii=False)
    },
    {
        'role':'user',
        'content': '한국 경제에 대한 뉴스를 요약해줘. 뉴스를 종합하여 제목과 본문이 있는 새로운 글로 작성해줘'
    }
])
print(result.content)

결과: 여러 뉴스를 종합한 완성된 요약 기사

 

데이터 흐름 순서

순서 입력 처리 출력
1 '프롬프트 엔지니어링' 직접 크롤링 뉴스 리스트 (테스트)
2 '한국 경제에 대한 뉴스를 요약해줘' GPT 분석 함수 호출 + 키워드
3 {"keyword": "한국 경제"} 웹 크롤링 실제 뉴스 데이터
4 뉴스 데이터 + 요약 요청 GPT 종합 완성된 요약 글

 

핵심 실행 흐름

사용자 요청
   
GPT 키워드 추출
   
Google 뉴스 크롤링
   
GPT 종합 요약
   
최종 결과 출력

이 순서를 통해 사용자의 자연어 요청이 실시간 뉴스 데이터 수집과 AI 요약으로 이어지는 완전 자동화된 파이프라인이 구현됩니다

반응형