feat: Token Plan 迁移与 .env 热加载,并修复日报 AI 摘要为空
Token Plan 迁移 / 配置热加载:
- configs/llm_models.yaml: 各场景切到 Token Plan(deepseek-v4.1-flash / qwen3.6-flash)
- 新增 configs/runtime_env.py: .env 按 (mtime_ns, size) 热加载并同步 os.environ,
统一 env_get 取值;llm / embedding / vectorstore / mcp / pipeline 改用 env_get
- configs/loader.py / scripts/run_scheduler.py 等配套调整
- 新增 tests/test_hot_reload.py
日报 AI 摘要为空修复(2026-09-25):
- 根因: 推理模型的 reasoning token 与正文共用 max_tokens, 预算 1500 被"思考"
占满 -> text_tokens=0 / finish_reason=length, 摘要静默为空且不重试
- daily_report 场景新增 max_tokens(默认 4000, YAML 保存即热生效);
LLMConfig 支持可选 max_tokens; 分块预算 800 -> 2000
- _llm_call 拆出 _call_once, 正文为空时自动加倍预算重试(上限 16000),
用尽才降级返回空串; 网络异常重试语义不变
- docs/user-guide.md 新增 FAQ; continuation.md 记录本次排查
- 已重跑 2026-09-25 日报(report_id=357)补回 466 字摘要
测试: 相关用例 56 passed(test_hot_reload 12 passed);
ruff 无新增问题; 3 个 crawler 既有失败与本改动无关
This commit is contained in:
+14
-17
@@ -12,7 +12,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
@@ -21,6 +20,8 @@ from pathlib import Path
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from configs.runtime_env import env_get, env_raw
|
||||
|
||||
from .timeutil import today_str
|
||||
|
||||
# 断点状态文件(按日期隔离,记录每步骤结果)
|
||||
@@ -159,20 +160,16 @@ def _llm_scene_desc(scene: str) -> str | None:
|
||||
try:
|
||||
from configs.loader import load_scene_config
|
||||
|
||||
def _env(key: str) -> str | None:
|
||||
v = os.environ.get(key)
|
||||
return v.strip() if v else None
|
||||
|
||||
sc = load_scene_config(scene)
|
||||
p = (sc.get("provider") or _env("LLM_PROVIDER") or "deepseek").lower()
|
||||
p = (sc.get("provider") or env_get("LLM_PROVIDER") or "deepseek").lower()
|
||||
if p in ("qwen", "dashscope"):
|
||||
p = "qwen"
|
||||
model = sc.get("model")
|
||||
if not model:
|
||||
if p == "qwen":
|
||||
model = _env("QWEN_MODEL") or _env("LLM_MODEL")
|
||||
model = env_get("QWEN_MODEL") or env_get("LLM_MODEL")
|
||||
else:
|
||||
model = _env("DEEPSEEK_MODEL") or _env("LLM_MODEL")
|
||||
model = env_get("DEEPSEEK_MODEL") or env_get("LLM_MODEL")
|
||||
if not model:
|
||||
return None
|
||||
return f"provider={p}, model={model}"
|
||||
@@ -192,9 +189,9 @@ def _embedding_desc() -> str | None:
|
||||
model = sc.get("model")
|
||||
if not model:
|
||||
if pt == "dashscope":
|
||||
model = os.environ.get("DASHSCOPE_EMBEDDING_MODEL") or "text-embedding-v3"
|
||||
model = env_get("DASHSCOPE_EMBEDDING_MODEL") or "text-embedding-v3"
|
||||
else:
|
||||
model = os.environ.get("LOCAL_EMBEDDING_MODEL") or "BAAI/bge-m3"
|
||||
model = env_get("LOCAL_EMBEDDING_MODEL") or "BAAI/bge-m3"
|
||||
return f"provider={pt}, model={model}"
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.debug("embedding 描述解析失败: {}", e)
|
||||
@@ -281,14 +278,14 @@ def run_step(name: str, date_str: str) -> StepResult:
|
||||
# 2. PIPELINE_STEP_TIMEOUT 环境变量 (全局兜底, 覆盖硬编码)
|
||||
# 3. STEP_TIMEOUTS 硬编码字典 (代码内默认值)
|
||||
# 4. 1800s (最终兜底)
|
||||
import os
|
||||
specific_key = f"TIMEOUT_{name.upper()}"
|
||||
if specific_key in os.environ:
|
||||
timeout = int(os.environ[specific_key])
|
||||
elif "PIPELINE_STEP_TIMEOUT" in os.environ:
|
||||
timeout = int(os.environ["PIPELINE_STEP_TIMEOUT"])
|
||||
else:
|
||||
timeout = STEP_TIMEOUTS.get(name, 1800)
|
||||
timeout_raw = env_raw(specific_key) or env_raw("PIPELINE_STEP_TIMEOUT")
|
||||
default_timeout = STEP_TIMEOUTS.get(name, 1800)
|
||||
try:
|
||||
timeout = int(timeout_raw) if timeout_raw else default_timeout
|
||||
except ValueError:
|
||||
logger.warning("超时配置 {!r} 非法,回退默认 {}s", timeout_raw, default_timeout)
|
||||
timeout = default_timeout
|
||||
started = datetime.now()
|
||||
logger.info("步骤 {} 开始: {}", name, " ".join(full_cmd))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user