项目初始化

This commit is contained in:
2026-07-02 11:31:16 +08:00
commit eef4c76e3f
64 changed files with 13369 additions and 0 deletions

134
daily/agent_workflow.py Normal file
View File

@@ -0,0 +1,134 @@
"""Agent 工作流:趋势分析 → 叙事化企微早报。"""
from __future__ import annotations
import json
import logging
import re
from pathlib import Path
from typing import Any
from daily.config import OUTPUT_DIR, ROOT, env
from daily.llm_client import extract_json_object, has_llm_configured, llm_chat
from daily.report_data import save_json
logger = logging.getLogger(__name__)
_SKILL_DIR = ROOT / "skills" / "daily-agent"
_MD_BLOCK = re.compile(r"```(?:markdown|md)?\s*([\s\S]*?)```", re.IGNORECASE)
_WECOM_NEW_ENTRY_NOTE = re.compile(r"(新入[^]*")
def _strip_new_entry_notes(md: str) -> str:
md = _WECOM_NEW_ENTRY_NOTE.sub("", md)
return re.sub(r"\*\*—", "** —", md)
def report_mode() -> str:
return (env("DAILY_REPORT_MODE") or "classic").strip().lower()
def is_agent_mode() -> bool:
if report_mode() != "agent":
return False
if not has_llm_configured():
logger.warning("DAILY_REPORT_MODE=agent 但未配置 LLM回退 classic")
return False
return True
def trends_json_path(date_str: str) -> Path:
return OUTPUT_DIR / f"{date_str}.trends.json"
def _load_skill() -> str:
path = _SKILL_DIR / "SKILL.md"
if path.exists():
return path.read_text(encoding="utf-8").strip()
return "你是早报主编 Agent。"
def _extract_markdown(text: str) -> str:
text = text.strip()
match = _MD_BLOCK.search(text)
if match:
return match.group(1).strip()
if text.startswith("📰"):
return text
return text
def analyze_trends(llm_input: dict[str, Any], *, date_str: str) -> dict[str, Any] | None:
skill = _load_skill()
system = (
f"{skill}\n\n"
"当前执行 **Step 1趋势分析**。\n"
"只输出 trends JSONheadline, opening, themes, top_picks, signals不要 Markdown。"
)
user = json.dumps(llm_input, ensure_ascii=False, indent=2)
try:
raw = llm_chat(system, user)
except Exception as exc:
logger.warning("Agent Step1 趋势分析失败:%s", exc)
return None
if not raw:
return None
parsed = extract_json_object(raw)
if not parsed.get("headline") and not parsed.get("opening"):
logger.warning("Agent Step1 JSON 无效")
return None
save_json(trends_json_path(date_str), parsed)
logger.info("Agent Step1 完成:%s", parsed.get("headline", "?"))
return parsed
def write_wecom_report(
llm_input: dict[str, Any],
trends: dict[str, Any],
*,
date_str: str,
time_str: str,
updated: str,
) -> str | None:
skill = _load_skill()
system = (
f"{skill}\n\n"
"当前执行 **Step 2撰写企微早报**。\n"
f"日期={date_str},时间={time_str},数据截至={updated}\n"
"只输出企微 Markdown 正文,不要代码块,不要 JSON。"
)
payload = {"data": llm_input, "trends": trends}
user = json.dumps(payload, ensure_ascii=False, indent=2)
try:
raw = llm_chat(system, user)
except Exception as exc:
logger.warning("Agent Step2 写稿失败:%s", exc)
return None
if not raw:
return None
md = _extract_markdown(raw)
if not md.startswith("📰"):
md = f"📰 **早报 · {date_str}**\n> ⏱ {time_str} · 数据截至 {updated}\n\n{md}"
md = _strip_new_entry_notes(md)
logger.info("Agent Step2 完成:%d bytes", len(md.encode("utf-8")))
return md
def run_agent_workflow(
llm_input: dict[str, Any],
*,
date_str: str,
time_str: str,
updated: str,
) -> str | None:
"""两步 Agent 工作流;成功返回企微 Markdown失败返回 None。"""
trends = analyze_trends(llm_input, date_str=date_str)
if not trends:
return None
return write_wecom_report(
llm_input,
trends,
date_str=date_str,
time_str=time_str,
updated=updated,
)