- 接入数据库驱动的七维风险评分第一阶段逻辑\n- 增加主模型失败后的 DeepSeek 语义、流式和预防建议兜底\n- 将 AI 预防建议改为基于评分、历史异常和标准规则动态生成\n- 增加无历史案例时的标准库兜底校验和通用规则匹配\n- 修复未知供应商识别及测试占位名称过滤\n- 增加后端滚动日志文件配置和 LLM 请求日志\n- 历史会话直接读取已保存结果,避免重新调用模型和重算风险\n- 补充评分、供应商识别和 LLM 协议测试
181 lines
6.1 KiB
Python
181 lines
6.1 KiB
Python
from datetime import UTC, datetime
|
||
|
||
import pytest
|
||
from app.models.entities import Conversation, ConversationMessage
|
||
from app.services.conversations import ConversationService
|
||
|
||
|
||
def test_conversation_message_data_reads_persisted_risk_result() -> None:
|
||
message = ConversationMessage(
|
||
id=10,
|
||
conversation_id=3,
|
||
role="assistant",
|
||
content="已完成风险预估。",
|
||
structured_data={
|
||
"riskResult": {
|
||
"subjectId": "软管新品",
|
||
"score": 82,
|
||
"level": "low",
|
||
"modelVersion": "baseline-safety-v2",
|
||
"factors": [],
|
||
}
|
||
},
|
||
created_at=datetime.now(UTC),
|
||
)
|
||
|
||
result = ConversationService._message(message)
|
||
|
||
assert result.role == "assistant"
|
||
assert result.risk_result is not None
|
||
assert result.risk_result.score == 82
|
||
assert result.risk_result.level.value == "low"
|
||
|
||
|
||
def test_conversation_data_includes_all_messages() -> None:
|
||
conversation = Conversation(
|
||
id=3,
|
||
user_id=1,
|
||
title="软管新品评估",
|
||
created_at=datetime.now(UTC),
|
||
updated_at=datetime.now(UTC),
|
||
messages=[
|
||
ConversationMessage(
|
||
id=1,
|
||
conversation_id=3,
|
||
role="user",
|
||
content="评估软管新品",
|
||
created_at=datetime.now(UTC),
|
||
),
|
||
ConversationMessage(
|
||
id=2,
|
||
conversation_id=3,
|
||
role="assistant",
|
||
content="已完成分析",
|
||
created_at=datetime.now(UTC),
|
||
),
|
||
],
|
||
)
|
||
|
||
result = ConversationService._data(conversation)
|
||
|
||
assert result.message_count == 2
|
||
assert [message.role for message in result.messages] == ["user", "assistant"]
|
||
|
||
|
||
def test_only_explicit_risk_requests_generate_risk_cards() -> None:
|
||
assert ConversationService._is_risk_request(
|
||
"你好",
|
||
{"intent": "greet", "risk_requested": False},
|
||
) is False
|
||
assert ConversationService._is_risk_request(
|
||
"请只回复:AI 连通成功。",
|
||
{"intent": "general", "risk_requested": False},
|
||
) is False
|
||
assert ConversationService._is_risk_request(
|
||
"请评估这个产品的风险",
|
||
{"intent": "assessment", "risk_requested": True},
|
||
) is True
|
||
|
||
|
||
def test_legacy_general_semantics_hide_old_risk_card() -> None:
|
||
messages = [
|
||
ConversationMessage(
|
||
id=1,
|
||
conversation_id=1,
|
||
role="user",
|
||
content="你好",
|
||
created_at=datetime.now(UTC),
|
||
),
|
||
ConversationMessage(
|
||
id=2,
|
||
conversation_id=1,
|
||
role="assistant",
|
||
content="你好,我可以帮你做什么?",
|
||
structured_data={
|
||
"semantic": {"intent": "general"},
|
||
"riskResult": {
|
||
"subjectId": "误判",
|
||
"score": 61,
|
||
"level": "moderately_low",
|
||
"modelVersion": "baseline-safety-v2",
|
||
"factors": [],
|
||
},
|
||
},
|
||
created_at=datetime.now(UTC),
|
||
),
|
||
]
|
||
|
||
assert ConversationService._risk_result_for_message(messages, 1) is None
|
||
assert ConversationService._is_risk_request(
|
||
"我要做一款脱毛膏新品,100g,PE软管包装,加工厂准备使用瑞沣",
|
||
{"intent": "general", "risk_requested": False},
|
||
) is True
|
||
|
||
|
||
def test_product_assessment_semantics_override_nlu_miss() -> None:
|
||
normalized = ConversationService._normalize_semantics(
|
||
"我要做一款脱毛膏新品,100g,PE软管包装,加工厂准备使用瑞沣",
|
||
{"intent": "general", "risk_requested": False, "category": "脱毛膏"},
|
||
)
|
||
|
||
assert normalized["intent"] == "assessment"
|
||
assert normalized["risk_requested"] is True
|
||
|
||
|
||
def test_short_known_supplier_follow_up_is_routed_to_supplier_card() -> None:
|
||
assert ConversationService._infer_supplier_follow_up(
|
||
"佳亿",
|
||
["佳亿", "祥印", "瑞沣"],
|
||
) == "佳亿"
|
||
assert ConversationService._infer_supplier_follow_up(
|
||
"这个供应商",
|
||
["佳亿", "祥印"],
|
||
) is None
|
||
|
||
|
||
def test_unknown_standalone_name_is_treated_as_new_supplier() -> None:
|
||
assert ConversationService._infer_unknown_supplier_follow_up("德盛") == "德盛"
|
||
assert ConversationService._infer_unknown_supplier_follow_up("新供应商A") is None
|
||
assert ConversationService._infer_unknown_supplier_follow_up("测试供应商") is None
|
||
assert ConversationService._infer_unknown_supplier_follow_up("新供应商B") is None
|
||
assert ConversationService._infer_unknown_supplier_follow_up("洗发水") is None
|
||
assert ConversationService._infer_unknown_supplier_follow_up("帮我查德盛") is None
|
||
assert ConversationService._response_type("测试供应商", {"intent": "supplier"}) == "general"
|
||
assert ConversationService._response_type("新供应商A", {"intent": "supplier"}) == "general"
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_any_product_without_history_uses_solution_card(monkeypatch: pytest.MonkeyPatch) -> None:
|
||
service = object.__new__(ConversationService)
|
||
|
||
async def no_history(_: str) -> int:
|
||
return 0
|
||
|
||
monkeypatch.setattr(service, "_count_category_anomalies", no_history)
|
||
|
||
response_type = ConversationService._response_type(
|
||
"评估防晒霜新品,50g,软管包装",
|
||
{"intent": "assessment", "risk_requested": True, "category": "防晒霜"},
|
||
)
|
||
result = await service._use_solution_card_without_history(
|
||
response_type,
|
||
"评估防晒霜新品,50g,软管包装",
|
||
{"intent": "assessment", "risk_requested": True, "category": "防晒霜"},
|
||
)
|
||
|
||
assert response_type == "risk_assessment"
|
||
assert result == "solution"
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_unknown_product_without_category_uses_solution_card() -> None:
|
||
service = object.__new__(ConversationService)
|
||
|
||
result = await service._use_solution_card_without_history(
|
||
"risk_assessment",
|
||
"评估这个新产品的风险",
|
||
{"intent": "assessment", "risk_requested": True},
|
||
)
|
||
|
||
assert result == "solution"
|