LangChain 챗봇2장 · 개발 환경 준비14 / 20← 이전목차다음 →MetaContents

2장. 개발 환경 준비

실습 2 — .env 파일 만들고 읽기

진짜 키 대신 지어낸 시험용 글자를 담은 연습용 .env 파일(out_ch02_demo.env)을 코드로 만들고, dotenv_values 와 load_dotenv 로 읽어 봅니다. 마지막으로 키를 화면에 찍어야 할 때 앞 몇 글자만 남기고 가리는 mask() 함수를 만듭니다. 과목 폴더의 진짜 .env 는 건드리지 않고, 이름도 OPENAI_ 대신 TEST_ 로 시작하게 해 섞이지 않게 했습니다. API 키가 없어도 됩니다.

1단계. 연습용 .env 파일 만들기

Python
from pathlib import Path

# 연습용 .env 글. 키는 진짜 키가 아니라 0 을 늘어놓아 지어낸 시험용 글자다
DEMO = """# 연습용 견본입니다. 진짜 키가 아닙니다
TEST_API_KEY=sk-test-0000000000000000
TEST_MODEL=gpt-4.1-mini
"""
Path("out_ch02_demo.env").write_text(DEMO, encoding="utf-8")
print("저장: out_ch02_demo.env")
출력
저장: out_ch02_demo.env

코드가 의미하는 것 write_text 로 만든 out_ch02_demo.env 는 진짜 .env 와 꼴이 같은 연습용 지갑입니다. 키 자리에 0 을 늘어놓은 시험용 글자를 넣어, 이 파일이 어디에 보여도 새어 나갈 것이 없게 했습니다.

2단계. 사전으로만 읽기

Python
import os

from dotenv import dotenv_values

# dotenv_values(경로): .env 를 읽어 {이름: 값} 사전으로 돌려준다. 환경 변수는 건드리지 않는다
values = dotenv_values("out_ch02_demo.env")
print("읽은 이름:", list(values))
print("TEST_MODEL 값:", values["TEST_MODEL"])
# 사전으로만 읽었으므로 환경 변수에는 아직 없다
print("환경 변수 TEST_MODEL:", os.getenv("TEST_MODEL"))
출력
읽은 이름: ['TEST_API_KEY', 'TEST_MODEL']
TEST_MODEL 값: gpt-4.1-mini
환경 변수 TEST_MODEL: None

코드가 의미하는 것 dotenv_values 는 지갑을 열어 들여다보기만 하는 함수입니다. 돌려받은 사전은 values 변수 안에만 있고, OpenAI() 나 ChatOpenAI 처럼 환경 변수에서 키를 찾는 코드에는 보이지 않습니다. 출력의 None 이 그 차이를 보여 줍니다.

3단계. 환경 변수로 올리기

Python
from dotenv import load_dotenv

# load_dotenv(경로): .env 의 줄을 환경 변수로 올린다. 읽은 줄이 있으면 True 를 돌려준다
print("올렸나:", load_dotenv("out_ch02_demo.env"))
# os.getenv(이름): 이제 환경 변수에서 읽힌다
print("환경 변수 TEST_MODEL:", os.getenv("TEST_MODEL"))
# os.getenv(이름, 기본값): .env 에 없는 이름은 기본값이 나온다
print("TEST_TIMEOUT:", os.getenv("TEST_TIMEOUT", "30"))
출력
올렸나: True
환경 변수 TEST_MODEL: gpt-4.1-mini
TEST_TIMEOUT: 30

코드가 의미하는 것 load_dotenv 는 지갑의 출입증을 앞치마 주머니로 옮기는 함수로, 이 뒤로는 같은 프로그램의 어느 코드든 os.getenv 로 값을 꺼냅니다. 실습 3부터 OpenAI() 와 ChatOpenAI 가 키를 따로 받지 않아도 되는 것은 이 한 줄 덕분입니다. os.getenv 의 기본값은 설정 한 줄이 빠져도 챗봇이 멈추지 않게 하는 장치입니다.

4단계. 키를 가려서 찍기

Python
def mask(key, keep=8):
    """키의 앞 keep 글자만 남기고 뒤는 **** 로 가린다. 키가 없으면 '없음'."""
    if not key:
        return "없음"
    return key[:keep] + "****"


