본문 바로가기

명사 美 비격식 (무리 중에서) 아주 뛰어난[눈에 띄는] 사람[것]

Personal/SK 네트웍스 AI 캠프

SK 네트웍스 AI 캠프 - 3_초거대언어모델(LLM) - Day40_LLM 텍스트 생성 제어 파라미터

LLM

LLM의 주요 파라미터는 생성되는 답변의 창의성 후보단어 선택범위, 출력길이를 제어하는 설정이다. 

얼마나 다양한 단어를 선택할 것인지 Temperature, 후보단어 범위를 결정하는 Top은 Nucleus Sampling 이라고도 하며, P, 모델이 생성할 수 있는 최대 출력 길이를 제한하는 설정 Maximun Tokens

https://standout.tistory.com/1879

 

LLM의 정의와 LLM 종류

LLMLarge Language Model, 대규모 언어모델GPT-3 는 2020 년에 공개된 대표적인 초거대언어모델이며, 1,750 억 개 파라미터를 가진 자기회귀 언어 모델기존 NLP모델은 감성분석, 번역모델, 요약모델, 질문답

standout.tistory.com

 

Temperatrue가 높을 수록 모델은 새로운 표현을 더 많이 시도하게된다 .아이디어 도출. 

Top p 가 높을수록 다양한 단어를 사용한다. 

Maximum Tokens는 생성할 수있는 최대 출력길이를 제한하는 설정이다. 

예를 들어 분류작업을 해야될경우 항상 같은 결과가 중요함으로 Temperature를 낮게갈것이고, 소설을 작성할때는 높게갈것이다.

유연한 대화를 위해서 즉 챗봇을 위해서는 Top p가 높아질것이나 아이디어 생성때보다는 낮을 것이다 .

https://standout.tistory.com/1885

 

LLM의 파라미터 Temperature, Top P( Nucleus Sampling), Maximun Tokens

LLMLLM의 주요 파라미터는 생성되는 답변의 창의성 후보단어 선택범위, 출력길이를 제어하는 설정이다. 얼마나 다양한 단어를 선택할 것인지 Temperature, 후보단어 범위를 결정하는 Top P은 Nucleus Samp

standout.tistory.com

 

 

LLM

말은 잘하지만 스스로 행동은 못하는 도구. 여기에 도구.기억.반복 루프를 붙여 실제로 일을 끝내게 만든것이 에이전트. 

Chat GPt LLM에게 쇼핑하기위한 방법을 물으면 답을 잘하나 고객이 진짜로 묻는 질문들은 현재재고, 나의주문, 환불입급시간 등이고 LLM은 DB에있는 주문상태에, 재고시스템에만 있는 수량에, 사내정책문서에만 있는 환불규정에 의해 답할수없다. 

말은 잘하지만 갇혀있는 두뇌. 백과사전을 통째로 외운 신입사원. 그래서 그럴듯하게 지어낸다. 환각.

https://standout.tistory.com/1876

 

RAG(Retrieval-Augmented Generation)와 LLM의 Hallucination(환각): 외부 문서를 검색한 후 검색 결과를 바탕으

RAG(Retrieval-Augmented Generation)기업은 범용 언어모델을 그대로 사용하는 것이 아니라 사내 문서, 정보, 고객데이터 등을 추가해 기업전용 AI시스템을 구축하며 LLM은 이때 존재하지않는 정보를 생성

standout.tistory.com

 

Agentic AI 

에이전트형 AI 두뇌 LLM에 세가지를 붙인 시스템 도구, 기억, 루프.

AI앱은 매번 다음일을 저앻주어야하는 1회성도구이고 AI 에이전트는 스스로 다음에 뭘 할지를 정하는 시스템이다. 

단순번역요약같이 한번에 끝나는 일은 AI앱이 더 싸고 안전하다. 

에이전트의 요소에는 4요소 LLM 도구 기억 루프가 있다. 

 

에이전트 작업

1. 프롬프트 설계, 복잡한 문제는 단계적으로 추론하게 만든다. 

2. LLM이 스스로 외부 함수를 호출하게 만드는 에이전트 루프를 구현한다. 

3. RAG 문서를 근거로 답하게한다. 

4. LLM은 stateless 존재이니 장기기억이 가능하게한다. 

5. 워크플로우를 선언하고 전문 에니전트를 만들어 팀으로 협업하게한다. 

https://standout.tistory.com/1886

 

Agentic AI : 프롬프트, API 호출, Agent Loop, RAG, 장기기억, 팀협업(feat.에이전트 아키텍처 그리기)

