웹/모바일 개인 AI CFO

발행: (2026년 2월 21일 오후 07:00 GMT+9)
16 분 소요
원문: Dev.to

Source: Dev.to

FinTrack
전통적인 CRUD와 AI 기능을 결합한 개인 재무 애플리케이션을 구축하는 기술적 고수준 walkthrough—그리고 내가 서비스를 분리한 이유.

Source:

문제

나는 단순히 거래를 추적하는 수준을 넘어서는 개인 재무 앱을 만들고 싶었습니다. 사용자의 재무 상황을 이해하도록 만들고 싶었죠:

  • 자연어로 질문에 답변하고,
  • 지출을 자동으로 분류하며,
  • 실제 개인 비서처럼 음성 입력도 받게 하려 했습니다 (Finny에게 물어보세요).

도전 과제는? 대부분의 AI 작업은 Python에서 이루어지는 반면, 일반적인 웹 백엔드는 Node.js에서 운영됩니다. 두 세계의 장점을 얽히지 않게 어떻게 결합할 수 있을까요?

Note: 고양이를 가죽을 벗기는 방법은 여러 가지가 있습니다. 올바른 솔루션은 동시성, 처리량, 비용 및 앱이 실행될 컨텍스트에 따라 달라집니다.

고수준 아키텍처

저는 이중 백엔드 아키텍처를 선택했습니다: 하나는 핵심 애플리케이션을 위한 서비스이고, 다른 하나는 전적으로 AI 전용 서비스입니다.

High level arch

구성 요소기술 스택책임
프론트엔드React + TypeScript + ViteUI, 라우팅, 상태 관리, 단일 API 추상화 레이어
ExpressoTS APINode.js (ExpressoTS 프레임워크) + PostgreSQLAI가 아닌 모든 작업: 트랜잭션, 계정, 목표, 예산, 인증
Python AI 서비스Python (FastAPI/Flask) + OpenRouter (LLM 제공자) + 벡터 스토어 (예: Pinecone)RAG 채팅, 트랜잭션 분류, 음성 전사 파싱, 문서 추출

Why Split the Backend?

  1. Keep API Keys Server‑Side
    AI 제공업체는 비밀 키가 필요합니다. Python 서비스가 해당 자격 증명을 보관하고 모든 AI 요청을 프록시하므로 브라우저가 제공업체와 직접 통신하지 않습니다.

  2. Python’s Strengths for AI
    분류, 임베딩, RAG 파이프라인은 Python의 성숙한 생태계(Numpy, pandas, OpenAI SDK 등)와 자연스럽게 맞습니다. 이를 Node로 재구현하면 맞춤 코드와 유지 보수 부담이 늘어납니다.

  3. Independent Scaling & Concurrency
    CRUD 트래픽과 AI 트래픽은 서로 다른 부하 프로파일을 가집니다. 두 서비스를 분리하면 각각을 독립적으로 확장할 수 있어, 채팅 사용량 급증이 핵심 거래 API에 영향을 주지 않습니다.

  4. Clear Separation of Concerns
    비즈니스 데이터와 CRUD는 ExpressoTS에, AI/ML은 Python에 존재합니다. 이렇게 하면 온보딩, 디버깅, 리팩토링이 훨씬 쉬워집니다.

프론트엔드 구조

앱은 깔끔한 레이어링 패턴을 따릅니다:

레이어위치책임
UIcomponents/, pages/프레젠테이션 전용 컴포넌트만
Statecontexts/, hooks/전역 상태 및 재사용 가능한 로직
Dataapi/모든 데이터‑fetching 및 API 호출
Utilsutils/순수 함수, 부수 효과 없음

api/ 폴더는 단일 진입점으로 백엔드와 통신합니다. 이 폴더는 요청이 ExpressoTS로 가는지 Python 서비스로 가는지를 추상화하며, fetchTransactions(), classifyTransactions(), askFinny()와 같은 함수를 노출합니다. 앱의 나머지 부분은 이중‑백엔드 설정을 알 필요가 없습니다.

파이썬 AI 서비스: 무엇을 하는가

1. 금융 Q&A를 위한 RAG

사용자는 “지난 달 식료품에 얼마를 썼나요?” 혹은 “식사 예산에 남은 금액은 얼마인가요?” 와 같은 질문을 합니다. RAG 서비스는:

  1. 사용자의 질문에 대한 임베딩을 생성합니다.
  2. 벡터 스토어에서 관련 거래, 목표, 지출 한도를 검색합니다.
  3. 해당 컨텍스트를 LLM 프롬프트에 삽입합니다.
  4. 사용자의 실제 데이터를 기반으로 한 답변을 반환합니다.

