
AI 빌링 어시스턴트 구축하기: LangChain ReAct 에이전트와 Spring Boot 마이크로서비스의 통합
요약
LangChain ReAct 에이전트와 Spring Boot 마이크로서비스를 통합하여 통신사 고객 지원을 위한 AI 빌링 어시스턴트를 구축하는 방법을 소개합니다. FastAPI 기반의 에이전트가 Java 기반의 빌링 및 프로비저닝 서비스를 오케스트레이션하는 아키텍처를 다룹니다.
핵심 포인트
- LangChain ReAct 에이전트를 활용한 자연어 인터페이스 구현
- Python FastAPI와 Java Spring Boot 마이크로서비스 간의 통합 아키텍처
- PostgreSQL 기반의 반응형 및 트랜잭션 서비스 오케스트레이션
- Claude Code를 활용한 AI 페어 프로그래밍 개발 프로세스
서론
통신사 고객 지원 전화의 상당 부분은 동일한 패턴을 따릅니다: "현재 청구 금액이 얼마인가요?", "왜 금액이 올랐나요?", "이 청구 금액에 대해 이의를 제기하고 싶습니다." 이는 구조화되어 있고 예측 가능한 요청이며 명확한 해결 경로를 가지고 있습니다. 이는 바로 잘 설계된 AI 에이전트가 사람의 개입(human in the loop) 없이도 안정적으로 처리할 수 있는 유형의 상호작용입니다.
이 포스트에서는 이러한 아이디어를 실제로 구현한 AI 기반 통신 고객 지원 에이전트인 _Smart Billing Assistant_의 설계 및 개발 과정을 소개합니다. Python FastAPI 서비스는 LangChain ReAct 에이전트를 호스팅하며, 고객에게 송장 조회, 청구 변경 사항 이해, 이의 제기 접수, 요금제 변경 요청, 결제 상태 확인이라는 다섯 가지 셀프 서비스 흐름에 대한 자연어 인터페이스를 제공합니다. 내부적으로 에이전트는 PostgreSQL을 기반으로 하는 두 개의 독립적인 Java Spring Boot 마이크로서비스 — 반응형 빌링 서비스 (Spring WebFlux + R2DBC)와 트랜잭션 기반 프로비저닝 서비스 (Spring MVC + JPA) — 를 오케스트레이션(orchestrate)합니다. 개발 라이프사이클은 명세 기반 요구사항(spec-driven requirements), 테스트 주도 구현(test-driven implementation), 그리고 AI 페어 프로그래머로서의 Claude Code에 의해 형성되었습니다.
1. 프로젝트 개요
참고: 이 포스트 전반에 걸쳐 언급되는 v1 용어는 탐색 목적으로 구축된 Smart Billing Assistant 프로젝트의 데모 버전입니다. 이 문서 전체에 걸친 여러 설계 결정은 범위를 관리 가능한 수준으로 유지하기 위해 명시적으로 단순화되었습니다.
왜 이 도메인인가
비즈니스 지원 시스템 (BSS, Business Support Systems)은 통신 회사의 소프트웨어 중추입니다: 빌링, 송장 발행, 고객 계정, 결제 등이 이에 해당합니다. 이 시스템들은 처리량이 많고 데이터 집약적이며, 역사적으로 고객 지원 팀에게 고통스러운 영역이었습니다. 인바운드 지원 전화의 상당 부분은 청구 관련 질문과 간단한 서비스 변경에 관한 것이며, 이는 정확히 AI 에이전트가 잘 처리할 수 있는 구조화되고 예측 가능한 요청 유형입니다.
에이전트가 할 수 있는 일
여섯 가지 사용자 스토리(user stories)가 구현되었습니다:
| 사용자 스토리 (User Story) | 고객의 요청 내용 | 발생하는 일 |
|---|---|---|
| US-01 | "내 현재 청구 금액이 얼마인가요?" | 에이전트가 청구 요약 및 총액을 구성하는 개별 항목(line items)을 포함하여 고객의 현재 인보이스(invoice)를 조회합니다. |
| ... | ||
![]() |
아키텍처 (The Architecture)
시스템은 독립적으로 배포 가능한 세 가지 서비스로 분해되었습니다:
- agent-service (Python/FastAPI + LangChain): 유일한 고객 진입점입니다. JWT 검증, 대화 세션 상태(conversational session state), LangChain ReAct 오케스트레이션(orchestration) 및 에스컬레이션(escalation) 로직을 담당합니다. 동기식 REST를 통해 Java 서비스들을 호출합니다.
- billing-service (Java/Spring WebFlux): 인보이스(invoices), 항목(line items), 결제(payments) 및 분쟁(disputes)에 대한 신뢰할 수 있는 데이터 소스(source of truth)입니다. 비차단(non-blocking) 데이터베이스 액세스를 위해 반응형 R2DBC를 사용합니다.
- provisioning-service (Java/Spring MVC): 요금제 카탈로그(plan catalogues), 자격 규칙(eligibility rules) 및 고객 라인 구성(customer line configuration)에 대한 신뢰할 수 있는 데이터 소스(source of truth)입니다. 차단형 I/O(blocking I/O)를 사용하는 JPA/Hibernate를 사용하며, 요금제 변경은 드물게 발생하는 트랜잭션 쓰기 작업입니다.
요약하자면, 이것은 프로젝트 아이디어와 그 결과로 도출된 아키텍처에 대한 전체적인 모습이며, 다음 섹션에서는 프로세스를 상세히 다룹니다.
2. 개발 프로세스 (The Development Process)
2.1 페어 프로그래머로서 Claude Code 사용하기
전체 프로젝트는 Anthropic의 CLI 기반 AI 코딩 에이전트인 Claude Code [1]를 대화형 페어 프로그래머(pair programmer)로 사용하여 구축되었습니다. 이를 일회성 코드 생성기로 취급하는 대신, 13번의 구현 세션에 걸쳐 지속적인 협업자로 활용하였으며, 각 세션은 TDD(테스트 주도 개발) 흐름(Red → Green → Refactor)에 따라 작업을 수행했습니다.
CLAUDE.md: 프로젝트 지침 파일
Claude Code를 여러 세션에 걸쳐 유용하게 만드는 핵심은 CLAUDE.md 파일입니다. 이 파일은 프로젝트 루트(root)에 위치하며, Claude Code가 매 세션 시작 시 자동으로 로드합니다. 이는 프로젝트 계약(contract) 역할을 합니다. 즉, 시스템이 무엇을 해야 하는지, 어떤 설계 원칙(design principles)을 따라야 하는지, 어떤 품질 게이트(quality gates)를 강제해야 하는지, 그리고 각 작업이 완료된 후 정확히 어떤 단계를 실행해야 하는지를 정의합니다.
프로젝트에 따라 CLAUDE.md에는 다음과 같은 내용이 포함될 수 있습니다:
- 설계 원칙 (Design principles): KISS, YAGNI, AHA, SOLID 등을 구체적인 강제 규칙으로 포함 (예: "요청된 것 이상의 기능은 추가하지 마세요", "v1에는 Redis를 사용하지 마세요 — YAGNI").
- 품질 게이트 (Quality gates): 라인 커버리지(line coverage) 80% 이상, SonarQube 유지보수성 등급(Maintainability Rating) 'A', 메서드당 인지 복잡도(Cognitive Complexity) 15 이하 (Python 운영 코드는 10 이하).
- 작업 완료 프로토콜 (Task Completion Protocol): 8단계 자동 루프 — 구현(implement) → 테스트 실행(run tests) → 로컬 검증(local validation,
feat:작업용) → 커밋(commit) → PR 오픈(open PR) → CI 모니터링(monitor CI) → 실패 수정(fix failures) → 성공 보고(report green). - Git 컨벤션 (Git conventions): Conventional Commits 필수; Semantic Release를 통한 버전 관리;
pom.xml또는pyproject.toml내 수동 버전 업데이트 금지.
단순하게 시작하여 진화시켜야 하는 이유
포괄적인 CLAUDE.md를 처음부터 작성하지는 않았습니다. 프로젝트는 TDD 및 커밋 컨벤션에 관한 기본적인 규칙을 담은 최소한의 버전으로 시작했으며, 실제 개발 과정에서 새로운 요구사항이 나타남에 따라 이를 확장해 나갔습니다. 따라서 CLAUDE.md에 추가된 각 항목은 실제 마찰 지점(friction point)에 의해 유도되었으며, 실제 필요에 따라 프로세스를 점진적으로 진화시켰습니다.
프롬프팅 대신 산출물을 문서화하는 이유
가장 영향력이 컸던 결정 중 하나는 PROJECT_IDEA.md, REQUIREMENTS.md, DESIGN.md, TASKS.md를 일급 프로젝트 산출물(first-class project artifacts)로 취급한 것입니다. 즉, 매 채팅 프롬프트마다 반복해서 입력하는 내용이 아니라, Claude Code가 직접 읽는 파일로 관리했습니다.
이점:
- 토큰 효율성 (Token efficiency): 컨텍스트가 매 세션마다 반복되는 것이 아니라, 안정적인 파일로부터 한 번만 로드됩니다.
- 일관성 (Consistency): 동일한 범위, 용어 및 결정 사항이 모든 세션에서 가시적으로 유지됩니다.
- 가드레일 (Guardrails): Claude Code는 문서화된 내용 내에서만 작동하며, 추측에 기반한 기능이 침투하지 않습니다.
- 메모리 (Memory): 문서 내의 세션 히스토리 노트가 모든 설계 결정을 포착합니다. 따라서 작업으로 복귀할 때 그 근거가 이미 준비되어 있습니다.
2.2 요구사항 정의
명세 기반 개발 (Spec-Driven Development)
코드 한 줄을 작성하기 전에 REQUIREMENTS.md를 생성했습니다. 여기에는 시스템이 처리해야 하는 모든 상태에 대한 명시적인 수락 시나리오 (acceptance scenarios)가 포함된 사용자 스토리 (user stories)가 담겨 있습니다. 이 접근 방식은 Kiro [2]와 같은 명세 기반 개발 (spec-driven development) 도구에서 영감을 얻었습니다. 즉, 요구사항이 우선이며, 코드는 이를 바탕으로 테스트됩니다.
요구사항을 실제 도메인 지식에 기반시키기 위해, Claude Code에게 BSS 통신 분야의 전문성을 가진 빌링 매니저 (Billing Manager) 이해관계자 역할을 맡도록 요청했습니다. 이 시뮬레이션된 탐색 세션은 구조화된 논의를 이끌어냈습니다. 고객들이 실제로 무엇에 대해 문의하는가? 엣지 케이스 (edge cases)는 무엇인가? 무엇이 범위 내에 있고, 무엇이 지원 에이전트가 넘지 말아야 할 선을 넘는 것인가? 이 대화를 통해 비즈니스 규칙(90일 분쟁 기간, 연체 임계값, OSS/BSS 경계 등)이 드러났으며, 그렇지 않았다면 구현이나 테스트 단계에서 뒤늦게 발견되었을 내용들이었습니다.
형식: 각 사용자 스토리는 "As a / I want / So that" 헤더를 가지며, 그 뒤에는 테스트 케이스 및 최종적으로 BDD Gherkin 기능 파일 (feature files)로 직접 매핑되는 번호가 매겨진 수락 시나리오 (S1, S2, S3...)가 뒤따릅니다.
예시 (US-03 — 요금 분쟁):
US-03: 고객으로서, 나는 송장(invoice)의 요금에 대해 분쟁을 제기하고 싶다,
그 이유는 잘못된 요금을 검토받기 위해서이다.
...
이러한 시나리오들이 모든 테스트를 주도했습니다. 단위 테스트(unit tests)는 리포지토리 계층(repository layer)을 모킹(mocked)하고 각 분기를 검증했으며, Testcontainers PostgreSQL을 활용한 통합 테스트(integration tests)는 엔드 투 엔드(end-to-end) 동작을 확인했습니다. Cucumber (Java) 및 pytest-bdd (Python)의 BDD 기능 파일(feature files)은 전체 대화 흐름을 검증했습니다.
GLOSSARY.md: 도메인 캡처하기
요구사항 세션의 결과물 중 하나는 BSS 통신 용어 사전인 GLOSSARY.md였습니다. 일할 계산(Proration), CDR, 독촉(dunning), 연체(delinquency), 콜 핸드오프(cold handoff) — 이러한 용어들은 코드와 테스트, 그리고 에이전트의 응답에 등장합니다. 공유된 용어 사전이 있으면 코드에서 OUTSTANDING이라고 명시하거나 에이전트가 "영업일 기준 5일 SLA"를 언급할 때, 이를 읽는 모든 사람에게 동일한 의미로 전달됨을 보장할 수 있습니다.
2.3 설계 정의
LangChain, LangGraph, 그리고 ReAct 패턴
LangChain [3]은 LLM(Large Language Model) 기반 애플리케이션을 구축하기 위한 Python 프레임워크입니다. 핵심 개념은 **도구(tool)**입니다. 이는 LLM이 작업을 수행하거나 정보를 검색하기 위해 호출할 수 있는 Python 함수입니다. LLM은 어떤 도구를 호출할지, 어떤 인자(arguments)를 전달할지, 그리고 그 결과로 무엇을 할지를 결정합니다.
각 도구는 @tool 데코레이터가 붙은 일반적인 Python 함수입니다. LLM은 함수의 독스트링(docstring)을 읽어 언제 어떻게 사용할지를 이해합니다. 라우팅 테이블(routing tables)이나 결정 트리(decision trees)는 필요하지 않습니다. LangChain은 도구 호출의 포맷팅, LLM 응답의 파싱(parsing), 그리고 함수 호출의 메커니즘을 처리합니다.
ReAct [4] (Reason + Act, 추론 + 행동)는 여기서 사용된 에이전트 패턴입니다. LLM은 다음 과정을 번갈아 수행합니다:
- 추론 (Reasoning): "고객이 청구서에 대해 문의했습니다.
get_current_invoice를 호출해야 합니다." - 행동 (Acting): 도구를 호출하고 결과를 가져옵니다.
- 관찰 (Observing): "송장에 45달러의 데이터 초과 요금이 표시되어 있습니다. 고객에게 설명이 필요합니다."
- 다시 추론 (Reasoning again): 추가 정보가 필요한가, 아니면 지금 바로 답변할 수 있는가?
이 루프는 완전히 에이전트 서비스 (agent-service) 내부에서 실행됩니다. 고객의 관점에서는 메시지를 보내고 답변을 받는 과정입니다. 내부적으로 LLM은 두세 개의 도구 (tools)를 호출하고, 중간 결과들을 관찰하며, 최종 응답을 생성하기 전에 각 단계에 대해 추론 (reasoning)을 수행했을 수 있습니다.
LangGraph는 이 루프에 상태 관리 (state management) 기능을 추가합니다. 이는 노드 (nodes)가 함수(예: "LLM 호출" 또는 "도구 호출 실행")이고 엣지 (edges)가 흐름을 제어하는 그래프 기반 런타임 (graph-based runtime)입니다. 결정적으로, LangGraph는 thread_id를 키로 사용하여 대화 기록을 턴(turn) 사이에도 유지하는 **MemorySaver 체크포인터 (checkpointer)**를 제공합니다. 이것이 에이전트에게 멀티턴 메모리 (multi-turn memory)를 부여하는 핵심 요소입니다.
thread_id는 세션 UUID입니다. 모든 POST /chat 요청 시, 에이전트는 해당 스레드의 대화 기록을 로드하고, ReAct 루프를 실행하며, 새로운 사용자 메시지, 모든 도구 호출, 그리고 최종 AI 응답을 포함하여 업데이트된 상태를 저장합니다. 고객의 다음 메시지는 마지막 메시지가 멈춘 바로 그 지점에서 다시 시작됩니다.
LangSmith는 모든 ReAct 루프를 추적 (trace)합니다. 어떤 도구가 호출되었는지, LLM이 각 단계에서 어떻게 추론했는지, 각 작업에 시간이 얼마나 걸렸는지 등을 파악할 수 있습니다. 이는 에이전트의 동작을 디버깅 (debugging)하는 데 매우 귀중한 도구입니다.
Spring WebFlux + R2DBC 및 Spring MVC + JPA
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기