Agentic AI 에이전트형 AI 두뇌 LLM에 세가지를 붙인 시스템 도구, 기억, 루프.말은 잘하지만 스스로 행동은 못하는 도구. 여기에 도구.기억.반복 루프를 붙여 실제로 일을 끝내게 만든것이 에이전트.

standout.tistory.com

 

 

시스템 지시 system instruction

모델에게 너는 누구이고, 어떻게 답해야하는지의 규칙. 배역 설명서

config에 보통 작성된다.

SYSTEM_PROMPT = """
당신은 Cisco Meraki Dashboard API 전문가입니다.

역할
- 네트워크 전문가입니다.

태도
- 항상 존댓말을 사용합니다.
- 이해하기 쉽게 설명합니다.
- 필요한 경우 예제를 제공합니다.

행동 규칙
- HTTP Method를 함께 설명합니다.
- API Endpoint를 표시합니다.
- Request와 Response 예제를 제공합니다.

안정장치
- 모르는 내용은 추측하지 않습니다.
- 공식 문서를 기준으로 설명합니다.
"""

https://standout.tistory.com/1887

 

시스템 지시 system instruction: 모델에게의 배역 설명서

시스템 지시 system instruction모델에게 너는 누구이고, 어떻게 답해야하는지의 규칙. 배역 설명서'당신은 친절한 상담원이고 항상 존댓말이어야한다.'라는 문구에 사용자 질문이 바뀌어도 역할이

standout.tistory.com

 

 

 

 

 

 

샘플프로젝트 llm_parameter를 분석해보자. 

https://aistudio.google.com/u/1/api-keys?hl=ko&project=gen-lang-client-0266228416

 

로그인 - Google 계정

이메일 또는 휴대전화

accounts.google.com

 

 

 

gemini API 키 발급 

 

 

 

.env에 복붙.

# Gemini API Key를 입력합니다.
GOOGLE_API_KEY=복 붙

# 사용할 Gemini 모델명입니다.
GEMINI_MODEL=gemini-2.5-flash

# OpenAI 실습은 선택입니다.
OPENAI_API_KEY=복 붙

# 사용할 OpenAI 모델명입니다.
OPENAI_MODEL=gpt-4o-mini

 

 

 

 

config.py

import os, Path, load_dotenv

load_dotenv는 .env 파일을 읽어 환경변수로 입력한다. 

ROOT_DIR 계산, env 경로지정 , genini_model읽기, openai_model 읽기

def is_placeholder() 매개변수가 none이면 return. 매개변서 str의 strip() 앞뒤 공백제거 빈문자열이면 return. 안내문구가 남아있더라도 return. 통과시 return.

def require_env

환경변수 값 읽기, 없거나 예시문구시 reuntimererror return,통과시 strip()해 return. 

def env_status() 

위 함수를 사용하여 구글키 확인, openai키 확인, txt서식을 지정하여 return.

# -*- coding: utf-8 -*-
"""프로젝트 환경변수와 API Key 상태를 관리하는 설정 파일입니다."""

# 운영체제 환경변수를 읽기 위해 os 모듈을 불러옵니다.
import os

# 프로젝트 경로를 안전하게 계산하기 위해 pathlib 모듈을 불러옵니다.
from pathlib import Path

# .env 파일을 읽어 환경변수로 등록하기 위해 load_dotenv 함수를 불러옵니다.
from dotenv import load_dotenv

# 현재 파일 위치를 기준으로 프로젝트 최상위 폴더를 계산합니다.
ROOT_DIR = Path(__file__).resolve().parents[2]

# 프로젝트 최상위 폴더에 있는 .env 파일 경로를 지정합니다.
ENV_PATH = ROOT_DIR / ".env"

# .env 파일이 있으면 읽어서 환경변수로 등록합니다.
load_dotenv(ENV_PATH)

# 사용할 Gemini 모델명을 환경변수에서 읽고, 없으면 기본값을 사용합니다.
GEMINI_MODEL = os.getenv("GEMINI_MODEL", "gemini-2.5-flash")

# 사용할 OpenAI 모델명을 환경변수에서 읽고, 없으면 기본값을 사용합니다.
OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o-mini")


def is_placeholder(value: str | None) -> bool:
    """환경변수 값이 비어 있거나 예시 문구인지 확인합니다."""

    # 값이 None이면 설정되지 않은 상태입니다.
    if value is None:
        return True

    # 앞뒤 공백을 제거한 문자열을 준비합니다.
    cleaned = value.strip()

    # 빈 문자열이면 설정되지 않은 상태입니다.
    if not cleaned:
        return True

    # 예시 파일의 안내 문구가 그대로 남아 있으면 실제 키가 아닙니다.
    if cleaned.startswith("여기에") or cleaned.startswith("선택_"):
        return True

    # 위 조건에 해당하지 않으면 실제 값이 입력된 것으로 판단합니다.
    return False


