세션 평가와 교수자 분석 보강
This commit is contained in:
parent
5c4ac04e06
commit
fe2796f05a
51 changed files with 4928 additions and 240 deletions
|
|
@ -87,16 +87,17 @@ def _assert_masked_ko_pii_present(test: unittest.TestCase, value: object) -> Non
|
|||
|
||||
|
||||
class CaptureGenerateEngine:
|
||||
def __init__(self) -> None:
|
||||
def __init__(self, text: str = "Masked engine reply.") -> None:
|
||||
self.request = None
|
||||
self.payload: dict[str, Any] | None = None
|
||||
self._payload_builder = EngineClient(base_url="http://engine.test")
|
||||
self.text = text
|
||||
|
||||
async def generate(self, req):
|
||||
self.request = req
|
||||
self.payload = self._payload_builder._payload(req)
|
||||
return GenerateResponse(
|
||||
text="Masked engine reply.",
|
||||
text=self.text,
|
||||
model="fake-model",
|
||||
provider="fake-provider",
|
||||
tokens_in=3,
|
||||
|
|
@ -109,16 +110,18 @@ class CaptureStreamEngine:
|
|||
engine_mode = "fake-provider"
|
||||
default_model = "fake-model"
|
||||
|
||||
def __init__(self) -> None:
|
||||
def __init__(self, chunks: list[str] | None = None) -> None:
|
||||
self.request = None
|
||||
self.payload: dict[str, Any] | None = None
|
||||
self._payload_builder = EngineClient(base_url="http://engine.test")
|
||||
self.chunks = chunks or ["Masked stream reply."]
|
||||
|
||||
async def stream(self, req):
|
||||
self.request = req
|
||||
self.payload = self._payload_builder._payload(req)
|
||||
yield "event: token"
|
||||
yield 'data: {"text":"Masked stream reply."}'
|
||||
for chunk in self.chunks:
|
||||
yield "event: token"
|
||||
yield "data: " + json.dumps({"text": chunk}, ensure_ascii=False)
|
||||
yield "event: done"
|
||||
yield (
|
||||
'data: {"provider":"fake-provider","model":"fake-model",'
|
||||
|
|
@ -200,6 +203,15 @@ class OrchestratorMaskingGateTest(unittest.IsolatedAsyncioTestCase):
|
|||
self.assertNotIn("NAME", masked.entities)
|
||||
self.assertNotIn("ORG", masked.entities)
|
||||
|
||||
def test_mask_pii_does_not_mask_career_topic_as_name(self) -> None:
|
||||
masked = guardrail.mask_pii("아직 정해진 건 없어요. 진로는 그대로고 엄마한테 말도 못 했어요.")
|
||||
|
||||
self.assertEqual(
|
||||
masked.text_masked,
|
||||
"아직 정해진 건 없어요. 진로는 그대로고 엄마한테 말도 못 했어요.",
|
||||
)
|
||||
self.assertNotIn("NAME", masked.entities)
|
||||
|
||||
def test_prepare_turn_masks_korean_pii_from_engine_messages(self) -> None:
|
||||
ctx = orchestrator.prepare_turn(
|
||||
session_id="masking-session",
|
||||
|
|
@ -316,7 +328,11 @@ class OrchestratorMaskingGateTest(unittest.IsolatedAsyncioTestCase):
|
|||
)
|
||||
]
|
||||
|
||||
self.assertEqual([event.event for event in events], ["token", "done"])
|
||||
self.assertEqual(events[-1].event, "done")
|
||||
self.assertEqual(
|
||||
"".join(str(event.data.get("text", "")) for event in events if event.event == "token"),
|
||||
"Masked stream reply.",
|
||||
)
|
||||
self.assertIsNotNone(engine.request)
|
||||
self.assertIsNotNone(engine.payload)
|
||||
_assert_no_raw_pii(self, engine.request.messages)
|
||||
|
|
@ -359,6 +375,53 @@ class OrchestratorMaskingGateTest(unittest.IsolatedAsyncioTestCase):
|
|||
_assert_masked_ko_pii_present(self, engine.payload)
|
||||
_assert_no_raw_ko_pii(self, audit_payloads)
|
||||
|
||||
async def test_run_turn_generate_humanizes_masked_placeholder_reply(self) -> None:
|
||||
ctx = orchestrator.prepare_turn(
|
||||
session_id="masking-session",
|
||||
case_id="masking-case",
|
||||
card=persona.P1,
|
||||
state=_initial_state(),
|
||||
learner_text="진로 이야기를 이어가고 싶어요.",
|
||||
)
|
||||
engine = CaptureGenerateEngine(
|
||||
text="아직 정해진 건 하나도 없어요. [NAME]는 그대로고, 엄마한테 뭐라고 말할지도 모르겠고요."
|
||||
)
|
||||
|
||||
result = await orchestrator.run_turn_generate(
|
||||
ctx,
|
||||
engine, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
self.assertNotIn("[NAME]", result.client_reply or "")
|
||||
self.assertIn("그 이름은 그대로고", result.client_reply or "")
|
||||
|
||||
async def test_run_turn_stream_humanizes_split_masked_placeholder_reply(self) -> None:
|
||||
ctx = orchestrator.prepare_turn(
|
||||
session_id="masking-session",
|
||||
case_id="masking-case",
|
||||
card=persona.P1,
|
||||
state=_initial_state(),
|
||||
learner_text="진로 이야기를 이어가고 싶어요.",
|
||||
)
|
||||
engine = CaptureStreamEngine(
|
||||
chunks=["아직 정해진 건 하나도 없어요. ", "[NA", "ME]는", " 그대로고요."]
|
||||
)
|
||||
|
||||
events = [
|
||||
event
|
||||
async for event in orchestrator.run_turn_stream(
|
||||
ctx,
|
||||
engine, # type: ignore[arg-type]
|
||||
)
|
||||
]
|
||||
streamed = "".join(
|
||||
str(event.data.get("text", "")) for event in events if event.event == "token"
|
||||
)
|
||||
|
||||
self.assertNotIn("[NAME]", streamed)
|
||||
self.assertIn("그 이름은 그대로고요.", streamed)
|
||||
self.assertEqual(events[-1].event, "done")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue