File size: 5,030 Bytes
6ef0a95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
139
140
141
142
143
144
145
146
147
"""One-shot patch: append natural-chat / media-request tests to test_companion_chat.py."""
from pathlib import Path

path = Path(__file__).resolve().parent / "tests" / "test_companion_chat.py"
text = path.read_text(encoding="utf-8")

# The provider's detector is imported directly: the tests pin the same function
# the service reads for its generation flags.
old_import = """from app.services.providers.chat_provider import (
    LocalCompanionChatProvider,
    OpenAICompanionChatProvider,
)"""
new_import = """from app.services.providers.chat_provider import (
    LocalCompanionChatProvider,
    OpenAICompanionChatProvider,
    detect_media_request,
)"""
assert old_import in text, "import block not found"
text = text.replace(old_import, new_import, 1)

ADDITIONS = '''

# ---------------------------------------------------------------------------
# Media requests: pic / photo / image / selfie and video / clip
# ---------------------------------------------------------------------------

GENERIC_DEFLECTIONS = (
    "interesting question",
    "i'm glad you told me",
    "i'm glad you shared that",
    "i hear you, darling",
)


def test_media_requests_are_detected_for_pics_photos_selfies_and_clips():
    """Every everyday way of asking for a picture or a clip is recognised."""
    cases = [
        ("send me a pic", True, False),
        ("give me a photo of you", True, False),
        ("can you send a selfie?", True, False),
        ("image please", True, False),
        ("a clip please", False, True),
        ("send me a video", False, True),
        ("send me a romantic picture", True, False),
        ("make me a short clip of you waving", False, True),
    ]
    for message, expected_image, expected_video in cases:
        wants_image, wants_video, _ = detect_media_request(message)
        assert wants_image is expected_image, message
        assert wants_video is expected_video, message


def test_ordinary_talk_about_photos_is_not_a_generation_request():
    """Mentioning a picture is not asking for one - no accidental jobs."""
    assert detect_media_request("I saw a lovely photo in a gallery today") == (False, False, "")


def test_image_request_acknowledgement_changes_every_time_and_keeps_the_style():
    """While the picture renders the companion says something new, not "OK"."""
    provider = LocalCompanionChatProvider()
    replies = [
        asyncio.run(
            provider.generate_response(make_character(), "send me a romantic picture", [], [])
        )[0]
        for _ in range(3)
    ]

    assert len(set(replies)) == len(replies), replies
    for reply in replies:
        assert "romantic" in reply, reply
        assert "picture" in reply or "photo" in reply, reply


def test_video_request_acknowledgement_talks_about_the_video():
    reply, *_ = asyncio.run(
        LocalCompanionChatProvider().generate_response(
            make_character(), "send me a romantic clip", [], []
        )
    )

    assert "video" in reply or "clip" in reply, reply
    assert "picture" not in reply, reply


def test_casual_chat_replies_answer_the_users_words_without_stock_lines():
    """No "Interesting question…" / "I'm glad you told me…" style deflections."""
    reply, *_ = asyncio.run(
        LocalCompanionChatProvider().generate_response(
            make_character(), "The sky looks lovely tonight.", [], []
        )
    )

    low = reply.lower()
    for phrase in GENERIC_DEFLECTIONS:
        assert phrase not in low, reply
    assert "sky" in low or "tonight" in low, reply
    assert "darling" in low


def test_the_saved_personality_shapes_the_reply():
    """Two companions answer one message differently, each in her saved voice."""
    provider = LocalCompanionChatProvider()
    message = "I fixed the trickiest bug of my week."
    histories = [
        [],
        [ChatMessage(role="assistant", content="Mmhm.")],
        [
            ChatMessage(role="assistant", content="Mmhm."),
            ChatMessage(role="assistant", content="Yeah."),
        ],
        [
            ChatMessage(role="assistant", content="Mmhm."),
            ChatMessage(role="assistant", content="Yeah."),
            ChatMessage(role="assistant", content="Mm."),
        ],
    ]

    playful = [
        asyncio.run(
            provider.generate_response(
                make_character(name="Mira", personality="Playful and teasing at all times"),
                message,
                history,
                [],
            )
        )[0]
        for history in histories
    ]
    witty = [
        asyncio.run(
            provider.generate_response(make_character(), message, history, [])
        )[0]
        for history in histories
    ]

    assert any("teasing you" in reply for reply in playful), playful
    assert any("on my toes" in reply for reply in witty), witty
    assert set(playful) != set(witty)
'''

if not text.endswith("\n"):
    text += "\n"
text += ADDITIONS.lstrip("\n")

path.write_text(text, encoding="utf-8")
print("patched", path)