LLM 엔지니어링6장 · RAG 의 원리와 로컬 모델로 옮기기9 / 23← 이전목차다음 →MetaContents

6장. RAG 의 원리와 로컬 모델로 옮기기

외부 API 로 만드는 RAG

집에 주방이 없으면 음식을 배달시킵니다. 메뉴를 고르고 주소를 알려 주면 바깥 식당이 만들어 보내 줍니다. 편하지만 주문할 때마다 돈이 들고, 내 주소와 주문 내용이 식당에 전해집니다. 외부 API 로 LLM 을 쓰는 것이 이 배달입니다.

내 PC 의 파이썬이 인터넷을 거쳐 API 서버의 큰 모델에 요청을 보내고 답을 받는 흐름도

1번 내 PC 에서 검색과 프롬프트 만들기를 하고, 2번 API 키와 함께 요청을 인터넷으로 보냅니다. 3번 공급사의 서버가 큰 모델로 답을 만들고, 4번 그 답이 돌아옵니다. 2번에서 질문과 문서 조각이 모두 내 PC 밖으로 나간다는 것을 기억해 둡니다. 여기서 배달 식당이 API 서버, 주문서가 요청입니다.

외부 API 의 좋은 점과 아쉬운 점

견줄 것내용
좋은 점내 PC 에 GPU 가 없어도 큰 모델을 쓴다. 설치가 간단하다
비용보낸 토큰과 받은 토큰 수만큼 돈을 낸다. 요금은 공급사의 요금표를 본다
개인정보질문과 문서가 공급사 서버로 간다. 사내 규정상 보내면 안 되는 문서가 있다
끊김인터넷이나 공급사 서버가 멈추면 같이 멈춘다
바뀜공급사가 모델을 바꾸거나 없애면 같은 질문에 다른 답이 나올 수 있다

코드의 생김새

아래는 OpenAI(ChatGPT 를 만든 회사)의 파이썬 라이브러리 openai 로 외부 API 를 부르는 코드입니다. 실습 2의 조항 나누기, 실습 3의 키워드 검색, 「근거를 붙인 프롬프트」의 build_messages 를 한 파일에 모았습니다. 그래서 이 파일 하나만 복사해 실행해도 됩니다.

API 키는 코드에 적지 않고, PowerShell 에서 환경 변수로 넣어 둡니다. 창을 닫아도 남게 하려면 아래 ".env 파일에 키를 두고 쓰기"처럼 파일에 적어 둘 수도 있습니다. 코드의 load_dotenv() 는 .env 파일이 없으면 아무 일도 하지 않으므로, 어느 방법으로 넣어도 같은 코드가 돌아갑니다.

PowerShell
pip install openai python-dotenv scikit-learn    # API 호출, .env 읽기, 키워드 검색에 쓰는 패키지
$env:OPENAI_API_KEY = "발급받은_인증키"           # 이 창에서만 살아 있는 환경 변수로 키를 넣는다

진짜 키가 있어야 돌아가므로 교안을 만든 PC 에서는 OpenAI 서버로 보내지 못했습니다. 이 PC 에서 실행하지 않은 안내 코드입니다. 다만 같은 코드를 주소만 Ollama 로 바꿔(아래 "Ollama 로 미리 시험하기") 끝까지 돌아가는 것은 확인했습니다. shop_policy.txt 는 실습 자료의 data 폴더에 있습니다. 이 파일과 같은 폴더에 복사하거나 DATA_DIR 을 그 폴더로 바꿉니다.

출력
# 6장 「외부 API 로 만드는 RAG」: 검색 + 프롬프트 + 외부 API 를 한 파일에
# 필요한 것: pip install openai python-dotenv scikit-learn
#            이 파일과 같은 폴더(또는 DATA_DIR)에 shop_policy.txt
import os
from pathlib import Path

from dotenv import load_dotenv
from openai import OpenAI
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

# shop_policy.txt 가 있는 폴더. 교안의 data 폴더에 있다면 그 경로로 바꾼다
DATA_DIR = Path(__file__).parent

