LangChain + FastAPI로 API 서버 만들기

📌 3줄 요약
· LangChain 체인을 노트북 안에만 두지 않고, FastAPI로 감싸면 누구나 호출할 수 있는 API 서버가 됩니다.
· 핵심은 요청(request) → 체인 실행 → 응답(response)의 흐름이며, FastAPI의 엔드포인트 함수 안에서 체인을 invoke하는 것이 전부입니다.
· 입력·출력 형식은 Pydantic 모델로 정의하고, 긴 답변은 스트리밍으로 흘려보내면 실제 서비스에 가깝게 다듬을 수 있습니다.
🌐 HTTP 요청 → 🚪 FastAPI 엔드포인트 → 🔗 체인 실행(invoke) → ✅ JSON 응답

지금까지는 LangChain 체인을 주로 노트북이나 터미널 안에서 직접 실행해 봤습니다. 그런데 만든 체인을 웹·앱·다른 서버에서 가져다 쓰려면, 결국 "밖에서 호출할 수 있는 창구"가 필요합니다. 그 창구가 바로 API 서버이고, 파이썬에서 이를 가장 가볍게 만들 수 있는 도구가 FastAPI입니다. 오늘은 완성한 체인을 FastAPI로 감싸 호출 가능한 API로 바꾸는 과정을 차근차근 따라가 보겠습니다.


🧩 왜 체인을 API로 감싸야 할까

잘 만든 체인이라도, 코드 안에서만 돌아간다면 그 코드를 실행할 수 있는 사람만 쓸 수 있습니다. 프런트엔드 개발자나 모바일 앱, 또는 다른 백엔드 서비스가 이 체인을 쓰고 싶어도 직접 파이썬을 돌릴 수는 없는 경우가 대부분입니다.

그래서 체인을 HTTP로 호출할 수 있는 형태로 열어 두는 것이 중요합니다. 한번 API로 감싸 두면, 어떤 언어·어떤 환경에서도 주소로 요청만 보내면 체인의 결과를 받아 갈 수 있습니다. 즉 API 서버는 체인과 바깥 세상을 이어 주는 다리인 셈입니다.

체인을 API로 감싸는 일은 "혼자 쓰던 기능"을 "누구나 호출할 수 있는 서비스"로 바꾸는 작업입니다.

🚪 FastAPI와 체인이 만나는 지점

FastAPI와 LangChain의 결합은 생각보다 단순합니다. FastAPI가 맡는 일과 체인이 맡는 일을 카드로 나눠 보면 역할이 또렷해집니다.

🚪 FastAPI
외부 요청을 받고, 입력을 검증하고, 체인의 결과를 JSON으로 돌려주는 창구 역할을 합니다.
🔗 LangChain 체인
전달받은 입력으로 실제 작업(LLM 호출 등)을 수행해 답을 만들어 냅니다.
📨 엔드포인트 함수
둘을 잇는 접점으로, 요청에서 꺼낸 값을 체인에 넘기고 결과를 받아 응답으로 포장합니다.

정리하면, FastAPI가 요청을 받아 엔드포인트 함수로 넘기고, 그 함수 안에서 체인을 실행해 답을 얻은 뒤, 다시 FastAPI가 응답으로 포장해 돌려줍니다. 결국 우리가 새로 쓸 코드는 "엔드포인트 함수 안에서 체인을 invoke하는 몇 줄"이 중심이 됩니다.


📋 입력·출력 형식 정의하기

API를 열 때 가장 먼저 정해야 할 것은 "어떤 형태의 데이터가 오가는가"입니다. FastAPI는 Pydantic 모델로 이 형식을 선언하도록 되어 있습니다. 입력 모델을 정의해 두면, 형식에 맞지 않는 요청은 FastAPI가 알아서 걸러 주고, 잘못된 부분을 친절한 오류 메시지로 알려 줍니다.

예를 들어 질문을 받아 답을 돌려주는 API라면, 입력에는 question이라는 문자열 필드가, 출력에는 answer라는 문자열 필드가 있으면 충분합니다. 이렇게 형식을 미리 선언해 두면 코드가 명확해지고, 자동 생성되는 API 문서에도 그대로 반영됩니다.


⚙️ 코드로 살펴보기

이제 실제로 체인을 FastAPI로 감싸 보겠습니다. 먼저 간단한 체인과 입력·출력 모델을 준비합니다. 체인은 앞서 다뤄 온 "프롬프트 | 모델 | 파서" 형태 그대로입니다.

from fastapi import FastAPI
from pydantic import BaseModel
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

# 1) 체인 준비
prompt = ChatPromptTemplate.from_template("질문에 한국어로 답해라.\n질문: {question}")
chain = prompt | ChatOpenAI(temperature=0) | StrOutputParser()

# 2) 입력·출력 형식 정의
class AskRequest(BaseModel):
    question: str

class AskResponse(BaseModel):
    answer: str

다음으로 FastAPI 앱을 만들고, /ask라는 엔드포인트를 추가합니다. 핵심은 함수 안에서 요청으로 받은 값을 체인에 넘기고, 그 결과를 응답 모델에 담아 돌려주는 부분입니다.

app = FastAPI()

