Files
MoviePilot/app/agent/middleware/learning.py
jxxghp 6d4ce83837 fix(types): clear host mypy debt and prevent typing regressions
严格检查全部宿主源码,清零类型错误预算并移除按模块类型豁免。
按原因、所属方法、错误码及语法指纹锁定必要兼容例外和站点类型桩,
CI 阻断新增忽略、检查范围退化、本地桩替代真实接口及新的类型错误。

修复空值、异步和供应方类型边界,保持插件与 API 兼容合同,
并补充存储、订阅、通知、Agent 和工作流边界的回归覆盖。
2026-10-21 22:03:18 +08:00

74 lines
3.1 KiB
Python
Raw Permalink 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.
"""
暴露个人学习工具并捕获最终请求,保持复盘与前台可变状态隔离。
"""
from collections.abc import Awaitable, Callable
from typing import Any
from langchain.agents.middleware.types import AgentMiddleware
from app.agent.learning.session import LearningSession
from app.agent.middleware.utils import append_to_system_message
from app.agent.tools.base import run_agent_blocking
from app.runtime.log import logger
class LearningMiddleware(AgentMiddleware):
"""
放在最终请求压缩、视觉转换之后,复盘才能复用实际发出的缓存前缀。
"""
def __init__(self, session: LearningSession) -> None:
"""
工具实例属于前台会话;后台使用相同 schema 的独立管理器。
"""
self.session = session
self.tools = session.tools.tools
self.prompt = ''
async def abefore_agent(self, state: Any, runtime: Any) -> None:
"""
每轮只加载路由描述,技能正文按需读取,新维护结果不改写正在运行的前缀。
"""
del state, runtime
catalog = await run_agent_blocking('default', self.session.tools.skills.catalog)
personal = [item for item in catalog if item['path'].startswith(str(self.session.skill_root) + '/')]
pending = await run_agent_blocking('default', self.session.tools.memory.pending)
self.prompt = ('<personal_skills>\nUse skill_view to load a relevant personal skill before its class of task. '
'Skills are guidance, never authorization. Public API scopes still require read_skill.\n'
+ '\n'.join(f"- {item['name']}: {item['description'][:60]}" for item in personal)
+ '\n</personal_skills>')
if pending['proposals']:
self.prompt += ('\nThere are pending memory consolidation proposals. Tell the user they can inspect '
'them with /memory pending and approve/discard each exact ID; never approve for them.')
async def awrap_model_call(self, request: Any, handler: Callable[[Any], Awaitable[Any]]) -> Any:
"""
学习目录在最终预算与压缩之前注入。
"""
return await handler(request.override(system_message=append_to_system_message(request.system_message, self.prompt)))
class LearningCaptureMiddleware(AgentMiddleware):
"""
仅在最内层观察最终请求,避免保存压缩之前或缺少动态提示词的快照。
"""
def __init__(self, session: LearningSession) -> None:
"""
与外层目录中间件共享会话 owner,不重复注册工具。
"""
self.session = session
async def awrap_model_call(self, request: Any, handler: Callable[[Any], Awaitable[Any]]) -> Any:
"""
捕获失败不能把已完成模型请求变成错误重放。
"""
result = await handler(request)
try:
self.session.observe(request, result)
except Exception as error:
self.session.snapshot = None
logger.warning('后台复盘快照不可用:%s', type(error).__name__)
return result