From 3eb339b8e0f3be431706e0973d053e7822d56738 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 16 Sep 2026 18:24:30 +0800 Subject: [PATCH] feat(agent): unify memory retrieval and activity history --- app/agent/callback/__init__.py | 8 +- app/agent/defaults/CURRENT_PERSONA.md | 4 +- app/agent/middleware/activity.py | 771 ----------- app/agent/middleware/memory.py | 1168 +++++++++++++---- app/agent/middleware/vision.py | 11 +- app/agent/orchestrator.py | 29 +- app/agent/runtime.py | 7 +- app/agent/tools/factory.py | 1 + docs/agent.md | 12 +- docs/refactor/agent-tool-refactor-plan.md | 2 +- docs/refactor/optimization-checklist.md | 2 +- .../scripts/collect_feedback_diagnostics.py | 1 - .../fixtures/architecture/mypy-baseline.json | 5 - tests/test_agent_activity_log.py | 520 -------- tests/test_agent_background_output.py | 70 +- tests/test_agent_graph_cache.py | 14 +- tests/test_agent_memory.py | 422 ++++++ tests/test_agent_runtime.py | 15 + tests/test_agent_summarization_streaming.py | 3 +- tests/test_agent_tool_factory_cache.py | 4 +- tests/test_agent_tool_policy.py | 11 +- tests/test_agent_tool_streaming.py | 8 +- tests/test_agent_view_image.py | 77 +- tests/test_architecture_dependencies.py | 1 - 24 files changed, 1497 insertions(+), 1669 deletions(-) delete mode 100644 app/agent/middleware/activity.py delete mode 100644 tests/test_agent_activity_log.py create mode 100644 tests/test_agent_memory.py diff --git a/app/agent/callback/__init__.py b/app/agent/callback/__init__.py index e73dda508..8669792a0 100644 --- a/app/agent/callback/__init__.py +++ b/app/agent/callback/__init__.py @@ -505,8 +505,8 @@ class StreamingHandler: if tool_name in {"read_skill", "skill"}: return "skill", tool_kwargs.get("name") - if tool_name == "query_activity_log": - return "activity_log", tool_kwargs.get("keyword") or tool_kwargs.get("date") + if tool_name == "search_memory": + return "memory", tool_kwargs.get("query") or tool_kwargs.get("category") if tool_name == "subagent_task": return "subagent", StreamingHandler._extract_subagent_targets(tool_kwargs) if tool_name == "task": @@ -671,8 +671,8 @@ class StreamingHandler: return f"查询了 {count} 次数据" if category == "skill": return f"查询了 {count} 个技能说明" - if category == "activity_log": - return f"查询了 {count} 次活动日志" + if category == "memory": + return f"检索了 {count} 次记忆" if category == "action": return f"执行了 {count} 次操作" if category == "interaction": diff --git a/app/agent/defaults/CURRENT_PERSONA.md b/app/agent/defaults/CURRENT_PERSONA.md index 55ecb9654..54cf7a35b 100644 --- a/app/agent/defaults/CURRENT_PERSONA.md +++ b/app/agent/defaults/CURRENT_PERSONA.md @@ -13,7 +13,7 @@ deprecated_phrases: [] 1. 核心系统提示词(程序内置,不可运行时覆盖) 2. `personas//PERSONA.md` 3. `extra_context_files` -4. `memory/*.md` -5. `activity/*.md` +4. `memory/MEMORY.md`(默认注入) +5. `memory/.md` 与 `memory/activity/*.md`(通过 search_memory 按需检索) `memory` 中的长期偏好可以细化回复方式,但不应覆盖系统核心身份、目标和安全边界。 diff --git a/app/agent/middleware/activity.py b/app/agent/middleware/activity.py deleted file mode 100644 index da3fbde9f..000000000 --- a/app/agent/middleware/activity.py +++ /dev/null @@ -1,771 +0,0 @@ -""" -活动日志中间件 - 自动记录 Agent 每次交互的操作摘要。 - -按日期存储在 CONFIG_PATH/agent/activity/YYYY-MM-DD.md 中, -每次 Agent 执行完毕后自动调用 LLM 对本轮对话生成简洁的活动摘要, -系统提示词只注入稳定的检索规则,完整日志由工具按需查询。 -""" - -import asyncio -import json -import os -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 - -import anyio -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.policy.sanitizer import ( - sanitize_for_host, - summarize_error, - summarize_result, -) -from app.agent.tools.tags import ToolTag -from app.runtime.log import logger -from app.runtime.tasks import TaskRegistry, get_task_registry - -# 活动日志保留天数 -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