# 1. 문서를 조항 단위로 나눈다 (실습 2와 같다)
text = (DATA_DIR / "shop_policy.txt").read_text(encoding="utf-8")
chunks = [c.strip() for c in text.split("\n\n")[1:]]       # 맨 앞의 제목 줄은 뺀다

# 2. 키워드 검색을 만든다 (실습 3과 같다)
vec = TfidfVectorizer(analyzer="char_wb", ngram_range=(2, 3))
X = vec.fit_transform(chunks)

def search(question, k=3):
    """질문과 비슷한 조항 k 개의 (번호, 점수)를 돌려준다"""
    scores = cosine_similarity(vec.transform([question]), X)[0]
    top = scores.argsort()[::-1][:k]
    return [(int(i), round(float(scores[i]), 3)) for i in top]

# 3. 근거를 붙인 프롬프트를 만든다 (「근거를 붙인 프롬프트」 절과 같다)
SYSTEM = ("당신은 쇼핑몰 상담원입니다. 아래 [문서]에 적힌 내용만 근거로 짧게 답하세요. "
          "문서에 답이 없으면 '문서에서 찾지 못했습니다.'라고만 답하세요.")

def build_messages(question, found):
    context = "\n".join(found)
    user = f"[문서]\n{context}\n\n[질문]\n{question}"
    return [{"role": "system", "content": SYSTEM}, {"role": "user", "content": user}]

# 4. 외부 API 에 묻는다
load_dotenv()                                              # .env 가 있으면 OPENAI_API_KEY 를 읽는다
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])      # 키는 환경 변수에서. 코드에 적지 않는다
MODEL = os.environ.get("OPENAI_MODEL", "gpt-6-luna")       # 쓸 모델 이름. 환경 변수로 바꿀 수 있다

question = "휴대폰으로 결제하면 한도가 있나요?"
found = [chunks[i] for i, _ in search(question)]           # 질문과 가까운 조항 셋
print("찾은 조항:", [c[:c.index(")") + 1] for c in found])

resp = client.chat.completions.create(
    model=MODEL,                                           # 어느 모델에게 물을지
    messages=build_messages(question, found),              # 시스템 지시 + [문서] + [질문]
    # temperature 는 주지 않는다. GPT 추론 모델은 0 을 받지 않는다(아래 설명)
)
print(resp.choices[0].message.content)                     # 모델의 답
print(resp.usage)                                          # 보낸 토큰과 받은 토큰 수

아래는 예상 출력(실행하지 않고 유추한 예시)입니다. 찾은 조항 줄은 이 PC 에서 실제로 나온 것이고, 답의 문장과 토큰 수는 모델에 따라 다릅니다.

출력
찾은 조항: ['제14조(결제 수단)', '제15조(무이자 할부)', '제3조(출고 마감)']
휴대폰 결제는 한 달에 50만원까지 할 수 있습니다.
CompletionUsage(completion_tokens=20, prompt_tokens=250, total_tokens=270, …)
코드쉬운 뜻
DATA_DIR = Path(__file__).parent__file__ 은 지금 실행하는 파일의 경로다. 그 파일이 있는 폴더에서 shop_policy.txt 를 찾는다
OpenAI(api_key=...)OpenAI 서버에 요청을 보낼 손님 창구(client)를 만든다. base_url 을 주지 않으면 OpenAI 의 주소로 간다
os.environ.get("OPENAI_MODEL", "gpt-6-luna")환경 변수 OPENAI_MODEL 이 있으면 그 이름을, 없으면 gpt-6-luna 를 쓴다. 코드를 고치지 않고 모델을 바꿀 수 있다
client.chat.completions.create(...)메시지 목록을 보내고 답을 받는다. chat completion 은 "대화를 이어 써 달라"는 요청이라는 뜻이다
resp.choices[0].message.content돌아온 답 후보 가운데 첫째의 글. 보통 후보는 하나다
resp.usage이번 요청에서 보낸 토큰(prompt_tokens)과 받은 토큰(completion_tokens)의 수. 요금이 이 수로 정해진다

프롬프트에 붙인 문서 조각도 보낸 토큰에 들어갑니다. 조각을 많이 붙일수록 비용이 늡니다. search(question) 의 k 를 3에서 10으로 늘리면 보낸 토큰도 그만큼 커집니다.