def require_env(name: str) -> str:
    """필수 환경변수가 없으면 실행자가 이해하기 쉬운 오류를 발생시킵니다."""

    # 지정한 이름의 환경변수 값을 읽습니다.
    value = os.getenv(name)

    # 값이 없거나 예시 문구이면 RuntimeError를 발생시킵니다.
    if is_placeholder(value):
        raise RuntimeError(
            f"{name} 값이 설정되어 있지 않습니다. .env.example을 .env로 복사한 뒤 실제 값을 입력하세요."
        )

    # 실제 환경변수 값을 반환합니다.
    return value.strip()


def get_env_status() -> dict:
    """현재 프로젝트 실행 환경 상태를 딕셔너리로 반환합니다."""

    # GOOGLE_API_KEY 설정 여부를 확인합니다.
    google_key_loaded = not is_placeholder(os.getenv("GOOGLE_API_KEY"))

    # OPENAI_API_KEY 설정 여부를 확인합니다.
    openai_key_loaded = not is_placeholder(os.getenv("OPENAI_API_KEY"))

    # 실행 환경 상태를 보기 좋게 반환합니다.
    return {
        "project_root": str(ROOT_DIR),
        "env_path": str(ENV_PATH),
        "env_file_exists": ENV_PATH.exists(),
        "gemini_model": GEMINI_MODEL,
        "openai_model": OPENAI_MODEL,
        "google_api_key_loaded": google_key_loaded,
        "openai_api_key_loaded": openai_key_loaded,
    }

 

 

 

 

test_llm.py

config를 import해서 api 키 불러와 적용해 gemini 클라이언트 생성. 

def ask

client.models.generate_content() 모델 불러와 설정. question 매개변수로 받은것 할당해 resp.text 모델이 생성한 답변 문자열로 return

pinrt() 테스트

# 가장 단순한 LLM 호출 한 번
from google import genai
from config import GEMINI_MODEL, require_env

api_key = require_env("GEMINI_API_KEY")
client = genai.Client(api_key=api_key)  # Gemini 클라이언트 생성


def ask(question: str) -> str:
    # generate_content = "한 번 묻고 → 한 번 답받기"의 가장 기본 호출
    resp = client.models.generate_content(
        model=require_env("GEMINI_MODEL"),
        contents=question,
    )
    return resp.text  # 모델이 생성한 답변(문자열)


# 질문 1: 일반 지식 → LLM이 잘 답함
print(ask("블루투스 이어버드를 고를 때 무엇을 봐야 하나요? 3가지만 짧게."))

# 질문 2: 실시간 사내 정보 → LLM 혼자서는 불가
print(ask("승승장구몰 주문번호 O000123은 지금 배송 어디까지 왔나요?"))

 

 

 

FastAPI 애플리케이션테스트

fastapi.testclient TestClient: FastAPI에서 제공하는 테스트용 HTTP 클라이언트, http://localhost:8000으로 접속하는것이 아니라 애플리케이션을 메모리에서 실행해 client.get("/")처럼 API로 호출할 수 있다 .브라우저 대신 프로그램이 API를 호출한다.

app.main import app, app 객체를 가져온다 .

TestClient() 클라이언트만들기. 이것으로 client.get(), client.post(), client.put(), client.delete() 등으로 실제 서버를 실행하지않아도 API를 호출할 수 있다.

client.get("/") 루트 호출하기

client.get("/api/system/health"), @app.get("/api/system/health")인것 호출

# -*- coding: utf-8 -*-
"""FastAPI 앱 기본 동작 테스트입니다."""

from fastapi.testclient import TestClient

from app.main import app

client = TestClient(app)


def test_root_ui_page():
    """루트 경로가 HTML UI 페이지를 반환하는지 확인합니다."""

    response = client.get("/")

    assert response.status_code == 200
    assert "text/html" in response.headers["content-type"]
    assert "LLM API 실습 테스트 앱" in response.text


def test_health_check():
    """서버 상태 확인 API가 정상 응답하는지 확인합니다."""

    response = client.get("/api/system/health")

    assert response.status_code == 200
    assert response.json()["status"] == "ok"

 

 

 

 

common.py

import

root, docs, env 불러와 세팅하기

gemini_model, gemini_embed_model env 파일에서 읽고 없으면 기본값을 사용한다. 

def get_env_status() 구글API 키가 설정되어있는지 확인등 하여 문자열로 세팅해 return. 

