sparta-msa-projectai-service (chat.lemuel.co.kr) 는 Spring AI 1.0.0 기반 Gemini 챗봇. 14 개월 운영 하면서 Python 의 LangChain + FastAPI직접 비교현실 의 trade-off 를 정리. 어느 한 쪽 의 우월 함주장 이 아니라팀 의 *맥락조직 의 *기존 stack 에 따른 합리적 선택 의 가이드.


1. 왜 Spring AI 인가 — sparta 의 맥락

sparta-msa 의 5 개 서비스 (gateway / auth / user / product / order) 가 전부 Spring Boot 4 + Java 25 + Spring Cloud 2025. AI 기능 추가 시3 옵션:

  • (A) Spring AI 로 기존 stack 통합같은 build / observability / security / deploy 파이프라인
  • (B) Python + FastAPI 의 별도 서비스AI 생태계 의 *압도적 우위
  • (C) OpenAI / Gemini SDK 의 raw HTTP 호출프레임워크 없음

내 선택 — (A). 이유:

  1. 팀 (1 인) 의 *Java 숙련 도
  2. 기존 의 *trace_id / metrics / circuit breaker 의 *재사용
  3. Resilience4j + Prometheus + JPA 의 *통합 의 *시간 절감

만약 조직 이 *이미 Python data engineer 가 있다 또는 최첨단 LangGraph / DSPy / vLLM 의 *필수 사용 이라면 — (B)합리적.


2. Spring AI 의 핵심 추상

@Configuration
class AiConfig {
    @Bean
    ChatClient chatClient(ChatClient.Builder builder) {
        return builder
            .defaultSystem("당신은 sparta 의 이커머스 상품 추천 도우미.")
            .defaultOptions(ChatOptions.builder()
                .temperature(0.7)
                .build())
            .build();
    }
}

ChatClientSpring AI 의 *통합 추상. OpenAI / Anthropic / Gemini / Ollama11 개 + 의 provider 가 *동일 API. provider 교체 시 코드 0 줄 변경, application.yml 의 *property 만.

spring:
  ai:
    vertex:
      ai:
        gemini:
          project-id: ${GCP_PROJECT_ID}
          location: us-central1
          chat:
            options:
              model: gemini-2.5-flash
              temperature: 0.7

Fluent API

@Service
@RequiredArgsConstructor
class ProductRecommendService {
    private final ChatClient chatClient;

    public String recommend(String userQuery, Long userId) {
        return chatClient.prompt()
            .system("사용자 의 *과거 구매 이력* 을 반영해.")
            .user(userQuery)
            .advisors(advisor -> advisor.param("userId", userId))
            .call()
            .content();
    }
}

type-safe + IDE 자동완성 + 컴파일 시점 검증. Python 의 런타임 에러 만 가능 한 부분 이 Java 에선 *컴파일 시 발견.

Python 의 동등 (LangChain)

from langchain_google_vertexai import ChatVertexAI
from langchain.prompts import ChatPromptTemplate

llm = ChatVertexAI(model="gemini-2.5-flash", temperature=0.7)

prompt = ChatPromptTemplate.from_messages([
    ("system", "당신은 sparta 의 이커머스 상품 추천 도우미."),
    ("user", "{question}"),
])

chain = prompt | llm
result = chain.invoke({"question": user_query})

간결 함 의 *압도적 우위. 5 줄 vs Spring AI 의 15 줄+. runtime flexibilityprototype 의 *극도 의 빠름.


3. RAG (Retrieval-Augmented Generation) — Vector Store

sparta 의 상품 추천진짜 가치상품 DB 에서 *관련 상품 검색 + LLM 응답 생성RAG 패턴.

Spring AI 의 RAG

@Configuration
class VectorStoreConfig {
    @Bean
    VectorStore vectorStore(JdbcTemplate jdbcTemplate, EmbeddingModel embeddingModel) {
        return PgVectorStore.builder(jdbcTemplate, embeddingModel)
            .dimensions(768)
            .distanceType(PgVectorStore.PgDistanceType.COSINE_DISTANCE)
            .indexType(PgVectorStore.PgIndexType.HNSW)
            .initializeSchema(true)
            .build();
    }
}

@Service
@RequiredArgsConstructor
class RagService {
    private final ChatClient chatClient;
    private final VectorStore vectorStore;

    public String answer(String question) {
        return chatClient.prompt()
            .advisors(new QuestionAnswerAdvisor(vectorStore,
                SearchRequest.builder().topK(5).build()))
            .user(question)
            .call()
            .content();
    }
}

