본문 바로가기
AI System/OpenAI API와 바이브 코딩으로 배우는 AI 서비스 개발

d01 - 01. text_generation - 03. 다중 턴 문맥 비교_v2

by Toddler_AD 2026. 6. 1.

03. 다중 턴 문맥 비교 v2

이 노트북은 2턴 대화를 구현합니다. Responses API는 previous_response_id를 사용하고, Chat Completions API는 이전 메시지 전체를 messages 배열에 다시 넣습니다.

# Colab 또는 로컬 노트북 실행 환경을 구분하기 위해 sys를 가져옵니다.
import sys
# 패키지 설치 명령을 현재 Python 커널에서 실행하기 위해 subprocess를 가져옵니다.
import subprocess
# 패키지 설치 여부를 확인하기 위해 importlib.util을 가져옵니다.
import importlib.util
# 환경 변수에서 API 키를 읽고 설정하기 위해 os를 가져옵니다.
import os
# API 키를 화면에 노출하지 않고 입력받기 위해 getpass를 가져옵니다.
import getpass
# .env 파일 위치를 다루기 위해 pathlib의 Path를 가져옵니다.
from pathlib import Path

# google.colab 모듈이 있으면 현재 런타임이 Google Colab이라고 판단합니다.
IN_COLAB = "google.colab" in sys.modules
# 노트북에서 사용하는 import 이름과 pip 패키지 이름을 짝지어 둡니다.
REQUIRED_PACKAGES = {
    "openai": "openai>=2.26.0",
    "dotenv": "python-dotenv>=1.2.2",
    "pydantic": "pydantic>=2.11.0",
    "PIL": "pillow>=12.1.1",
    "requests": "requests>=2.32.5",
    "numpy": "numpy>=2.3.3",
    "websockets": "websockets>=15.0.1",
    "websocket": "websocket-client>=1.8.0",
    "nest_asyncio": "nest-asyncio>=1.6.0",
}

# 현재 커널에서 import할 수 없는 패키지만 설치하는 함수입니다.
def ensure_package(import_name: str, package_name: str) -> None:
    # 이미 import 가능한 패키지는 설치를 건너뜁니다.
    if importlib.util.find_spec(import_name) is not None:
        # 설치가 필요 없음을 호출자에게 조용히 알리고 돌아갑니다.
        return
    # Colab에서는 현재 노트북 커널의 Python에 패키지를 설치해야 합니다.
    subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", package_name])

# Colab 기본 런타임에는 일부 OpenAI 실습 패키지가 없을 수 있으므로 먼저 준비합니다.
if IN_COLAB:
    # 실습 전체에서 필요한 패키지를 하나씩 확인하고 부족한 것만 설치합니다.
    for import_name, package_name in REQUIRED_PACKAGES.items():
        # 누락된 패키지를 현재 Colab 런타임에 설치합니다.
        ensure_package(import_name, package_name)

# 패키지 설치 이후 .env 로딩 기능을 사용할 수 있도록 load_dotenv를 가져옵니다.
from dotenv import load_dotenv

# 현재 작업 폴더의 .env 파일이 있으면 로컬 실행처럼 환경 변수로 올립니다.
load_dotenv(Path.cwd() / ".env")
# OPENAI_API_KEY가 이미 있으면 그대로 사용하고, 없으면 Colab Secrets 또는 입력으로 보완합니다.
if not os.getenv("OPENAI_API_KEY"):
    # Colab Secrets에서 OPENAI_API_KEY를 읽어 볼 변수를 준비합니다.
    secret_key = None
    # Colab이 아닌 로컬 환경에서는 google.colab import가 실패할 수 있으므로 예외를 허용합니다.
    try:
        # Colab Secrets의 userdata API를 가져옵니다.
        from google.colab import userdata
        # Secrets에 저장된 OPENAI_API_KEY 값을 읽습니다.
        secret_key = userdata.get("OPENAI_API_KEY")
    except Exception:
        # Colab Secrets를 사용할 수 없으면 이후 수동 입력 단계로 넘어갑니다.
        secret_key = None
    # Secrets에서 키를 찾았다면 현재 런타임 환경 변수로 설정합니다.
    if secret_key:
        # OpenAI SDK가 자동으로 읽을 수 있도록 표준 환경 변수 이름에 저장합니다.
        os.environ["OPENAI_API_KEY"] = secret_key
    else:
        # 키가 없으면 노트북 실행자가 직접 입력하도록 요청합니다.
        entered_key = getpass.getpass("OPENAI_API_KEY를 입력하세요: ").strip()
        # 빈 문자열이 아닌 값을 입력한 경우에만 환경 변수로 등록합니다.
        if entered_key:
            # OpenAI SDK가 자동으로 읽을 수 있도록 표준 환경 변수 이름에 저장합니다.
            os.environ["OPENAI_API_KEY"] = entered_key