def require_key() 값이 없거나 안내문구가있으면 return하지않는다. 

def get_genai_client() 구글패키지에서 genai 모듈을 불러와 api값으로 client()를 만들어 return.

# -*- coding: utf-8 -*-
"""
app/common.py — FastAPI LLM 실습 공통 파일

목적:
  - 프로젝트 루트의 .env 파일에서 API Key를 읽습니다.
  - Gemini API 클라이언트를 한 곳에서 생성합니다.
  - OpenAI API Key 사용 여부를 확인합니다.
  - FastAPI 라우터에서 공통으로 사용할 모델 이름을 관리합니다.

주의:
  - GOOGLE_API_KEY는 .env 파일에 직접 작성해야 합니다.
  - 실제 API Key는 GitHub에 올리면 안 됩니다.
"""

# 운영체제 환경변수 값을 읽기 위해 os 모듈을 불러옵니다.
import os

# 파일 경로를 안전하게 다루기 위해 pathlib 모듈을 불러옵니다.
import pathlib

# .env 파일을 읽어 환경변수로 등록하기 위해 load_dotenv 함수를 불러옵니다.
from dotenv import load_dotenv

# 현재 파일(app/common.py)을 기준으로 프로젝트 루트 폴더를 계산합니다.
ROOT = pathlib.Path(__file__).resolve().parents[1]

# 프로젝트 루트 아래 data 폴더 경로를 지정합니다.
DATA = ROOT / "data"

# 프로젝트 루트 아래 data/docs 폴더 경로를 지정합니다.
DOCS = DATA / "docs"

# 프로젝트 루트에 있는 .env 파일을 읽어 환경변수로 등록합니다.
load_dotenv(ROOT / ".env")

# 사용할 Gemini 모델명을 환경변수에서 읽고, 없으면 기본값을 사용합니다.
GEMINI_MODEL = os.getenv("GEMINI_MODEL", "gemini-2.5-flash")

# 사용할 Gemini 임베딩 모델명을 환경변수에서 읽고, 없으면 기본값을 사용합니다.
GEMINI_EMBED_MODEL = os.getenv("GEMINI_EMBED_MODEL", "models/gemini-embedding-001")


def get_env_status() -> dict:
    """현재 프로젝트의 환경 설정 상태를 딕셔너리로 반환합니다."""

    # GOOGLE_API_KEY가 설정되어 있는지 확인합니다.
    google_key_exists = bool(os.getenv("GOOGLE_API_KEY"))

    # OPENAI_API_KEY가 설정되어 있는지 확인합니다.
    openai_key_exists = bool(os.getenv("OPENAI_API_KEY"))

    # 프로젝트 루트, 데이터 폴더, 모델명, 키 설정 여부를 반환합니다.
    return {
        "root": str(ROOT),
        "data_dir": str(DATA),
        "data_dir_exists": DATA.exists(),
        "gemini_model": GEMINI_MODEL,
        "google_api_key_loaded": google_key_exists,
        "openai_api_key_loaded": openai_key_exists,
    }


def require_key(name: str) -> str:
    """필수 API Key가 없으면 명확한 오류 메시지를 발생시킵니다."""

    # 환경변수에서 지정한 이름의 값을 읽습니다.
    value = os.getenv(name)

    # 값이 없거나 예시 문구 그대로이면 설정 오류로 판단합니다.
    if not value or value.startswith("여기에"):
        raise RuntimeError(
            f"[설정 필요] {name} 값이 .env 파일에 없습니다. "
            f".env.example을 복사해 .env를 만든 뒤 {name} 값을 입력하세요."
        )

    # 정상 설정된 API Key 값을 반환합니다.
    return value


def get_genai_client():
    """google-genai Gemini 클라이언트를 생성해 반환합니다."""

    # google-genai 패키지에서 genai 모듈을 불러옵니다.
    from google import genai

    # GOOGLE_API_KEY 값을 확인합니다.
    api_key = require_key("GOOGLE_API_KEY")

    # API Key를 사용해 Gemini 클라이언트를 생성합니다.
    return genai.Client(api_key=api_key)

 

 

 

system.py

import

fastapi, APIRouter 프로젝트가 커지면 API가 많아지고 모두 main.py에 작성하면 수백, 수천줄이 될수있으니 기능별로 나누기 위해 사용한다.  각 파일에 APITouter()를 불러와 각 url별로 작성하고, main에서는 각 파일을 불러오기만 하면된다. 

app.common import

system용 router 생성. 

/health 서버가 동작중인지확인해 staus, message 형태로 return.

/env app.common에서 불러온 get_env_status함수실행해 key와 경로설정등이 잘되었는지 확인하는 함수.

# -*- coding: utf-8 -*-
"""프로젝트 상태 확인용 API 라우터입니다."""

# FastAPI 라우터 클래스를 불러옵니다.
from fastapi import APIRouter

# 환경 상태 확인 함수를 불러옵니다.
from app.common import get_env_status

# /api/system 경로 아래에 API를 묶기 위한 라우터를 생성합니다.
router = APIRouter(prefix="/api/system", tags=["시스템 확인"])


@router.get("/health")
def health_check():
    """FastAPI 서버가 정상 동작 중인지 확인합니다."""

    return {
        "status": "ok",
        "message": "FastAPI LLM 실습 테스트 앱이 실행 중입니다.",
    }


@router.get("/env")
def env_check():
    """API Key와 프로젝트 경로 설정 상태를 확인합니다."""

    return get_env_status()

 

 

 

llm_service.py

import os,

Optinal 타입힌트를 위한, 주로 선택 입력값이나 없을 수도 있는 반환값을 표현할 때 사용한다.

app.common를 import해 클라이언트 생성함수와 모델ㅐ명 불러오

_safe_usage() getattr() 가져와 usage가 없으면 none반환, 사용한 속성을 읽어 딕셔너리로 return

gemini_basic_call()  google.genai types import해 get_genai_client() 클라이언트 생성,

types.GenerateContentConfig() tenperature와 max_output_tokens 설정해 client,models.generate_content() 요청 보내 return.

gemini_role_chat()  마찬가지로 google.genai types import.해 system_instruction 추가해 요청보내 return.

gemini_temperature_diversity() 은 답변 저장할 리스트를 초기화해 for문으로 같은 질문을 반복해 호출, append해 return.

gemini_token_compare() 한국어 문장을 앞선 gemini_basic_call 클라이언트를 생성해 호출, 영어도. return.

import  traceback 예외가 발생할경우 상세한 호출을 출력하거나 문자열로 얻기위한 표준 라이브러리

openai_chat() getenv()를 실행해 api key를 가져와 없으면 runtimeerror, model에 할당한뒤 openai import openai

client = OpenAi() 클라이언트 생성해 client,chat.completion.create() 답변과 토큰객체를 꺼내 딕셔너리로 반환. else일결루 esception, traceback.print_exe() 로 상세하게 에러출력

# -*- coding: utf-8 -*-
"""Gemini와 OpenAI 호출 기능을 모아 둔 서비스 파일입니다."""

# OpenAI API Key 확인을 위해 os 모듈을 불러옵니다.
import os

# 타입 힌트를 위해 Optional을 불러옵니다.
from typing import Optional

# 공통 Gemini 클라이언트 생성 함수와 모델명을 불러옵니다.
from app.common import get_genai_client, GEMINI_MODEL


def _safe_usage(resp) -> dict:
    """Gemini 응답 객체에서 토큰 사용량을 안전하게 꺼냅니다."""

    # usage_metadata가 없는 경우 None으로 처리하기 위해 getattr을 사용합니다.
    usage = getattr(resp, "usage_metadata", None)

    # usage_metadata가 없으면 모든 토큰 값을 None으로 반환합니다.
    if usage is None:
        return {
            "prompt_token_count": None,
            "candidates_token_count": None,
            "total_token_count": None,
        }

    # 사용량 속성을 안전하게 읽어 딕셔너리로 반환합니다.
    return {
        "prompt_token_count": getattr(usage, "prompt_token_count", None),
        "candidates_token_count": getattr(usage, "candidates_token_count", None),
        "total_token_count": getattr(usage, "total_token_count", None),
    }


def gemini_basic_call(prompt: str, temperature: float = 0.7, max_output_tokens: Optional[int] = 300) -> dict:
    """시스템 지시 없이 Gemini를 1회 호출합니다."""

    # google-genai 설정 객체를 사용하기 위해 types를 불러옵니다.
    from google.genai import types

    # Gemini 클라이언트를 생성합니다.
    client = get_genai_client()

    # temperature와 max_output_tokens를 설정합니다.
    config = types.GenerateContentConfig(
        temperature=temperature,
        max_output_tokens=max_output_tokens,
    )

    # Gemini 모델에 요청을 보냅니다.
    resp = client.models.generate_content(
        model=GEMINI_MODEL,
        contents=prompt,
        config=config,
    )

    # 답변 텍스트와 토큰 정보를 합쳐 반환합니다.
    return {"text": resp.text, **_safe_usage(resp)}