temperature 를 넣지 않는 까닭

실습 5, 6의 Ollama 코드는 temperature=0 을 주어 다시 물어도 같은 답이 나오게 했습니다. 그런데 같은 줄을 gpt-6-luna 에 보내면 아래 오류가 납니다. 수강생이 이 코드를 진짜 키로 실행해 받은 오류입니다.

출력
openai.BadRequestError: Error code: 400 - Unsupported value: 'temperature' does not support 0 with this model. Only the default (1) value is supported.

"이 모델은 temperature 를 0 으로 줄 수 없고, 기본값 1 만 쓸 수 있다"는 뜻입니다. 요즘 GPT 모델은 답하기 전에 속으로 먼저 따져 보는 추론(reasoning) 모델이라, 온도를 사용자가 바꾸지 못하게 막아 두었습니다. 그래서 완성본에서는 temperature 를 아예 넣지 않았습니다.

견줄 것Ollama 의 Qwen(실습 5, 6)GPT 추론 모델(gpt-6-luna 등)
temperature=0된다. 가장 그럴듯한 낱말만 고른다400 오류. 기본값 1 만 된다
다시 물으면거의 같은 답이 나온다문장이 조금씩 달라질 수 있다
답의 내용붙인 조항에서 온다붙인 조항에서 오므로 크게 흔들리지 않는다

400 은 HTTP 상태 코드로 "요청이 잘못됐다(Bad Request)"는 뜻입니다. 키가 틀렸을 때는 401, 모델 이름이 없을 때는 404 가 옵니다. 400 이 왔다는 것은 키와 모델 이름은 맞았고 인자 하나가 문제였다는 신호입니다.

모델 이름 고르기

model= 에는 공급사가 정한 모델 이름(model ID)을 글자 그대로 적습니다. GPT 는 Generative Pre-trained Transformer(미리 학습한, 글을 만들어 내는 트랜스포머)의 머리글자로, OpenAI 모델 이름의 앞머리입니다. 아래는 2026년 11월에 OpenAI 의 공식 모델 문서에서 확인한 이름입니다. 넷 모두 이 코드가 쓰는 chat.completions.create 로 부를 수 있습니다.

model= 에 적는 이름공식 문서의 설명이 예시에
gpt-6-luna가장 효율적인 모델. 많은 요청을 빠르고 싸게 처리하는 단순한 일에 맞는다권장. 근거가 프롬프트에 붙어 있어 큰 모델이 아니어도 된다
gpt-6.1-terra빠르고 효율적인 중간 모델. 자연스러운 글쓰기에 강하다답의 문장을 더 다듬고 싶을 때
gpt-6.1-sol가장 큰 모델에 가까운 성능을 더 낮은 값으로이 예시에는 과하다
gpt-6-astra가장 뛰어나고 가장 비싼 모델. 어렵고 복잡한 일에 쓴다이 예시에는 과하다

RAG 는 답의 근거를 검색으로 찾아 프롬프트에 붙여 주므로, 모델이 스스로 많이 알 필요가 적습니다. 그래서 작고 싼 모델부터 써 보고, 답이 모자랄 때만 큰 모델로 올립니다. luna, terra, sol, astra 는 라틴어로 달, 땅, 해, 별이라는 낱말입니다. 이름만 보고 크기를 짐작하지 말고 공식 문서의 설명으로 고릅니다.

모델 이름과 값은 자주 바뀝니다. 위 표의 이름이 없어졌거나 내 계정에서 쓸 수 없으면 NotFoundError(model_not_found)가 납니다. 그럴 때는 내 키로 부를 수 있는 모델 목록을 직접 확인합니다. 이것도 진짜 키가 필요해 실행하지 않은 안내 코드입니다.

출력
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()
client = OpenAI()                                   # api_key 를 주지 않으면 OPENAI_API_KEY 를 스스로 찾는다
print(sorted(m.id for m in client.models.list()))   # 내 키로 쓸 수 있는 모델 이름 목록

목록에서 고른 이름은 코드를 고치지 않고 환경 변수로 바꿔 넣을 수 있습니다.

