Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions tests/test_prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ def test_prompt_uses_a_soft_briefing_target_and_hard_output_limits() -> None:

def test_prompt_requires_attribution_and_preserves_source_conflicts() -> None:
assert "headline_source_ids 以及 conclusions" in SYSTEM_PROMPT
assert "只能包含纯文本" in SYSTEM_PROMPT
assert "不得使用 Markdown" in SYSTEM_PROMPT
assert "不得拼接成无争议的单一结论" in SYSTEM_PROMPT
assert "优先采用可识别的当地权威气象机构" in SYSTEM_PROMPT
assert "input.required_advice_topics" in SYSTEM_PROMPT
51 changes: 45 additions & 6 deletions tests/test_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,48 @@ def test_bark_text_renderer_uses_numbered_sources_without_urls() -> None:

rendered = BarkTextRenderer().render_briefing(result, (article,), (context,))

assert rendered.body == "Daily [1]\n- Rain [1][2]\nSources: [1] Feed; [2] Weather API"
assert rendered.body == "Daily [1]\nRain [1][2]\n[1] Feed\n[2] Weather API"
assert article.url not in rendered.body
assert context.url not in rendered.body
assert "- " not in rendered.body


def test_bark_text_renderer_merges_source_ids_with_the_same_display_name() -> None:
weather = SourceDocument("weather:open-meteo", "Open-Meteo", "https://example.invalid/weather", "Forecast")
air_quality = SourceDocument(
"air-quality:open-meteo",
" open-meteo ",
"https://example.invalid/air-quality",
"Air quality",
)
result = BriefingResult(
"Daily",
("weather:open-meteo",),
(Conclusion("Rain", ("weather:open-meteo", "air-quality:open-meteo")),),
advice=(Advice(AdviceTopic.MASK, "Limit exposure", ("air-quality:open-meteo",)),),
output_language="en",
)

rendered = BarkTextRenderer().render_briefing(result, (), (weather, air_quality))

assert rendered.body == "Daily [1]\nRain [1]\nAdvice\nLimit exposure [1]\n[1] Open-Meteo"
assert "[2]" not in rendered.body
assert "Sources:" not in rendered.body


def test_bark_text_renderer_uses_source_ids_for_distinct_blank_names() -> None:
weather = SourceDocument("weather:blank", " ", "https://example.invalid/weather", "Forecast")
air_quality = SourceDocument("air-quality:blank", "", "https://example.invalid/air-quality", "Air quality")
result = BriefingResult(
"Daily",
("weather:blank",),
(Conclusion("Rain", ("weather:blank", "air-quality:blank")),),
output_language="en",
)

rendered = BarkTextRenderer().render_briefing(result, (), (weather, air_quality))

assert rendered.body == ("Daily [1]\nRain [1][2]\n[1] weather:blank\n[2] air-quality:blank")


def test_bark_text_renderer_trims_outer_whitespace() -> None:
Expand All @@ -92,7 +131,7 @@ def test_bark_text_renderer_trims_outer_whitespace() -> None:

rendered = BarkTextRenderer().render_briefing(result, (), (context,))

assert rendered.body == "Daily [1]\nSources: [1] Weather API"
assert rendered.body == "Daily [1]\n[1] Weather API"
assert rendered.visible_length == len(rendered.body)


Expand All @@ -114,12 +153,12 @@ def test_bark_text_renderer_compacts_warning_disaster_and_advice_sections() -> N
assert rendered.body == (
"Rain today [1]\n"
"Weather warnings\n"
"- Heavy rain (active): Avoid low areas [1]\n"
"Heavy rain (active): Avoid low areas [1]\n"
"Natural disaster updates\n"
"- Storm approaching [1]\n"
"Storm approaching [1]\n"
"Advice\n"
"- Exercise indoors [1]\n"
"Sources: [1] Weather API"
"Exercise indoors [1]\n"
"[1] Weather API"
)


