Files
daily-robots/daily/agent_workflow.py

145 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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
md = write_wecom_report(
llm_input,
trends,
date_str=date_str,
time_str=time_str,
updated=updated,
)
if md is None:
logger.warning("Agent Step2 首次失败,重试一次")
md = write_wecom_report(
llm_input,
trends,
date_str=date_str,
time_str=time_str,
updated=updated,
)
return md