# API 키가 끝까지 없으면 다음 OpenAI API 호출이 실패하므로 명확한 오류를 냅니다.
if not os.getenv("OPENAI_API_KEY"):
    # Colab에서는 Secrets 또는 입력, 로컬에서는 .env 또는 환경 변수를 설정해야 함을 알려 줍니다.
    raise RuntimeError("OPENAI_API_KEY가 없습니다. Colab Secrets, 수동 입력, 또는 .env 파일로 설정해 주세요.")

# 이후 셀에서 Colab 여부를 참고할 수 있도록 간단히 출력합니다.
print(f"개발환경: {'Colab' if IN_COLAB else '로컬'}")
# API 키를 직접 출력하지 않고 준비 완료 여부만 알려 줍니다.
print("OPENAI_API_KEY 준비 완료")

 

1단계. 최소 실행 환경 준비

이 단계에서는 API 호출에 필요한 기본 준비를 합니다. import, .env 로드, OpenAI() 클라이언트 생성, output 폴더 생성을 차례대로 확인합니다.

아래 셀에서 특히 client = OpenAI() 줄을 확인하세요. 이후 모든 API 호출은 이 client 객체에서 시작됩니다.

# 운영체제 환경 변수와 .env 값을 읽기 위해 os를 사용합니다.
import os
# JSON 메타데이터를 파일로 저장하기 위해 json을 사용합니다.
import json
# 실행 시간을 비교하는 예제에서 사용할 시간 측정 모듈입니다.
import time
# 노트북 실행 위치와 output 폴더 경로를 안전하게 다루기 위한 Path입니다.
from pathlib import Path

# .env 파일에 저장된 OPENAI_API_KEY를 현재 Python 환경으로 불러옵니다.
from dotenv import load_dotenv
# OpenAI API를 직접 호출하기 위한 공식 SDK 클라이언트입니다.
from openai import OpenAI

# 현재 노트북이 워크스페이스 루트에서 실행되는지, 01.text_generation 폴더에서 실행되는지 확인합니다.
CURRENT_DIR = Path.cwd().resolve()
# 워크스페이스 루트에서 실행 중이면 01.text_generation 폴더를 실습 루트로 사용합니다.
if (CURRENT_DIR / "01.text_generation").exists():
    TEXT_GENERATION_DIR = CURRENT_DIR / "01.text_generation"
# 이미 01.text_generation 폴더 안에서 실행 중이면 현재 폴더를 실습 루트로 사용합니다.
else:
    TEXT_GENERATION_DIR = CURRENT_DIR

# 워크스페이스 루트는 01.text_generation 폴더의 부모 폴더입니다.
WORKSPACE_ROOT = TEXT_GENERATION_DIR.parent
# 실행 결과를 저장할 output 폴더 경로입니다.
OUTPUT_DIR = TEXT_GENERATION_DIR / "output"
# output 폴더가 없으면 새로 만들고, 이미 있으면 그대로 둡니다.
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

# 워크스페이스 루트의 .env 파일을 읽습니다.
load_dotenv(WORKSPACE_ROOT / ".env")

# OpenAI API 키가 설정되어 있는지 먼저 확인합니다.
if not os.getenv("OPENAI_API_KEY"):
    raise RuntimeError("OPENAI_API_KEY가 없습니다. 워크스페이스 루트의 .env 파일을 확인해 주세요.")