임베딩은 거래, 카테고리, 목표, 예산에 대해 저장됩니다. 벡터 검색은 가장 관련성 높은 데이터 조각을 반환해 숫자에 대한 환상을 없애줍니다.

2. 배치 거래 분류

사용자가 은행에서 CSV 파일을 업로드하면 수십에서 수백 개의 분류되지 않은 거래가 나타납니다. 분류기는:

  • 사용자의 기존 카테고리 계층 구조를 사용합니다.
  • 과거 분류(예: “Starbucks” → 커피)에서 학습합니다.
  • categoryIdsubcategoryId를 포함한 구조화된 JSON을 반환합니다.

거래는 토큰 제한을 초과하지 않도록 배치 처리되며, 속도를 위해 비동기적으로 처리됩니다. 프런트엔드는 진행 상황을 표시하고 완료되면 목록을 새로 고칩니다.

3. 음성 입력

사용자는 거래를 음성으로 입력할 수 있습니다: “연료를 위해 주유소에서 50달러.” 음성 파서는 이를 구조화된 객체로 변환합니다:

{
  "amount": 50,
  "description": "Gas station",
  "category": "Transportation",
  "subcategory": "Fuel"
}

전사된 텍스트는 파이썬 서비스로 전송되고, 서비스는 사용자가 확인하거나 편집할 수 있는 거래 객체를 반환합니다.

성공한 패턴

  1. Context‑Aware AI – RAG 서비스는 인증 토큰에서 user_idhousehold_id를 받아, 모든 쿼리를 해당 사용자의 데이터로 제한하고 가구 간 데이터 유출을 방지합니다.
  2. Rate Limiting & Usage Tiers – AI 호출은 비용이 많이 듭니다. 우리는 사용자당 제한을 적용하여(예: X개의 채팅 쿼리, Y개의 음성 세션, Z개의 분류) 비용을 관리하고 공정한 사용을 보장합니다.
  3. Asynchronous Job Queue – 장시간 실행되는 AI 작업(배치 분류, 음성 파싱)은 워커 시스템(예: Celery 또는 RQ)으로 큐에 넣습니다. 프런트엔드는 작업 상태를 폴링하여 UI가 응답성을 유지하도록 합니다.
  4. Caching Embeddings – 자주 접근되는 임베딩(예: 최근 거래)은 Redis에 캐시하여 벡터 스토어 지연 시간을 줄이고 제공자 비용을 절감합니다.
  5. Observability – 구조화된 로그, OpenTelemetry 트레이싱, Prometheus 메트릭을 통해 두 서비스 전반의 요청 지연 시간, 오류율, 토큰 사용량을 가시화합니다.

TL;DR

  • 백엔드를 빠르고 동시성 있는 CRUD를 위한 Node/ExpressoTS 서비스와 AI‑집약 작업을 위한 Python 서비스로 분리합니다.
  • API 키를 서버 측에 보관하고, Python의 AI 생태계를 활용하며, 각 서비스를 독립적으로 확장합니다.
  • 깨끗한 프론트엔드 레이어링과 단일 api/ 추상화를 통해 UI에서 이중 백엔드 복잡성을 숨깁니다.

The result is a responsive, AI‑enhanced personal‑finance app that can handle real‑world traffic without sacrificing performance or security.

결과는 성능과 보안을 희생하지 않으면서 실제 트래픽을 처리할 수 있는 반응형 AI‑강화 개인 재무 앱입니다.

3. Caching Where It Makes Sense

Stateless questions like “What’s my total income this year?” can be cached. We cache by query + user_id with a short TTL. Conversational threads aren’t cached—they’re too dynamic.

4. 도구 호출을 통한 작업

채팅은 단순한 Q&A가 아닙니다. 사용자는 “점심에 20달러를 추가해줘” 라고 말할 수 있고, AI는 거래를 생성하는 도구를 호출합니다.

  • 우리는 도구 스키마(add_transaction, get_balance 등)를 정의합니다.
  • LLM이 언제 호출할지 결정합니다.
  • Python 서비스가 호출을 실행하고, 모델이 요약할 수 있도록 결과를 반환합니다.

내가 다르게 할 것

