""" 活动日志中间件 - 自动记录 Agent 每次交互的操作摘要。 按日期存储在 CONFIG_PATH/agent/activity/YYYY-MM-DD.md 中, 每次 Agent 执行完毕后自动调用 LLM 对本轮对话生成简洁的活动摘要, 并在每次 Agent 启动时注入轻量索引,完整日志由工具按需查询。 """ import json import re from collections.abc import Awaitable, Callable from datetime import datetime, timedelta from pathlib import Path from typing import Annotated, Any, NotRequired, Optional, TypedDict from anyio import Path as AsyncPath from langchain.agents.middleware.types import ( AgentMiddleware, AgentState, ContextT, ModelRequest, ModelResponse, PrivateStateAttr, # noqa ResponseT, ToolCallRequest, ) from langchain_core.messages import AIMessage, HumanMessage, ToolMessage from langchain_core.tools import StructuredTool from langgraph.runtime import Runtime from pydantic import BaseModel, Field from app.agent.middleware.utils import append_to_system_message from app.agent.tools.tags import ToolTag from app.log import logger # 活动日志保留天数 DEFAULT_RETENTION_DAYS = 7 # 注入系统提示词时索引的天数 PROMPT_LOAD_DAYS = 3 # 工具默认查询的天数 DEFAULT_QUERY_DAYS = 7 # 工具单次返回的最大条数 DEFAULT_QUERY_LIMIT = 20 MAX_QUERY_LIMIT = 50 # 每日日志文件最大大小 (256KB) MAX_LOG_FILE_SIZE = 256 * 1024 # 提取本轮对话上下文的最大字符数(避免过长的对话消耗太多 token) MAX_CONTEXT_FOR_SUMMARY = 4000 SUMMARY_SKIP_MARKER = "SKIP" QUERY_ACTIVITY_LOG_TOOL_NAME = "query_activity_log" QUERY_ACTIVITY_LOG_TOOL_DESCRIPTION = ( "Query recent MoviePilot Agent activity logs on demand. Use this when the user asks what was done before, " "asks to continue a previous task, or explicitly references recent agent activity. Supports keyword, date, " "recent-day window, limit, and optional regex filters. If a keyword search returns no results, retry with " "a shorter keyword, a larger days window, or no keyword to inspect recent entries." ) # LLM 总结的提示词 SUMMARY_PROMPT = """请判断以下 AI 助手与用户的对话是否值得写入 MoviePilot 活动日志。 如果本轮只是问候、寒暄、感谢、确认、闲聊、没有实际任务、没有工具动作、任务没有推进、纯粹的格式纠正或无意义空转,请只输出:SKIP 如果值得记录,请输出一条中文单行活动摘要,要求: - 40 到 160 个汉字左右,信息密度高,不要写成泛泛一句话。 - 只输出摘要正文,不要标题、编号、Markdown、JSON 或解释。 - 尽量包含:用户目标、关键对象(影片/剧集/站点/路径/任务/设置)、助手采取的关键动作或工具、结果状态、失败原因或下一步。 - 如果有明确 ID、路径、站点名、任务状态、成功/失败数量,请保留关键值。 - 不要记录 API Key、Cookie、Token、密码等敏感信息;如出现请写成“敏感信息已省略”。 推荐格式示例: 用户要求整理 `/downloads/Show`,助手识别为《示例剧》TMDB 12345,并提交 transfer_file 整理,结果成功。 用户排查下载失败,助手查询 qBittorrent 任务和站点状态,发现 tracker 超时,建议更换站点或重试。 对话记录: {conversation}""" ACTIVITY_ENTRY_PATTERN = re.compile(r"^-\s+\*\*(?P