# OpenAI API를 호출할 클라이언트를 생성합니다. 이 줄이 모든 API 호출의 출발점입니다.
client = OpenAI()

# 이후 셀에서 저장 경로를 확인할 수 있도록 출력합니다.
print(f"실습 폴더: {TEXT_GENERATION_DIR}")
print(f"결과 저장 폴더: {OUTPUT_DIR}")
실습 폴더: C:\Users\ai4nu\2026-ai-tech\AS.1.1_0601\01.text_generation
결과 저장 폴더: C:\Users\ai4nu\2026-ai-tech\AS.1.1_0601\01.text_generation\output
 

2단계. 모델과 2턴 프롬프트 준비하기

# Responses API 요청 모델입니다.
RESPONSES_MODEL = os.getenv("OPENAI_RESPONSES_MODEL", "gpt-4.1-mini")
# Chat Completions API 요청 모델입니다.
CHAT_MODEL = os.getenv("OPENAI_CHAT_MODEL", "gpt-4.1-mini")

# 두 API에 공통으로 사용할 상위 지시문입니다.
SYSTEM_INSTRUCTIONS = "당신은 실무 코치입니다. 초급자도 이해할 수 있는 한국어로 간단히 설명하세요."
# 첫 번째 사용자 요청입니다.
TURN1_PROMPT = "신입사원 첫 주 적응 체크리스트를 4개 항목으로 작성해 주세요."
# 두 번째 사용자 요청입니다.
TURN2_PROMPT = "방금 답변을 표로 바꾸고 각 항목에 예상 소요 시간을 추가해 주세요."

print(TURN1_PROMPT)
print(TURN2_PROMPT)
신입사원 첫 주 적응 체크리스트를 4개 항목으로 작성해 주세요.
방금 답변을 표로 바꾸고 각 항목에 예상 소요 시간을 추가해 주세요.
 

3단계. Responses API 1턴과 2턴 직접 호출하기

1턴 응답의 id를 2턴 요청의 previous_response_id에 넣는 부분을 직접 확인하세요

# Responses API 1턴 호출 시작 시간입니다.
responses_turn1_started = time.perf_counter()
# Responses API 1턴을 직접 호출합니다.
responses_turn1 = client.responses.create(
    # 사용할 모델입니다.
    model=RESPONSES_MODEL,
    # 모델의 역할 지시문입니다.
    instructions=SYSTEM_INSTRUCTIONS,
    # 첫 번째 사용자 요청입니다.
    input=TURN1_PROMPT,
    # 최대 출력 토큰 수입니다.
    max_output_tokens=240,
)
# 1턴 실행 시간을 계산합니다.
responses_turn1_elapsed = time.perf_counter() - responses_turn1_started
# 1턴 텍스트를 읽습니다.
responses_turn1_text = responses_turn1.output_text

# Responses API 2턴 호출 시작 시간입니다.
responses_turn2_started = time.perf_counter()
# Responses API 2턴을 직접 호출합니다.
responses_turn2 = client.responses.create(
    # 사용할 모델입니다.
    model=RESPONSES_MODEL,
    # 두 번째 사용자 요청만 input에 넣습니다.
    input=TURN2_PROMPT,
    # 1턴 응답 ID를 전달해 문맥을 연결합니다.
    previous_response_id=responses_turn1.id,
    # 표 형식 답변을 위해 출력 토큰을 조금 더 줍니다.
    max_output_tokens=320,
)
# 2턴 실행 시간을 계산합니다.
responses_turn2_elapsed = time.perf_counter() - responses_turn2_started
# 2턴 텍스트를 읽습니다.
responses_turn2_text = responses_turn2.output_text

print("[Responses 1턴]")
print(responses_turn1_text)
print()
print("[Responses 2턴]")
print(responses_turn2_text)
print(f"1턴 response.id: {responses_turn1.id}")
print(f"2턴 모델: {responses_turn2.model}")
[Responses 1턴]
신입사원 첫 주 적응 체크리스트 4가지입니다:

1. **회사와 팀 소개 받기**  
   회사 규칙과 팀원들을 소개받아요.

2. **업무 도구 설정하기**  
   컴퓨터, 이메일, 업무 프로그램을 사용할 수 있게 설정해요.