PowerShell
$env:OPENAI_MODEL = "gpt-6.1-terra"    # 다음 실행부터 이 모델에게 묻는다(이 창에서만)
알아 둘 것내용
ChatGPT 구독과 API 는 따로다ChatGPT 유료 구독이 있어도 API 요금은 따로 낸다. 키는 platform.openai.com 에서 만들고 결제 수단도 따로 등록한다
요금모델마다 100만 토큰당 값이 다르고 자주 바뀐다. 쓰기 전에 공식 요금표를 본다
도구 호출9장의 도구 호출(function calling)을 OpenAI 모델로 하려면, 모델에 따라 Chat Completions 대신 Responses API 를 써야 한다고 공식 문서에 적혀 있다
공식 문서developers.openai.com/api/docs/models

Ollama 로 미리 시험하기

키를 만들기 전에 코드가 맞게 짜였는지 보고 싶으면, 같은 코드를 Ollama 로 돌려 봅니다. openai 라이브러리는 OPENAI_BASE_URL 환경 변수가 있으면 그 주소로 요청을 보냅니다. 코드는 한 글자도 고치지 않습니다. 이 시험을 한 PowerShell 창은 진짜 키로 돌리기 전에 닫습니다(아래 "진짜 키로 돌릴 때는 새 PowerShell 창에서").

PowerShell
$env:OPENAI_BASE_URL = "http://localhost:11434/v1"   # 요청을 OpenAI 대신 내 PC 의 Ollama 로 보낸다
$env:OPENAI_API_KEY = "ollama"                       # Ollama 는 키를 보지 않는다. 칸만 채운다
$env:OPENAI_MODEL = "qwen2.5:3b"                      # Ollama 에 받아 둔 모델 이름
python 06_외부API로만드는RAG.py                        # 위 코드를 저장한 파일을 실행한다

교안을 만든 PC 에서 이렇게 돌린 결과는 아래와 같습니다. 이것은 실제로 실행한 출력입니다(토큰 줄은 길어서 앞부분만 적었습니다).

출력
찾은 조항: ['제14조(결제 수단)', '제15조(무이자 할부)', '제3조(출고 마감)']
휴대폰으로 결제할 때 한 달에 50만원까지 이용할 수 있습니다.
CompletionUsage(completion_tokens=24, prompt_tokens=240, total_tokens=264, …)

temperature 를 넣지 않았으므로 같은 명령을 한 번 더 돌리면 "한 달에"가 "한달에"로 바뀌는 것처럼 문장이 조금씩 달라졌습니다. 답의 내용(한 달 50만원)은 두 번 모두 같았습니다.

Ollama 는 6장 「Ollama 로 로컬 모델 띄우기」에서 설치합니다.

진짜 키로 돌릴 때는 새 PowerShell 창에서

Ollama 로 시험한 창에서 바로 진짜 키로 돌리면 오류가 나거나, 오류 없이 엉뚱한 곳에 묻게 됩니다. 시험할 때 넣은 환경 변수 셋이 그 창에 그대로 남아 있기 때문입니다. .env 에 진짜 키를 적어도 소용이 없습니다. load_dotenv() 는 이미 있는 환경 변수를 덮어쓰지 않으므로(아래 ".env 파일에 키를 두고 쓰기"), 창에 남은 시험용 값이 이깁니다.

남은 환경 변수진짜 키로 돌렸을 때
OPENAI_BASE_URL요청이 OpenAI 가 아니라 계속 Ollama 로 간다. 오류가 나지 않아 알아채기 어렵다
OPENAI_API_KEY = "ollama"주소만 지웠다면 OpenAI 가 가짜 키를 거절해 401 오류가 난다
OPENAI_MODEL = "qwen2.5:3b"OpenAI 에는 그 이름의 모델이 없어 404 오류가 난다

그래서 진짜 키로 돌릴 때는 PowerShell 창을 새로 열어 실행합니다. $env: 로 넣은 값은 그 창에만 있으므로, 새 창에는 셋 모두 없습니다. 이 방법이 가장 확실합니다. 새 창에서는 가상환경도 다시 켜야 합니다.

