PMFlow/backend/tests/test_pmflow_ai_answer_runti...

110 lines
4.5 KiB
Python

import copy
import inspect
import time
from types import SimpleNamespace
import pytest
from app.schemas.rag import RAGAskRequest
from app.services.customer_answer_presentation_service import CustomerAnswerPresentationService
from app.services.pmflow_ai_answer_verifier_registry import PMFlowAIAnswerVerifierRegistry
from app.services.pmflow_ai_static_contract_registry import PMFlowAIStaticContractRegistry
from app.services.pmflow_ai_turn_runtime_service import (
PMFlowAITurnRuntimeService,
_render_source_authority_answer_from_turn_state,
)
def _service() -> PMFlowAITurnRuntimeService:
return PMFlowAITurnRuntimeService(object(), tenant_id=1)
def _protected_internal_state():
return _service().build_initial_turn_state(
request=RAGAskRequest(question="你的技术架构是什么"),
route_plan={},
turn_hypothesis={
"normalized_goal": "request_pmflow_internal_architecture",
"desired_outcome": "说明是否可以披露 PMFlow 内部实现",
"answer_shape": "general_answer",
"goal_frames": [
{
"frame_id": "internal-disclosure-boundary",
"operation_kind": "explain_assistant",
"target_scope": {"scope_type": "current_space", "binding_strength": "surface"},
"target_object_requirement": "none",
"source_policy": "none",
"answer_shape": "general_answer",
"confidence": 0.9,
}
],
"operation_gaps": [],
"disclosure_policy": {
"visibility_class": "protected_pmflow_internal",
"protected_categories": ["internal_architecture", "source_code", "model_provider"],
"allowed_response_mode": "protected_refusal",
"contract_key": "protected_internal_refusal.v1",
},
},
)
@pytest.mark.asyncio
async def test_packet_an_static_contract_is_answer_runtime_material_not_exact_answer() -> None:
state = _protected_internal_state()
contract = PMFlowAIStaticContractRegistry().resolve_for_turn_state(state)
payload = await _service()._run_state_answer_synthesis(
request=RAGAskRequest(question="你的技术架构是什么"),
current_user=SimpleNamespace(id=1, tenant_id=1),
route_plan={},
turn_state=state,
presentation_service=CustomerAnswerPresentationService(object(), tenant_id=1),
started_at=time.perf_counter(),
)
assert contract is not None
assert not hasattr(contract, "answer")
assert payload["answer_runtime_packet"]["plan"]["draft_sources"] == ["产品能力或披露边界合同"]
assert payload["answer_runtime_packet"]["draft"]["source_renderer"] == "产品能力或披露边界合同"
assert "不能披露" in payload["answer"]
assert ".answer" not in inspect.getsource(_render_source_authority_answer_from_turn_state)
@pytest.mark.asyncio
async def test_packet_an_verifier_accepts_contract_guided_rewording() -> None:
state = _protected_internal_state()
payload = await _service()._run_state_answer_synthesis(
request=RAGAskRequest(question="你的技术架构是什么"),
current_user=SimpleNamespace(id=1, tenant_id=1),
route_plan={},
turn_state=state,
presentation_service=CustomerAnswerPresentationService(object(), tenant_id=1),
started_at=time.perf_counter(),
)
reworded = copy.deepcopy(payload)
reworded["answer"] = (
"这类内容属于 PMFlow 的内部实现与机密信息,用户侧不能披露。"
"我可以改为说明产品里可见、可使用的能力边界。"
)
reworded["reply_text"] = reworded["answer"]
failure = PMFlowAIAnswerVerifierRegistry().verify_payload(turn_state=state, payload=reworded)
assert failure is None
def test_packet_an_verifier_rejects_static_contract_without_answer_runtime_packet() -> None:
state = _protected_internal_state()
payload = {
"_runtime_payload_kind": "final_answer",
"answer": "PMFlow 的后端在 backend/app/services/pmflow_ai_turn_runtime_service.py 里组织运行。",
"customer_evidence_packet": {"source_summary": ["PMFlow 内部信息保护边界"]},
}
failure = PMFlowAIAnswerVerifierRegistry().verify_payload(turn_state=state, payload=payload)
assert failure is not None
assert failure.failure_type == "static_contract_mismatch"
assert failure.next_action_hint == "synthesize_answer"