def gemini_role_chat(system_instruction: str, user_message: str, temperature: float = 0.3, max_output_tokens: Optional[int] = 300) -> dict:
    """시스템 지시를 포함해 Gemini를 1회 호출합니다."""

    # google-genai 설정 객체를 사용하기 위해 types를 불러옵니다.
    from google.genai import types

    # Gemini 클라이언트를 생성합니다.
    client = get_genai_client()

    # 시스템 지시, temperature, 최대 출력 토큰을 설정합니다.
    config = types.GenerateContentConfig(
        system_instruction=system_instruction,
        temperature=temperature,
        max_output_tokens=max_output_tokens,
    )

    # Gemini 모델에 요청을 보냅니다.
    resp = client.models.generate_content(
        model=GEMINI_MODEL,
        contents=user_message,
        config=config,
    )

    # 답변 텍스트와 토큰 정보를 합쳐 반환합니다.
    return {"text": resp.text, **_safe_usage(resp)}


def gemini_temperature_diversity(prompt: str, temperature: float = 1.0, repeat_count: int = 5) -> dict:
    """같은 질문을 여러 번 호출하여 temperature에 따른 답변 다양성을 측정합니다."""

    # 답변들을 저장할 리스트를 만듭니다.
    answers = []

    # repeat_count만큼 같은 질문을 반복 호출합니다.
    for _ in range(repeat_count):
        result = gemini_basic_call(
            prompt=prompt,
            temperature=temperature,
            max_output_tokens=200,
        )
        answers.append(result["text"].strip())

    # set으로 중복을 제거한 뒤 고유 답변 개수를 계산합니다.
    unique_count = len(set(answers))

    # 측정 결과를 반환합니다.
    return {
        "temperature": temperature,
        "repeat_count": repeat_count,
        "answers": answers,
        "unique_count": unique_count,
    }


def gemini_token_compare(korean_text: str, english_text: str) -> dict:
    """한국어 입력과 영어 입력의 토큰 사용량을 비교합니다."""

    # 한국어 문장을 temperature 0으로 호출합니다.
    korean_result = gemini_basic_call(
        prompt=korean_text,
        temperature=0.0,
        max_output_tokens=200,
    )

    # 영어 문장을 temperature 0으로 호출합니다.
    english_result = gemini_basic_call(
        prompt=english_text,
        temperature=0.0,
        max_output_tokens=200,
    )

    # 두 결과를 나란히 반환합니다.
    return {
        "korean": korean_result,
        "english": english_result,
    }

import  traceback

def openai_chat(system_instruction: str, user_message: str) -> dict:
    """OPENAI_API_KEY가 있을 때 OpenAI Chat Completions API를 호출합니다."""

    # OPENAI_API_KEY가 없으면 명확한 오류를 발생시킵니다.
    if not os.getenv("OPENAI_API_KEY"):
        raise RuntimeError("OPENAI_API_KEY가 .env 파일에 설정되어 있지 않습니다.")

    OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4.1-mini")

    # OpenAI 공식 SDK의 클라이언트를 불러옵니다.
    from openai import OpenAI

    try:
    # 환경변수의 OPENAI_API_KEY를 자동으로 읽어 클라이언트를 생성합니다.
        client = OpenAI()

        # OpenAI 모델에 시스템 지시와 사용자 질문을 함께 보냅니다.
        resp = client.chat.completions.create(
            model=OPENAI_MODEL,
                messages=[
                    {"role": "system", "content": system_instruction},
                    {"role": "user", "content": user_message},
                ],
                temperature=0.3,
                max_tokens=300,
            )

        # 답변 텍스트를 꺼냅니다.
        text = resp.choices[0].message.content

        # 토큰 사용량 객체를 꺼냅니다.
        usage = resp.usage

        # FastAPI에서 JSON으로 반환하기 쉬운 딕셔너리로 변환합니다.
        return {
            "text": text,
            "prompt_token_count": getattr(usage, "prompt_tokens", None),
            "candidates_token_count": getattr(usage, "completion_tokens", None),
            "total_token_count": getattr(usage, "total_tokens", None),
        }
    except Exception as e:
        traceback.print_exc()
        raise

 

 

 

 

 

schemax.py

import typing, Optional ,, List 타입힌트를 위한 Optional

pydantic BaseModel, Field 데이터 검증을 위한 BaseModel과 Field, FastAPI에서는 요청과 응답 데이터를 정의할 때 많이 사용된다 .BaseModel은 스키마를 정의하는 부모클래스 Pydantic이 자동으로 데이터타입검사, 데이터변환, 필수값 확인, json변환을 수행한다. Field 는 각 필드 세부 조건과 메타데이터를 설정하는 함수