QuestionAnswerAdvisorSpring AI 의 *advisor 추상RAG 의 *boilerplate 제거. embedding + retrieval + context injection자동화.

인덱싱

@Service
@RequiredArgsConstructor
class ProductIndexingService {
    private final VectorStore vectorStore;
    private final ProductRepository productRepo;

    @Scheduled(cron = "0 0 3 * * *")
    public void reindexAll() {
        List<Document> documents = productRepo.findAll().stream()
            .map(p -> new Document(
                p.getDescription(),
                Map.of(
                    "productId", p.getId(),
                    "category", p.getCategory(),
                    "price", p.getPrice()
                )))
            .toList();

        vectorStore.add(documents);
    }
}

pgvectorautomatic schema creation + HNSW 인덱스 + cosine similarity. 모두 *자동.

Python 의 동등

from langchain_community.vectorstores import PGVector
from langchain_google_vertexai import VertexAIEmbeddings

embeddings = VertexAIEmbeddings(model_name="textembedding-gecko@003")
vector_store = PGVector(
    collection_name="products",
    connection_string="postgresql://...",
    embedding_function=embeddings,
)

# RAG chain
from langchain.chains import RetrievalQA
qa = RetrievalQA.from_chain_type(
    llm=llm,
    retriever=vector_store.as_retriever(search_kwargs={"k": 5}),
)
result = qa.invoke({"query": question})

비교

| 측면 | Spring AI | Python (LangChain) | |—|—|—| | boilerplate | 더 많음 (15~20 줄) | 더 적음 (8~10 줄) | | type safety | 컴파일 시점 검증 | runtime 만 | | observability | Micrometer / Actuator 자동 | OpenTelemetry 수동 | | 벡터 store provider | PgVector / Redis / Pinecone / Chroma / Weaviate (~15) | 압도적 (모든 vector store 의 사실 상 표준) | | 새 모델 대응 | 느림 (provider PR 기다림) | 빠름 (community 가 즉시 추가) | | production 안정성 | Spring Boot 의 14 년 운영 노하우 상속 | 상대적 으로 *불안정 (LangChain 의 빠른 변경) |


4. Tool Calling — 함수 자동 호출

LLM 이 코드 의 함수 를 *호출 하는 패턴. sparta 의 주문 조회 / 상품 검색 / 결제 같은 비즈니스 로직자연어 인터페이스.

Spring AI 의 Tool Calling

@Service
@RequiredArgsConstructor
class OrderQueryTool {
    private final OrderRepository orderRepo;

    @Tool(description = "특정 사용자 의 *최근 주문 N 건 조회*")
    public List<OrderDto> getRecentOrders(
            @ToolParam(description = "조회 할 사용자 ID") Long userId,
            @ToolParam(description = "조회 할 주문 수") int limit) {
        return orderRepo.findRecentByUserId(userId, limit)
            .stream().map(OrderDto::from).toList();
    }
}

@Service
@RequiredArgsConstructor
class ChatService {
    private final ChatClient chatClient;
    private final OrderQueryTool orderQueryTool;

    public String chat(String userMessage, Long userId) {
        return chatClient.prompt()
            .system(s -> s.text("사용자 의 *주문 정보 가 필요 하면 *tool 을 호출*."))
            .tools(orderQueryTool)
            .user(userMessage)
            .call()
            .content();
    }
}

Spring AI 가 *자동 으로 *JSON schema 생성 + LLM 의 function call 응답 파싱 + Java 메서드 호출 + 결과 를 LLM 에 재전달. 모든 boilerplate 제거.

핵심 강점기존 Spring service / repository 를 *그대로 *tool 로 노출. 새 layer 없음.

Python 의 동등

from langchain_core.tools import tool

@tool
def get_recent_orders(user_id: int, limit: int) -> list:
    """특정 사용자 의 최근 주문 N 건 조회"""
    return order_repo.find_recent_by_user_id(user_id, limit)

llm_with_tools = llm.bind_tools([get_recent_orders])
response = llm_with_tools.invoke("user 123 의 최근 주문 3 개 알려줘")

Python 의 *간결 함 의 *우위. 그러나 기존 코드 가 *FastAPI 라우터별도 의 *tool wrapper 작성 의 *중복.


5. Structured Output — type-safe response

LLM 의 raw textstructured 객체 로 변환.

Spring AI

record ProductRecommendation(
    String productName,
    String reasoning,
    Double confidence,
    List<String> alternativeOptions) {}

