AI를 활용한 건강 데이터 분석 챗봇 서비스 개발 경험

소개

안녕하세요, 지피터스 커뮤니티에서 많은 인사이트를 얻으며 AI 개발을 배우고 있는 개발자입니다. 오늘은 최근 완성한 AI 프로젝트 경험을 공유하고자 합니다.

Garmin 스마트워치를 사용하며 수집되는 다양한 건강 데이터(심박수, 스트레스, 수면 패턴 등)를 AI로 분석해 개인화된 건강 인사이트를 제공하는 챗봇 서비스를 개발했습니다.

프로젝트의 출발점은 단순한 의문이었습니다.

"내 건강 데이터를 AI가 종합 분석해서 알려준다면 얼마나 좋을까?"

Garmin Connect 앱에서는 기본적인 요약만 제공할 뿐, 깊이 있는 분석이나 맞춤형 인사이트는 부족했습니다. 예를 들어, 수면 데이터에 대해 "양호한 수면"과 같은 단순한 코멘트만 제공할 뿐이었죠.

이 프로젝트의 주요 목표는 다음과 같습니다.

  1. 사용자의 Garmin 건강 데이터를 자동으로 수집

  2. AI를 통한 종합적인 분석과 맞춤형 인사이트 제공

  3. 카카오톡 챗봇이라는 친숙한 인터페이스를 통해 접근성 확보

진행 방법

기술 스택

  • 백엔드: Python, FastAPI, Celery, PostgreSQL, Redis

  • 프론트엔드: SvelteKit, TailwindCSS

  • 인프라: AWS EC2, ECS, Vercel(프론트엔드), Prometheus(모니터링)

  • AI: Langchain, LangGraph, Langsmith, Google Gemini 2.0 flash

AI 구현 방식

초기에는 LangGraph와 함께 ReAct(Reasoning + Acting) 패턴을 도입했습니다. 이론적으로는 AI가 스스로 추론하고 행동하는 방식이 이상적으로 보였습니다.

시작, 에이전트, 도구, 종료라는 단어가있는 프로그램 다이어그램

그러나 이 접근법은 여러 문제에 직면했습니다.

  • AI가 무한 루프에 빠지는 현상

  • 적절한 도구를 찾지 못하고 엉뚱한 분석을 시도

  • 이전 분석 결과를 망각하고 같은 작업을 반복

특히 시계열 데이터 처리가 큰 도전이었습니다. 심박수, 스트레스 데이터는 각각 수백 개의 데이터 포인트를 가지고 있어, 이 모든 데이터를 한번에 AI에 입력하면 AI가 모든 데이터를 제대로 반영하지 못했습니다.

이 문제를 해결하기 위해 에이전트를 단계로 나누어 재설계했습니다.

def _create_graph(self):
        """그래프 생성"""

        tools_node = ToolNode(tools=self.tools)
        plan_node_title = "plan"
        execute_tool_node_title = "execute_tool"
        analysis_node_title = "analysis"
        report_node_title = "report"
        tools_node_title = "tools"

        # 그래프 생성
        workflow = StateGraph(AgentState)

        def custom_tool_condition(state: AgentState):
            """도구 선택 조건"""
            last_analysis = (
                state["analysis_history"][-1] if state["analysis_history"] else None
            )

            if state.get("loop_count", 0) >= 7:
                return "리포트 생성"

            return (
                "추가 분석 요청"
                if last_analysis and last_analysis.additional_analysis_needed
                else "리포트 생성"
            )

        # 노드 추가
        workflow.add_node(plan_node_title, self._create_plan_node())
        workflow.add_node(analysis_node_title, self._create_analysis_node())
        workflow.add_node(execute_tool_node_title, self._create_execute_tool_node())
        workflow.add_node(report_node_title, self._create_report_node())
        workflow.add_node(tools_node_title, tools_node)

        # 엣지 추가
        workflow.set_entry_point(plan_node_title)
        workflow.add_edge(plan_node_title, execute_tool_node_title)
        workflow.add_edge(execute_tool_node_title, tools_node_title)
        workflow.add_edge(tools_node_title, analysis_node_title)
        workflow.add_edge(report_node_title, END)

        # 조건 엣지 추가
        workflow.add_conditional_edges(
            analysis_node_title,
            custom_tool_condition,
            {
                "추가 분석 요청": execute_tool_node_title,
                "리포트 생성": report_node_title,
            },
        )

        return workflow.compile()