@app.post("/ask", response_model=AskResponse)
async def ask(req: AskRequest):
    # 요청에서 꺼낸 question을 체인에 넘긴다
    result = await chain.ainvoke({"question": req.question})
    return AskResponse(answer=result)

이 파일을 main.py로 저장했다면, 아래 명령으로 서버를 띄울 수 있습니다. 그러면 /ask 주소로 질문을 POST하는 순간 체인이 실행되고, 답이 JSON으로 돌아옵니다.

# 터미널에서 서버 실행
uvicorn main:app --reload

# 브라우저에서 http://127.0.0.1:8000/docs 를 열면
# 자동 생성된 API 문서로 바로 테스트할 수 있다

여기서 눈여겨볼 점은, FastAPI의 엔드포인트를 async 함수로 두고 체인도 ainvoke(비동기 실행)로 호출했다는 것입니다. LLM 호출처럼 기다림이 많은 작업은 비동기로 처리해야, 한 요청이 끝나길 기다리는 동안 다른 요청도 함께 받을 수 있습니다.


📡 스트리밍 응답 다루기

LLM의 답은 길어질 수 있어서, 완성될 때까지 통째로 기다리면 사용자가 한참 빈 화면을 보게 됩니다. 이럴 때는 답을 조각조각 흘려보내는 스트리밍이 유용합니다. FastAPI에서는 StreamingResponse를 쓰고, 체인 쪽에서는 astream으로 토큰을 하나씩 받아 그대로 내보내면 됩니다.

from fastapi.responses import StreamingResponse

@app.post("/ask-stream")
async def ask_stream(req: AskRequest):
    async def gen():
        # 체인이 만드는 토큰을 받는 대로 흘려보낸다
        async for chunk in chain.astream({"question": req.question}):
            yield chunk
    return StreamingResponse(gen(), media_type="text/plain")

이렇게 하면 답이 생성되는 즉시 조금씩 전달되어, 사용자는 글자가 타이핑되듯 차오르는 화면을 보게 됩니다. 체감 속도가 크게 달라지는 부분이라, 대화형 서비스라면 거의 기본으로 챙기는 방식입니다.


🔧 실무 팁

  • 비동기 호출을 기본으로 — LLM 요청은 대기가 길므로 엔드포인트를 async + ainvoke로 두어야, 동시에 들어오는 여러 요청을 효율적으로 받을 수 있습니다.
  • API 키는 코드 밖에 — OpenAI 키 같은 비밀값은 소스에 직접 쓰지 말고 환경 변수로 두어, 코드가 공유돼도 키가 새지 않게 합니다.
  • 자동 문서를 활용 — FastAPI는 /docs에 대화형 문서를 자동으로 만들어 줍니다. 입력·출력 모델을 잘 정의할수록 이 문서가 더 정확해집니다.
  • 오류 처리도 미리 — 체인 실행이 실패할 수 있으니, 예외를 받아 적절한 상태 코드와 메시지로 돌려주면 호출하는 쪽이 원인을 파악하기 쉽습니다.

❓ 자주 묻는 질문 (FAQ)

Q. 꼭 FastAPI여야 하나요? Flask로는 안 되나요?
A. Flask로도 가능합니다. 다만 FastAPI는 비동기 처리와 Pydantic 기반 입력 검증, 자동 문서 생성을 기본으로 지원해, LLM처럼 대기가 긴 작업을 API로 여는 데 특히 잘 맞습니다.

Q. LangServe라는 것도 들었는데, 이것과 뭐가 다른가요?
A. LangServe는 체인을 더 적은 코드로 API에 붙이도록 도와주는 도구입니다. 내부적으로는 역시 FastAPI 위에서 동작하므로, 오늘처럼 직접 엔드포인트를 짜는 방식을 이해해 두면 LangServe의 동작도 훨씬 수월하게 받아들일 수 있습니다.

Q. 요청마다 체인을 새로 만들어야 하나요?
A. 아닙니다. 체인은 서버가 뜰 때 한 번만 만들어 두고, 각 요청에서는 그 체인을 invoke만 하면 됩니다. 매 요청마다 다시 만들면 불필요한 비용과 지연이 생깁니다.


마무리

정리하면, LangChain 체인을 FastAPI로 감싸는 일은 요청 → 체인 실행 → 응답이라는 간단한 흐름을 코드로 옮기는 작업이었습니다. 핵심은 엔드포인트 함수 안에서 체인을 invoke하는 것이었고, 입력·출력은 Pydantic 모델로 명확히 선언해 FastAPI가 검증과 문서화를 대신 맡도록 했습니다. 또한 LLM 호출은 대기가 길기 때문에 비동기 처리를 기본으로 두고, 긴 답변은 스트리밍으로 흘려보내 체감 속도를 끌어올릴 수 있다는 점도 살펴봤습니다. 이렇게 한 번 API로 열어 두면, 혼자 쓰던 체인이 누구나 호출할 수 있는 서비스로 거듭납니다. 다음 편에서는 이렇게 만든 API에 인증과 사용량 관리를 더해 실제 운영에 가깝게 다듬는 이야기로 이어가 보겠습니다.

댓글

이 블로그의 인기 게시물

LCEL — LangChain 표현식으로 체인 깔끔하게 잇기

Streaming — 답변을 실시간으로 흘려보내기

Callback와 로깅 — LangChain 내부 들여다보기