PowerShell
cd C:\llm-course                  # cd = change directory. 코드가 있는 폴더로 간다(내 폴더 이름으로 바꾼다)
.venv\Scripts\Activate.ps1        # 가상환경을 다시 켠다. 줄 앞에 (.venv) 가 붙으면 켜진 것이다
Get-ChildItem Env:OPENAI*         # 남은 OPENAI 환경 변수를 본다. 아무것도 나오지 않아야 한다
python 06_외부API로만드는RAG.py    # 이제 .env 의 진짜 키로 OpenAI 에 묻는다

VS Code 의 터미널이라면 터미널 창 오른쪽 위의 휴지통 아이콘(터미널 끄기)을 누른 뒤 새 터미널을 엽니다. 창을 새로 열기 어렵다면 아래처럼 남은 값을 하나씩 지워도 됩니다.

시험이 끝나면 원래대로 돌리기

창을 새로 열지 않을 때는 넣었던 주소부터 지웁니다. 이 값이 남아 있으면 진짜 키를 넣어도 요청이 Ollama 로 갑니다. Remove-Item 은 지우기에 성공하면 아무것도 찍지 않으므로, Test-Path 로 정말 지워졌는지 확인합니다.

PowerShell
Remove-Item Env:OPENAI_BASE_URL    # Ollama 로 돌려 두었던 주소를 지운다. 성공하면 아무것도 찍지 않는다
Test-Path Env:OPENAI_BASE_URL      # 그 환경 변수가 남아 있으면 True, 없으면 False

수강생의 PC(가상환경 .venv 를 켠 PowerShell)에서 실행한 결과입니다.

출력
(.venv) PS C:\llm-course> Remove-Item Env:OPENAI_BASE_URL
(.venv) PS C:\llm-course> Test-Path Env:OPENAI_BASE_URL
False

첫 줄 뒤에 아무 글자도 없는 것이 성공한 모습이고, False 는 "그 환경 변수가 이제 없다"는 뜻입니다. 이제 요청은 원래 주소인 OpenAI 로 갑니다.

명령과 옵션원래 말뜻
Remove-Itemremove(지우다) + item(항목)파일, 폴더, 환경 변수 같은 항목을 지운다
Env:environment(환경)PowerShell 이 환경 변수를 모아 둔 곳. 폴더처럼 Env:이름 으로 가리킨다
Test-Pathtest(시험하다) + path(경로)그 경로에 항목이 있는지 보고 True 나 False 를 돌려준다
$env:OPENAI_BASE_URL값을 바로 찍는다. 지워졌으면 빈 줄만 나온다
Get-ChildItem Env:OPENAI*get(가져오다) + child item(안에 든 항목)이름이 OPENAI 로 시작하는 환경 변수를 모두 늘어놓는다

시험할 때 함께 넣은 키와 모델 이름도 지웁니다. 키를 ollama 로 둔 채 OpenAI 로 보내면 401 오류가 납니다. 키를 .env 에 적어 두었다면 창의 키는 지우기만 하면 됩니다. 그러면 load_dotenv() 가 .env 의 진짜 키를 넣습니다.

PowerShell
Remove-Item Env:OPENAI_API_KEY            # 시험용 'ollama' 키를 지운다. 진짜 키는 .env 에서 읽힌다
Remove-Item Env:OPENAI_MODEL              # 지우면 코드의 기본값 gpt-6-luna 를 쓴다
Get-ChildItem Env:OPENAI*                 # 남은 OPENAI 환경 변수를 본다. 아무것도 나오지 않아야 한다

.env 를 쓰지 않는다면 키를 지우는 대신 $env:OPENAI_API_KEY = "발급받은_인증키" 로 진짜 키를 다시 넣습니다.

알아 둘 것이 셋 있습니다.

  • 이미 지운 것을 한 번 더 지우면 Cannot find path 'Env:\OPENAI_BASE_URL' because it does not exist. 라는 빨간 오류가 납니다. "이미 없다"는 뜻이라 그대로 두어도 됩니다.
  • 하나라도 빠뜨리면 위 표의 오류가 납니다. 헷갈리면 지우는 대신 창을 새로 여는 편이 빠릅니다.
  • .env 파일에 OPENAI_BASE_URL 을 적어 두었다면 Remove-Item 으로는 지워지지 않습니다. 그 줄은 .env 파일에서 지웁니다.

