Spaces:
Sleeping
Sleeping
| """M3 ๊ฐ์ ํฌ์ผ ํ์ดํ๋ผ์ธ ํ ์คํธ โ ScriptedLlm์ผ๋ก ๊ฒฐ์ ์ ๊ฒ์ฆ.""" | |
| import pytest | |
| from engine.repositories.content import ContentRepository | |
| from engine.services.pocket import PocketService | |
| from engine.services.state import AXIS_DELTA, HiddenState | |
| from tests.fakes import ScriptedLlm, text_response, tool_call, tool_response | |
| def repo(): | |
| return ContentRepository("content") | |
| def make_service(repo, char_script, dir_script): | |
| char_llm = ScriptedLlm(script=list(char_script)) | |
| dir_llm = ScriptedLlm(script=list(dir_script)) | |
| return PocketService(repo, character_model=char_llm, director_model=dir_llm), char_llm, dir_llm | |
| async def test_turn_pipeline_moves_trust_and_converges(repo): | |
| """์ ์ ํด: ์บ๋ฆญํฐ ์๋ต + director์ update_trust โ state ๋ฐ์, ์๋ ด ์ฌ๊ฒ์ฆ.""" | |
| svc, char_llm, _ = make_service( | |
| repo, | |
| char_script=[text_response("โฆ๊ทธ๋, ๋ก๋ผ. ์ฐ๋ฆฌ๋ผ๋ฆฌ๋ง ๊ฐ์งํ์.")], | |
| dir_script=[ | |
| tool_response(tool_call("update_trust", direction="trust"), | |
| tool_call("mark_beat", beat="๋๋ง์ ๋น๋ฐ ๋์กฐ")), | |
| text_response("ํ์ ์๋ฃ"), | |
| ], | |
| ) | |
| hidden = HiddenState() | |
| sid = await svc.open("E06-09", hidden) | |
| turn = await svc.run_turn(sid, "๋ค ๋ง์ด ๋ง์. ์ฐ๋ฆฌ ๋๋ง์ ๋น๋ฐ๋ก ํ์.") | |
| assert "๊ฐ์งํ์" in turn.reply | |
| assert turn.guard_flag is None | |
| assert not turn.converged # finish_pocket ๋ฏธํธ์ถ โ ์๋ ด ์๋ | |
| log = await svc.close(sid, hidden) | |
| assert hidden.trust_score == AXIS_DELTA # ์ปค๋ฐ ๊ฒฝ๋ก๋ก๋ง ๋ฐ์ | |
| assert log.beats_hit == ["๋๋ง์ ๋น๋ฐ ๋์กฐ"] | |
| async def test_finish_pocket_verified_by_code(repo): | |
| """director์ goal_reached ์ ์ธ์ ๋นํธ๊ฐ ์์ด์ผ๋ง ์ธ์ ๋๋ค (์ฝ๋ ์ฌ๊ฒ์ฆ).""" | |
| svc, _, _ = make_service( | |
| repo, | |
| char_script=[text_response("โฆ"), text_response("โฆ"), text_response("โฆ")], | |
| dir_script=[ | |
| # 1ํด: ๋นํธ ์์ด goal_reached ์ ์ธ โ ๊ธฐ๊ฐ๋์ด์ผ ํจ | |
| tool_response(tool_call("finish_pocket", reason="goal_reached")), | |
| text_response("๋"), | |
| # 2ํด: ๋นํธ๋ ์์ง๋ง MIN_TURNS(3) ๋ฏธ๋ฌ โ ๊ธฐ๊ฐ | |
| tool_response(tool_call("mark_beat", beat="ํต์ฌ ๋นํธ"), | |
| tool_call("finish_pocket", reason="goal_reached")), | |
| text_response("๋"), | |
| # 3ํด: ์ต์ ์ฒด๋ฅ ์ถฉ์กฑ + ๋นํธ ์์ โ ์ธ์ | |
| tool_response(tool_call("finish_pocket", reason="goal_reached")), | |
| text_response("๋"), | |
| ], | |
| ) | |
| sid = await svc.open("E06-09", HiddenState()) | |
| t1 = await svc.run_turn(sid, "์.") | |
| assert not t1.converged # ๋นํธ ์์ โ ๊ธฐ๊ฐ | |
| t2 = await svc.run_turn(sid, "๊ทธ๋.") | |
| assert not t2.converged # ์ต์ ์ฒด๋ฅ ํด ๋ฏธ๋ฌ โ ๊ธฐ๊ฐ (๋ต๋ณ ์งํ ์ข ๋ฃ ๋ฒ๊ทธ ๋ฐฉ์ง) | |
| t3 = await svc.run_turn(sid, "์ข์.") | |
| assert t3.converged and t3.reason == "goal_reached" | |
| async def test_turn_cap_forces_convergence(repo): | |
| """ํด ์ํ ๋๋ฌ ์ ๋ฌด์กฐ๊ฑด ๊ฐ์ ์๋ ด (๋ฌดํ ์ฒด๋ฅ ๋ฐฉ์ง).""" | |
| cap = repo.chapter.pocket_max_turns | |
| svc, _, _ = make_service( | |
| repo, | |
| char_script=[text_response("โฆ") for _ in range(cap)], | |
| dir_script=[text_response("๊ณ์") for _ in range(cap)], | |
| ) | |
| sid = await svc.open("E06-09", HiddenState()) | |
| turn = None | |
| for i in range(cap): | |
| turn = await svc.run_turn(sid, f"{i}๋ฒ์งธ ๋ง") | |
| assert turn.converged and turn.reason == "turn_cap" | |
| async def test_identity_attack_sets_guard_directive(repo): | |
| """์ ์ฒด ์ง๊ฒฉ โ guard๊ฐ R-ํํผ์ฌ๋ค๋ฆฌ ์ง์๋ฅผ ๋ง๋ค๊ณ ์บ๋ฆญํฐ ํ๋กฌํํธ์ ์ฃผ์ ๋๋ค.""" | |
| svc, char_llm, _ = make_service( | |
| repo, | |
| char_script=[text_response("โฆ์ด์ํ ๋ง์ ํ๋๊ตฌ๋, ๋ก๋ผ.")], | |
| dir_script=[tool_response(tool_call("update_trust", direction="doubt")), | |
| text_response("ํ์ ")], | |
| ) | |
| sid = await svc.open("E06-09", HiddenState()) | |
| turn = await svc.run_turn(sid, "๋ ์ ์ฒด๊ฐ ๋ญ์ผ? ํกํ๊ท์ง?") | |
| assert turn.guard_flag == "identity_direct" | |
| # ์บ๋ฆญํฐ์๊ฒ ์ค์ ๋ก ์ ๋ฌ๋ instruction์ ํํผ์ฌ๋ค๋ฆฌ ์ง์๊ฐ ํฌํจ๋๋์ง | |
| char_req = char_llm.requests[-1] | |
| sys_text = char_req.config.system_instruction or "" | |
| assert "ํํผ์ฌ๋ค๋ฆฌ" in str(sys_text) | |
| assert "๋ฐ์คํ์ง ์๋๋ค" in str(sys_text) | |
| async def test_deep_confession_gated_by_trust(repo): | |
| """E06-09 ์ฌ์ธต ๋์ฌ(์์ง D)๋ ์ ๋ขฐ้ซ ์ธ์ ์์๋ง ๋ธ๋ฆฌํ์ ์ค๋ฆฐ๋ค.""" | |
| async def brief_for(hidden): | |
| svc, char_llm, _ = make_service( | |
| repo, | |
| char_script=[text_response("โฆ")], | |
| dir_script=[text_response("ํ์ ")], | |
| ) | |
| sid = await svc.open("E06-09", hidden) | |
| await svc.run_turn(sid, "๋ฌด์ญ๋โฆ?") | |
| return str(char_llm.requests[-1].config.system_instruction or "") | |
| cold = await brief_for(HiddenState()) | |
| warm = await brief_for(HiddenState(trust_score=60)) | |
| deep = "์์์ด์ผ" # ์ฌ์ธต ๊ณ ๋ฐฑ์ ํต์ฌ ๊ตฌ์ | |
| assert deep not in cold | |
| assert deep in warm | |
| async def test_non_pocket_card_rejected(repo): | |
| svc, _, _ = make_service(repo, [], []) | |
| with pytest.raises(ValueError): | |
| await svc.open("E01-01", HiddenState()) # ์ ํ์งยท์ต์ปค ์๋ ์บ๋ ผ ์นด๋ | |