이 방식의 주요 이점은 다음과 같습니다.

  1. AI가 한 번에 처리하는 데이터 양 감소

  2. 필요한 데이터에만 집중할 수 있는 구조

  3. 단계별 특화된 프롬프트로 명확한 지시 가능

  4. LangGraph의 상태 관리를 통해 데이터 누락 방지

핵심 프롬프트

각 단계별로 특화된 프롬프트를 사용했습니다. 예를 들어, 계획 단계의 프롬프트는 다음과 같습니다.

def create_planner_prompt():
    """건강 분석을 위한 SystemMessage 프롬프트"""
    return SystemMessage(
        content="""
        당신은 **건강 데이터를 분석하는 전문가**입니다.
        사용자의 질문을 분석하여 **필요한 분석 목표**를 수립하세요.

        🔍 **작업 목표**
        1. **분석해야 할 건강 데이터 유형을 결정하세요.**  
           - 사용자의 질문에서 **심박수, 걸음 수, 수면, 스트레스, 활동** 등의 키워드를 찾아내세요.
           - 단일 지표(예: 스트레스)에 대한 질문이라도 **관련된 건강 데이터**(예: 심박수, 수면, 활동량 등)를 함께 고려해 복합적으로 분석하세요.
           - 예를 들어, "최근 스트레스 상태 어때?"라는 질문이 들어오면: 
                - 스트레스 데이터를 기본 분석 대상으로 설정하고  
                - **관련성이 높은 수면, 심박수, 활동 데이터도 함께 분석 대상에 포함**하세요.  
           - 또 다른 예시로, "최근 운동량이 어때?"라는 질문이 들어오면:
                - 활동량 데이터를 기본 분석 대상으로 설정하고  
                - **관련성이 높은 걸음 수, 심박 수 데이터도 함께 분석 대상에 포함**하세요.  
           - 사용자가 특정 데이터를 지정하지 않았다면, **전반적인 건강 상태 분석을 수행하세요.**
        
        2. **질문의 유형을 분석하세요.**  
           - 특정 날짜에 대한 질문인지, 기간에 대한 질문인지 판단하세요.  
           - **특정 날짜 질문 예시:**  
             - "오늘 내 수면 상태에 대해 피드백 해줘." → **특정 날짜(오늘)의 수면 데이터 분석**
             - "어제 스트레스가 높았어?" → **특정 날짜(어제)의 스트레스 데이터 분석**
           - **기간 질문 예시:**  
             - "최근 건강 상태가 어때?" → **최근 N일 동안의 전반적인 건강 분석**
             - "지난주 수면 패턴이 어땠어?" → **2025-01-01부터 2025-01-07까지 수면 데이터 분석**
             - "지난달 내 걸음 수 추이는?" → **2025-02-01부터 2025-02-28까지 걸음 수, 활동 데이터 분석**

        3. **분석 목표 설정**  
           - 데이터의 일반적인 패턴을 분석하세요.  
           - 특정 날짜 기반 질문이면, 해당 날짜의 데이터를 집중 분석하세요.  
           - 기간 기반 질문이면, **전반적인 경향성과 이상 징후를 분석**하세요. 
           - **질문이 단일 지표에 국한되어 있더라도**, 관련된 데이터를 포함하여 분석 목표를 **복합적으로** 설정하세요. 
           - 예를 들어, **"최근 스트레스 높았어?"**라는 질문이 들어오면 
             - "스트레스 수준의 변화, 심박수 변화, 활동량, 수면 패턴을 함께 분석해야 함"   
             - "스트레스 증가와 관련된 수면 질 저하나 활동 감소가 있었는지 확인해야 함"
             - "특이점이 있는 경우 추가 분석 수행"

        ---

        📌 **출력 형식 (JSON)**
        분석 목표, 주요 관심 지표, 질문의 핵심 의도를 **JSON 형식**으로 반환하세요.

        ```json
        {
            "analysis_plan": [
                "2025-01-01부터 2025-01-07까지 걸음 수와 수면 패턴을 분석해야 함",
                "2025-01-01부터 2025-01-07까지 심박수 변화를 분석해야 함",
                "오늘 수면 데이터에서 총 수면 시간, REM 수면, 깊은 수면 비율을 분석해야 함",
                "오늘의 수면 패턴과 지난 7일 평균 수면 패턴을 비교해야 함",
                "오늘 수면 시간이 평소보다 1시간 이상 줄어든 경우, 추가 분석이 필요함",
                "스트레스 수준과 수면 패턴의 관계를 분석해야 함"
            ],
            "focus_areas": [
                "수면",
                "활동량",
                "스트레스"
            ],
            "user_intent": "최근 수면의 질이 어떤지 알고 싶음"
        }
        ```

        🎯 **주의사항**
        - 결과는 반드시 JSON 형식으로 출력하세요.
        - 분석 계획이 너무 많지 않도록 **가장 중요한 8~10개의 목표만 설정하세요.**
        """
    )