.env 파일에 키를 두고 쓰기

위의 $env:OPENAI_API_KEY = … 는 그 PowerShell 창에서만 살아 있습니다. 창을 닫거나 VS Code 를 다시 켜면 사라져서 매번 다시 넣어야 합니다. 그래서 키를 .env 파일에 적어 두고, 코드가 시작할 때 읽어 오게 하는 방법을 많이 씁니다.

현관 비밀번호를 떠올려 보세요. 문에 비밀번호를 써 붙이지 않고, 집 안 서랍의 수첩에 적어 둡니다. 집에 들어온 사람만 수첩을 볼 수 있고, 집 사진을 남에게 보낼 때 서랍은 찍지 않습니다. 여기서 수첩이 .env 파일, 서랍에서 꺼내 읽는 일이 load_dotenv(), 집 사진을 보낼 때 서랍을 빼는 일이 .gitignore 입니다. 비유와 다른 점은, .env 파일은 잠기지 않는 보통 글자 파일이라 PC 를 함께 쓰는 사람은 열어 볼 수 있다는 것입니다.

견줄 것PowerShell 의 $env:.env 파일
값을 두는 곳그 PowerShell 창의 기억코드 옆의 글자 파일
살아 있는 동안창을 닫으면 사라진다지우기 전까지 남는다
코드에서 할 일없다. os.environ 으로 바로 읽는다맨 앞에서 load_dotenv() 를 한 번 부른다
조심할 것거의 없다깃허브에 올라가지 않게 .gitignore 에 적는다

이름의 .env 는 env(environment, 환경)에 점을 붙인 것입니다. Linux 와 macOS 에서는 점으로 시작하는 파일이 숨김 파일이 되어, 설정 파일 이름에 흔히 씁니다. 이 파일을 읽어 주는 패키지가 python-dotenv 이고, 코드에서는 dotenv 라는 이름으로 불러옵니다. 교안을 만든 PC 는 python-dotenv 1.2.4 입니다.

1단계. 패키지 설치

PowerShell
pip install python-dotenv    # .env 파일을 읽어 환경 변수에 넣어 주는 패키지

2단계. .env 파일 만들기

코드 파일과 같은 폴더에 이름이 정확히 .env 인 파일을 만들고 아래처럼 적습니다. VS Code 의 탐색기에서 "새 파일"을 누르고 .env 라고 적으면 됩니다. 메모장으로 만들면 .env.txt 로 저장되기 쉬우니, 저장할 때 파일 형식을 "모든 파일"로 고릅니다.

출력
# .env 파일. 한 줄에 "이름=값" 하나. = 양쪽에 빈칸을 두지 않는다
OPENAI_API_KEY=발급받은_인증키

따옴표는 없어도 됩니다. # 으로 시작하는 줄은 설명이라 읽지 않습니다.

3단계. 깃에서 빼고, 예시 파일 남기기

깃(Git)으로 코드를 관리한다면 같은 폴더의 .gitignore 파일에 .env 를 한 줄 적습니다. 그러면 git add 를 해도 .env 는 올라가지 않습니다. 대신 키 자리를 비워 둔 .env.example 을 올려, 다른 사람이 어떤 이름이 필요한지 알게 합니다. 14장의 배포 폴더도 이 방법을 씁니다.

출력
# .gitignore 에 적는 줄
.env

# .env.example (깃에 올리는 견본. 진짜 키를 적지 않는다)
OPENAI_API_KEY=발급받은_인증키

4단계. 코드에서 읽기

코드 맨 앞에서 load_dotenv() 를 부르면, 그 뒤로는 PowerShell 에서 넣었을 때와 똑같이 os.environ 으로 읽습니다. 아래는 위의 API 코드에 두 줄을 더한 것이고, 마찬가지로 이 PC 에서 실행하지 않은 안내 코드입니다.

출력
import os
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()                                          # 코드 파일 옆(없으면 위쪽 폴더)의 .env 를 읽는다
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])  # 그 뒤로는 PowerShell 에서 넣었을 때와 같다

