📅 Sense Stock 개발 일지 (2025-08-11)
n8n, 사용자 질의 기반 경제/시장/주식 분석 자동화 파이프라인 구축 중 진행한 작업들을 정리합니다.
오늘은 사용자 질문 의도를 더 똑똑하게 파악하고, 얽히고설킨 데이터 흐름을 정비한 작업을 정리했습니다.
지난번에는 데이터 보강, API 호출 방식 변경, 데이터 자동 삭제 로직 구현 등 데이터의 기반을 다지는 작업을 진행했었다. 이번에는 사용자의 질문을 더 깊이 이해하고 그에 맞는 데이터를 정확히 찾아내는 지능 고도화에 집중적으로 고민하고 작업한 내용을 공유한다.
사용자가 어떤 질문을 할지는 예측하기 어렵다. "테슬라 주가 어때?"처럼 명확한 질문도 있지만, "AI 반도체 관련주 알려줘"처럼 산업이나 테마를 묻는 질문도 있다.
기존의 단순 키워드 매칭 방식으로는 이런 다양한 의도를 파악하기 어려웠고, 종종 엉뚱하거나 부실한 답변을 내놓는 문제가 있었다. 게다가 기능을 하나씩 추가하다 보니 데이터 구조가 복잡해져서, 작은 수정 하나가 연쇄적으로 다른 기능의 오류를 일으키는 일도 잦아졌다.
그래서 '질문 의도 분석'과 '데이터 검색 로직'을 대대적으로 손보기로 했다.
🧱 Workflow 구조
사용자 질문을 받으면, 가장 먼저 질문의 유형을 분류하고 핵심 기업들을 확장하여 추출하는 단계를 추가했다.
- Best Keyword Extract 프롬프트 고도화
질문에서 핵심 기업을 최대 5개까지 추출한다. 만약 "애플"이 언급되면, 단순히 애플만 보는 게 아니라 경쟁사나 주요 파트너사인 "삼성전자", "마이크로소프트" 등을 함께 리스트업한다. - 질문 유형 분류
질문을 4가지 유형으로 나눈다.- direct_company: 명확한 기업이 언급된 경우
- industry_theme: 산업/테마에 대한 질문인 경우
- macro_market: 시장/거시 경제 질문인 경우
- unanalyzable: 분석이 불가능한 경우 (이 경우, 더 이상 진행하지 않고 종료)
- Data Search 로직 변경
분류된 유형과 추출된 기업 리스트(최대 5개)를 바탕으로, 관련된 모든 데이터를 탐색한다.
💨 데이터 작업 흐름 정리
새로운 구조에서 데이터는 다음과 같은 단계를 거쳐 처리된다.
- 사용자 질문 입력
사용자가 "테슬라랑 전기차 시장 전망 좀 알려줘" 라고 질문한다. - 키워드 추출 및 유형 분석
- best_keyword는 ["TSLA", "F", "GM", "RIVN", "LCID" ] 등을 추출한다.
- 질문 유형은 direct_company (Tesla가 직접 언급됨)로 분류된다.
- 데이터 동시 검색
S&P500 시트와 각종 경제 데이터에서 companies.some(company => item.종목명.includes(company)) 로직을 통해 추출된 기업 리스트 전체와 일치하는 데이터를 모두 찾아낸다.
Best Keyword Extract Agent / Data Search Node Output
[
{
"status": "success",
"analysis_plan": {
"question_type": "direct_company",
"answer_type": "in-depth_company_analysis"
},
"derived_data": {
"request": "테슬라 어때?",
"inferred_companies": [
"Tesla",
"Ford",
"General Motors",
"Rivian",
"Lucid Motors"
],
"inferred_tickers": [
"TSLA",
"F",
"GM",
"RIVN",
"LCID"
],
"related_keywords": [
"전기차",
"자동차 제조",
"자율주행",
"배터리 기술",
"재활용"
]
}
}
]
[
{
"context": "## Tesla 및 관련 기업 종합 리포트\n\n**분석 대상:** Tesla, Ford, General Motors, Rivian, Lucid Motors\n\n### 기업별 변동률\n**Daily:**\n```json\n[\n {\n \"종목명\": \"General Motors\",\n \"2025년 8월 8일 금요일\": \"+0.91%\"\n },\n {\n \"종목명\": \"Ford Motor\",\n \"2025년 8월 8일 금요일\": \"+0.71%\"\n },\n {\n \"종목명\": \"Tesla\",\n \"2025년 8월 8일 금요일\": \"+0.74%\"\n },\n {\n \"row_number\": 3,\n \"종목명\": \"General Motors\",\n \"종가\": 52.95,\n \"고가\": 53.43,\n \"저가\": 52.54,\n \"변동\": 0.48,\n \"변동률\": 0.0091,\n \"거래량\": \"6.03M\",\n \"티커\": \"GM\",\n \"섹터명\": \"Consumer Discretionary\",\n \"평균거래량\": \"9.75M\",\n \"총 시가\": \"50.39B\",\n \"매출\": \"187.60B\",\n \"주가수익비율\": 8.34\n },\n {\n \"row_number\": 11,\n \"종목명\": \"Ford Motor\",\n \"종가\": 11.28,\n \"고가\": 11.39,\n \"저가\": 11.1,\n \"변동\": 0.07,\n \"변동률\": 0.0062,\n \"거래량\": \"71.77M\",\n \"티커\": \"F\",\n \"섹터명\": \"Consumer Discretionary\",\n \"평균거래량\": \"86.64M\",\n \"총 시가\": \"44.85B\",\n \"매출\": \"185.25B\",\n \"주가수익비율\": 14.1\n },\n {\n \"row_number\": 330,\n \"종목명\": \"Tesla\",\n \"종가\": 322.27,\n \"고가\": 322.4,\n \"저가\": 316.16,\n \"변동\": 2.36,\n \"변동률\": 0.0074,\n \"거래량\": \"66.66M\",\n \"티커\": \"TSLA\",\n \"섹터명\": \"Consumer Discretionary\",\n \"평균거래량\": \"106.34M\",\n \"총 시가\": \"1.04T\",\n \"매출\": \"92.72B\",\n \"주가수익비율\": 173.97\n }\n]\n```\n**Weekly:**\n```json\n[\n {\n \"종목명\": \"General Motors\",\n \"일간\": \"+0.46%\",\n \"주간\": \"+1.27%\",\n \"월간\": \"-0.23%\",\n \"YTD\": \"-0.94%\",\n \"1년\": \"+29.94%\",\n \"3년\": \"+46.34%\"\n },\n {\n \"종목명\": \"Ford Motor\",\n \"일간\": \"+1.20%\",\n \"주간\": \"-1.17%\",\n \"월간\": \"-7.28%\",\n \"YTD\": \"+12.38%\",\n \"1년\": \"+13.76%\",\n \"3년\": \"-23.69%\"\n },\n {\n \"종목명\": \"Tesla\",\n \"일간\": \"+2.19%\",\n \"주간\": \"-3.72%\",\n \"월간\": \"-1.93%\",\n \"YTD\": \"-23.42%\",\n \"1년\": \"+54.14%\",\n \"3년\": \"+7.32%\"\n }\n]\n```\n\n### 관련 섹터 정보\n**관련 섹터:** Consumer Discretionary\n**Daily Sector Perf:**\n```json\n[\n {\n \"섹터명\": \"Consumer Cyclical\",\n \"2025년 8월 6일 수요일\": \"0.13%\"\n }\n]\n```\n**Weekly Sector Perf:**\n```json\n[\n {\n \"No.\": \"3\",\n \"Name\": \"Consumer Cyclical\",\n \"Perf Week\": \"-4.66%\",\n \"Perf Month\": \"-1.62%\",\n \"Perf Quart\": \"8.56%\",\n \"Perf Half\": \"-7.38%\",\n \"Perf Year\": \"15.19%\",\n \"Perf YTD\": \"-2.57%\",\n \"Recom\": \"1.82\",\n \"Avg Volume\": \"1.45B\",\n \"Rel Volume\": \"1.10\",\n \"Change\": \"-3.07%\",\n \"Volume\": \"1.59B\"\n }\n]\n```\n\n### 기업 실적\n관련 기업들의 실적 발표 데이터가 없습니다.\n",
"originalRequest": "테슬라 어때?"
}
]
🏸 워크플로우 효율성 및 안정성 강화
- 분기 처리, Merge 노드로 해결
조건에 따라 A 또는 B 노드, 둘 중 하나만 실행되는 흐름이 있었다. 이전에는 이 두 분기의 결과를 합치기가 까다로웠는데, Merge 노드를 도입하면서 깔끔하게 해결했다. 어느 쪽이 실행되든, Merge 노드가 실행된 노드의 최종 결과(Output)를 받아 다음 단계로 넘겨주니 로직이 훨씬 간결해졌다. - 데이터 크롤링 실패 방지
웹 크롤링은 언제나 변수가 많다. 가끔 데이터를 제대로 가져오지 못하는 경우가 발생했는데, 이를 해결하기 위해 크롤링 직후에 If 노드를 추가했다. If 노드가 크롤링 결과가 비어있는지(False) 확인하고, 만약 비어있다면 성공할 때까지 다시 크롤링을 시도하도록 만들었다. 덕분에 데이터 누락이 거의 사라졌다. - 데이터 보강, 신규 데이터 지표 추가
기존 데이터에 깊이를 더하기 위해 파이썬 스크립트를 구현했다. 이 스크립트는 S&P 500 종목의 평균 거래량, 총 시가, 매출, 주가수익비율(PER) 같은 핵심적인 재무 데이터를 S&P500 데이터에 추가되어 저장된다. 이를 통해 더 풍부한 컨텍스트를 가진 분석이 가능해졌다. - Excel 시트 초기화 최적화
구글 시트 노드는 시트를 비울 때 반드시 1개의 아이템(Item)만 받아야 하는 제약이 있었다. 여러 아이템이 들어오면 그만큼 API호출을 요청하게된다. 그래서 Aggregate 노드로 모든 아이템을 하나로 합친 뒤 시트를 비우고, 다시 Split 노드로 분리하는 방식으로 해결했다. 이 간단한 트릭으로 Daily Workflow 실행 시간이 1분 14초에서 1분으로, 무려 14초나 단축되었고 API 호출 제한(Limit)에 걸릴 위험도 줄였다. - 자동 데이터 삭제
주간(Weekly) 워크플로우에 '오래된 데이터 삭제 노드'를 추가하여 1주일이 지난 Sector/S&P500 Daily 데이터를 자동으로 정리한다.
- AI의 데이터 참조 방식( 분기 처리, Merge 노드로 해결 이미지 참조)
AI가 최신 데이터를 쓰도록 어떻게 강제할 수 있을까? 그냥 두면 편리한 로컬 데이터를 먼저 참조할 위험이 있었다. 해결책은 의외로 간단했다. 프롬프트에 "Economic 데이터는 반드시 Workflow를 호출해서 가져와라"고 명시적으로, 그리고 강하게 지시했다. 때로는 기술적인 해결책보다 명확한 지시가 더 효과적일 때가 있다.
👍 프롬프트 고도화 및 결과물 개선
- 가독성 높은 HTML 테이블
기존의 결과물 테이블은 경계선이 없어 보기가 매우 불편했다. Markdown To HTML 변환기에 Table Support 속성을 추가하는 간단한 조치만으로, 선이 명확한 격자 테이블을 구현하여 가독성을 극적으로 끌어올렸다.
- 프롬프트의 진화 (Lv1, Lv2, Lv3)
답변이 너무 부실하다는 느낌을 받았다. 이러한 아쉬움을 해결하기 위해 프롬프트를 등급별로 설계했다. 질문의 의도에 따라 단순 정보를 제공하는 Lv1, Lv2 / 심층 분석을 제공하는 Lv3까지, 상황에 맞는 답변을 생성하도록 했다.
또한, Economic/Earning 데이터는 AI가 직접 API를 호출하도록 프롬프트에 명시하여 항상 최신 정보를 참조하도록 만들었다.
🧠 진행 중 고민한 점들
- 수정의 연쇄 작용: Column 명 하나, 참조 데이터 이름 하나 바꿨을 뿐인데 수많은 노드에서 에러가 터져 나왔다. 유지보수가 쉬울 줄 알았는데, 생각보다 너무 복잡하게 얽혀있었다. 처음부터 데이터 구조와 명명 규칙을 얼마나 신중하게 정의해야 하는지 뼈저리게 느꼈다.
- 부실한 답변 퀄리티업: 단순히 데이터를 요약해주는 수준을 넘어서려면 어떻게 해야 할까? 키워드를 추출하는 과정에서 얻은 "핵심 경쟁사"나 "주요 파트너사" 같은 정보들을 답변에 어떻게 자연스럽게 녹여낼지 고민이 많았다. 일단 프롬프트 수정과 최종 응답 테이블을 개선하는 것으로 급한 불은 껐다.
- 데이터 동기화 문제: 구글 시트에 Sector 정보를 추가하고 다시 로컬 DB와 동기화하는 과정이 꽤 번거로웠다. 데이터의 근원(Source of Truth)을 하나로 통일하고 관리하는 체계가 필요해 보였다.
"하나를 바꾸면 열 군데서 터진다"는 개발 괴담을 직접 체험한 기분이다. 쉽게 생각했던 유지보수가 발목을 잡으니, 처음부터 큰 그림을 그리고 확장성을 고려한 설계가 얼마나 중요한지 다시 한번 깨달았다. 물론, 이런 복잡한 구조를 유지보수하기 쉽게 리팩토링하는 것 또한 개발자의 중요한 역량이겠지.
❓ 다음 단계에서 고민 중인 것들
- Data Search 노드 리팩토링: 현재 가장 시급하게 개선해야 할 부분은 Data Search 노드입니다.
- 에러 처리: 현재는 검색 결과가 없을 때 노드에서 바로 에러(Error)가 발생하며 워크플로우가 멈춘다. 다음 단계에서는 검색된 데이터가 없더라도 에러를 내지 않고, '결과 없음'이라는 상태를 다음 노드로 정상적으로 전달하도록 예외 처리 로직을 추가
- 데이터 구조화: 지금은 찾아낸 모든 데이터를 context라는 키 하나에 전부 담아 출력. 이러니 후속 프롬프트가 필요한 정보를 정확히 찾아 쓰기 어렵다.
이 구조를 변경하여, 기업 정보, 시장 데이터, 관련 뉴스처럼 데이터를 종류별로 명확히 구분해서 출력하도록 변경. 이렇게 해야 "기업 정보에서 CEO 이름을 찾아줘" 와 같이 훨씬 정교하고 안정적인 프롬프팅이 가능해질 것
'Automation Tool > n8n Project' 카테고리의 다른 글
💣 프로젝트 트러블슈팅 및 삽질 기록 (4) | 2025.08.12 |
---|---|
Sense Stock 개발 회고, 세 번째 (3) | 2025.08.12 |
Sense Stock, D+22 (4) | 2025.08.06 |
Sense Stock, D+21 (4) | 2025.08.04 |
Sense Stock, D+20 (3) | 2025.07.28 |
댓글