본문 바로가기
AI 오케스트레이션 캠프/일차별 회고

13일차 회고|Streamlit·FastAPI API 연동과 팀 프로젝트|AI 오케스트레이션 개발자 국비지원

by 랩보다 AI 더 잘해지기 2026. 7. 28.
728x90

도입부

오늘은 지금까지 따로 연습했던 Streamlit 프론트엔드와 FastAPI 백엔드를 실제로 연결하는 과정을 집중적으로 학습했다.

Streamlit에서 버튼을 누르면 FastAPI 서버에 요청을 보내고, 서버가 반환한 JSON 데이터를 화면에 표와 차트로 출력했다. 같은 네트워크에 연결된 팀원의 컴퓨터를 서버로 사용해 보는 실습도 진행하면서 127.0.0.1, 내부 IP 주소, 포트 번호가 어떤 역할을 하는지도 확인했다.

오후에는 프론트엔드와 백엔드를 나눈 팀 프로젝트를 시작했다. 우리 팀은 학생 성적 데이터를 주제로 정하고, FastAPI가 제공하는 성적 데이터를 Streamlit에서 차트와 테이블로 보여주는 대시보드를 만들기 시작했다.


주요 학습 내용

REST API와 FastAPI의 차이

REST API와 FastAPI가 계속 함께 등장해 처음에는 같은 개념처럼 느껴졌다.

두 개의 차이를 간단하게 정리하면 다음과 같다.

REST API = API를 설계하는 방식
FastAPI = REST API 서버를 만드는 파이썬 도구

예를 들어 학생 목록을 조회하는 기능을 다음처럼 설계했다면,

GET /students

이것은 REST API 방식이다.

FastAPI에서는 이를 다음처럼 실제 코드로 구현할 수 있다.

@app.get("/students")
def get_students():
    return {"students": []}

즉, REST API는 서버와 통신하는 약속이고 FastAPI는 그 약속에 맞는 서버를 만드는 도구라고 이해했다.


GET·POST·PUT·PATCH·DELETE

HTTP 요청 방식도 다시 정리했다.

GET     데이터 조회
POST    데이터 생성
PUT     데이터 전체 수정
PATCH   데이터 일부 수정
DELETE  데이터 삭제

오늘 실습에서는 주로 GET과 POST를 사용했다.

GET은 FastAPI에서 상태나 목록을 가져올 때 사용했다.

response = httpx.get(
    "http://127.0.0.1:8000/health",
    timeout=5.0,
)

POST는 Streamlit 화면에서 입력한 이름이나 메시지를 백엔드에 전달할 때 사용했다.

payload = {
    "name": name,
    "message": message,
}

response = httpx.post(
    "http://127.0.0.1:8000/messages",
    json=payload,
    timeout=5.0,
)

여기서 payload는 서버에 전달하는 실제 데이터 내용이다.


Streamlit과 FastAPI의 통신 구조

오늘 수업에서 가장 중요했던 흐름은 다음과 같다.

사용자가 버튼 클릭
→ Streamlit 프론트엔드
→ httpx로 FastAPI 호출
→ FastAPI가 요청 처리
→ JSON 응답
→ Streamlit 화면에 결과 출력

Streamlit은 화면을 보여주고 사용자 입력을 받는다. FastAPI는 요청을 받아 데이터를 조회하거나 처리한다.

두 서버는 각각 다른 포트를 사용했다.

FastAPI   http://127.0.0.1:8000
Streamlit http://127.0.0.1:8501

127.0.0.1은 현재 사용 중인 컴퓨터 자기 자신을 의미한다. 따라서 다른 팀원의 컴퓨터에 접속하려면 127.0.0.1이 아니라 서버를 실행한 컴퓨터의 실제 내부 IP 주소를 사용해야 한다.


httpx로 API 요청하기

Streamlit이나 일반 파이썬 코드에서 API를 호출할 때 httpx를 사용했다.

import httpx

response = httpx.get(
    API_URL,
    timeout=5.0,
)