Expand Down
2 changes: 2 additions & 0 deletions weather_briefing/data/system_prompt.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ recent_context_documents 中的 language 是来源正文的实际语言;来源
不得把翻译结果冒充来源原文或改变专名、预警编号和数值。
headline_source_ids 以及 conclusions、active_warnings、disaster_tracking 和 advice
中的每一项都必须包含至少一个 source_id。
headline、conclusions[].text、active_warnings 中的 title、status、detail、disaster_tracking[].text
以及 advice[].text 只能包含纯文本,不得使用 Markdown 标题、列表、强调、代码或链接语法;章节标题和项目符号由发布端统一渲染。
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
headline 必须是一句简洁、信息密集的标题,将当下最重要的天气概况浓缩其中;优先包含天气现象、
高低温或显著体感,以及需要立即准备的短时降水等变化,不要另写摘要段落。
不同来源对同一时段的天气现象有冲突时,不得拼接成无争议的单一结论;应明确说明差异,
Expand Down
40 changes: 31 additions & 9 deletions weather_briefing/delivery/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ def render_briefing(
source_references.update(
{document.id: self._source_reference(document.name, document.url) for document in context}
)
numbered_references, source_footer = _numbered_source_references(result, source_references, labels)
numbered_references, source_footer = _bark_numbered_source_references(result, source_references)
lines = [
f"{result.headline} "
f"{_plain_attribution(result.headline_source_ids, numbered_references, labels, numbered=True)}"
Expand All @@ -227,7 +227,7 @@ def render_briefing(
if result.active_warnings:
lines.append(labels["warnings"])
lines.extend(
f"- {warning.title}{labels['status_open']}{warning.status}{labels['status_close']}"
f"{warning.title}{labels['status_open']}{warning.status}{labels['status_close']}"
f"{labels['detail_separator']}{warning.detail} "
f"{_plain_attribution(warning.source_ids, numbered_references, labels, numbered=True)}"
for warning in result.active_warnings
Expand Down Expand Up @@ -295,8 +295,7 @@ def _compact_plain_items(
return []
lines = [title] if title is not None else []
lines.extend(
f"- {item.text} {_plain_attribution(item.source_ids, source_references, labels, numbered=True)}"
for item in items
f"{item.text} {_plain_attribution(item.source_ids, source_references, labels, numbered=True)}" for item in items
)
return lines

Expand Down Expand Up @@ -329,11 +328,7 @@ def _numbered_source_references(
source_references: dict[str, str],
labels: Mapping[str, str],
) -> tuple[dict[str, str], str]:
ordered_source_ids = list(result.headline_source_ids)
for items in (result.conclusions, result.active_warnings, result.disaster_tracking, result.advice):
for item in items:
ordered_source_ids.extend(item.source_ids)
ordered_source_ids = list(dict.fromkeys(ordered_source_ids))
ordered_source_ids = _ordered_source_ids(result)
numbered_references = {source_id: f"[{index}]" for index, source_id in enumerate(ordered_source_ids, start=1)}
source_list = labels["plain_source_separator"].join(
f"{numbered_references[source_id]} {source_references[source_id]}" for source_id in ordered_source_ids
Expand All @@ -342,6 +337,33 @@ def _numbered_source_references(
return numbered_references, footer


def _bark_numbered_source_references(
result: BriefingResult,
source_references: dict[str, str],
) -> tuple[dict[str, str], str]:
numbered_references: dict[str, str] = {}
numbers_by_name: dict[str, str] = {}
source_lines: list[str] = []
for source_id in _ordered_source_ids(result):
source_name = " ".join(source_references[source_id].split()) or source_id
normalized_name = source_name.casefold()
number = numbers_by_name.get(normalized_name)
if number is None:
number = f"[{len(numbers_by_name) + 1}]"
numbers_by_name[normalized_name] = number
source_lines.append(f"{number} {source_name}")
numbered_references[source_id] = number
return numbered_references, "\n".join(source_lines)
Comment thread
qodo-code-review[bot] marked this conversation as resolved.


def _ordered_source_ids(result: BriefingResult) -> list[str]:
ordered_source_ids = list(result.headline_source_ids)
for items in (result.conclusions, result.active_warnings, result.disaster_tracking, result.advice):
for item in items:
ordered_source_ids.extend(item.source_ids)
return list(dict.fromkeys(ordered_source_ids))


def _briefing_labels(language: str) -> Mapping[str, str]:
selected = _BRIEFING_LANGUAGE_SUPPORT.match(language)
return _BRIEFING_LABELS[selected]
Expand Down