@Service
class RecommendService {
    public ProductRecommendation recommend(String query) {
        return chatClient.prompt()
            .user(query)
            .call()
            .entity(ProductRecommendation.class);  // ← 자동 변환
    }
}

record 의 *typeJackson schema 로 변환LLM 의 *JSON 응답자동 으로 *POJO 로 deserialize. runtime error 가 *컴파일 시 발견.

Python (Pydantic)

from pydantic import BaseModel

class ProductRecommendation(BaseModel):
    product_name: str
    reasoning: str
    confidence: float
    alternative_options: list[str]

structured_llm = llm.with_structured_output(ProductRecommendation)
result = structured_llm.invoke(query)

Pydantic 의 *runtime validation. Java 의 *compile-time 만큼 강 하진 않지만 *충분.


6. Streaming — 실시간 응답

ChatGPT 처럼 토큰 별 점진 적 응답. sparta 의 chat.lemuel.co.krUX 의 핵심.

Spring AI

@RestController
@RequiredArgsConstructor
class ChatController {
    private final ChatClient chatClient;

    @GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> chat(@RequestParam String message) {
        return chatClient.prompt()
            .user(message)
            .stream()
            .content();
    }
}

Spring WebFlux 의 *Fluxstreaming 의 *자연 표현. Server-Sent Events자동 직렬화.

Python (FastAPI)

from fastapi.responses import StreamingResponse

@app.get("/chat")
async def chat(message: str):
    async def event_stream():
        async for chunk in llm.astream(message):
            yield f"data: {chunk.content}\n\n"
    return StreamingResponse(event_stream(), media_type="text/event-stream")

async generator 의 *간결 함. Python 의 async 가 *streaming 에 *자연.

비슷 한 수준 의 *동등성. 언어 차이 만.


7. Observability — 진짜 강점

운영 시 AI 의 *비용 / latency / error추적 의 *필수.

Spring AI (Micrometer 자동)

management:
  endpoints.web.exposure.include: prometheus,health,metrics
  metrics:
    distribution.percentiles-histogram.spring.ai.chat.client: true

자동 메트릭:

  • spring.ai.chat.client호출 횟수 + latency
  • spring.ai.chat.client.tokens입력 / 출력 토큰
  • spring.ai.chat.client.tokens.cost추정 비용 (USD)
  • 기존 의 *trace_id 자동 전파
@Observed(name = "product.recommend",
         contextualName = "ai-product-recommendation")
public ProductRecommendation recommend(String query) {
    return chatClient.prompt().user(query).call().entity(...);
}

Prometheus / Grafana / ELK 의 *기존 대시보드자동 통합. 별도 의 *AI dashboard 없음.

Python (수동)

from opentelemetry import trace, metrics

tracer = trace.get_tracer(__name__)
meter = metrics.get_meter(__name__)
ai_calls = meter.create_counter("ai_calls")
ai_latency = meter.create_histogram("ai_latency_ms")

with tracer.start_as_current_span("ai-chat") as span:
    start = time.time()
    result = llm.invoke(message)
    ai_latency.record((time.time() - start) * 1000)
    ai_calls.add(1, {"model": "gemini-2.5"})

수동 instrumentationboilerplate. Spring AI 의 *자동큰 차이.

이게 Spring AI 의 진짜 우위. 14 개월 운영관점 에서 *압도적.


8. 의존성 / 빌드 / 배포

Spring AI

dependencies {
    implementation 'org.springframework.ai:spring-ai-starter-model-vertex-ai-gemini'
    implementation 'org.springframework.ai:spring-ai-starter-vector-store-pgvector'
    implementation 'org.springframework.ai:spring-ai-advisors-vector-store'
}
  • Gradle 의 *transitive dependency 해결
  • Docker image 의 *대략 250 MB (JRE + Spring Boot + AI libs)
  • startup time 의 *3~5 초
  • production 의 *안정성상속

Python (LangChain)

[project]
dependencies = [
    "langchain>=0.3",
    "langchain-google-vertexai>=2.0",
    "langchain-postgres>=0.0.13",
    "fastapi>=0.115",
    "uvicorn>=0.32",
]
  • pip 의 *의존성 지옥흔함 (LangChain 의 빠른 변경breaking change)
  • Docker image 의 *대략 800 MB (Python + 모든 ML libs)
  • startup time 의 *1~2 초
  • production 의 *상대적 으로 *덜 안정

경험 — sparta 의 ai-service 가 14 개월 동안 *0 회 의 *의존성 충돌 사고. 경험 상 LangChain 은 *분기 마다 *upgrade 시 *breaking change각오 필요.