response에는 상태 코드, 응답 데이터, 헤더 등 서버가 보낸 전체 응답 정보가 들어 있다.

print(response.status_code)

JSON 데이터만 파이썬에서 사용하려면 다음처럼 변환한다.

result = response.json()

API가 게시글 여러 개를 반환한 경우 결과는 리스트가 된다.

for post in result:
    print(post["userId"])
    print(post["id"])
    print(post["title"])
    print(post["body"])

이때 userId는 작성자 번호라 반복될 수 있지만, id는 게시글 자체의 번호이므로 게시글마다 달랐다.


JSON과 파이썬 자료형

프론트엔드와 백엔드는 주로 JSON 형식으로 데이터를 주고받는다.

예를 들어 학생 한 명의 데이터는 다음과 같은 형태로 만들 수 있다.

{
  "id": 1,
  "subject": "Python",
  "score": 100
}

여러 학생 데이터를 전달하면 리스트 안에 딕셔너리가 들어가는 구조가 된다.

[
    {"id": 1, "subject": "Python", "score": 100},
    {"id": 2, "subject": "Streamlit", "score": 95},
]

FastAPI에서 받은 JSON은 response.json()을 사용하면 파이썬의 리스트나 딕셔너리처럼 다룰 수 있다.


HTTP 상태 코드와 예외 처리

API 요청 결과는 상태 코드로 확인할 수 있다.

200 정상 처리
201 데이터 생성 성공
404 요청한 주소 또는 데이터가 없음
500 백엔드 내부 오류

예를 들어 실제 경로가 /health인데 /healths로 요청하면 서버까지는 도착하지만 해당 경로가 없으므로 404가 반환된다.

반면 백엔드가 실행되지 않았거나 잘못된 포트로 접속하면 상태 코드조차 받지 못하고 연결 오류가 발생한다.

404
→ 서버는 실행 중이지만 요청한 경로가 없음

ConnectError
→ 서버가 꺼져 있거나 해당 주소와 포트에 접속할 수 없음

그래서 상태 코드 확인과 try-except를 함께 사용해야 한다.

try:
    response = httpx.get(API_URL, timeout=5.0)

    if response.status_code == 200:
        st.success("요청에 성공했습니다.")
    elif response.status_code == 404:
        st.warning("API 경로를 찾을 수 없습니다.")
    else:
        st.error(f"요청 실패: {response.status_code}")

except httpx.TimeoutException:
    st.error("서버 응답 시간이 초과되었습니다.")

except httpx.RequestError:
    st.error("서버에 연결할 수 없습니다.")

스피너로 로딩 상태 보여주기

API나 AI 모델은 응답이 바로 오지 않을 수 있다. 아무런 안내가 없으면 사용자는 화면이 멈췄다고 생각할 수 있다.

이때 st.spinner()를 사용했다.

with st.spinner("데이터를 가져오는 중입니다..."):
    response = httpx.get(
        API_URL,
        timeout=10.0,
    )

with 안의 작업이 끝날 때까지 스피너가 나타나고, 응답이 도착하면 자동으로 사라진다.

앞으로 Gemini나 AI 에이전트를 호출할 때도 필요한 기능이라고 이해했다.


외부 날씨 API 연동

Open-Meteo API를 이용해 서울, 부산, 대전, 제주의 날씨 데이터를 가져오는 실습도 진행했다.

Open-Meteo는 도시 이름이 아니라 위도와 경도를 전달해야 했다.

CITIES = {
    "서울": {
        "latitude": 37.5665,
        "longitude": 126.9780,
    },
    "부산": {
        "latitude": 35.1796,
        "longitude": 129.0756,
    },
}

선택한 도시의 좌표를 API에 전달했다.

location = CITIES[city]

response = httpx.get(
    WEATHER_URL,
    params={
        "latitude": location["latitude"],
        "longitude": location["longitude"],
        "hourly": (
            "temperature_2m,"
            "relative_humidity_2m,"
            "precipitation_probability"
        ),
        "forecast_days": forecast_days,
        "timezone": "Asia/Seoul",
    },
    timeout=10.0,
)

