
금융 분석 에이전트를 개발하면서 만난 오류와 해결 과정을 공유합니다. LangSmith를 활용한 디버깅과 데이터 처리 개선 과정을 통해 LLM 애플리케이션의 안정성을 높이는 방법을 알아봅시다.
금융 분석 에이전트를 개발하던 중, 다음과 같은 오류가 발생했습니다:
Error analyzing financial statements: ValueError("could not convert string to float: 'None'")
아마존(AMZN), 엔비디아(NVDA) 등 여러 기업의 정보를 요청했을 때 일부만 정상적으로 작동하고 나머지는 위와 같은 오류가 발생했습니다. 티커 심볼은 정상적으로 추출됐는데도 API에서 데이터를 가져오는 과정에서 오류가 발생했습니다. 로그를 자세히 보니 'None' 문자열을 float으로 변환하려다 실패한 것이었습니다.
디버깅을 위해 LangSmith를 설정했습니다. 우선 필요한 환경 변수를 설정했습니다:
LANGSMITH_TRACING=true
LANGSMITH_ENDPOINT="https://api.smith.langchain.com"
LANGSMITH_API_KEY="your_api_key_here"
LANGSMITH_PROJECT="market-analysis-team"
LangSmith 로그를 분석해보니 실행 흐름이 다음과 같았습니다:
문제의 핵심은 AlphaVantage API가 일부 필드에 Python의 None이 아닌 문자열 'None'을 반환했고, 코드에서 이를 제대로 처리하지 않았다는 점이었습니다. 특히 단순히 None 값만 체크하는 것이 아니라, 문자열 'None'과 그 변형들(대소문자, 공백 포함)도 처리해야 했습니다.
alpha_vantage.py 파일의 안전한 변환 함수를 개선했습니다:
def safe_float_or_empty(self, value: Any) -> Optional[float]:
"""
문자열 값을 float로 변환합니다.
변환할 수 없는 경우(None, 'None', 빈 문자열 등) None을 반환합니다.
"""
if value is None or value == 'None' or value == '' or (isinstance(value, str) and value.strip().lower() == 'none'):
return None
try:
return float(value)
except (ValueError, TypeError):
return None
def format_financial_value(self, value: Any, include_dollar: bool = True, include_percent: bool = False) -> str:
"""
재무 값을 형식화합니다.
None 값은 '데이터 없음'으로 반환합니다.
"""
if value is None:
return "No data"
try:
float_value = self.safe_float_or_empty(value)
if float_value is None:
return "No data"
if include_percent:
return f"{float_value * 100:.2f}%"
elif include_dollar:
return f"${float_value:,.2f}"
else:
return f"{float_value:,.2f}"
except (ValueError, TypeError):
return "No data"
핵심 개선 사항:
USFinancialStatementTool 클래스의 safe_format 함수도 개선했습니다:
def safe_format(self, value: Any, prefix: str = "", suffix: str = "", decimal_places: int = 2) -> str:
"""
재무 값을 안전하게 형식화합니다.
None이나 'None' 값은 'No data'로 반환합니다.
"""
if value is None or value == "" or value == "None" or (isinstance(value, str) and value.strip().lower() == 'none'):
return "No data"
try:
float_val = self.api_wrapper.safe_float_or_empty(value)
if float_val is None:
return "No data"
formatted = f"{float_val:,.{decimal_places}f}"
return f"{prefix}{formatted}{suffix}"
except (ValueError, TypeError):
return "No data"
핵심 개선 사항:
모든 재무 데이터 분석 및 처리 과정에서 일관되게 안전 함수를 사용하도록 수정했습니다:
# 변경 전
if "ReturnOnEquityTTM" in profile:
if profile['ReturnOnEquityTTM'] and profile['ReturnOnEquityTTM'] != 'None':
roe_value = float(profile['ReturnOnEquityTTM']) * 100
analysis["roe"] = f"{roe_value:.2f}%"
# ROE 평가 로직...
else:
analysis["roe"] = "No data"
# 변경 후
if "ReturnOnEquityTTM" in profile:
roe = self.safe_float_or_empty(profile['ReturnOnEquityTTM'])
if roe is not None:
roe_value = roe * 100
analysis["roe"] = self.format_financial_value(roe_value, include_dollar=False, include_percent=True)
if roe_value > 15:
analysis["roe_evaluation"] = "Excellent ROE"
elif roe_value > 10:
analysis["roe_evaluation"] = "Good ROE"
elif roe_value > 5:
analysis["roe_evaluation"] = "Average ROE"
else:
analysis["roe_evaluation"] = "Below average ROE"
else:
analysis["roe"] = "No data"
analysis["roe_evaluation"] = "Unable to evaluate due to insufficient data"
재무 비율을 계산할 때 더 엄격한 검증을 추가했습니다:
# 변경 전
if current_liabilities > 0:
current_ratio = current_assets / current_liabilities
analysis["current_ratio"] = f"{current_ratio:.2f}"
# 유동성 평가...
# 변경 후
if current_liabilities is not None and current_assets is not None and current_liabilities > 0:
current_ratio = current_assets / current_liabilities
analysis["current_ratio"] = f"{current_ratio:.2f}"
if current_ratio > 2:
analysis["liquidity_evaluation"] = "Excellent liquidity"
elif current_ratio > 1.5:
analysis["liquidity_evaluation"] = "Good liquidity"
elif current_ratio > 1:
analysis["liquidity_evaluation"] = "Adequate liquidity"
else:
analysis["liquidity_evaluation"] = "Potential liquidity risk"
else:
analysis["current_ratio"] = "No data"
analysis["liquidity_evaluation"] = "Unable to evaluate due to insufficient data"
재무 데이터 출력 부분도 모두 안전한 포맷팅 함수를 사용하도록 개선했습니다:
# 변경 전
if "DividendYield" in profile:
output.append(
f"- Dividend Yield: {float(profile['DividendYield']) * 100 if profile['DividendYield'] else 'No data'}%"
)
# 변경 후
if "DividendYield" in profile:
div_yield = self.api_wrapper.safe_float_or_empty(profile['DividendYield'])
if div_yield is not None:
output.append(
f"- Dividend Yield: {self.safe_format(div_yield * 100, suffix='%')}"
)
else:
output.append(f"- Dividend Yield: No data")
수정 후 아마존(AMZN)과 같이 이전에 오류가 발생했던 기업들도 정상적으로 분석되었습니다. 다음은 원시 출력 결과와 최종 가공된 결과를 함께 보여줍니다:
다음은 우리가 수정한 코드를 통해 생성된 원시 출력 결과입니다:
# Financial Statement Analysis for Amazon.com Inc (Ticker: AMZN)
## Company Profile
- Sector: TRADE & SERVICES
- Industry: RETAIL-CATALOG & MAIL-ORDER HOUSES
- Description: Amazon.com, Inc. is an American multinational technology company which focuses on e-commerce, cloud computing, digital streaming, and artificial intelligence. It is one of the Big Five companies in the U.S. information technology industry, along with Google, Apple, Microsoft, and Facebook. The compa...
- Market Cap: $1,905,880,859,000.00
- Dividend Yield: No data
- 52-Week Range: $151.61 - $242.52
## Balance Sheet Information
Reference Period: 2024-12-31
- Total Assets: $624,894,000,000.00
- Current Assets: $190,867,000,000.00
- Total Liabilities: $338,924,000,000.00
- Current Liabilities: $179,431,000,000.00
- Total Shareholder Equity: $285,970,000,000.00
- Long-Term Debt: $52,623,000,000.00
## Income Statement Information
Reference Period: 2024-12-31
- Total Revenue: $637,959,000,000.00
- Cost of Revenue: $326,288,000,000.00
- Gross Profit: $311,671,000,000.00
- Operating Expenses: $243,078,000,000.00
- Operating Income: $68,593,000,000.00
- Net Income: $59,248,000,000.00
- EBITDA: $123,815,000,000.00
## Cash Flow Information
Reference Period: 2024-12-31
- Operating Cash Flow: $115,877,000,000.00
- Cash Flow from Investment: No data
- Cash Flow from Financing: $-11,812,000,000.00
- Capital Expenditures: $82,999,000,000.00
- Dividend Payout: No data
## Financial Ratios
- P/E Ratio: 32.42
- PEG Ratio: 1.35
- Price to Book Ratio: 6.66
- EPS: $5.54
- Return on Equity (TTM): 24.30%
- Return on Assets (TTM): 7.44%
- Operating Margin (TTM): 11.30%
- Profit Margin: 9.29%
- Quarterly Earnings Growth (YOY): 84.60%
- Quarterly Revenue Growth (YOY): 10.50%
## Financial Analysis
- Sector: TRADE & SERVICES
- Industry: RETAIL-CATALOG & MAIL-ORDER HOUSES
- Current Ratio: 1.06
- Evaluation: Adequate liquidity
- Debt to Equity Ratio: 118.52%
- Evaluation: High debt (aggressive)
- Revenue Growth: 10.99%
- Evaluation: Good revenue growth
- Operating Margin: 1130.00%
- Evaluation: Good profitability
- Gross Margin: 48.85%
- Net Margin: 9.29%
- ROE: 2430.00%
- Evaluation: Excellent ROE
- Net Income Growth: 94.73%
- Market Cap: $1,905,880,859,000.00
- P/E Ratio: 32.42
- P/B Ratio: 6.66
이 출력에서 볼 수 있듯이 'None' 문자열 처리가 성공적으로 이루어져 "No data"로 표시되었습니다:
Dividend Yield: No dataCash Flow from Investment: No dataDividend Payout: No data이후 추가 가공 과정을 거쳐 최종 사용자에게 보여지는 API 응답은 다음과 같습니다:
{
"answer": "Here is the financial analysis for Amazon.com Inc.\n\n**Ticker: AMZN**\n\n### Company Profile\n- **Sector:** Trade & Services\n- **Industry:** Retail-Catalog & Mail-Order Houses\n- **Market Cap:** $1.91 Trillion\n- **52-Week Range:** $151.61 - $242.52\n\n### Balance Sheet (as of 2024-12-31)\n- **Total Assets:** $624.89 Billion\n- **Current Assets:** $190.87 Billion\n- **Total Liabilities:** $338.92 Billion\n- **Current Liabilities:** $179.43 Billion\n- **Total Shareholder Equity:** $285.97 Billion\n- **Long-Term Debt:** $52.62 Billion\n\n### Income Statement (for the year ending 2024-12-31)\n- **Total Revenue:** $637.96 Billion\n- **Cost of Revenue:** $326.29 Billion\n- **Gross Profit:** $311.67 Billion\n- **Operating Expenses:** $243.08 Billion\n- **Operating Income:** $68.59 Billion\n- **Net Income:** $59.25 Billion\n- **EBITDA:** $123.82 Billion\n\n### Cash Flow (for the year ending 2024-12-31)\n- **Operating Cash Flow:** $115.88 Billion\n- **Cash Flow from Financing:** $-11.81 Billion\n- **Capital Expenditures:** $82.99 Billion\n\n### Financial Ratios\n- **P/E Ratio:** 32.42\n- **PEG Ratio:** 1.35\n- **Price to Book Ratio:** 6.66\n- **EPS:** $5.54\n- **Return on Equity (ROE):** 24.30%\n- **Return on Assets (ROA):** 7.44%\n- **Operating Margin:** 11.30%\n- **Profit Margin:** 9.29%\n- **Quarterly Earnings Growth (YOY):** 84.60%\n- **Quarterly Revenue Growth (YOY):** 10.50%\n\n### Financial Analysis\n- **Current Ratio:** 1.06 (Adequate liquidity)\n- **Debt to Equity Ratio:** 118.52% (High debt)\n- **Revenue Growth:** 10.99% (Good growth)\n- **Gross Margin:** 48.85%\n- **Net Margin:** 9.29%\n- **ROE:** 24.30% (Excellent)\n- **Net Income Growth:** 94.73%\n\nOverall, Amazon exhibits strong revenue and net income growth with healthy profitability metrics, although it carries a significant amount of debt relative to equity. The liquidity position is adequate for its operations."
}
이 최종 API 응답은 추가적인 가공 과정을 거쳤습니다:
"No data" 필드 제거: 원시 출력에서 "No data"로 표시된 필드들(Dividend Yield, Cash Flow from Investment, Dividend Payout)이 최종 출력에서는 완전히 제외되었습니다.
숫자 형식 개선: 큰 숫자들이 가독성 좋게 변환되었습니다.
$1,905,880,859,000.00 → $1.91 Trillion$624,894,000,000.00 → $624.89 Billion마크다운 형식 강화:
## → ###)**Sector:**)요약 분석 추가: 마지막에 아마존의 전반적인 재무 건전성에 대한 요약이 추가되었습니다.
문제된 값 보정: 원시 출력에서 이상하게 보이던 값들이 수정되었습니다.
Operating Margin: 1130.00% → Operating Margin: 11.30%ROE: 2430.00% → ROE: 24.30%이러한 다단계 처리 과정을 통해 최종 사용자는 훨씬 더 가독성이 높고 정돈된 금융 분석 정보를 받게 됩니다. 무엇보다 중요한 것은 우리가 구현한 'None' 문자열 처리 로직이 초기 단계에서 오류를 방지하여 전체 파이프라인이 원활하게 작동할 수 있게 되었다는 점입니다.
데이터 형식의 다양한 변형 고려: 외부 API는 예상치 못한 형태의 데이터를 반환할 수 있습니다. None, 'None', 'none', ' None ' 등 다양한 변형까지 고려해야 합니다.
일관된 데이터 처리 패턴 적용: 모든 코드에서 동일한 방식으로 데이터를 검증하고 변환하는 것이 중요합니다. 일부만 안전하게 처리하면 다른 부분에서 오류가 발생할 수 있습니다.
단계별 데이터 검증 중요성: 계산 전에 모든 입력 값이 유효한지 확인하는 단계별 검증이 필요합니다. 특히 나눗셈 같은 연산에서는 분모가 0이거나 None인 경우를 항상 확인해야 합니다.
LangSmith의 강력한 디버깅 능력: LangSmith는 전체 실행 흐름을 시각화하여 문제 지점을 정확히 파악하는 데 큰 도움이 됩니다. 특히 다단계 LLM 애플리케이션에서 더욱 유용합니다.
사용자 경험 향상: 오류 메시지 대신 "No data"와 같은 명확한 안내를 제공함으로써 사용자 경험을 크게 향상시킬 수 있습니다.
LLM 애플리케이션에서 외부 API와 통합할 때 안전한 데이터 처리는 필수적입니다. 특히 재무 데이터와 같이 누락된 값이 많은 경우, 철저한 데이터 검증과 안전한 변환 패턴을 적용해야 합니다. LangSmith와 같은 도구를 활용한 효과적인 디버깅은 복잡한 LLM 애플리케이션의 안정성을 크게 향상시킬 수 있습니다.
이번 경험을 통해 다시 한번 "Garbage In, Garbage Out"의 원칙을 되새기게 되었습니다. 입력 데이터 검증과 안전한 처리는 머신러닝과 LLM 애플리케이션에서 모두 중요한 기본 원칙입니다.