File size: 8,392 Bytes
108a536
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
148
149
150
151
152
153
154
155
156
"""Single source of truth for the tool schema, developer prompt, and prompt
rendering โ€” imported by both step 1 (dataset synthesis) and step 4 (export gate).

Keeping these in one module guarantees the developer+tools prefix the model is
trained on is byte-identical to the one the export gate (and the Unity runtime)
renders. Never redefine TOOLS or DEV anywhere else.
"""
import os
from transformers import AutoTokenizer

import paths

# ============================ tool schema ============================
# Rooms apply ONLY to the light tools. TV / computer / vacuum / speaker are
# single devices and take no room. play_music has a small genre enum (default
# jazz). web_search is the general-knowledge escape hatch.
TOOLS = [
    {"type": "function", "function": {
        "name": "turn_on_light",
        "description": "Turn on the light in a room, optionally at a brightness percent.",
        "parameters": {"type": "object", "properties": {
            "room": {"type": "string", "description": "Which room's light."},
            "brightness": {"type": "integer", "description": "Brightness percent 0-100 (default 100)."},
        }, "required": ["room"]}}},
    {"type": "function", "function": {
        "name": "turn_off_light",
        "description": "Turn off the light in a room.",
        "parameters": {"type": "object", "properties": {
            "room": {"type": "string", "description": "Which room's light."},
        }, "required": ["room"]}}},
    {"type": "function", "function": {
        "name": "set_light_color",
        "description": "Change a room's light color.",
        "parameters": {"type": "object", "properties": {
            "room": {"type": "string", "description": "Which room's light."},
            "color": {"type": "string", "description": "Color name.",
                      "enum": ["red", "orange", "yellow", "green", "blue",
                               "purple", "pink", "white", "warm", "cool"]},
        }, "required": ["room", "color"]}}},
    {"type": "function", "function": {
        "name": "turn_on_tv", "description": "Turn on the TV.",
        "parameters": {"type": "object", "properties": {}, "required": []}}},
    {"type": "function", "function": {
        "name": "turn_off_tv", "description": "Turn off the TV.",
        "parameters": {"type": "object", "properties": {}, "required": []}}},
    {"type": "function", "function": {
        "name": "turn_on_computer", "description": "Turn on the computer.",
        "parameters": {"type": "object", "properties": {}, "required": []}}},
    {"type": "function", "function": {
        "name": "turn_off_computer", "description": "Turn off the computer.",
        "parameters": {"type": "object", "properties": {}, "required": []}}},
    {"type": "function", "function": {
        "name": "start_vacuum", "description": "Start the robot vacuum.",
        "parameters": {"type": "object", "properties": {}, "required": []}}},
    {"type": "function", "function": {
        "name": "play_music",
        "description": "Start playing music, optionally of a genre (default jazz).",
        "parameters": {"type": "object", "properties": {
            "genre": {"type": "string", "description": "Music genre.",
                      "enum": ["rock", "jazz", "lofi"]},
        }, "required": []}}},
    {"type": "function", "function": {
        "name": "stop_music", "description": "Stop the music.",
        "parameters": {"type": "object", "properties": {}, "required": []}}},
    {"type": "function", "function": {
        "name": "set_volume",
        "description": "Set the speaker volume to an absolute percent.",
        "parameters": {"type": "object", "properties": {
            "volume": {"type": "integer", "description": "Volume percent 0-100."},
        }, "required": ["volume"]}}},
    {"type": "function", "function": {
        "name": "get_volume", "description": "Get the current speaker volume.",
        "parameters": {"type": "object", "properties": {}, "required": []}}},
    {"type": "function", "function": {
        "name": "get_weather",
        "description": "Get the current weather / temperature for a city.",
        "parameters": {"type": "object", "properties": {
            "city": {"type": "string", "description": "City name (corrected if misspelled)."},
        }, "required": ["city"]}}},
    {"type": "function", "function": {
        "name": "get_time",
        "description": "Get the current time; no city = the device's current city.",
        "parameters": {"type": "object", "properties": {
            "city": {"type": "string", "description": "Optional city for world time."},
        }, "required": []}}},
    {"type": "function", "function": {
        "name": "get_location",
        "description": "Get the device's current location (city / area).",
        "parameters": {"type": "object", "properties": {}, "required": []}}},
    {"type": "function", "function": {
        "name": "web_search",
        "description": "Search the web for general knowledge / facts and answer from the results.",
        "parameters": {"type": "object", "properties": {
            "query": {"type": "string", "description": "The search query."},
        }, "required": ["query"]}}},
]