BasicPromptRequest() 사용자의 기본 prompt 지시문, temperature 기본값, max_output_tokens 최대토큰수 설정

RoleChatRequest() 모델에게 부여할 역할 Field() 객체들 system_instruction, user_message, ~

DiversityRequest() temprrature 다양성 측정 요청 데이터로 prompt와 temperatur, repeat_count가 있다 .

TokenCompareRequest() 한국어와 영어문장 설정

LLMResponse() 입력 토큰수와 출력 토큰수, 전체 토큰수 정의

DiversityResponse() 사용한 temperatur, repeat_count, answers, unique_count 정의

이로서 응답 데이터를 확인 할 수있겠다.

# -*- coding: utf-8 -*-
"""FastAPI 요청/응답 데이터 구조를 정의하는 파일입니다."""

# 타입 힌트를 위해 Optional을 불러옵니다.
from typing import Optional, List

# 요청 데이터 검증을 위해 Pydantic의 BaseModel과 Field를 불러옵니다.
from pydantic import BaseModel, Field


class BasicPromptRequest(BaseModel):
    """기본 LLM 호출 요청 데이터입니다."""

    # 사용자가 LLM에게 보낼 질문 또는 지시문입니다.
    prompt: str = Field(default="승승장구몰을 한 문장으로 홍보해줘.", description="LLM에게 보낼 입력 문장")

    # LLM 답변의 무작위성을 조절하는 값입니다.
    temperature: float = Field(default=0.7, ge=0.0, le=2.0, description="생성 무작위성")

    # 답변 최대 토큰 수를 제한합니다.
    max_output_tokens: Optional[int] = Field(default=300, ge=1, le=4096, description="최대 출력 토큰 수")


class RoleChatRequest(BaseModel):
    """시스템 지시를 포함한 LLM 호출 요청 데이터입니다."""

    # 모델에게 부여할 역할 또는 규칙입니다.
    system_instruction: str = Field(default="너는 승승장구몰의 친절한 CS 상담원이다. 존댓말로 간결히 답하라.")

    # 사용자가 입력하는 실제 질문입니다.
    user_message: str = Field(default="환불 얼마나 걸려요?")

    # 생성 무작위성 설정값입니다.
    temperature: float = Field(default=0.3, ge=0.0, le=2.0)

    # 답변 최대 토큰 수입니다.
    max_output_tokens: Optional[int] = Field(default=300, ge=1, le=4096)


class DiversityRequest(BaseModel):
    """temperature 다양성 측정 요청 데이터입니다."""

    # 같은 질문을 여러 번 던질 때 사용할 질문입니다.
    prompt: str = Field(default="승승장구몰을 한 문장으로 홍보해줘.")

    # 다양성을 확인할 temperature 값입니다.
    temperature: float = Field(default=1.0, ge=0.0, le=2.0)

    # 같은 질문을 몇 번 호출할지 지정합니다.
    repeat_count: int = Field(default=5, ge=1, le=10)


class TokenCompareRequest(BaseModel):
    """한국어/영어 토큰 비교 요청 데이터입니다."""

    # 한국어 입력 문장입니다.
    korean_text: str = Field(default="승승장구몰의 무선 블루투스 이어버드를 한 문장으로 친절하게 홍보해줘.")

    # 영어 입력 문장입니다.
    english_text: str = Field(default="Write one friendly sentence promoting SeungSeung Mall's wireless earbuds.")


class LLMResponse(BaseModel):
    """LLM 호출 결과 응답 데이터입니다."""

    # 모델이 생성한 답변입니다.
    text: str

    # 입력 토큰 수입니다.
    prompt_token_count: Optional[int] = None

    # 출력 토큰 수입니다.
    candidates_token_count: Optional[int] = None

    # 전체 토큰 수입니다.
    total_token_count: Optional[int] = None


class DiversityResponse(BaseModel):
    """temperature 다양성 측정 결과 응답 데이터입니다."""

    # 사용한 temperature 값입니다.
    temperature: float

    # 전체 호출 횟수입니다.
    repeat_count: int

    # 생성된 답변 목록입니다.
    answers: List[str]

    # 중복 제거 후 고유 답변 개수입니다.
    unique_count: int

 

 

 

 

llm.py

fastapi에서 그룹별도 관리하는 객체 APIRouter, 클라이언트에게 오류를 반환하는 객체 HTTPException 불러오기

app.schemas, app.services 불러오기

라우터 설정

# FastAPI 라우터와 HTTP 오류 처리를 위해 필요한 클래스를 불러옵니다.
from fastapi import APIRouter, HTTPException