3. **기본 업무 배우기**  
   맡은 업무의 기본 절차와 방법을 익혀요.

4. **질문하고 소통하기**  
   모르는 점이 있으면 팀장이나 동료에게 물어봐요.  

이렇게 하면 첫 주에 잘 적응할 수 있어요!

[Responses 2턴]
네! 신입사원 첫 주 적응 체크리스트를 표로 정리하고, 각 항목별 예상 소요 시간을 추가해 드리겠습니다.

| 항목               | 내용                              | 예상 소요 시간       |
|------------------|---------------------------------|------------------|
| 1. 회사와 팀 소개 받기    | 회사 규칙과 팀원들을 소개받기             | 2~3시간           |
| 2. 업무 도구 설정하기     | 컴퓨터, 이메일, 업무 프로그램 설정 완료       | 3~4시간           |
| 3. 기본 업무 배우기       | 맡은 업무의 기본 절차와 방법 익히기          | 1~2일 (여러 시간)    |
| 4. 질문하고 소통하기      | 궁금한 점을 팀장이나 동료에게 질문하고 소통하기  | 지속적 (필요 시 30분~1시간) |

필요에 따라 소요 시간은 다를 수 있으며, 참고용으로 활용해 주세요!
1턴 response.id: resp_01cff88b2a2d54c0006a1c47619ff881989a44ea2e1ce56a05
2턴 모델: gpt-4.1-mini-2025-04-14
 

4단계. Chat Completions API 1턴과 2턴 직접 호출하기

Chat 방식에서는 2턴 요청에 이전 사용자 요청과 assistant 응답을 직접 포함합니다.

# Chat 1턴 messages 배열입니다.
chat_messages_turn1 = [
    # system 메시지입니다.
    {"role": "system", "content": SYSTEM_INSTRUCTIONS},
    # 첫 번째 user 메시지입니다.
    {"role": "user", "content": TURN1_PROMPT},
]

# Chat API 1턴 호출 시작 시간입니다.
chat_turn1_started = time.perf_counter()
# Chat Completions API 1턴을 직접 호출합니다.
chat_turn1 = client.chat.completions.create(
    # 사용할 Chat 모델입니다.
    model=CHAT_MODEL,
    # 1턴 messages 배열입니다.
    messages=chat_messages_turn1,
)
# 1턴 실행 시간을 계산합니다.
chat_turn1_elapsed = time.perf_counter() - chat_turn1_started
# 1턴 assistant 응답 텍스트를 읽습니다.
chat_turn1_text = chat_turn1.choices[0].message.content

# Chat 2턴 messages 배열입니다. 이전 대화 전체를 직접 다시 넣습니다.
chat_messages_turn2 = [
    # system 메시지입니다.
    {"role": "system", "content": SYSTEM_INSTRUCTIONS},
    # 1턴 user 메시지입니다.
    {"role": "user", "content": TURN1_PROMPT},
    # 1턴 assistant 응답입니다.
    {"role": "assistant", "content": chat_turn1_text},
    # 2턴 user 메시지입니다.
    {"role": "user", "content": TURN2_PROMPT},
]

# Chat API 2턴 호출 시작 시간입니다.
chat_turn2_started = time.perf_counter()
# Chat Completions API 2턴을 직접 호출합니다.
chat_turn2 = client.chat.completions.create(
    # 사용할 Chat 모델입니다.
    model=CHAT_MODEL,
    # 이전 대화를 포함한 2턴 messages 배열입니다.
    messages=chat_messages_turn2,
)
# 2턴 실행 시간을 계산합니다.
chat_turn2_elapsed = time.perf_counter() - chat_turn2_started
# 2턴 assistant 응답 텍스트를 읽습니다.
chat_turn2_text = chat_turn2.choices[0].message.content

print("[Chat 1턴]")
print(chat_turn1_text)
print()
print("[Chat 2턴]")
print(chat_turn2_text)
print(f"Chat 모델: {chat_turn2.model}")
[Chat 1턴]
신입사원 첫 주 적응 체크리스트 4가지입니다:

