오늘은 FastAPI 백엔드에 실제 Gemini API를 연결하고, 하나의 파일에 있던 코드를 역할별로 분리했다. 이후 같은 구조로 Product API를 구현하고, pytest 테스트와 README 작성, GitHub 업로드까지 진행했다.
오후에는 오늘 배운 계층형 구조를 활용해 블로그 팀 프로젝트의 계획서를 만들고 역할을 나눴다. 지금까지 따로 배웠던 FastAPI, Pydantic, GitHub, Codex가 하나의 프로젝트 안에서 연결되기 시작한 하루였다.
FastAPI 프로젝트를 역할별로 나누는 이유
기능이 적을 때는 하나의 파이썬 파일에 모든 코드를 작성할 수 있다. 하지만 프로젝트가 커지면 코드가 길어지고, 여러 사람이 같은 파일을 수정하면서 충돌이 발생할 수 있다.
이를 해결하기 위해 프로젝트를 다음과 같이 나눴다.
mini_project/
├── app/
│ ├── main.py
│ ├── routers/
│ ├── schemes/
│ ├── services/
│ └── core/
└── tests/
각 부분의 역할은 다음과 같다.
- main.py: FastAPI 앱 실행과 Router 등록
- routers: API 경로와 요청 처리
- schemes: Pydantic을 이용한 요청·응답 데이터 구조 정의
- services: 데이터베이스나 LLM을 이용한 실제 기능 처리
- core: 환경변수와 공통 설정 관리
- tests: API 자동 테스트
Router는 요청을 받아 Service를 호출하고, 실제 Gemini 연동이나 데이터 처리는 Service가 담당한다. 이렇게 분리하면 나중에 데이터베이스나 LLM 코드가 바뀌어도 Router와 Main은 대부분 그대로 유지할 수 있다.
pytest로 FastAPI 자동 테스트하기
기존에는 서버를 실행하고 Swagger에서 API를 하나씩 눌러보며 확인했다. 이번에는 FastAPI의 TestClient와 pytest로 상태 코드와 응답 데이터를 자동 검사했다.
API 경로를 일부러 잘못 바꾸자 6개의 테스트 중 1개가 실패했다. 이를 통해 테스트 코드가 예상한 결과와 실제 결과의 차이를 찾아내는 과정을 확인할 수 있었다.
python -m pytest -q
실제 개발 현장에서는 API가 수십 개에서 수백 개까지 늘어날 수 있다. 이때 모든 기능을 직접 눌러보는 대신 테스트를 한 번 실행해 전체 상태를 확인한다.
Codex로 테스트 코드를 만들 때도 단순히 “테스트를 작성해줘”라고 요청하는 것보다 다음 내용을 구체적으로 알려주는 것이 중요하다.
- 테스트할 폴더와 파일
- API 경로
- HTTP Method
- 입력할 데이터
- 예상하는 상태 코드
- 확인해야 할 응답값
- 생성할 테스트 파일명
AI가 테스트 코드를 작성해 줄 수는 있지만, 무엇을 검사할지는 개발자가 먼저 정해야 한다.
ChatGPT, Codex, LLM API의 차이
오늘은 ChatGPT, Codex, LLM API가 서로 다른 서비스라는 점도 배웠다.
- ChatGPT: 사용자가 직접 AI와 대화하는 서비스
- Codex: 코드 작성과 수정을 돕는 개발 도구
- LLM API: 내가 만든 프로그램과 AI 모델을 연결하는 기능
예를 들어 쇼핑몰에서 사용자가 상품 추천을 요청했을 때, 백엔드가 Gemini나 GPT에 질문을 보내고 결과를 화면에 보여주는 기능이 LLM API 연동이다.
LLM API는 사용량에 따라 비용이 발생할 수 있다. 따라서 개발 초기에는 실제 API 대신 Mock 함수를 사용해 입력값과 응답 구조를 먼저 만든다.
Mock 함수에서는 다음과 같은 흐름을 연습했다.
AI 역할 + 참고 정보 + 사용자 질문
→ 프롬프트 생성
→ Mock LLM 호출
→ 딕셔너리 응답
→ answer 값 출력
temperature는 답변의 다양성을 조절하고, max_tokens는 최대 출력량을 제한한다.
Gemini API 키 발급과 보안
실제 Gemini API를 사용하기 위해 API 키를 발급받고 .env 파일에 저장했다.
GEMINI_API_KEY=발급받은_API_키
GEMINI_MODEL=사용할_모델명
API 키를 파이썬 코드에 직접 입력하면 GitHub에 함께 올라갈 수 있다. 다른 사람이 해당 키를 사용하면 비용이 발생하거나 서비스가 악용될 수 있으므로 .env는 반드시 .gitignore에 등록해야 한다.
환경변수의 형식만 보여주는 .env.example은 GitHub에 올릴 수 있지만 실제 키는 입력하지 않는다.
실제 Gemini API 싱글 턴 호출
Mock 응답을 확인한 뒤 실제 Gemini API를 호출했다.
.env 불러오기
→ API 키와 모델명 확인
→ Gemini 클라이언트 생성
→ 프롬프트 전송
→ 응답에서 텍스트 추출
load_dotenv()로 환경변수를 메모리에 올리고, os.getenv()로 API 키와 모델명을 가져왔다.
실제 API의 응답은 단순한 문자열이 아니라 여러 정보가 포함된 객체다. 따라서 화면에 답변을 보여주려면 응답에서 필요한 텍스트만 꺼내야 한다.
Gemini와 OpenAI의 전체 호출 흐름은 비슷하지만, 사용하는 SDK와 응답에서 텍스트를 추출하는 방법은 조금씩 다르다.
싱글 턴과 멀티 턴의 차이
반복문으로 여러 질문을 입력하며 싱글 턴의 특징을 확인했다.
AI에게 새로운 이름을 정해준 뒤 다시 이름을 물어봤지만, 이전 대화를 기억하지 못했다.
- 싱글 턴: 현재 질문만 전송하므로 이전 대화를 기억하지 못함
- 멀티 턴: 이전 대화를 저장하고 다음 질문과 함께 다시 전송함
멀티 턴을 구현하려면 대화 기록을 서버, 컴퓨터 또는 데이터베이스 등에 별도로 저장해야 한다. 대화가 길어지면 함께 전송하는 내용과 토큰 사용량도 증가하므로 저장 방식과 비용까지 고려해야 한다.
외부 API에 예외 처리가 필요한 이유
실습 중에는 모델 지원 여부나 무료 사용 조건 때문에 요청이 실패하기도 했다. 실제 서비스에서도 다음과 같은 문제가 발생할 수 있다.
- API 키가 없거나 잘못된 경우
- 필요한 외부 패키지가 설치되지 않은 경우
- 네트워크가 끊긴 경우
- LLM 서버가 응답하지 않는 경우
- 사용량 제한을 넘은 경우
- 설정한 모델을 더 이상 지원하지 않는 경우
따라서 실행 전에 API 키와 모델명을 확인해야 한다. 외부 API를 호출하는 부분에는 try-except를 적용해 프로그램 전체가 갑자기 종료되지 않도록 만드는 것도 중요하다.
FastAPI와 Gemini 채팅 API 연결
Gemini 호출 코드를 FastAPI와 연결했다. Swagger에서 질문을 입력하면 다음 과정으로 답변이 반환됐다.
사용자 질문
→ Chat Router
→ Chat Service
→ Gemini API
→ ChatResponse
→ Swagger 화면
Pydantic으로 요청과 응답 모델을 만들었다.
- ChatRequest: 사용자 ID와 프롬프트
- ChatResponse: Gemini가 생성한 답변
각 문자열에는 최소 한 글자 이상 입력하도록 검증 조건을 적용했다.
채팅 Router에는 다음 경로를 만들었다.
POST /chat/gemini
채팅 기능은 사용자 ID와 질문을 JSON 본문으로 전달해야 하므로 GET이 아닌 POST를 사용했다.
- GET: 저장된 정보를 조회할 때 주로 사용
- POST: 입력한 데이터를 서버에 보내 처리하거나 생성할 때 주로 사용
Router는 ChatRequest를 받아 Service에 전달하고, Service는 Gemini를 호출한 뒤 ChatResponse를 반환한다.
환경변수를 읽지 못한 오류 해결
처음에는 .env가 있는데도 Gemini API 키를 찾지 못했다.
원인은 chat_config.py를 만들어 놓기만 하고 애플리케이션 실행 과정에 연결하지 않았기 때문이었다. main.py에서 설정 파일을 import하도록 수정하자 load_dotenv()가 실행되고 Service에서도 API 키를 사용할 수 있었다.
이를 통해 파일이 존재하는 것과 실제 프로그램에서 실행되는 것은 다르다는 점을 알게 됐다.
프로젝트를 다른 폴더로 옮겼을 때는 .env의 위치도 달라졌다. 기존에는 네 단계 위를 찾았지만, 독립 프로젝트로 분리한 뒤에는 두 단계 위를 찾도록 상대 경로를 변경했다.
Product 생성·조회 API 구현
채팅 API와 같은 계층형 구조로 Product 기능도 만들었다. 이번에는 CRUD 전체 중 Create와 Read를 구현했다.
Product 모델에는 다음 정보를 정의했다.
- 상품 ID
- 상품명
- 가격
Service에는 상품 생성, 전체 조회, 특정 상품 조회 기능을 작성했다. 아직 데이터베이스를 연결하지 않았기 때문에 Mock 데이터를 반환했다.
특정 상품을 조회할 때는 Path Parameter를 사용했다.
GET /products/{product_id}
쇼핑몰 주소 뒤에 붙는 상품 번호처럼 URL의 숫자를 받아 해당 상품을 조회하는 방식이다.
처음에는 Router 함수와 Service 함수의 이름이 같아 어떤 함수를 호출하는지 충돌이 발생했다. Router 함수명을 다르게 변경해 문제를 해결했다.
완성된 채팅과 Product API는 pytest에서 6개 테스트를 모두 통과했고, Swagger에서도 정상적으로 작동했다.
독립 프로젝트 실행 환경 구성
수업 폴더 안에 있던 미니 프로젝트를 별도 폴더로 복사하고 VS Code의 새 창에서 열었다. 다른 컴퓨터에서도 실행할 수 있는 독립 프로젝트로 만들기 위해 다음 환경을 구성했다.
- 프로젝트 전용 .venv 생성
- 가상환경 활성화
- pip 업그레이드
- requirements.txt 패키지 설치
- .env와 .gitignore 설정
- 환경변수 파일의 상대 경로 수정
- pytest와 Swagger 테스트
Codex로 README 초안도 만들었다. README에는 프로젝트 구조, 설치 방법, 환경변수 설정, 서버 실행, API 경로, 테스트 방법을 정리했다.
완성한 프로젝트는 개인 비공개 GitHub 저장소에 올렸다. 코드와 테스트, requirements.txt, .gitignore, README는 정상적으로 올라갔고 실제 API 키가 들어 있는 .env는 제외된 것을 확인했다.
블로그 팀 프로젝트 시작
마지막에는 오늘 배운 계층형 구조를 활용해 블로그 팀 프로젝트를 시작했다.
| 윤기화 | 공통 구조 구성 및 LLM API 연동 |
| 장상옥 | User CRUD |
| 손영민 | Post CRUD |
| 권오현 | Comment CRUD |
바로 코드를 작성하지 않고 Markdown 계획서에 다음 내용을 먼저 정리했다.
- 전체 디렉터리와 파일 구조
- 팀원별 담당 파일
- 입력 데이터와 출력 데이터
- API 경로와 HTTP Method
- Scheme과 Service 구성
- 테스트 파일명과 검사 조건
- Git 브랜치와 Merge 방법
팀장은 기본 구조와 공통 파일을 GitHub에 올리고, 팀원들은 각자의 브랜치에서 담당 기능과 테스트 코드를 작성하기로 했다.
계획서 작성
→ 기본 구조 업로드
→ 저장소 Clone
→ 개인 브랜치 생성
→ CRUD와 테스트 구현
→ Commit·Push
→ Main 브랜치에 Merge
→ 최종본 실행 및 확인
기존 프로젝트의 파일과 브랜치가 일부 남아 새로운 구조와 섞이는 문제도 있었다. 불필요한 파일을 정리하고 main.py, routers, schemes, services가 모두 app 안에 들어가는 공통 구조를 다시 확인했다.
Codex에는 계획서를 먼저 읽게 하고, 폴더 구조를 바꾸지 않은 상태에서 담당 파일만 작성하도록 요청하기로 했다. 작업 범위가 크다는 메시지가 나오면 한 번에 전체 기능을 맡기지 않고 Scheme, Service, Router, 테스트 순서로 나누어 요청하는 방법도 사용했다.
오늘의 회고
오늘은 Mock LLM 호출에서 시작해 실제 Gemini API를 FastAPI에 연결하고, 이를 계층형 구조로 분리한 뒤 테스트와 GitHub 업로드까지 경험했다.
하나의 기능을 여러 파일로 나누는 것이 처음에는 복잡하게 느껴졌지만, Router는 요청을 받고 Service는 실제 기능을 처리하며 Scheme은 데이터 구조를 검사한다는 역할이 조금씩 분명해졌다. 이후 실제 데이터베이스를 연결해도 Service만 수정하면 Router와 Main을 대부분 유지할 수 있다는 점에서 구조 분리의 장점을 이해할 수 있었다.
팀 프로젝트에서는 코딩을 빨리 시작하는 것보다 입력값, 반환값, API 경로, 파일명과 담당 범위를 먼저 통일하는 것이 중요했다. 기존 파일과 브랜치가 섞여 시간이 걸리기도 했지만, 협업에서 계획서와 공통 구조가 왜 필요한지 직접 느낄 수 있었다.
AI가 코드와 테스트, README를 빠르게 만들어주는 것은 분명 편리하다. 하지만 원하는 결과를 얻으려면 개발자가 먼저 구조와 입력·출력, 테스트 기준을 이해하고 구체적으로 지시해야 한다는 점도 다시 확인했다.
댓글