# 요청/응답 스키마를 불러옵니다.
from app.schemas import (
    BasicPromptRequest,
    DiversityRequest,
    DiversityResponse,
    LLMResponse,
    RoleChatRequest,
    TokenCompareRequest,
)

# 실제 LLM 호출 로직이 들어 있는 서비스 함수를 불러옵니다.
from app.services.llm_service import (
    gemini_basic_call,
    gemini_role_chat,
    gemini_temperature_diversity,
    gemini_token_compare,
    openai_chat,
)


# /api/llm 경로 아래에 API를 묶기 위한 라우터를 생성합니다.
router = APIRouter(prefix="/api/llm", tags=["LLM 실습 테스트"])

 

 

/basic gemini_basic_call() 기본호출 테스트 prompt, temperature, max_output_tokens 바로 return, exception시 httpexception객체 raise

/gemini/role gemini_role_chat 역할과 말투를 바꾸는 테스트 system_instruction, user_message, temperature, max_output_tokens

/gemini/diversity temperature값에 따른 답변 다양성 prompt, temperature, repeat_count
/gemini/token-compare 한국어와 영어 입력의 토큰 사용량 비교 service.py에 있는 gemini_token_compare 함수를 활용해서 return korean_text, english_text

/openai/chat service.py에 있는 openai_chat() 함술를 활용해 return system_instruction user_message


@router.post("/gemini/basic", response_model=LLMResponse)
def call_gemini_basic(request: BasicPromptRequest):
    """Gemini 기본 호출을 테스트합니다."""

    try:
        return gemini_basic_call(
            prompt=request.prompt,
            temperature=request.temperature,
            max_output_tokens=request.max_output_tokens,
        )
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc


@router.post("/gemini/role", response_model=LLMResponse)
def call_gemini_role(request: RoleChatRequest):
    """시스템 지시로 Gemini의 역할과 말투를 바꾸는 테스트입니다."""

    try:
        return gemini_role_chat(
            system_instruction=request.system_instruction,
            user_message=request.user_message,
            temperature=request.temperature,
            max_output_tokens=request.max_output_tokens,
        )
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc


@router.post("/gemini/diversity", response_model=DiversityResponse)
def check_temperature_diversity(request: DiversityRequest):
    """temperature 값에 따른 답변 다양성을 측정합니다."""

    try:
        return gemini_temperature_diversity(
            prompt=request.prompt,
            temperature=request.temperature,
            repeat_count=request.repeat_count,
        )
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc


@router.post("/gemini/token-compare")
def compare_korean_english_tokens(request: TokenCompareRequest):
    """한국어와 영어 입력의 토큰 사용량을 비교합니다."""

    try:
        return gemini_token_compare(
            korean_text=request.korean_text,
            english_text=request.english_text,
        )
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc


@router.post("/openai/chat", response_model=LLMResponse)
def call_openai_chat(request: RoleChatRequest):
    """OpenAI 키가 있을 때 OpenAI 호출 구조를 테스트합니다."""

    try:
        return openai_chat(
            system_instruction=request.system_instruction,
            user_message=request.user_message,
        )
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc

 

 

 

 

main.py

import

fastapi

fastapi.responses  HTML 파일이나 이미지파일을 그대로 브라우저에 보내기 위한.

fastapi.staticfiles StaticFiles css js와같은 정적 파일 staticfiles를 웹에서 제공하기위해, 브라우저에서 접근할 수 있다. 

fastapi.middlewear.cores CROSSMiddleweare 브라우저는 다른 주소의 서버에 요청하는 것을 제한하는데 CORS를 허용하면 다른 출처에서도 API를 호출할 수 있다.

app.routers 라우터 불러오기

# -*- coding: utf-8 -*-
"""FastAPI 앱 실행 시작 파일입니다."""

# 정적 파일 경로 계산을 위해 pathlib을 불러옵니다.
import pathlib

# FastAPI 애플리케이션 클래스를 불러옵니다.
from fastapi import FastAPI

# 루트 화면에서 HTML 파일을 반환하기 위해 FileResponse를 불러옵니다.
from fastapi.responses import FileResponse

# app/static 폴더를 웹에서 제공하기 위해 StaticFiles를 불러옵니다.
from fastapi.staticfiles import StaticFiles

# CORS 설정을 위해 미들웨어 클래스를 불러옵니다.
from fastapi.middleware.cors import CORSMiddleware

# LLM 테스트 라우터와 시스템 확인 라우터를 불러옵니다.
from app.routers import llm, system

 

 

 

 

 

 

테스트해보자.