openai 라이브러리는 api_key 를 주지 않으면 OPENAI_API_KEY 환경 변수를 스스로 찾습니다. 그래서 load_dotenv() 뒤에는 OpenAI() 만 써도 됩니다. 다만 어디서 키를 읽는지 눈에 보이게 하려고 교안은 api_key= 를 적었습니다.

진짜 키 없이 동작 확인하기

진짜 키가 없어도 load_dotenv() 가 어떻게 움직이는지는 볼 수 있습니다. 아래 코드는 임시 폴더에 가짜 값이 든 .env 를 만들어 읽어 보고, 끝나면 폴더와 환경 변수를 모두 지웁니다.

Python
import os
import tempfile
from pathlib import Path
from dotenv import load_dotenv, dotenv_values

with tempfile.TemporaryDirectory() as folder:          # 끝나면 저절로 지워지는 임시 폴더
    env_file = Path(folder) / ".env"
    # 연습용 .env 파일을 만든다. 값은 가짜다
    env_file.write_text("DEMO_API_KEY=시험용_가짜_키\nDEMO_MODEL=쓸_모델_이름\n", encoding="utf-8")

    print("파일 속 값:", dict(dotenv_values(env_file)))  # 환경 변수에 넣지 않고 내용만 읽는다
    print("읽기 전:", os.environ.get("DEMO_API_KEY", "(없음)"))
    load_dotenv(env_file)                              # 파일의 값을 환경 변수에 넣는다
    print("읽은 뒤:", os.environ.get("DEMO_API_KEY", "(없음)"))

    os.environ["DEMO_API_KEY"] = "창에서_넣은_값"       # PowerShell 에서 미리 넣은 경우를 흉내 낸다
    load_dotenv(env_file)                              # 이미 있는 값은 덮어쓰지 않는다
    print("이미 있으면:", os.environ["DEMO_API_KEY"])
    load_dotenv(env_file, override=True)               # override=True 면 파일의 값으로 덮어쓴다
    print("override=True:", os.environ["DEMO_API_KEY"])

for name in ["DEMO_API_KEY", "DEMO_MODEL"]:            # 예시가 넣은 환경 변수를 지운다
    os.environ.pop(name, None)
출력
파일 속 값: {'DEMO_API_KEY': '시험용_가짜_키', 'DEMO_MODEL': '쓸_모델_이름'}
읽기 전: (없음)
읽은 뒤: 시험용_가짜_키
이미 있으면: 창에서_넣은_값
override=True: 시험용_가짜_키
코드쉬운 뜻
from dotenv import load_dotenv, dotenv_values패키지 이름은 python-dotenv 이지만 불러올 때는 dotenv 로 쓴다
dotenv_values(파일).env 의 내용을 사전으로 읽기만 한다. 환경 변수는 건드리지 않는다
load_dotenv(파일).env 의 값을 환경 변수에 넣는다. 파일을 주지 않으면 코드 파일이 있는 폴더에서 위쪽 폴더로 올라가며 .env 를 찾는다
override=Trueoverride 는 "덮어쓰다"라는 뜻이다. 기본값 False 에서는 이미 있는 환경 변수가 이긴다
os.environ.pop(name, None)예시가 넣은 환경 변수를 지운다. 없으면 오류 대신 그냥 넘어간다

출력의 넷째 줄이 눈여겨볼 곳입니다. PowerShell 에서 이미 넣은 값이 있으면 load_dotenv() 는 그 값을 그대로 두고, .env 의 값은 쓰지 않습니다. 그래서 .env 의 키를 고쳤는데도 예전 키로 요청이 가면, 창에 남은 $env:OPENAI_API_KEY 를 먼저 의심합니다. 창을 새로 열거나 override=True 를 주면 됩니다.

주의할 점

  • API 키가 든 파일을 깃허브에 올리면 남이 내 돈으로 API 를 씁니다. 키는 환경 변수나 .env 파일(깃에서 빼 둔)에 둡니다.
  • 사내 문서를 보내기 전에 회사의 보안 규정과 공급사의 데이터 처리 약관(입력을 학습에 쓰는지, 얼마나 보관하는지)을 확인합니다.
← 이전 절6장 · 근거를 붙인 프롬프트다음 절 →6장 · 로컬 모델이 필요한 까닭