챗봇 데모 스크린샷

사용자는 카카오톡 챗봇에서 분석을 요청하고, 웹에서 분석 진행 상황과 결과를 함께 확인할 수 있는 구조로 설계했습니다. 챗봇에서는 간결한 텍스트 형태의 인사이트를 제공하고, 동시에 웹페이지에서는 더 상세한 분석 결과를 확인할 수 있습니다.

한국인 두 사람 간의 대화의 스크린 샷
한국 문자 메시지 앱의 스크린 샷

결과와 배운 점

배운 점

  1. AI의 데이터 처리 한계: 대규모 시계열 데이터를 처리할 때 AI는 문맥 길이의 제한으로 인해 어려움을 겪습니다. 이를 해결하기 위해 데이터를 적절히 가공하고, 필요한 데이터만 선택적으로 제공하는 것이 중요합니다.

  2. 단계 분리: ReAct 패턴보다 명확한 단계로 나누어 AI 에이전트를 설계하는 것이 더 안정적이었습니다. 각 단계가 특정 작업에 집중하면 무한루프나 오류가 눈에 띄게 줄어듭니다.

  3. 프롬프트 엔지니어링: 각 단계별로 특화된 프롬프트를 사용하는 것이 중요합니다. 특히 AI가 무엇을 해야 하는지, 어떤 형식으로 출력해야 하는지 명확히 지정하는 것이 좋습니다.

과정 중에 어떤 시행착오를 겪었나요?

  1. ReAct 패턴의 한계: 처음에는 AI가 자율적으로 추론하고 행동하는 ReAct 패턴이 적합할 것이라 생각했지만, 복잡한 건강 데이터 분석에서는 오히려 불안정했습니다. AI가 무한 루프에 빠지거나 적절한 도구를 선택하지 못하는 문제가 자주 발생했습니다.

  2. 타임존 이슈: UTC 기준으로 데이터를 조회 했을 때, 한국 시간(UTC+9)으로 '오늘'의 데이터를 조회하면 데이터가 누락되는 문제가 있었습니다. 결국 로컬 시간을 기준으로 인덱싱하는 방식을 도입했습니다.

  3. 서버 안정성 문제: 여러 도커 컨테이너를 한 인스턴스에서 운영하면서 메모리 부족 현상이 발생했습니다. 이를 해결하기 위해 Redis를 외부 서비스(Upstash)로 분리하고, 스왑 파일 설정, 자동 스케일링 등의 조치를 취했습니다.

  4. AI 문맥 길이 제한: 건강 데이터가 너무 많아 AI의 문맥 길이를 초과하는 문제가 있었습니다. 이를 해결하기 위해 필요한 데이터만 선택적으로 제공하고, 분석 단계를 여러 단계로 나누어 진행했습니다.

서비스 링크:

2

👉 이 게시글도 읽어보세요