Spaces:
Sleeping
Sleeping
File size: 5,598 Bytes
1c9c13f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | """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
@pytest.fixture(scope="module")
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
@pytest.mark.asyncio
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 == ["๋๋ง์ ๋น๋ฐ ๋์กฐ"]
@pytest.mark.asyncio
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"
@pytest.mark.asyncio
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"
@pytest.mark.asyncio
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)
@pytest.mark.asyncio
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
@pytest.mark.asyncio
async def test_non_pocket_card_rejected(repo):
svc, _, _ = make_service(repo, [], [])
with pytest.raises(ValueError):
await svc.open("E01-01", HiddenState()) # ์ ํ์งยท์ต์ปค ์๋ ์บ๋
ผ ์นด๋
|