mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-31 13:07:56 +08:00
123 lines
3.9 KiB
Python
123 lines
3.9 KiB
Python
"""Agent 图缓存行为测试。"""
|
|
|
|
from datetime import datetime
|
|
from types import SimpleNamespace
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
import pytest
|
|
from langchain_core.messages import AIMessage, HumanMessage
|
|
|
|
from app.agent import MoviePilotAgent, ReplyMode, _CompiledAgentBundle
|
|
from app.core.config import settings
|
|
|
|
|
|
@pytest.fixture
|
|
def anyio_backend():
|
|
"""使用 asyncio 后端运行 anyio 异步测试。"""
|
|
return "asyncio"
|
|
|
|
|
|
class _FakeGraphState:
|
|
"""提供 LangGraph get_state 测试替身。"""
|
|
|
|
def __init__(self, messages):
|
|
"""保存测试消息状态。"""
|
|
self.values = {"messages": messages}
|
|
|
|
|
|
class _CapturingAgent:
|
|
"""捕获传入消息的非流式 Agent 测试替身。"""
|
|
|
|
def __init__(self):
|
|
"""初始化捕获容器。"""
|
|
self.payload = None
|
|
|
|
async def ainvoke(self, payload, config=None):
|
|
"""记录 Agent 调用输入。"""
|
|
self.payload = payload
|
|
|
|
def get_state(self, _config):
|
|
"""返回包含最终 AI 回复的图状态。"""
|
|
return _FakeGraphState([AIMessage(content="ok")])
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_create_agent_reuses_cached_graph_when_signature_matches():
|
|
"""构造签名一致时应直接复用已编译 Agent 图。"""
|
|
cached_graph = object()
|
|
agent = MoviePilotAgent(session_id="cache-hit", user_id="user-1")
|
|
agent._compiled_agent_bundle = _CompiledAgentBundle(
|
|
signature=("sig",),
|
|
agent=cached_graph,
|
|
streaming=False,
|
|
created_at=datetime.now(),
|
|
)
|
|
|
|
with patch.object(
|
|
agent,
|
|
"_agent_bundle_signature",
|
|
new=AsyncMock(return_value=("sig",)),
|
|
), patch("app.agent.create_agent") as create_agent:
|
|
graph = await agent._create_agent(streaming=False)
|
|
|
|
assert graph is cached_graph
|
|
assert agent._last_agent_cache_hit is True
|
|
create_agent.assert_not_called()
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_agent_bundle_signature_changes_with_temperature(monkeypatch) -> None:
|
|
"""温度配置变化时应使会话内 Agent 图缓存失效。"""
|
|
agent = MoviePilotAgent(session_id="temperature-change", user_id="user-1")
|
|
runtime_config = {
|
|
"provider": "openai",
|
|
"model": "gpt-test",
|
|
"api_key": "test-key",
|
|
"base_url": "https://llm.example.com/v1",
|
|
"base_url_preset": None,
|
|
"user_agent": None,
|
|
"use_proxy": False,
|
|
"thinking_level": "off",
|
|
"api_protocol": "auto",
|
|
}
|
|
|
|
with patch.object(
|
|
agent,
|
|
"_resolve_llm_runtime_config",
|
|
new=AsyncMock(return_value=runtime_config),
|
|
):
|
|
monkeypatch.setattr(settings, "LLM_TEMPERATURE", 0.3)
|
|
initial_signature = await agent._agent_bundle_signature(streaming=False)
|
|
monkeypatch.setattr(settings, "LLM_TEMPERATURE", 1.0)
|
|
updated_signature = await agent._agent_bundle_signature(streaming=False)
|
|
|
|
assert updated_signature != initial_signature
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_execute_agent_sends_only_latest_message_on_cache_hit():
|
|
"""缓存命中时只把本轮新消息交给 LangGraph,避免重复提交历史。"""
|
|
fake_graph = _CapturingAgent()
|
|
agent = MoviePilotAgent(session_id="cache-hit", user_id="user-1")
|
|
agent.reply_mode = ReplyMode.CAPTURE_ONLY
|
|
agent._tool_context = {"user_reply_sent": False}
|
|
agent._streamed_output = ""
|
|
agent._should_stream = lambda: False
|
|
agent.stream_handler = SimpleNamespace(
|
|
stop_streaming=AsyncMock(return_value=(False, ""))
|
|
)
|
|
|
|
async def _create_agent(streaming=False):
|
|
"""模拟缓存命中后的 Agent 创建结果。"""
|
|
agent._last_agent_cache_hit = True
|
|
return fake_graph
|
|
|
|
agent._create_agent = _create_agent
|
|
messages = [HumanMessage(content="上一轮"), HumanMessage(content="本轮")]
|
|
|
|
with patch("app.agent.eventmanager.send_event"):
|
|
await agent._execute_agent(messages)
|
|
|
|
assert agent._streamed_output == "ok"
|
|
assert fake_graph.payload["messages"] == [messages[-1]]
|