첫날부터 이중 백엔드 분리를 시작한다.
나는 모놀리스를 시도했다가 나중에 분리했는데, 빌드 중간에 마이그레이션하는 것이 고통스러웠다.

초기에 오류 처리를 표준화한다.
ExpressoTS와 Python은 서로 다른 형태로 오류를 반환한다. 나중에 공유 오류 형식 레이어를 추가했지만, 처음부터 해두었다면 시간을 절약할 수 있었을 것이다.

임베딩 파이프라인에 더 일찍 투자한다.
RAG 품질은 좋은 임베딩에 달려 있다. 우리는 무엇을 임베딩할지(전체 거래 텍스트 vs. 요약)와 언제 재인덱싱할지를 반복해서 결정했는데, 이를 더 일찍 수행했다면 채팅 경험을 더 빨리 개선할 수 있었을 것이다.

더 많은 내용이 있지만(당연히 모든 전략을 공개할 수는 없다), 이것이 좋은 시작이다. :)

주요 내용

AI‑powered 앱을 만든다고 해서 모든 것을 하나의 스택에 넣어야 하는 것은 아닙니다, 특히 “바이브 코딩”(주의 필요)이라면 더욱 그렇습니다. 백엔드를 분리—CRUD는 ExpressoTS, AI는 Python—했을 때 얻은 이점은 다음과 같습니다:

  • API 키와 AI 로직에 대한 명확한 소유권
  • 각 작업에 맞는 적절한 도구
  • 독립적인 확장성, 동시성, 배포
  • 유지보수가 쉬운 코드베이스

기존 앱에 AI를 추가하거나 AI를 염두에 두고 새로 시작하려는 경우, 모든 AI 워크로드를 담당하는 전용 Python, Rust, 혹은 C++ 서비스를 구축하는 패턴이 확장성이 뛰어납니다.

Performance and Cost: Why This Matters When You’re Starting Out

첫 번째 제품을 만들거나 사이드 프로젝트를 부트스트랩할 때는 한 푼 한 푼이 중요합니다. 앱을 호스팅하고 AI를 실행하는 비용이 금방 늘어날 수 있습니다. 이 아키텍처는 두 측면 모두에서 도움이 됩니다.

과도한 프로비저닝 없이 동시성 확보

이중 백엔드 분할 덕분에 CRUD와 AI가 서로 다른 프로세스에서 실행됩니다. ExpressoTS API는 가벼운 요청(트랜잭션, 계정 조회, 인증 체크 등)을 수천 개까지 소규모 인스턴스에서 처리할 수 있고, Python 서비스는 AI 작업을 비동기적으로 실행합니다. AI 호출을 기다리며 유휴 상태인 거대한 서버에 비용을 지불하지 않고, 각 서비스를 적절한 규모로 운영합니다. API용 작은 Node 인스턴스와 AI용 작은 Python 인스턴스는 두 작업을 모두 수행하려는 하나의 대형 모놀리스보다 비용이 적게 듭니다.

인프라 비용 절감

AI 워크로드를 분리함으로써 채팅 사용량이 급증해도 전체 앱을 스케일링할 필요가 없습니다. 초기에는 무료 티어나 저비용 Python 호스트가 AI 서비스를 충분히 감당할 수 있습니다. 사용량이 늘어나면 해당 서비스만 확장하면 됩니다. CRUD API는 안정적이고 저렴하게 유지됩니다.

첫 날부터 비용 효율성 확보

저는 호스팅 비용이 비용 효율적이어야 한다는 점을 이해하고 이 구조를 만들었습니다. 특히 자신의 비즈니스를 시작하려는 개발자들에게는 더욱 중요합니다. 쿠버네티스나 컨테이너 군대가 필요하지 않습니다. 두 개의 작은 서비스, 명확한 경계, 그리고 각각을 독립적으로 확장할 수 있는 능력—이것이 제품을 검증하면서 청구서를 낮게 유지할 수 있는 길입니다.

저는 FinTrack의 개발자이며, AI 비서인 Finny와 함께 개인 재무 관리 앱을 제공하고 있습니다. 비슷한 무언가를 만들고 있다면 어떻게 접근했는지 알려 주세요—아래에 댓글을 남겨 주세요.

0 조회
Back to Blog

관련 글

더 보기 »

서브넷팅 설명

Subnetting이란 무엇인가? 큰 아파트 건물을 여러 층으로 나누는 것과 같다. 각 층 서브넷은 자체 번호가 매겨진 유닛(hosts)을 가지고, 그리고 건물…