Sky-Kim's picture
Initial commit
108a536
Raw History Blame Contribute Delete
8.39 kB
"""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):]