93 lines
3.2 KiB
Python
93 lines
3.2 KiB
Python
"""T-07 agent_service StateGraph 骨架(mock LLM,不依赖外网/真 key)。
|
|
|
|
FakeLLM 捕获收到的 messages 供断言(system 边界 + 历史窗口 + 本轮消息);
|
|
降级路径(无 key)与免责声明护栏单独覆盖。图/LLM 缓存经 reset_cache 隔离。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
from langchain_core.messages import AIMessage
|
|
|
|
from app.config.settings import settings
|
|
from app.service import agent_service
|
|
|
|
|
|
class FakeLLM:
|
|
def __init__(self, reply: str = "模拟回复"):
|
|
self.reply = reply
|
|
self.calls: list[list] = []
|
|
|
|
def invoke(self, messages):
|
|
self.calls.append(list(messages))
|
|
return AIMessage(content=self.reply)
|
|
|
|
|
|
@pytest.fixture()
|
|
def fake_llm(monkeypatch):
|
|
llm = FakeLLM()
|
|
monkeypatch.setattr(agent_service, "_llm", llm)
|
|
monkeypatch.setattr(settings, "deepseek_api_key", "test-key")
|
|
yield llm
|
|
agent_service.reset_cache()
|
|
|
|
|
|
HISTORY = [
|
|
{"role": "user", "content": "你好"},
|
|
{"role": "assistant", "content": "您好,有什么可以帮您?"},
|
|
]
|
|
|
|
|
|
def test_chat_customer_appends_disclaimer(fake_llm):
|
|
out = agent_service.chat("customer", HISTORY, "查一下我的持仓")
|
|
assert out["has_disclaimer"] is True
|
|
assert out["reply"].endswith(agent_service.CHAT_DISCLAIMER)
|
|
assert "模拟回复" in out["reply"]
|
|
|
|
|
|
def test_chat_risk_also_disclaimer(fake_llm):
|
|
out = agent_service.chat("risk", [], "今天有多少待审预警")
|
|
assert out["has_disclaimer"] is True
|
|
|
|
|
|
def test_chat_advisor_no_disclaimer(fake_llm):
|
|
out = agent_service.chat("advisor", [], "总结客户需求")
|
|
assert out["has_disclaimer"] is False
|
|
assert agent_service.CHAT_DISCLAIMER not in out["reply"]
|
|
|
|
|
|
def test_messages_compose_system_history_and_user(fake_llm):
|
|
agent_service.chat("customer", HISTORY, "最新问题")
|
|
msgs = fake_llm.calls[0]
|
|
assert msgs[0].__class__.__name__ == "SystemMessage"
|
|
assert "不得提供投资建议" in msgs[0].content # customer 边界注入
|
|
assert [m.__class__.__name__ for m in msgs[1:]] == ["HumanMessage", "AIMessage", "HumanMessage"]
|
|
assert msgs[-1].content == "最新问题"
|
|
|
|
|
|
def test_role_boundary_per_agent_type(fake_llm):
|
|
agent_service.chat("analyst", [], "聚合一下")
|
|
system = fake_llm.calls[0][0]
|
|
assert "只读" in system.content
|
|
|
|
|
|
def test_degraded_reply_without_api_key(monkeypatch):
|
|
"""key 缺失 → 降级固定提示(不抛异常,演示链路不断)。"""
|
|
monkeypatch.setattr(settings, "deepseek_api_key", "")
|
|
agent_service.reset_cache()
|
|
out = agent_service.chat("customer", [], "在吗")
|
|
assert "LLM 未配置" in out["reply"] and "在吗" in out["reply"]
|
|
assert out["has_disclaimer"] is True # 降级回复同样经 guard 护栏
|
|
|
|
|
|
def test_graph_skeleton_nodes():
|
|
graph = agent_service.build_graph()
|
|
assert {"llm", "guard"} <= set(graph.nodes) # __start__ 为 LangGraph 内部节点
|
|
|
|
|
|
def test_unknown_agent_type_falls_back_customer_boundary(fake_llm):
|
|
"""未知 agent_type 兜底 customer 边界(fail-safe:最严声明口径)。"""
|
|
out = agent_service.chat("unknown", [], "hi")
|
|
assert out["has_disclaimer"] is True
|
|
assert "不得提供投资建议" in fake_llm.calls[0][0].content
|