받은 시간별 날씨 데이터는 Pandas의 DataFrame으로 변환했다.

hourly_data = response.json()["hourly"]
weather_df = pd.DataFrame(hourly_data)

이후 컬럼 이름을 한글로 변경하고 표와 선그래프로 출력했다.

weather_df = weather_df.rename(
    columns={
        "time": "시간",
        "temperature_2m": "기온",
        "relative_humidity_2m": "습도",
        "precipitation_probability": "강수확률",
    }
)

실습 및 문제 해결

다른 컴퓨터에서 서버에 접속하기

팀원 한 명이 FastAPI 서버를 실행하고, 다른 팀원이 Streamlit 프론트엔드를 실행한 뒤 나머지 팀원들이 브라우저로 접속하는 실습을 진행했다.

다른 컴퓨터에서도 접속할 수 있도록 FastAPI를 다음처럼 실행했다.

uvicorn main:app --reload --host 0.0.0.0 --port 8000

Streamlit도 외부 접속이 가능하도록 실행할 수 있다.

py -m streamlit run app.py --server.address 0.0.0.0

여기서 0.0.0.0은 서버를 여러 네트워크 연결에 열어두는 설정이다. 실제 브라우저나 API 주소에는 서버 컴퓨터의 내부 IP 주소를 사용해야 한다.


IP 주소가 서로 달랐던 문제

팀원들은 192.100.200.xxx 형태의 IP를 사용하고 있었지만, 내 컴퓨터는 192.168.1.xxx 형태로 나왔다.

이는 컴퓨터가 서로 다른 네트워크나 어댑터를 사용하고 있을 가능성이 있다는 의미였다.

다음 명령으로 현재 네트워크 정보를 확인했다.

ipconfig

Wi-Fi, 유선 랜, 가상 네트워크 어댑터 중 실제 사용 중인 어댑터의 IPv4 주소를 확인해야 했다.


http:// 누락 오류

FastAPI 주소를 다음처럼 작성하면 오류가 발생했다.

API_BASE_URL = "192.100.200.198:8000"

httpx는 통신 방식을 알아야 하므로 앞에 http://가 필요하다.

API_BASE_URL = "http://192.100.200.198:8000"

오류 메시지에 http:// 또는 https:// 프로토콜이 없다고 표시되어 원인을 확인할 수 있었다.


연결 거부 오류

다음과 같은 오류도 반복해서 발생했다.

WinError 10061
대상 컴퓨터에서 연결을 거부했으므로 연결하지 못했습니다.

이 문제는 방화벽뿐 아니라 다음 원인으로 발생할 수 있었다.

FastAPI 서버가 실행되지 않음
잘못된 포트 사용
잘못된 IP 주소 사용
127.0.0.1로만 서버 실행
프론트엔드와 백엔드가 서로 다른 네트워크 사용

먼저 백엔드 컴퓨터에서 Swagger에 접속되는지 확인하는 것이 중요했다.

http://127.0.0.1:8000/docs

그다음 프론트엔드 컴퓨터에서 백엔드 담당자의 실제 IP로 접속되는지 확인했다.


st.set_page_config() 중복 오류

로컬에서는 실행되지만 Streamlit Cloud에 배포했을 때 다음 오류가 발생했다.

StreamlitSetPageConfigMustBeFirstCommandError

멀티페이지 앱의 하위 파일에도 st.set_page_config()가 들어 있어 발생한 문제였다.

안전하게 사용하려면 공통 시작 파일인 app.py에만 한 번 작성해야 한다.

app.py
→ st.set_page_config() 사용

하위 페이지 파일
→ st.set_page_config() 제거

Git 저장소 위치 문제

프로젝트 폴더가 같은 이름으로 한 번 더 들어가면서 Git 명령이 실행되지 않는 문제도 있었다.

C:\mini\team01_0728
└─ team01_0728
   └─ .git

바깥 폴더에서 git status를 실행하면 .git이 없기 때문에 다음 오류가 발생했다.

