From ec0ccf649e5a06780c10b672b3265577094eab35 Mon Sep 17 00:00:00 2001 From: gahusb Date: Thu, 14 May 2026 02:07:01 +0900 Subject: [PATCH] feat(ai_news): include summary + pub_date in LLM prompt --- stock-lab/app/screener/ai_news/analyzer.py | 21 ++++++++++++++++++++- stock-lab/tests/test_ai_news_analyzer.py | 17 ++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/stock-lab/app/screener/ai_news/analyzer.py b/stock-lab/app/screener/ai_news/analyzer.py index cff224d..35c8cdb 100644 --- a/stock-lab/app/screener/ai_news/analyzer.py +++ b/stock-lab/app/screener/ai_news/analyzer.py @@ -27,6 +27,25 @@ def _clamp(x: float, lo: float = -10.0, hi: float = 10.0) -> float: return max(lo, min(hi, x)) +def _format_news_block(news: List[Dict[str, Any]]) -> str: + """news dict 리스트 → prompt 에 들어가는 텍스트 블록. + + summary 가 있으면 title 다음 줄에 indent 해서 포함 (최대 200자). + pub_date 가 있으면 title 앞에 표시. + """ + lines: List[str] = [] + for n in news: + date = (n.get("pub_date") or "").strip() + title = (n.get("title") or "").strip() + summary = (n.get("summary") or "").strip() + prefix = f"[{date}] " if date else "" + if summary: + lines.append(f"- {prefix}{title}\n {summary[:200]}") + else: + lines.append(f"- {prefix}{title}") + return "\n".join(lines) + + async def score_sentiment( llm, ticker: str, @@ -36,7 +55,7 @@ async def score_sentiment( model: str = DEFAULT_MODEL, ) -> Dict[str, Any]: """Returns {ticker, score_raw, reason, news_count, tokens_input, tokens_output, model}.""" - news_block = "\n".join(f"- {n['title']}" for n in news) + news_block = _format_news_block(news) prompt = PROMPT_TEMPLATE.format( name=name or ticker, ticker=ticker, n=len(news), news_block=news_block, diff --git a/stock-lab/tests/test_ai_news_analyzer.py b/stock-lab/tests/test_ai_news_analyzer.py index 311e8e9..0ab4c09 100644 --- a/stock-lab/tests/test_ai_news_analyzer.py +++ b/stock-lab/tests/test_ai_news_analyzer.py @@ -17,7 +17,10 @@ def _mk_llm(content_text: str, in_tokens: int = 100, out_tokens: int = 20): return llm -NEWS = [{"title": "삼성전자, HBM 양산"}, {"title": "메모리 가격 반등"}] +NEWS = [ + {"title": "삼성전자, HBM 양산", "summary": "1분기 영업이익 사상 최대", "pub_date": "2026-05-14"}, + {"title": "메모리 가격 반등", "summary": "", "pub_date": "2026-05-14"}, +] @pytest.mark.asyncio @@ -53,3 +56,15 @@ async def test_score_sentiment_clamps_negative_out_of_range(): llm = _mk_llm(json.dumps({"score": -42.0, "reason": "초악재"})) out = await analyzer.score_sentiment(llm, "005930", NEWS) assert out["score_raw"] == -10.0 + + +@pytest.mark.asyncio +async def test_score_sentiment_includes_summary_in_prompt(): + """summary 가 있으면 prompt 에 포함, 없으면 title 만.""" + llm = _mk_llm(json.dumps({"score": 5.0, "reason": "ok"})) + await analyzer.score_sentiment(llm, "005930", NEWS, name="삼성전자") + call = llm.messages.create.call_args + user_msg = call.kwargs["messages"][0]["content"] + assert "1분기 영업이익 사상 최대" in user_msg # summary 포함 + assert "삼성전자, HBM 양산" in user_msg # title 포함 + assert "2026-05-14" in user_msg # pub_date 포함