9. 어떨 때 무엇 을 쓰는가내 가이드

Spring AI 가 적합

  • 기존 Java / Spring 팀기존 stack 통합 우선
  • 프로덕션 운영안정성 + observability 우선
  • RAG / Tool Calling 의 *기본 기능 만 필요
  • 조직 의 *type safety + 컴파일 검증문화

Python 이 적합

  • 데이터 엔지니어 / ML 엔지니어있는 팀
  • 최첨단 기술 (LangGraph / DSPy / vLLM / 새 모델 의 바로 사용) 의 필수
  • prototyping 의 *극도 의 빠름조직 문화
  • 대규모 batch 추론 (HuggingFace + GPU)

둘 다 적합상황 별 선택

  • 프로덕션 의 *API 서버 = Spring AI, batch ML pipeline = Python분리 — sparta + airflow / prefect 같은 조합
  • MCP 의 *server / client둘 다 지원 — 어떤 stack 이든 상호 호환

10. MCP둘 의 *통합 접점

Model Context Protocol (Anthropic 2024 11월 발표) — LLM 의 *도구 / 데이터 source표준 protocol.

// Spring AI 의 MCP Server
@Bean
McpSyncServer mcpServer(OrderQueryTool orderQueryTool) {
    return McpServer.sync()
        .tool(orderQueryTool)
        .build();
}
# Python 의 MCP Server
from mcp.server import Server

server = Server("product-search")

@server.tool()
async def search_products(query: str) -> list:
    return await product_repo.search(query)

Spring AI 의 *MCP server 가 *Python 의 *Claude Desktop 에서 그대로 사용 가능. 교차 호환2026 의 큰 변화.

cluster 의 *cluster-coordinator hookSpring AIMCP server 로 *제공, Claude Code 의 *Python / Node.js 에서 호출. language-agnostic 통합.


11. 운영 함정 — 내가 *14 개월 *겪은 것들

(1) Gemini 451 error (lemuel-xr — 2026-06-21)

옛 API key 가 *Google 측 의 *블락. Spring AI 의 *exception 이 *Resilience4j 의 *circuit breaker 와 *통합자동 fallback. Python 이면 *수동 retry / circuit.

(2) Token 비용 폭증

무한 retry 또는 streaming 의 *중단 안 됨비용 폭증. Spring AI 의 *advisors 의 *budget guard 추가:

@Component
class BudgetGuardAdvisor implements CallAroundAdvisor {
    @Override
    public AdvisedResponse aroundCall(AdvisedRequest req, CallAroundAdvisorChain chain) {
        if (todaySpend() > DAILY_LIMIT_USD) {
            throw new BudgetExceededException();
        }
        return chain.nextAroundCall(req);
    }
}

(3) PgVector 의 HNSW 인덱스 의 *느린 빌드

초기 10만 row 인덱스수십 분. Spring AI 의 *initializeSchema=false수동 관리 권장.

(4) Tool Call 의 무한 루프

LLM 이 *같은 tool 을 *반복 호출max_tool_calls 제한:

ChatOptions.builder()
    .toolCallbacks(List.of(...))
    .maxToolRoundTrips(5)
    .build();

(5) Embedding 의 cache miss

매 query 마다 *embedding비용 + latency. Caffeine cache 의 *embedding 결과 cache:

@Cacheable("embeddings")
public List<Float> embed(String text) {
    return embeddingModel.embed(text);
}

12. 마치며 — 작은 결론

Spring AI 의 진짜 가치기존 Spring 운영 노하우 의 *완전 상속. 14 년 의 *Spring Boot 의 *안정성 / observability / securityAI 부분 에도 *자동 적용. 별도 운영 의 *추가 비용 없음.

Python 의 진짜 가치생태계 의 *압도적 우위. 모든 새 모델 / 새 기법Python 에 먼저 등장. 최첨단 의 *현장 의 *기본 언어.

sparta 의 ai-serviceSpring AI 의 *합리적 선택. 그러나 lemuel-xr 의 *RAG + Scene 분기복잡 도수개월 후 *Python 으로 *분리 검토 의 *가능성 도 *남김. 기술 의 선택 은 *조직 의 *상황 의 *함수. 어느 한 쪽 의 우월 함주장비현실 적.

핵심 메시지: 팀 의 *기존 stack + 운영 노하우기술 선택 의 *제 1 변수. 최신 기술조급 한 도입 보다 기존 의 *통합 의 *시간 절감대부분 의 경우 *더 큰 가치. 이게 14 개월 운영 의 *진짜 학습.


참고