LangChain 데모에서 프로덕션 준비된 FastAPI 백엔드로
Source: Dev.to
(번역할 텍스트가 제공되지 않았습니다. 번역이 필요한 본문을 함께 알려주시면 한국어로 번역해 드리겠습니다.)
Source: …
왜 LangChain은 적절한 백엔드 아키텍처가 필요한가
대부분의 LangChain 예제는 실제 백엔드 작업이 시작되는 지점에서 멈춥니다.
많은 AI 예제가 노트북, 스크립트, 혹은 Streamlit 데모 형태로 존재하지만, 프로덕션 백엔드 시스템 안에서 실행되어야 할 때 곧 무너지게 됩니다. AI가 API의 일부가 되면, 다른 백엔드 컴포넌트와 동일한 규칙을 따라야 합니다: 입력과 출력은 명확히 정의되어야 하고, 의존성은 명시적이어야 하며, 전체 구조는 모든 것을 다시 작성하지 않고도 변경을 허용해야 합니다.
이 글은 바로 그 시작점을 다룹니다.
우리는 LangChain을 백엔드 친화적인 방식으로 통합한 FastAPI 엔드포인트를 깔끔하고 유지보수하기 쉬운 형태로 구축할 것입니다. 목표는 단계별로 확장할 수 있는 견고한 아키텍처 기반을 만드는 것입니다. 현재 단계에서는 구현을 의도적으로 단순하게 유지하고; 이후 글에서는 이 기본 위에 더 고급 LLM 및 에이전트 기능을 점진적으로 도입할 예정입니다.
여기서의 초점은 LangChain 기능을 보여주는 것이 아니라, 복잡성이 증가함에 따라 이해하기 쉽고, 테스트 가능하며, 확장 가능한 명확하고 견고한 엔드포인트 아키텍처를 정의하는 데 있습니다.
AI를 백엔드 컴포넌트로 생각하기
코드를 보기 전에, 백엔드 시스템 안에서 AI를 어떻게 다루어야 할지에 대해 합의하는 것이 중요합니다. 목표는 LLM을 직접 노출하는 것이 아니라, 안정적이고 예측 가능한 API 뒤에 AI 로직을 내장하는 것입니다.
백엔드에 적합한 AI 엔드포인트는 다음과 같은 보장을 제공해야 합니다:
- 명확한 요청 및 응답 계약
- 의존성의 명시적 오케스트레이션
- HTTP 관점과 분리된 AI 로직 캡슐화
- 다른 시스템이 검증하고 활용할 수 있는 예측 가능한 출력
FastAPI는 이미 Pydantic 모델과 의존성 주입을 통해 구조를 강제하기 때문에 이 모델에 자연스럽게 맞습니다. 이를 통해 특수 케이스나 임시 결합 코드를 사용하지 않고도 LangChain을 통합할 수 있습니다.
Pydantic으로 계약 정의하기
첫 번째 빌딩 블록은 엄격한 API 계약입니다. 입력과 출력은 Pydantic 모델을 사용해 명시적으로 정의됩니다.
# Request model
class InsightQuery(BaseModel):
question: str
context: str
# Response model
class Insight(BaseModel):
title: str
summary: str
confidence: float
@field_validator("confidence")
@classmethod
def clamp_confidence(cls, v):
"""Clamp confidence to the range [0.0, 1.0]."""
if v is None:
return 0.0
if v 1:
return 1.0
return float(v)
이 계약은 기본 AI 로직이 어떻게 변하든 API가 예측 가능하도록 보장합니다. confidence 검증자는 중요한 원칙을 보여줍니다: AI가 완벽하지 않은 값을 생성하더라도, 백엔드가 응답을 반환하기 전에 일관성을 강제한다는 점입니다. 이를 하지 않으면 LLM 출력이 급격히 예측 불가능해져 실제 시스템에 통합하기 어려워집니다.
FastAPI Depends 로 LLM 주입하기
엔드포인트 내부(또는 체인 내부)에서 LLM을 직접 생성하는 대신, FastAPI 의존성을 사용해 주입합니다.
# FastAPI endpoint definition
@router.post(path="/query", response_model=Insight)
def create_insight(
request: InsightQuery,
settings: Settings = Depends(get_settings),
llm: BaseChatModel = Depends(init_openai_chat_model),
):
...
언어 모델 자체는 별도의 의존성 함수에서 초기화됩니다.
def init_openai_chat_model(settings: Settings = Depends(get_settings)):
"""
Initializes and returns the LangChain OpenAI chat model.
"""
return ChatOpenAI(
model=settings.openai_model.model_name,
temperature=settings.openai_model.temperature,
api_key=settings.openai_model.api_key,
)
장점
- 엔드포인트는 오케스트레이션에만 집중합니다.
- 설정이 중앙 집중화됩니다.
- 테스트 시 LLM을 쉽게 교체하거나 모킹할 수 있습니다.
FastAPI 관점에서 보면, 언어 모델은 단순히 또 다른 의존성일 뿐입니다.
Source:
dependency—no different from a database session or a service client.
LangChain 로직 캡슐화
LangChain 로직 자체는 전용 함수에 캡슐화됩니다. 엔드포인트는 체인이 어떻게 구축되고 실행되는지 알 필요가 없습니다.
def run_insight_chain(
prompt_messages: ChatModelPrompt,
llm: BaseChatModel,
question: str,
context: str,
) -> Insight:
"""
Builds and runs the LangChain insight chain.
"""
prompt_template = ChatPromptTemplate([
("system", prompt_messages.system),
("human", prompt_messages.human),
])
parser = PydanticOutputParser(pydantic_object=Insight)
chain = prompt_template | llm | parser
response = chain.invoke({
"format_instruction": parser.get_format_instructions(),
"question": question,
"context": context,
})
return response
이 설계는 관심사를 깔끔하게 분리합니다. 프롬프트 구성, 모델 실행, 출력 파싱이 한 곳에 모여 있으며, 애플리케이션의 나머지 부분은 입력과 출력만 다룹니다.
FastAPI 엔드포인트에서 모든 것을 조정하기
엔드포인트는 이제 얇은 오케스트레이션 레이어가 됩니다.
@router.post(path="/query", response_model=Insight)
def create_insight(
request: InsightQuery,
settings: Settings = Depends(get_settings),
llm: BaseChatModel = Depends(init_openai_chat_model),
):
"""
POST /query – Creates a new insight for a given context and related question.
"""
prompt_messages = load_prompt_messages(
settings.prompt.insight_path,
settings.prompt.insight_version,
)
response = run_insight_chain(
prompt_messages,
llm,
request.question,
request.context,
)
return response
엔드포인트는 이제:
- 적절한 프롬프트 템플릿을 로드합니다.
- 캡슐화된 LangChain 함수를 호출합니다.
- 검증된
Insight응답을 반환합니다.
요약
- AI를 백엔드 컴포넌트처럼 다루기: 안정적인 계약, 명시적인 의존성, 캡슐화된 로직.
- Pydantic 사용: 엄격한 요청/응답 모델을 위해.
- FastAPI 의존성 주입 활용: LLM 인스턴스를 관리하기 위해.
- LangChain 체인을 재사용 가능한 함수에 캡슐화.
이 기반이 마련되면 핵심 엔드포인트 로직을 다시 작성하지 않고도 더 정교한 LLM 기능, 에이전트 오케스트레이션, 캐싱, 모니터링 및 테스트를 자신 있게 추가할 수 있습니다.
# Fullscreen Mode
The endpoint coordinates configuration, prompt loading, and chain execution without embedding business logic. This keeps the API readable and makes future extensions straightforward.
왜 이 구조가 확장성을 갖는가
예제가 단순하지만, 구조는 의도적으로 앞으로의 확장을 고려해 설계되었습니다.
- 검색 기능을 나중에 또 다른 의존성으로 추가할 수 있습니다.
- 에이전트 로직은 엔드포인트 계약을 건드리지 않고 체인 함수를 대체할 수 있습니다.
- 상태 관리와 오류 처리를 핵심 흐름을 다시 작성하지 않고도 위에 겹쳐서 적용할 수 있습니다.
가장 중요한 점은 AI가 특수한 경우가 아니라 백엔드 관점에서 다뤄진다는 것입니다. 이는 프로덕션 시스템의 다른 구성 요소와 동일한 아키텍처 규칙을 따릅니다.
최종 생각
이 글은 AI를 실험하는 것과 백엔드 시스템의 일부로 운영하는 것 사이의 차이를 보여줍니다. 이는 프로덕션 지향 AI 백엔드의 첫 번째 빌딩 블록을 구축합니다. 여기서부터 검색, 메모리, 혹은 에이전트를 추가하는 것은 리팩터링이 아니라 아키텍처적 결정이 됩니다.
💻 Code on GitHub:
hamluk/fastapi-ai-backend/part-2