fix(定时任务): 抽取开关只控制模板HTML数据,不再影响Markdown等正常操作

- 之前关闭抽取会整段跳过 gather/agent,连带把 Markdown 生成也跳过了
- 改为:抽取开关只决定 HTML 用「抽取数据」还是「模板默认数据」;require_markdown 独立——
  需要时照常跑 gather 产出 .md 并校验/带出,与开关无关
- gather 仅在 do_extract(开关开+有描述+可转换) 或 require_markdown 时跑一次,两者复用同一次
- 只展示模板恒不抽取,但仍尊重 require_markdown;_finalize_template_html 支持 extra_files 带出 .md
- 测试:关闭抽取+必须Markdown → 仍产出.md/不转换/HTML用默认数据

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
董宏凯andClaude Opus 4.8 committed 2026-06-24 14:23:45 +08:00
1 parent 6673121f3f
commit 089d3e171f
2 files changed
+53 -52

No files matched your search

@@ -1496,13 +1496,17 @@ class ScheduledTaskService:
content: str,
source_chars: int,
used_default: bool,
extra_files: list[dict[str, Any]] | None = None,
) -> dict[str, Any]:
"""归档 index.html + 持久化 scheduled_html_pages + 构造 format=html 结果。"""
"""归档 index.html + 持久化 scheduled_html_pages + 构造 format=html 结果。
``extra_files`` 会排在 index.html 之前一并带上(如必须生成 Markdown 时的 .md)。
"""
task_tz = ZoneInfo(task.get("timezone") or "UTC")
local_time = scheduled_for.astimezone(task_tz)
archive_path = self._archive_html_index(run_id, html)
title = str(task.get("name") or spec.name).strip()
files: list[dict[str, Any]] = []
files: list[dict[str, Any]] = list(extra_files or [])
if archive_path is not None:
files.append(
{
@@ -1569,8 +1573,40 @@ class ScheduledTaskService:
raise HtmlGenerationError(f"未知的 HTML 模板:{tpl_id}")
cfg = self._html_config(task)
model_name = str(cfg.get("model_name") or "").strip() or None
# 「执行数据抽取」开关**只**控制 HTML 用「抽取数据」还是「模板默认数据」,
# 不影响 Markdown 生成等其它正常操作。
extract = bool(cfg.get("template_extract"))
also_markdown = _task_requires_markdown(task)
prompt = str(task.get("prompt") or "").strip()
description = str(task.get("description") or "").strip()
# 真正抽取 HTML 数据:开关开 且 有描述/任务 prompt 且 模板可转换(只展示模板恒不抽取)。
do_extract = extract and bool(prompt or description) and spec.convertible
task_tz = ZoneInfo(task.get("timezone") or "UTC")
local_time = scheduled_for.astimezone(task_tz)
# 只展示模板:原样输出(含全部子页面)。
def _pick_md(gathered: list[dict[str, Any]]) -> list[dict[str, Any]]:
return [
item
for item in (gathered or [])
if isinstance(item, dict) and str(item.get("name") or "").lower().endswith((".md", ".markdown"))
]
# gather:HTML 要抽取 或 需要 Markdown 时才跑(同一次 agent 运行可兼顾两者);
# 都不需要则完全不跑 agent。Markdown 校验独立于抽取开关。
data_text = ""
md_files: list[dict[str, Any]] = []
if do_extract or also_markdown:
data_text, gathered = await self._gather_html_data(
task, runtime_thread_id=runtime_thread_id, scheduled_for=scheduled_for, run_id=run_id
)
if also_markdown:
if not _has_valid_markdown_file(gathered):
raise MarkdownRequirementError(
f"未满足输出要求:需生成 Markdown(.md) 文件,且文件正文不少于 {_MARKDOWN_MIN_CHARS} 字"
)
md_files = _pick_md(gathered)
# 只展示模板:HTML 原样输出(含全部子页面);Markdown 已按需独立生成。
if not spec.convertible:
html = template_fill.read_template_text(tpl_id)
return await self._finalize_template_html(
@@ -1583,54 +1619,13 @@ class ScheduledTaskService:
content=f"已套用模板「{spec.name}」(含全部子页面)",
source_chars=0,
used_default=True,
extra_files=md_files,
)
# 数据抽取开关(html_config.template_extract):关闭 → 直接用模板默认数据,不检索/
# 不转换;开启 → 才执行下面的检索+转换抽取。
if not bool(cfg.get("template_extract")):
data_obj = template_fill.reference_data(tpl_id)
return await self._finalize_template_html(
task,
spec=spec,
template_id=tpl_id,
html=template_fill.fill_template(
tpl_id, data_obj, update_date=scheduled_for.astimezone(ZoneInfo(task.get("timezone") or "UTC")).date()
),
run_id=run_id,
scheduled_for=scheduled_for,
content=f"未开启数据抽取,已用模板「{spec.name}」默认数据(含全部子页面)",
source_chars=0,
used_default=True,
)
# 用户的描述与任务 prompt 都为空 → 不检索/不转换,直接用模板原有数据。
prompt = str(task.get("prompt") or "").strip()
description = str(task.get("description") or "").strip()
if not prompt and not description:
data_obj = template_fill.reference_data(tpl_id)
return await self._finalize_template_html(
task,
spec=spec,
template_id=tpl_id,
html=template_fill.fill_template(tpl_id, data_obj, update_date=scheduled_for.astimezone(ZoneInfo(task.get("timezone") or "UTC")).date()),
run_id=run_id,
scheduled_for=scheduled_for,
content=f"未提供描述,已用模板「{spec.name}」原有数据(含全部子页面)",
source_chars=0,
used_default=True,
)
# 可转换模板:检索真实数据(复用 gather) → 转换成模板 DATA → 填进真模板。
data_text, _files = await self._gather_html_data(
task, runtime_thread_id=runtime_thread_id, scheduled_for=scheduled_for, run_id=run_id
)
# HTML 数据来源:抽取(开关开 + 有数据)→ 转换填充;否则用模板默认数据(子页面齐全)。
source = (data_text or "").strip()
used_default = False
if not source:
data_obj = template_fill.reference_data(tpl_id)
content = f"未获取到数据,已用模板「{spec.name}」默认数据生成(含全部子页面)"
used_default = True
else:
used_default = True
if do_extract and source:
try:
converted = await self._convert_to_template_data(
tpl_id, source, model_name=model_name, soul=None, run_id=run_id
@@ -1638,14 +1633,19 @@ class ScheduledTaskService:
# 子页面没抽到有效内容的,回退模板原有数据,避免子页面空白。
data_obj = template_fill.merge_with_reference(tpl_id, converted)
content = f"已检索数据并套用模板「{spec.name}」(空子页面回退原数据,含全部子页面)"
used_default = False
except TemplateFillError:
logger.warning("模板参考转换失败,回退默认数据 run=%s tpl=%s", run_id, tpl_id, exc_info=True)
data_obj = template_fill.reference_data(tpl_id)
content = f"数据转换未通过,已回退模板「{spec.name}」默认数据(含全部子页面)"
used_default = True
else:
data_obj = template_fill.reference_data(tpl_id)
content = (
f"未开启数据抽取,已用模板「{spec.name}」默认数据(含全部子页面)"
if not extract
else f"未提供描述/未获取到数据,已用模板「{spec.name}」默认数据(含全部子页面)"
)
task_tz = ZoneInfo(task.get("timezone") or "UTC")
local_time = scheduled_for.astimezone(task_tz)
try:
html = template_fill.fill_template(tpl_id, data_obj, update_date=local_time.date())
except template_fill.TemplateError as exc:
@@ -1660,6 +1660,7 @@ class ScheduledTaskService:
content=content,
source_chars=len(source),
used_default=used_default,
extra_files=md_files,
)
async def _run_template_fill_attempt(