
숫자를 절대 지어내지 않는 LLM 리포팅 레이어 구축하기
요약
태양광 발전 데이터를 기반으로 정확한 보고서를 생성하기 위해 LLM의 계산 역할을 배제하고 사전 계산된 데이터만 전달하는 설계 방식을 소개합니다. 환각 현상을 방지하기 위해 데이터 집계와 이상 탐지를 Python 단계에서 처리하는 그라운딩(Grounding) 전략을 다룹니다.
핵심 포인트
- LLM은 원시 데이터 계산 대신 사전 계산된 요약본만 활용하여 환각 방지
- 데이터 집계 및 이상 탐지는 모델 호출 전 Python 단계에서 수행
- 임계값 설정 시 근거 없는 수치 대신 실제 데이터 패턴을 반영하는 것이 중요
- 모델의 역할을 '분석'이 아닌 '구조화된 사실의 서술'로 제한
태양광 발전 보고서 생성기가 왜 그라운딩 (Grounding)을 있으면 좋은 기능이 아닌 필수 제약 조건으로 취급하는지 — 그리고 시스템 프롬프트에서 이를 실제로 강제하는 모습은 어떠한지에 대하여.
문제점
대부분의 PV (태양광) 모니터링 대시보드는 데이터를 보여주는 데는 능숙하지만, 이를 설명하는 데는 서툽니다. 차트는 어제 시스템이 26 kWh를 생산했다는 것을 알려주지만, 그것이 정상인지, 무언가 잘못된 것인지, 아니면 사용자가 신경을 써야 하는 상황인지는 알려주지 않습니다. 발전 곡선 (production curves)을 읽는 데 이미 능숙하지 않은 사람에게 대시보드는 아무런 판결도 붙어 있지 않은 숫자의 벽일 뿐입니다.
solar-report는 주기적인 발전 데이터와 (선택적으로) 기록된 인버터 이벤트를 짧은 서면 보고서로 변환하는 작은 오픈 소스 CLI입니다. 이 보고서는 개요 (Overview), 추세 (Trend) 설명, 관찰 사항 (Observations) 목록, 그리고 필요한 경우 권장 사항 (Recommendations)으로 구성됩니다. 흥미로운 점은 보고서 형식이 아닙니다. 제가 LLM (Anthropic API를 통한 Claude)이 문단을 작성하도록 신뢰하기 전에, 파이프라인에 대해 무엇이 반드시 충족되어야 했는가 하는 점입니다.
LLM은 절대 아무것도 계산하지 않는다
첫 번째 설계 결정이자 다른 모든 결정의 근간이 되는 사항은 다음과 같습니다: 보고서를 작성하는 모델은 원시 시계열 데이터 (raw time-series data)를 절대 보지 않으며, 산술 연산도 절대 수행하지 않습니다.
집계 (Aggregation), 기준선 계산 (baseline computation), 그리고 이상 탐지 (anomaly detection)는 API 호출이 이루어지기 전, 일반 Python 단계에서 모두 수행됩니다. 모델에 전달되는 것은 이미 중요한 숫자로 축소된 사전 계산된 요약본입니다 — 총 kWh, 이동 기준선 (rolling baseline), 플래그가 지정된 이상 징후 목록, 선택적인 이벤트 목록 등입니다. 모델의 역할은 "이 데이터를 분석하고 보고하라"보다 훨씬 좁습니다. 모델의 역할은 "자신의 의견을 추가하지 말고, 이 구조에 따라 이러한 특정 사실들을 서술하라"입니다.
이러한 구분은 매우 중요합니다. 왜냐하면 이는 가끔 비교 수치를 환각 (hallucinate)하는 도구와, 환각을 일으킬 수 있는 시스템 부분에서 비교 자체가 계산되지 않기 때문에 구조적으로 환각을 일으킬 수 없는 도구 사이의 차이이기 때문입니다.
추측이 아닌 데이터에서 나온 임계값
이상 탐지 임계값(anomaly detection threshold)은 실무에서 "근거에 기반한(grounded)" 방식이 어떤 비용을 치르는지에 대한 좋은 사례입니다. 왜냐하면 이를 올바르게 설정하기까지 한 번의 잘못된 시도가 있었기 때문입니다.
첫 번째 버전은 생산량이 이동 4주 기준선(rolling 4-week baseline)보다 15% 이상 낮아지는 모든 날을 플래그(flag)했습니다. 별다른 근거 없이 선택된, 그럴싸하게 들리는 숫자였습니다. 실제 생산 데이터를 몇 주간 적용해 본 결과, 평범한 날씨 변화조차 거의 매주 이상 징후(anomaly)로 탐지되었습니다. 약간 구름이 낀 화요일이 실제 결함이 발생했을 때와 동일한 "주목할 만한 사건"이라는 문구를 트리거(trigger)하는 식이었습니다. 보고서가 끊임없이 '양치기 소년'처럼 잘못된 경보를 울렸고, 이는 아예 이상 징후를 탐지하지 않는 것보다 더 나쁩니다. 독자가 '관찰(Observations)' 섹션을 읽지 않도록 길들이기 때문입니다.
그럴싸해 보이는 것이 아니라 실제 데이터가 어떻게 보이는지에 따라 두 가지 변경 사항이 적용되었습니다. 임계값을 25%로 높이고, 오직 음의(negative) 편차에만 제한을 두는 것이었습니다. 기대치를 상회하는 날은 시스템 운영자가 관심을 가질 만한 의미에서의 이상 징후가 아닙니다. 그저 운이 좋은 날일 뿐입니다. 이 두 가지 변경 사항이 적용되자, 이상 징후 목록은 데이터를 직접 살펴보는 사람이 실제로 이상하다고 판단할 만한 내용과 일치하기 시작했습니다.
이 내용이 단순한 통계학적 각주가 아니라 LLM에 관한 글에서 언급될 가치가 있는 이유는 다음과 같습니다. 모델은 이 과정에서 전혀 관여하지 않았기 때문입니다. 무엇을 이상 징후로 간주할지 결정하는 것은 모델이 아니라, 모델이 실행되기 전 실제 데이터를 바탕으로 테스트된 Python 함수입니다. 이상 징후에 대한 LLM의 유일한 역할은 전달받은 이상 징후를 설명하는 것뿐입니다.
의도적으로 시스템 프롬프트의 역할을 줄이기
모델에 입력되는 값이 완전히 사전 계산(pre-computed)되고 나면, 시스템 프롬프트(system prompt)의 역할은 "이 데이터를 이해하라"에서 "데이터에 아무것도 추가하지 마라"로 전환됩니다. 그런데 이것이 역방향의 작업보다 더 명시적인 엔지니어링을 필요로 한다는 사실이 밝혀졌습니다.
프롬프트에 거의 그대로 포함된 규칙 중 몇 가지는 다음과 같습니다:
엄격한 관찰 규칙(STRICT OBSERVATIONS RULE): 관찰(Observations) 섹션은 입력값의 "탐지된 이상 징후(ANOMALIES DETECTED)" 항목만을 반영해야 합니다. 추가적인 패턴을 식별하기 위해 일일 수치를 직접 비교하지 마십시오. 다음을 언급하지 마십시오...
두 번째 규칙이 존재하는 이유는 특정한 실패 모드 (failure mode) 때문입니다. 이벤트 로그(인버터 알람, 출력 제한 경고 등)가 선택적 입력값으로 추가되자, 모델이 어떤 이벤트가 어떤 이상 징후를 설명하는지에 대해 스스로 결론을 내리기 시작했습니다. 때로는 정확했지만, 때로는 그렇지 않았으며, 독자가 어떤 것이 맞는지 구분할 방법도 없었습니다. 해결책은 모델에게 "주의하세요"라고 요청하는 더 똑똑한 프롬프트 (prompt)를 만드는 것이 아니었습니다. 상관관계 로직 (correlation logic) 자체를 Python으로 옮기는 것이었습니다. 즉, 모델이 데이터를 보기 전에 이벤트 타임스탬프 (timestamp)를 이상 징후 발생일과 미리 매칭시킨 뒤, 모델이 스스로 그러한 추론 (inference)을 수행하는 것을 명시적으로 금지하는 방식이었습니다. 모델에 입력되는 데이터에는 상관관계가 있는 이벤트 옆에 이미 [matches anomaly day]라고 표시되어 있습니다. 그렇게 표시되지 않은 항목에 대해 모델은 연결을 시도할 수 없습니다.
동일한 패턴이 프롬프트의 다른 곳에서도 나타납니다. 숫자는 항상 맥락과 함께 제공되어야 하며 (단독 수치 사용 금지), 이상 징후에 대한 인과 관계 언어는 확정적이기보다는 유보적이어야 하며 ("가능성 있음", "확인 필요" 등), 그리고 — 아마도 가장 매력적이지 않은 규칙이지만 반복되는 리포트에서 중요한 규칙으로서 — 어떤 사실도 두 개 이상의 섹션에 나타나서는 안 됩니다. 그래야 하나의 리포트 안에서 동일한 이상 징후가 세 가지 방식으로 다르게 설명되는 것을 방지할 수 있습니다.
이러한 규칙 중 그 어떤 것도 모델을 더 똑똑하게 만들지는 않습니다. 대신 모델의 범위를 의도적으로 좁힙니다. 이를 통해 판단처럼 보이는 리포트의 부분들이 실제로는 테스트가 가능한 상류 단계인 Python에서 수행된 판단이 되도록 만드는 것입니다.
현재 상태
이것은 v0.1 버전이며, 그 의미에 대해 솔직하게 밝히자면 다음과 같습니다:
- 데이터 소스는 현재 CSV만 지원합니다. 구조화된 운영 데이터 소스(production-data source)로 교체하는 것은 쉽습니다. Home Assistant의 REST API와 장기 통계(long-term statistics)가 다음 단계로 예정되어 있습니다. 하지만 현재로서는
timestamp와production_wh컬럼이 포함된 CSV를 통해 데이터를 입력해야 합니다. - 이벤트(Events)는 선택 사항이며 특정 벤더에 종속되지 않습니다. 기록된 알람/이벤트가 담긴 두 번째 CSV가 있으면 보고서가 더욱 풍부해지지만, 해당 파일이 없어도 도구는 정확히 동일하게 작동합니다.
- Anthropic SDK의 기본값 외에 재시도 로직(retry logic)이나 스트리밍(streaming)은 지원하지 않습니다. 정해진 일정에 따라 생성되는 보고서에는 적합하지만, 일시적인 API 오류가 귀하의 설정에 중요한 영향을 미친다면 이 점을 숙지할 필요가 있습니다.
직접 시도해보기
생성된 보고서의 실제 모습은 다음과 같습니다:
샘플 데이터와 위에 명시된 정확한 시스템 프롬프트(system prompt)를 포함한 리포지토리(repo)는 github.com/contimarco77/solar-report에서 확인할 수 있습니다. 실제 보고서 생성에 비용을 지불하기 전에 작동 원리를 확인하고 싶다면, API를 호출하지 않고도 CSV 파싱, 집계(aggregation), 이상 탐지(anomaly detection) 등 전체 파이프라인을 실행하는 --dry-run 모드를 사용할 수 있습니다.
만약 다른 도메인에서 LLM 출력의 근거 제시(grounding) 문제와 유사한 문제를 겪었거나, 제가 고려하지 못한 사용 사례에 대해 이상치 임계값(anomaly threshold)이 잘못되었다고 생각하신다면, 진심으로 의견을 듣고 싶습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기