FastAPI 서버 (AI 기능 백엔드 / 머신러닝 마이크로서비스)
역할: 오직 LLM과 관련된 복잡하고 전문적인 작업을 처리하는 특화된 백엔드
주요 책임
## FastAPI 서버 개발 및 디버깅 최종 정리
7.24(금)
1. 주요 오류 및 해결 과정
1.1. LangChain 파이프라인 데이터 흐름 오류
- 에러: TypeError: argument 'text': 'dict' object cannot be converted to 'PyString'
- 이유: retriever(검색기)가 **문자열(text)**을 입력받아야 하는데, {"question": "..."} **딕셔너리(dict)**를 통째로 입력받아 발생했습니다.
- **해결 방법**: `chain`의 데이터 흐름을 수정하여, `retriever`가 딕셔너리 대신 **사용자의 질문 문자열**을 입력으로 받도록 파이프라인 구조를 변경했습니다.
- **수정 전 코드 (`rag_pipeline.py`)**
Python
```
# retriever가 딕셔너리를 통째로 입력받아 오류 발생
chain = (
{"context": retriever, "question": lambda x: x["question"]}
| real_estate_prompt
| llm
| StrOutputParser()
)
```
- **수정 후 코드 (`rag_pipeline.py`)**
Python
```
# retriever 앞에 "(lambda x: x["question"]) |"를 추가하여
# 입력된 딕셔너리에서 질문(문자열)만 꺼내서 retriever에게 전달하도록 수정
chain = (
{
"context": (lambda x: x["question"]) | retriever,
"question": (lambda x: x["question"])
}
| real_estate_prompt
| llm
| StrOutputParser()
)
```
1.2 Pydantic alias 기능 충돌 오류 (최종)
- 에러: ValidationError: 1 validation error for ChatResponse. sessionId Field required
- 이유: ChatResponse 모델에 alias='sessionId'가 설정된 상태에서, 객체를 생성할 때 파이썬 변수명인 session_id를 키워드 인자로 사용하자 Pydantic 라이브러리가 이를 인식하지 못하는 매우 드문 충돌이 발생했습니다.
- **해결 방법**: `ChatResponse` 모델 객체를 생성할 때, 파이썬 변수명(`session_id`) 대신 **`alias`로 지정한 이름(`sessionId`)을 직접 키워드로 사용**하여 Pydantic의 내부적인 충돌을 우회했습니다.
- **수정 전 코드 (`api.py`)**
Python
```
# alias가 설정된 모델에 파이썬 변수명으로 값을 전달하여 오류 발생
return ChatResponse(
response="...",
session_id="...", # 이 부분에서 Pydantic이 sessionId를 인식 못함
success=True
)
```
- **수정 후 코드 (`api.py`)**
Python
```
# alias로 지정한 'sessionId'를 직접 키워드로 사용하여 충돌을 우회
return ChatResponse(
response="...",
sessionId="...", # session_id -> sessionId 로 키워드 직접 변경
success=True
)
```
1.3. 클라이언트-서버 응답 모델 불일치 오류
- 에러: ValidationError 또는 클라이언트(스프링부트) 측의 파싱 오류
- 이유: FastAPI가 보내는 JSON 응답의 구조가 스프링부트가 기대하는 Java 클래스의 구조와 달라 발생했습니다.
- Case 1 (필드 누락): return 구문에서 필수 필드인 session_id를 전달하지 않았습니다.
- Case 2 (필드명 불일치): FastAPI 모델에는 used_mydata: bool 필드가 있었지만, 스프링부트는 success: bool 필드를 필수로 요구했습니다.
- **수정 전 코드 (`api.py`)**
Python
```
# 모델 필드가 클라이언트 요구사항과 달랐음
class ChatResponse(BaseModel):
response: str
used_mydata: bool # success 필드 대신 잘못된 필드가 있었음
# return 구문에서 필수 필드 누락 및 잘못된 필드 전달
return ChatResponse(
response="...",
# session_id=... 부분이 없었음
used_mydata=True
)
```
- **수정 후 코드 (`api.py`)**
Python
```
# 스프링부트 클래스와 동일하게 필드를 수정
class ChatResponse(BaseModel):
response: str
session_id: str = Field(alias='sessionId')
success: bool # 올바른 필드명으로 수정
# return 구문에서 모든 필수 필드를 올바르게 전달
return ChatResponse(
response="...",
sessionId="...",
success=True
)
```
2. 재발 방지를 위한 핵심 원칙
2.1. 명확한 API 명세(Contract) 정의
- 문제점: 클라이언트(스프링부트)와 서버(FastAPI)가 주고받을 데이터의 구조가 달라 ValidationError, 필드 누락 등의 오류가 발생했습니다.
- 예방책
- API 문서 공유: FastAPI의 자동 생성 문서 (/docs)를 활용해 양측 개발자가 필드명, 데이터 타입, 필수 여부를 사전에 정확히 맞춥니다.
- 네이밍 컨벤션 통일: API를 통해 주고받는 JSON 데이터는 camelCase(예: sessionId)로 통일하기로 약속합니다. 파이썬에서는 alias를 사용해 이를 맞춰줍니다.
프롬프트(Prompt)
샷(Shot)
에이전트(Agent)
툴(Tool)
결론
프롬프트는 LLM에게 제공하는 형식 같은 것이며 그 안에 샷(예시)가 포함되어 있음
에이전트는 스스로 생각해서 사용자의 발화를 분석하고 프롬프트에 따라서 툴을 선택하여 서비스를 제공함
좋았던 점
어쩌다 보니 FastAPI를 맡게 되었다 배워본 적도 없고 하나도 모르던 터라 걱정을 많이 했지만 이왕 맡게 된 김에 한 번 배워보자 라느나 마음으로 시작했다 처음에는 흐름을 이해하는 것이 어려웠지만 코드를 보면서 공부하니 조금씩 감을 잡았다 무엇보다 팀원분이 도와주셔서 훨씬 수월하게 진행할 수 있었고 덕분에 많은 것을 배울 수 있었다 아직 부족하지만 FastAPI를 접해볼 수 있었던 좋은 기회였다
아쉬운 점
전체적인 흐름을 이해하는 데 여전히 어려움을 느꼈다 어렵다고 느끼는 순간 스스로 깊이 고민하거나 이해하려는 노력을 충분히 하지 않았던 것 같다 완벽하게 이해하지 못하면 쉽게 포기하려는 경향이 있는데 이번에도 그런 모습이 보였던 것 같다 이런 습관을 꼭 고치고 개선해야한다고 생각한다
개선 방향
앞으로는 처음부터 완벽하게 이해하려 하기보다는 전체적인 흐름이나 감을 먼저 잡고 코드를 작성해보면서 몸으로 익히는 방식을 시도해보려고 한다 LLM을 실제로 돌려보면서 동작을 체감하고 그 과정을 통해 이해를 쌓아가야겠다 이제 한 주밖에 남지 않았지만 남은 시간 동안 더 열심히 참여해서 랭체인에 대한 이해를 넓히고 프로젝트도 꼭 완성하고 싶다