DEV = ("You convert the user's command (English or Korean) into one or more function calls "
       "with correctly filled arguments, in the order the user asked. Rooms apply only to lights. "
       "If the request does not match any function, reply briefly in text instead of calling.")

# Held-out evaluation queries โ€” kept OUT of the training set (step 1 filters on
# this) and used by the step 4 ORT parity gate, so the gate always tests unseen
# phrasings. Keep the two in sync.
BENCH = {
    "๊ฑฐ์‹ค ๋ถˆ 30ํผ์„ผํŠธ๋กœ ์ผœ์ค˜", "turn on the living room light at 30 percent", "๋งˆ๋ฃจ ๋ถˆ ์ผœ์ค˜",
    "๊ฑฐ์‹ค ๋ถˆ ๊บผ์ค˜", "turn off the kitchen light", "๊ฑฐ์‹ค ์กฐ๋ช… ํŒŒ๋ž€์ƒ‰์œผ๋กœ ๋ฐ”๊ฟ”์ค˜",
    "ํ‹ฐ๋น„ ์ผœ์ค˜", "turn on the tv", "์ปดํ“จํ„ฐ ์ผœ์ค˜", "turn off the computer", "์ฒญ์†Œ๊ธฐ ๋Œ๋ ค์ค˜",
    "์Œ์•… ํ‹€์–ด์ค˜", "play music", "๋ฝ ์Œ์•… ํ‹€์–ด์ค˜", "play some jazz", "๋กœํŒŒ์ด ํ‹€์–ด์ค˜",
    "์Œ์•… ๊บผ์ค˜", "๋ณผ๋ฅจ 30์œผ๋กœ ํ•ด์ค˜", "set the volume to 60", "๋ณผ๋ฅจ ๋ช‡์ด์•ผ", "what's the volume",
    "์„œ์šธ ๋‚ ์”จ ์–ด๋•Œ?", "what's the weather in london right now?", "๋‰ด์š•์€ ์ง€๊ธˆ ๋ช‡ ์‹œ์•ผ?",
    "์ง€๊ธˆ ๋ช‡์‹œ์•ผ?", "์—ฌ๊ธฐ๊ฐ€ ์–ด๋””์•ผ?", "ํ”„๋ž‘์Šค ์ˆ˜๋„๊ฐ€ ์–ด๋””์•ผ", "what is the capital of france?",
    "๋ถ€์—Œ, ๊ฑฐ์‹ค, ์•ˆ๋ฐฉ ๋ถˆ ์ผœ์ค˜", "๋งˆ๋ฃจ์— ๋นจ๊ฐ„ ๋ถˆ ์ผœ๊ณ  ์žฌ์ฆˆ ํ‹€์–ด์ค˜",
}

# ============================ prompt rendering ============================
# The tokenizer + chat template come from the base model; merging LoRA leaves
# both untouched, so training and export render identically.
_tok = AutoTokenizer.from_pretrained(paths.BASE_MODEL)
_chat_template = open(os.path.join(paths.BASE_MODEL, "chat_template.jinja")).read()


def prompt_only(user):
    """The rendered prompt up to (and including) the model's generation turn."""
    return _tok.apply_chat_template(
        [{"role": "developer", "content": DEV}, {"role": "user", "content": user}],
        tools=TOOLS, chat_template=_chat_template,
        add_generation_prompt=True, tokenize=False)


def render_calls(user, calls):
    """Render one training row for a tool-calling turn.

    `calls` is a list of (name, args_dict). Returns (prompt, completion) where
    completion is just the assistant's function-call span.
    """
    prompt = prompt_only(user)
    tool_calls = [{"type": "function", "function": {"name": n, "arguments": a}} for n, a in calls]
    full = _tok.apply_chat_template(
        [{"role": "developer", "content": DEV}, {"role": "user", "content": user},
         {"role": "assistant", "tool_calls": tool_calls}],
        tools=TOOLS, chat_template=_chat_template,
        add_generation_prompt=False, tokenize=False)
    return prompt, full[len(prompt):]


def render_text(user, reply):
    """Render one training row for a plain-text refusal turn."""
    prompt = prompt_only(user)
    full = _tok.apply_chat_template(
        [{"role": "developer", "content": DEV}, {"role": "user", "content": user},
         {"role": "assistant", "content": reply}],
        tools=TOOLS, chat_template=_chat_template,
        add_generation_prompt=False, tokenize=False)
    return prompt, full[len(prompt):]