feat: harden article summary timeout and fallback
This commit is contained in:
@@ -300,6 +300,7 @@ python scripts/apply_term_suggestions.py ^
|
||||
- `ARTICLE_SUMMARY_LLM_API_KEY`
|
||||
|
||||
如果这些变量未设置,单篇总结会回退使用主流程中的 `LLM_*` / `OPENAI_*` 配置。
|
||||
如果显式指定 DeepSeek 作为单篇总结模型且请求超时或失败,当前实现会自动再用主流程默认模型配置重试一次。
|
||||
|
||||
示例(PowerShell 风格):
|
||||
|
||||
@@ -316,7 +317,8 @@ set ARTICLE_SUMMARY_LLM_MODEL=deepseek-chat
|
||||
python scripts/run_article_summaries.py ^
|
||||
--extracted outputs/freshrss/extracted/freshrss.extracted.json ^
|
||||
--ids 12345 67890 ^
|
||||
--output-dir outputs/freshrss/single_summaries
|
||||
--output-dir outputs/freshrss/single_summaries ^
|
||||
--timeout 120
|
||||
```
|
||||
|
||||
也可以通过 `summary_mcp.server` 暴露的 MCP 工具 `generate_article_summaries` 调用:
|
||||
|
||||
@@ -37,7 +37,7 @@ def main() -> None:
|
||||
help="Directory to write per-article Markdown summaries",
|
||||
)
|
||||
parser.add_argument("--max-retries", type=int, default=2, help="Maximum LLM retry attempts per article")
|
||||
parser.add_argument("--timeout", type=float, default=60.0, help="LLM request timeout in seconds")
|
||||
parser.add_argument("--timeout", type=float, default=120.0, help="LLM request timeout in seconds")
|
||||
parser.add_argument("--api-key", type=str, default=None, help="Override article-summary LLM API key")
|
||||
parser.add_argument("--model", type=str, default=None, help="Override article-summary LLM model")
|
||||
parser.add_argument("--api-url", type=str, default=None, help="Override article-summary LLM API URL/base URL")
|
||||
|
||||
@@ -172,7 +172,7 @@ def generate_article_summaries(
|
||||
selected_ids: list[str],
|
||||
output_dir: str | None = None,
|
||||
max_retries: int = 2,
|
||||
timeout_seconds: float = 60.0,
|
||||
timeout_seconds: float = 120.0,
|
||||
llm_api_key: str | None = None,
|
||||
llm_model: str | None = None,
|
||||
llm_api_url: str | None = None,
|
||||
|
||||
@@ -4,6 +4,8 @@ from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Iterable, Mapping, Sequence
|
||||
|
||||
import httpx
|
||||
|
||||
from summary_mcp.core.summary_loop import run_loop_payload
|
||||
from summary_mcp.validators.article_summary import validate_article_summary_payload
|
||||
|
||||
@@ -18,7 +20,7 @@ DEFAULT_PROMPT_PATH = OUTPUT_ROOT / "prompts" / "article-summary-prompt.txt"
|
||||
class ArticleSummaryConfig:
|
||||
prompt_path: Path = DEFAULT_PROMPT_PATH
|
||||
max_retries: int = 2
|
||||
timeout_seconds: float = 60.0
|
||||
timeout_seconds: float = 120.0
|
||||
llm_api_key: str | None = None
|
||||
llm_model: str | None = None
|
||||
llm_api_url: str | None = None
|
||||
@@ -187,17 +189,45 @@ def summarize_selected_articles(
|
||||
"warnings": warnings,
|
||||
}
|
||||
|
||||
summary_exit_code, summary_payload, _ = run_loop_payload(
|
||||
extracted_payload=extracted_payload,
|
||||
prompt_path=cfg.prompt_path,
|
||||
max_retries=cfg.max_retries,
|
||||
timeout_seconds=cfg.timeout_seconds,
|
||||
api_key=resolved_api_key,
|
||||
model=resolved_model,
|
||||
api_url=resolved_api_url,
|
||||
output_path=None,
|
||||
validator=validate_article_summary_payload,
|
||||
summary_exit_code = 1
|
||||
summary_payload = None
|
||||
|
||||
try:
|
||||
summary_exit_code, summary_payload, _ = run_loop_payload(
|
||||
extracted_payload=extracted_payload,
|
||||
prompt_path=cfg.prompt_path,
|
||||
max_retries=cfg.max_retries,
|
||||
timeout_seconds=cfg.timeout_seconds,
|
||||
api_key=resolved_api_key,
|
||||
model=resolved_model,
|
||||
api_url=resolved_api_url,
|
||||
output_path=None,
|
||||
validator=validate_article_summary_payload,
|
||||
)
|
||||
except httpx.TimeoutException:
|
||||
summary_exit_code = 1
|
||||
summary_payload = None
|
||||
|
||||
should_fallback = (
|
||||
(summary_exit_code != 0 or summary_payload is None)
|
||||
and resolved_model is not None
|
||||
and resolved_model == (cfg.llm_model or model or resolved_model)
|
||||
and resolved_model.startswith("deepseek")
|
||||
)
|
||||
|
||||
if should_fallback:
|
||||
summary_exit_code, summary_payload, _ = run_loop_payload(
|
||||
extracted_payload=extracted_payload,
|
||||
prompt_path=cfg.prompt_path,
|
||||
max_retries=cfg.max_retries,
|
||||
timeout_seconds=cfg.timeout_seconds,
|
||||
api_key=None,
|
||||
model=None,
|
||||
api_url=None,
|
||||
output_path=None,
|
||||
validator=validate_article_summary_payload,
|
||||
)
|
||||
|
||||
if summary_exit_code != 0 or summary_payload is None:
|
||||
continue
|
||||
|
||||
|
||||
Reference in New Issue
Block a user