fatal: not a git repository

원하는 위치에 저장소를 다시 clone하고, 개인 브랜치에서 main의 최신 내용을 가져오는 흐름도 정리했다.

git switch jso
git pull origin main
git push origin jso

팀 프로젝트에서는 각자 개인 브랜치에서 작업하고, 검토가 끝난 코드만 main에 반영하는 것이 안전하다.


팀 프로젝트: 학생 성적 대시보드

오후에는 학생 10명의 Python, Streamlit, FastAPI 성적 데이터를 보여주는 대시보드를 만들기 시작했다.

프로젝트는 다음처럼 나누어 진행했다.

FastAPI 백엔드
→ 학생 점수 JSON 데이터 제공

Streamlit 프론트엔드
→ 데이터 조회
→ DataFrame 변환
→ 차트와 테이블 출력

현재 프론트엔드에서는 백엔드가 완성되지 않아도 작업할 수 있도록 샘플 데이터를 함께 준비했다.

SAMPLE_DATA = [
    {"id": 1, "subject": "Python", "score": 100},
    {"id": 2, "subject": "Streamlit", "score": 95},
    {"id": 3, "subject": "FastAPI", "score": 90},
]

API 연결 여부에 따라 샘플 데이터와 실제 데이터를 선택할 수 있도록 만들었다.

if use_sample:
    raw_data = SAMPLE_DATA
else:
    raw_data = fetch_students(api_url)

가져온 데이터는 필수 컬럼을 검사한 뒤 DataFrame으로 변환했다.

required = {"id", "subject", "score"}

화면에는 다음 정보를 표시했다.

  • 응시 학생 수
  • 전체 평균
  • 최고 점수와 학생 번호
  • 90점 이상 인원
  • 과목별 평균 막대그래프
  • 학생별 점수 선그래프
  • 학생 번호·과목·점수·등급 테이블

이 과정에서 백엔드와 프론트엔드가 같은 JSON 키 이름을 사용해야 한다는 점도 중요했다.

백엔드가 subject로 전송
→ 프론트엔드도 subject로 조회

백엔드가 score로 전송
→ 프론트엔드도 score로 조회

데이터 구조를 먼저 정하지 않으면 백엔드는 정상이어도 프론트엔드에서 KeyError가 발생할 수 있다.


오늘의 회고

오늘은 단순히 Streamlit 화면을 만드는 단계를 넘어, 브라우저와 프론트엔드, 백엔드가 어떤 순서로 통신하는지 조금씩 이해할 수 있었다.

특히 404와 연결 오류가 서로 다른 문제라는 점이 기억에 남았다. 서버에 도착했지만 경로가 없는 경우와, 서버에 아예 접속하지 못한 경우는 처리 방법부터 달랐다.

팀 실습에서는 코드를 작성하는 것만큼 폴더 구조, API 주소, JSON 형식, 브랜치 운영 규칙을 먼저 정하는 것이 중요했다. 프론트엔드와 백엔드가 따로 개발되더라도 데이터 형식을 먼저 공유하면 각자 동시에 작업할 수 있다는 점도 새롭게 이해했다.

아직 네트워크와 Git 브랜치 작업은 명령어를 바로 떠올리기 어렵다. 다음 복습에서는 다음 흐름을 직접 다시 실행해 봐야겠다.

FastAPI 실행
→ Swagger 확인
→ Streamlit 실행
→ httpx 요청
→ 상태 코드 확인
→ JSON 변환
→ DataFrame 생성
→ 차트와 테이블 출력

이번 프로젝트에서는 기능을 크게 늘리기보다, 학생 성적 데이터가 FastAPI에서 Streamlit까지 정확히 전달되고 배포 환경에서도 정상적으로 실행되도록 완성하는 것을 우선 목표로 삼아야겠다.

해시태그

#SK네트웍스Family #엔코아AI캠퍼스 #AI오케스트레이션 #멀티AI에이전트 #국비지원개발자과정 #Streamlit #FastAPI #RESTAPI #Python개발 #GitHub협업

댓글