1. 회사와 팀 소개 받기  
- 회사 문화, 조직 구조, 팀원 이름을 알아둡니다.

2. 업무 도구 익히기  
- 컴퓨터, 이메일, 사내 시스템 사용법을 배웁니다.

3. 업무 내용 파악하기  
- 맡은 업무와 목표를 확인하고 기본 절차를 이해합니다.

4. 규칙과 복장 확인하기  
- 회사 생활 규칙, 출퇴근 시간, 복장 규정을 숙지합니다.

[Chat 2턴]
| 항목             | 내용                                   | 예상 소요 시간  |
|----------------|--------------------------------------|--------------|
| 1. 회사와 팀 소개 받기 | 회사 문화, 조직 구조, 팀원 이름 알아보기             | 2시간          |
| 2. 업무 도구 익히기    | 컴퓨터, 이메일, 사내 시스템 사용법 익히기             | 3시간          |
| 3. 업무 내용 파악하기  | 맡은 업무와 목표 확인, 기본 절차 이해                  | 3시간          |
| 4. 규칙과 복장 확인하기 | 회사 생활 규칙, 출퇴근 시간, 복장 규정 숙지             | 1시간          |
Chat 모델: gpt-4.1-mini-2025-04-14
 

5단계. 문맥 전달 방식 비교 저장하기

# Responses 2턴은 새 프롬프트와 response id 정도만 전달합니다.
responses_turn2_payload_chars = len(TURN2_PROMPT) + len(responses_turn1.id)
# Chat 2턴은 이전 messages 전체를 다시 전달합니다.
chat_turn2_payload_chars = sum(len(message["content"]) for message in chat_messages_turn2)

# 저장 경로를 정합니다.
responses_path = OUTPUT_DIR / "03_responses_turn2_v2_output.txt"
chat_path = OUTPUT_DIR / "03_chat_turn2_v2_output.txt"
metadata_path = OUTPUT_DIR / "03_multiturn_context_v2_meta.json"

# 결과 텍스트를 저장합니다.
responses_path.write_text(responses_turn2_text + "\\n", encoding="utf-8")
chat_path.write_text(chat_turn2_text + "\\n", encoding="utf-8")

# 비교 정보를 메타데이터로 저장합니다.
metadata = {
    "example": "03.multiturn_context_v2",
    "responses_turn1_id": responses_turn1.id,
    "responses_model": responses_turn2.model,
    "chat_model": chat_turn2.model,
    "responses_turn2_payload_chars": responses_turn2_payload_chars,
    "chat_turn2_payload_chars": chat_turn2_payload_chars,
    "responses_elapsed_seconds": {"turn1": round(responses_turn1_elapsed, 4), "turn2": round(responses_turn2_elapsed, 4)},
    "chat_elapsed_seconds": {"turn1": round(chat_turn1_elapsed, 4), "turn2": round(chat_turn2_elapsed, 4)},
    "responses_output_path": str(responses_path),
    "chat_output_path": str(chat_path),
}
metadata_path.write_text(json.dumps(metadata, ensure_ascii=False, indent=2) + "\\n", encoding="utf-8")

print(json.dumps(metadata, ensure_ascii=False, indent=2))
{
  "example": "03.multiturn_context_v2",
  "responses_turn1_id": "resp_01cff88b2a2d54c0006a1c47619ff881989a44ea2e1ce56a05",
  "responses_model": "gpt-4.1-mini-2025-04-14",
  "chat_model": "gpt-4.1-mini-2025-04-14",
  "responses_turn2_payload_chars": 93,
  "chat_turn2_payload_chars": 336,
  "responses_elapsed_seconds": {
    "turn1": 4.1391,
    "turn2": 3.6398
  },
  "chat_elapsed_seconds": {
    "turn1": 1.8699,
    "turn2": 2.4216
  },
  "responses_output_path": "C:\\Users\\ai4nu\\2026-ai-tech\\AS.1.1_0601\\01.text_generation\\output\\03_responses_turn2_v2_output.txt",
  "chat_output_path": "C:\\Users\\ai4nu\\2026-ai-tech\\AS.1.1_0601\\01.text_generation\\output\\03_chat_turn2_v2_output.txt"
}