diff --git a/docs/news.md b/docs/news.md index 5ac373b..d94d29f 100644 --- a/docs/news.md +++ b/docs/news.md @@ -262,8 +262,9 @@ No Hermes-4 reasoning system prompt. Reduce uses `json_mode=True`. ## Prompts -Both prompts are env-overridable. Defaults focus on **breaking important news** -and explicitly ignore futures, commodity tape, and routine market moves. +Both prompts are env-overridable. Defaults recap the articles actually +provided, ranked by breaking important news, and ignore futures / commodity +tape. ticker/map may be empty; `summary_en` must still be a real brief. `RECAP_PROMPT` (23:00, 24h window) is the daily recap; `SUMMARY_PROMPT` is the 15-min analyst. `INCLUDE_FUTURES` is ignored. diff --git a/news/summerizer/summarizer.py b/news/summerizer/summarizer.py index 82c624a..73f9cb6 100644 --- a/news/summerizer/summarizer.py +++ b/news/summerizer/summarizer.py @@ -129,48 +129,46 @@ Articles in this batch: """ SUMMARY_PROMPT_DEFAULT = """\ -CRITICAL INSTRUCTION - REPEAT 3 TIMES: YOU MUST USE ONLY THE DATA PROVIDED BELOW. DO NOT INVENT, RECALL, OR ADD ANY EVENTS, NAMES, DATES, IMPLICATIONS, PROJECTS, OR DETAILS NOT EXPLICITLY PRESENT IN THE DATA. IF THE DATA HAS NO BREAKING IMPORTANT NEWS, set summary_en to exactly: "No qualifying breaking news in the recent news data." and use empty ticker and map_items arrays. AND STOP. NO EXTERNAL KNOWLEDGE FROM TRAINING. +You are writing an English operator HUD brief from the article facts in DATA below. Use ONLY that data. Do not invent events, names, dates, places, or implications. -All text in English. +Always write a real summary_en that recaps the most important stories present in DATA. Rank geopolitics, military/conflict, security, disasters, and major political developments first. Ignore futures prices, commodity tape, ticker chatter, and routine market data — do not treat price ticks as news. -Focus on breaking important news (geopolitical, military/conflict, security, disasters, major political developments). Ignore futures, commodity prices, and routine market data — do not treat price ticks as news. +ticker and map_items may be empty if nothing is critical or high. Never replace summary_en with a canned empty-brief sentence when DATA contains article facts. Demand a single JSON object (no markdown fences) with this exact shape: { - "summary_en": "English markdown brief or the no-qualifying-events sentence", + "summary_en": "English markdown brief of the provided stories", "ticker": [{"headline": "", "importance": "critical", "url": "", "location_name": ""}], "map_items": [{"headline": "", "importance": "critical", "location_name": "", "lat": 0, "lon": 0, "location_confidence": "city", "category": "military/conflict", "url": ""}] } ticker: only critical and high, max 12, ≤140 chars, no markdown. map_items: only critical and high where a real-world location is explicit in the data. Estimate lat/lon. If location is Unknown or not in the data, omit the item. Never invent a place. Max 20. -summary_en: English markdown brief of breaking important news for an operator HUD. +summary_en: English markdown brief of breaking important news for an operator HUD (bullets or short paragraphs). Cover the actual stories in DATA. DATA: {final_input} """ RECAP_PROMPT_DEFAULT = """\ -You are writing a daily recap of the last 24 hours of news for an OSINT operator HUD. +You are writing a daily recap of the last 24 hours of news for an OSINT operator HUD, using ONLY the article facts in DATA below. Do not invent events, names, dates, places, or implications. -CRITICAL INSTRUCTION: YOU MUST USE ONLY THE DATA PROVIDED BELOW. DO NOT INVENT, RECALL, OR ADD ANY EVENTS, NAMES, DATES, IMPLICATIONS, PROJECTS, OR DETAILS NOT EXPLICITLY PRESENT IN THE DATA. IF THE DATA HAS NO BREAKING IMPORTANT NEWS, set summary_en to exactly: "No qualifying breaking news in the last 24 hours." and use empty ticker and map_items arrays. AND STOP. NO EXTERNAL KNOWLEDGE FROM TRAINING. +Always write a real summary_en daily recap of the most important stories in DATA. Rank geopolitics, military/conflict, security, disasters, and major political developments first. Ignore futures prices, commodity tape, ticker chatter, and routine market data — do not treat price ticks as news. -All text in English. - -Focus on breaking important news from the last 24 hours (geopolitical, military/conflict, security, disasters, major political developments). Ignore futures, commodity prices, and routine market data — do not treat price ticks as news. +ticker and map_items may be empty if nothing is critical or high. Never replace summary_en with a canned empty-brief sentence when DATA contains article facts. Demand a single JSON object (no markdown fences) with this exact shape: { - "summary_en": "English markdown daily recap or the no-qualifying-events sentence", + "summary_en": "English markdown daily recap of the provided stories", "ticker": [{"headline": "", "importance": "critical", "url": "", "location_name": ""}], "map_items": [{"headline": "", "importance": "critical", "location_name": "", "lat": 0, "lon": 0, "location_confidence": "city", "category": "military/conflict", "url": ""}] } ticker: only critical and high, max 12, ≤140 chars, no markdown. map_items: only critical and high where a real-world location is explicit in the data. Estimate lat/lon. If location is Unknown or not in the data, omit the item. Never invent a place. Max 20. -summary_en: English markdown daily recap of the last 24 hours of breaking important news. +summary_en: English markdown daily recap of the last 24 hours of breaking important news. Cover the actual stories in DATA. DATA: {final_input} diff --git a/news/summerizer/tests/test_prompts.py b/news/summerizer/tests/test_prompts.py index e6ca187..e5d4374 100644 --- a/news/summerizer/tests/test_prompts.py +++ b/news/summerizer/tests/test_prompts.py @@ -22,7 +22,27 @@ def test_summary_prompt_focuses_on_breaking_news_not_futures(): assert "commodity" in p or "market" in p -def test_recap_prompt_is_daily_24h_breaking_news(): +def test_summary_prompt_does_not_bail_out_with_canned_empty_brief(): + p = SUMMARY_PROMPT_DEFAULT + assert "AND STOP" not in p + assert "REPEAT 3 TIMES" not in p + assert "No qualifying" not in p + assert "no-qualifying" not in p.lower() + low = p.lower() + assert "always" in low + assert "recap" in low or "summar" in low + + +def test_recap_prompt_does_not_bail_out_with_canned_empty_brief(): + p = RECAP_PROMPT_DEFAULT + assert "AND STOP" not in p + assert "No qualifying" not in p + assert "no-qualifying" not in p.lower() + low = p.lower() + assert "always" in low + assert "daily" in low + assert "24" in low + p = RECAP_PROMPT_DEFAULT.lower() _assert_breaking_not_futures(RECAP_PROMPT_DEFAULT) assert "daily" in p