key = os.getenv("TEST_API_KEY")
# 키를 그대로 print 하지 않고 가린 꼴과 글자 수만 찍는다
print("가린 키:", mask(key))
print("글자 수:", len(key))
print("sk- 로 시작하나:", key.startswith("sk-"))
# .env 에 없는 이름이면 None 이 오고, mask 는 '없음' 을 돌려준다
print("없는 키:", mask(os.getenv("TEST_NO_SUCH_KEY")))
출력
가린 키: sk-test-****
글자 수: 24
sk- 로 시작하나: True
없는 키: 없음

코드가 의미하는 것 mask() 는 키를 화면이나 기록에 남겨야 할 때 쓰는 가리개입니다. 앞 몇 글자와 글자 수, sk- 로 시작하는지만 보면 키가 제대로 들어왔는지 알 수 있고, 키 전체는 드러나지 않습니다. 키가 없을 때 오류 대신 "없음"을 돌려주게 한 것은 프로젝트 1의 점검표처럼 끝까지 찍어야 하는 곳에서 쓰려는 것입니다.

이 실습이 뜻하는 것

이 실습은 지갑에 출입증을 넣고, 꺼내 쓰고, 남에게 보일 때는 손가락으로 가리는 연습을 모형 출입증으로 해 보는 일과 같습니다. 모형 출입증을 지갑에 넣고(1단계), 지갑 속을 들여다보기만 하고(2단계), 앞치마 주머니로 옮기고(3단계), 보일 때는 가립니다(4단계). 여기서 지갑이 out_ch02_demo.env, 앞치마 주머니가 환경 변수, 손가락이 mask() 입니다. 비유와 다른 점은, 진짜 지갑과 달리 .env 는 열어 보면 글자가 그대로 보이는 글 파일이라는 것입니다. 그래서 파일 자체를 남에게 보내지 않습니다.

단계출력뜻
1. 파일 만들기저장: out_ch02_demo.env설명 한 줄과 "이름=값" 두 줄짜리 연습용 .env 가 생겼습니다
2. 사전으로 읽기읽은 이름 TEST_API_KEY, TEST_MODEL, 환경 변수 None설명 줄은 빠졌고, 사전으로만 읽었으므로 환경 변수에는 아직 없습니다
3. 환경 변수로올렸나 True, TEST_MODEL gpt-4.1-mini, TEST_TIMEOUT 30load_dotenv 뒤에는 os.getenv 로 읽히고, 없는 이름은 기본값 30 이 나왔습니다
4. 가리기sk-test-****, 24글자, True, 없음앞 8글자만 남기고 가렸고, 없는 키는 오류 대신 "없음"이 나왔습니다

한 줄로 줄이면, .env 를 사전으로 읽는 법과 환경 변수로 올리는 법의 차이를 보고, 키를 가려 찍는 함수를 만든 실습입니다. 2단계와 3단계의 TEST_MODEL 이 None 에서 gpt-4.1-mini 로 바뀐 것이 load_dotenv 가 한 일입니다. 실습 3부터는 같은 방법으로 과목 폴더의 진짜 .env 에서 OPENAI_API_KEY 를 읽습니다. mask() 는 프로젝트 1에서 다시 씁니다.

코드 설명

  • dotenv_values("out_ch02_demo.env") 는 파일을 사전으로만 읽습니다. 값을 들여다보거나 견줄 때 쓰고, 프로그램 전체에 키를 알려야 할 때는 load_dotenv 를 씁니다.
  • load_dotenv("out_ch02_demo.env") 는 경로를 직접 주었습니다. 모델을 부르는 실습에서는 load_dotenv(find_dotenv(usecwd=True)) 로 과목 폴더의 .env 를 찾게 합니다.
  • os.getenv("TEST_TIMEOUT", "30") 처럼 기본값을 주면 .env 에 그 줄이 없어도 코드가 멈추지 않습니다. 모델 이름을 os.getenv("OPENAI_MODEL", "gpt-4.1-mini") 로 읽는 것도 같은 방법입니다.
  • mask() 는 앞 8글자만 남깁니다. 키의 꼴(sk- 로 시작하는지)은 확인할 수 있고, 키 전체는 화면에 남지 않습니다.
  • 이름을 TEST_ 로 시작하게 한 것은 이 실습이 진짜 OPENAI_API_KEY 를 덮지 않게 하려는 것입니다. load_dotenv 는 이미 있는 환경 변수를 덮지 않지만, 먼저 올라간 쪽이 이기므로 이름을 나눠 두는 편이 안전합니다.
← 이전 절2장 · 실습 1 — 가상환경과 패키지 점검하기다음 절 →2장 · 실습 3 — OpenAI 라이브러리로 첫 질문 보내기