feat: 收口通知原因语义与留痕校验

This commit is contained in:
2026-06-04 03:44:04 +08:00
parent 742d5e9a42
commit dc86fc0e58
4 changed files with 79 additions and 2 deletions

View File

@@ -181,8 +181,9 @@ def _build_node_results(output_type: str, structured_output: dict) -> list[dict]
def _build_notification_payload(structured_output: dict, options: dict, status: str) -> dict:
notify_reason = structured_output.get("notify_reason") or (
"task_completed" if status == "success" else "task_failed"
notify_reason = _normalize_notify_reason(
structured_output.get("notify_reason"),
status=status,
)
owners = structured_output.get("owner_roles") or []
if not owners:
@@ -202,6 +203,15 @@ def _build_notification_payload(structured_output: dict, options: dict, status:
}
def _normalize_notify_reason(notify_reason: str | None, *, status: str) -> str:
"""
将通知原因收口到 Demo 固定支持的两类语义。
"""
if notify_reason in {"task_completed", "task_failed"}:
return notify_reason
return "task_completed" if status == "success" else "task_failed"
def _build_registration_node_results(output_type: str, structured_output: dict) -> list[dict]:
nodes = [
{"code": "package_import", "label": "资料包导入", "status": "已完成"},

View File

@@ -5,6 +5,9 @@ from apps.documents.models import SubmissionBatch
from .models import AgentAuditLog, NotificationRecord
SUPPORTED_NOTIFY_REASONS = {"task_completed", "task_failed"}
def create_audit_log(
scenario_id: str,
scenario_name: str,
@@ -84,6 +87,8 @@ def create_notification_record(
V1 先把通知载荷和结果状态稳定落库,
真实飞书发送可在后续阶段接入。
"""
if notify_reason not in SUPPORTED_NOTIFY_REASONS:
raise ValueError(f"notify_reason 不受支持:{notify_reason}")
return NotificationRecord.objects.create(
batch_id=batch_id,
conversation_id=conversation_id,

View File

@@ -513,6 +513,49 @@ def test_feishu_notification_report_builds_notification_payload_with_receipt_and
assert result.notification_payload["receipt"]["message_id"] == "msg-3"
def test_notification_payload_normalizes_unsupported_notify_reason():
scenario = {
"id": "document_review",
"name": "注册审核智能体",
"agent": {
"role": "注册审核助手",
"goal": "输出通知结果",
"instructions": ["输出结构化通知结果"],
},
"rag": {"enabled": False},
"tools": [],
"output": {"type": "feishu_notification_report"},
}
provider_response = """
{
"batch_id": "SUB-20260604-004",
"conversation_id": "conv-004",
"notify_reason": "custom_reason",
"mentioned_users": ["ou_demo_1"],
"message_status": "sent"
}
"""
class FakeProvider:
def generate(self, messages, response_format=None):
from agent_core.llm_provider import LLMResponse
return LLMResponse(content=provider_response, model_name="demo-model", success=True)
result = run_agent(
scenario,
"请生成通知结果",
options={
"llm_provider": FakeProvider(),
"batch_id": "SUB-20260604-004",
"conversation_id": "conv-004",
"product_name": "产品D",
},
)
assert result.notification_payload["notify_reason"] == "task_completed"
def test_registration_word_export_report_preserves_formal_export_flag_and_blocked_items():
scenario = {
"id": "document_review",

View File

@@ -170,6 +170,25 @@ def test_create_notification_record_persists_task_completed_and_task_failed(db):
assert failed.notify_reason == "task_failed"
def test_create_notification_record_rejects_unsupported_notify_reason(db):
try:
create_notification_record(
batch_id="SUB-20260604-001",
conversation_id="conv-001",
product_name="产品A",
trigger_source="risk_report",
notify_reason="custom_reason",
owner_role="注册资料负责人",
feishu_user_id="ou_demo_1",
message_status="sent",
web_detail_url="https://example.com/detail/1",
receipt={"message_id": "msg-1"},
)
assert False, "expected ValueError"
except ValueError as exc:
assert "notify_reason" in str(exc)
def test_audit_list_supports_batch_and_product_filters(client, db):
create_audit_log(
"document_review",