Normalize product data prompt formatting
This commit is contained in:
@@ -397,6 +397,8 @@ def build_planning_prompt(
|
|||||||
|
|
||||||
|
|
||||||
def wrap_chat_prompt(system_prompt: str, instruction: str) -> str:
|
def wrap_chat_prompt(system_prompt: str, instruction: str) -> str:
|
||||||
|
system_prompt = prompt_section_without_trailing_newline(system_prompt)
|
||||||
|
instruction = prompt_section_with_one_trailing_newline(instruction)
|
||||||
return (
|
return (
|
||||||
f"<|im_start|>system\n{system_prompt}<|im_end|>\n"
|
f"<|im_start|>system\n{system_prompt}<|im_end|>\n"
|
||||||
f"<|im_start|>user\n{instruction}<|im_end|>\n"
|
f"<|im_start|>user\n{instruction}<|im_end|>\n"
|
||||||
@@ -404,6 +406,14 @@ def wrap_chat_prompt(system_prompt: str, instruction: str) -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def prompt_section_without_trailing_newline(text: str) -> str:
|
||||||
|
return str(text or "").rstrip("\n")
|
||||||
|
|
||||||
|
|
||||||
|
def prompt_section_with_one_trailing_newline(text: str) -> str:
|
||||||
|
return prompt_section_without_trailing_newline(text) + "\n"
|
||||||
|
|
||||||
|
|
||||||
def build_training_instruction(record: dict[str, Any], session_num: int, session_time_minutes: int) -> str:
|
def build_training_instruction(record: dict[str, Any], session_num: int, session_time_minutes: int) -> str:
|
||||||
turn = record.get("turn") if isinstance(record.get("turn"), dict) else {}
|
turn = record.get("turn") if isinstance(record.get("turn"), dict) else {}
|
||||||
query = str(turn.get("query") or "")
|
query = str(turn.get("query") or "")
|
||||||
@@ -414,7 +424,7 @@ def build_training_instruction(record: dict[str, Any], session_num: int, session
|
|||||||
return (
|
return (
|
||||||
"请参考用户的[当前query]、[对话历史]、[知识注入]、[系统状态]识别出[当前query]的[function]结果,[function]是python的code形式。\n"
|
"请参考用户的[当前query]、[对话历史]、[知识注入]、[系统状态]识别出[当前query]的[function]结果,[function]是python的code形式。\n"
|
||||||
"[知识注入]\n"
|
"[知识注入]\n"
|
||||||
f"{json.dumps({'location': str(context.get('location') or ''), 'rag': str(context.get('rag') or '')}, ensure_ascii=False, indent=0)}\n"
|
f"{context_prompt_block(context)}\n"
|
||||||
"[系统状态]\n"
|
"[系统状态]\n"
|
||||||
"{}\n"
|
"{}\n"
|
||||||
"[对话历史]\n"
|
"[对话历史]\n"
|
||||||
@@ -425,6 +435,17 @@ def build_training_instruction(record: dict[str, Any], session_num: int, session
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def context_prompt_block(context: dict[str, Any]) -> str:
|
||||||
|
fields = ("location", "rag")
|
||||||
|
lines = ["{"]
|
||||||
|
for index, field in enumerate(fields):
|
||||||
|
comma = "," if index < len(fields) - 1 else ""
|
||||||
|
value = str(context.get(field) or "")
|
||||||
|
lines.append(f'"{field}": {json.dumps(value, ensure_ascii=False)}{comma}')
|
||||||
|
lines.append("}")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
def render_history(prev_session: list[Any], current_ts: int | None, session_num: int, session_time_minutes: int) -> str:
|
def render_history(prev_session: list[Any], current_ts: int | None, session_num: int, session_time_minutes: int) -> str:
|
||||||
usable: list[dict[str, Any]] = []
|
usable: list[dict[str, Any]] = []
|
||||||
for item in prev_session[-session_num:]:
|
for item in prev_session[-session_num:]:
|
||||||
|
|||||||
@@ -242,6 +242,7 @@ canonical records 落盘必须使用 `export_dataset_records.py`,默认格式
|
|||||||
- 评测数据:执行 `export_planning_eval_csv.py`,输出 `eval_planning.csv`,字段为 `request_id,newPrompt,query,类别真实标签,code标签,complex`。
|
- 评测数据:执行 `export_planning_eval_csv.py`,输出 `eval_planning.csv`,字段为 `request_id,newPrompt,query,类别真实标签,code标签,complex`。
|
||||||
- 训练 `output` 和流转表格 `function` 列都会自动组合为两行:第一行 `complex=true/false`,第二行原监督标签;评测 `complex` 列直接来自 canonical record 的 `dimensions.complex`,按评测表习惯输出 `TRUE/FALSE`。
|
- 训练 `output` 和流转表格 `function` 列都会自动组合为两行:第一行 `complex=true/false`,第二行原监督标签;评测 `complex` 列直接来自 canonical record 的 `dimensions.complex`,按评测表习惯输出 `TRUE/FALSE`。
|
||||||
- 训练 `instruction` 和评测 `newPrompt` 复用相同 prompt 主体:`[知识注入]`、`[系统状态]`、`[对话历史]`、`[当前query]`、`[function]`;评测 `newPrompt` 还会额外包上 `<|im_start|>system`、`<|im_start|>user`、`<|im_start|>assistant` chat template。
|
- 训练 `instruction` 和评测 `newPrompt` 复用相同 prompt 主体:`[知识注入]`、`[系统状态]`、`[对话历史]`、`[当前query]`、`[function]`;评测 `newPrompt` 还会额外包上 `<|im_start|>system`、`<|im_start|>user`、`<|im_start|>assistant` chat template。
|
||||||
|
- prompt 格式必须由转换脚本生成并保持稳定:`[知识注入]` 固定为多行 JSON 块,当前 query 固定写成 `用户: <query>`,`[function]` 后必须保留换行;不要在 `[function]` 下追加 `def Agent(...)` / `def Skill(...)` 等签名说明。
|
||||||
- 对话历史默认最多取 5 轮,且相邻轮间隔不超过 5 分钟;`context` 默认注入 `location` 和 `rag` 两个字段。
|
- 对话历史默认最多取 5 轮,且相邻轮间隔不超过 5 分钟;`context` 默认注入 `location` 和 `rag` 两个字段。
|
||||||
|
|
||||||
当前 canonical record v1 工作格式:
|
当前 canonical record v1 工作格式:
|
||||||
|
|||||||
@@ -68,3 +68,10 @@ canonical record 是产品数据生成链路的中间元数据格式。它不是
|
|||||||
- `eval_planning.csv`:字段为 `request_id,newPrompt,query,类别真实标签,code标签,complex`,由 `export_planning_eval_csv.py` 生成;`newPrompt` 会包上 `<|im_start|>system/user/assistant` chat template;`complex` 列来自 `dimensions.complex`,按评测表习惯输出 `TRUE/FALSE`,不是固定默认值。
|
- `eval_planning.csv`:字段为 `request_id,newPrompt,query,类别真实标签,code标签,complex`,由 `export_planning_eval_csv.py` 生成;`newPrompt` 会包上 `<|im_start|>system/user/assistant` chat template;`complex` 列来自 `dimensions.complex`,按评测表习惯输出 `TRUE/FALSE`,不是固定默认值。
|
||||||
|
|
||||||
`instruction` 和 `newPrompt` 使用同一套 prompt 主体,默认包含 `[知识注入]`、`[系统状态]`、`[对话历史]`、`[当前query]`、`[function]`。历史轮次默认最多取 5 轮,且相邻时间间隔不超过 5 分钟。
|
`instruction` 和 `newPrompt` 使用同一套 prompt 主体,默认包含 `[知识注入]`、`[系统状态]`、`[对话历史]`、`[当前query]`、`[function]`。历史轮次默认最多取 5 轮,且相邻时间间隔不超过 5 分钟。
|
||||||
|
|
||||||
|
格式约束:
|
||||||
|
|
||||||
|
- `[知识注入]` 必须是多行 JSON 块,默认只输出 `location` 和 `rag` 两个字段。
|
||||||
|
- `[当前query]` 下一行必须带 `用户:` 前缀。
|
||||||
|
- `[function]` 后必须保留换行;评测 `newPrompt` 中随后才是 `<|im_end|>`。
|
||||||
|
- 不要在 `[function]` 下追加 `def Agent(...)`、`def Skill(...)` 等函数签名说明。
|
||||||
|
|||||||
@@ -651,12 +651,15 @@ def build_planning_prompt(
|
|||||||
) -> str:
|
) -> str:
|
||||||
"""把训练 instruction 包成评测侧使用的 chat template。"""
|
"""把训练 instruction 包成评测侧使用的 chat template。"""
|
||||||
|
|
||||||
instruction = build_training_instruction(
|
instruction = prompt_section_with_one_trailing_newline(
|
||||||
|
build_training_instruction(
|
||||||
record,
|
record,
|
||||||
session_num=session_num,
|
session_num=session_num,
|
||||||
session_time_minutes=session_time_minutes,
|
session_time_minutes=session_time_minutes,
|
||||||
context_fields=context_fields,
|
context_fields=context_fields,
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
system_prompt = prompt_section_without_trailing_newline(system_prompt)
|
||||||
return (
|
return (
|
||||||
f"<|im_start|>system\n{system_prompt}<|im_end|>\n"
|
f"<|im_start|>system\n{system_prompt}<|im_end|>\n"
|
||||||
f"<|im_start|>user\n{instruction}<|im_end|>\n"
|
f"<|im_start|>user\n{instruction}<|im_end|>\n"
|
||||||
@@ -664,6 +667,14 @@ def build_planning_prompt(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def prompt_section_without_trailing_newline(text: str) -> str:
|
||||||
|
return str(text or "").rstrip("\n")
|
||||||
|
|
||||||
|
|
||||||
|
def prompt_section_with_one_trailing_newline(text: str) -> str:
|
||||||
|
return prompt_section_without_trailing_newline(text) + "\n"
|
||||||
|
|
||||||
|
|
||||||
def training_jsonl_line(
|
def training_jsonl_line(
|
||||||
record: dict[str, Any],
|
record: dict[str, Any],
|
||||||
*,
|
*,
|
||||||
@@ -673,7 +684,7 @@ def training_jsonl_line(
|
|||||||
system_prompt: str,
|
system_prompt: str,
|
||||||
) -> str:
|
) -> str:
|
||||||
payload = {
|
payload = {
|
||||||
"system": system_prompt,
|
"system": prompt_section_without_trailing_newline(system_prompt),
|
||||||
"instruction": build_training_instruction(
|
"instruction": build_training_instruction(
|
||||||
record,
|
record,
|
||||||
session_num=session_num,
|
session_num=session_num,
|
||||||
|
|||||||
@@ -732,12 +732,15 @@ def build_planning_prompt(
|
|||||||
) -> str:
|
) -> str:
|
||||||
"""把训练 instruction 包成评测侧使用的 chat template。"""
|
"""把训练 instruction 包成评测侧使用的 chat template。"""
|
||||||
|
|
||||||
instruction = build_training_instruction(
|
instruction = _prompt_section_with_one_trailing_newline(
|
||||||
|
build_training_instruction(
|
||||||
record,
|
record,
|
||||||
session_num=session_num,
|
session_num=session_num,
|
||||||
session_time_minutes=session_time_minutes,
|
session_time_minutes=session_time_minutes,
|
||||||
context_fields=context_fields,
|
context_fields=context_fields,
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
system_prompt = _prompt_section_without_trailing_newline(system_prompt)
|
||||||
return (
|
return (
|
||||||
f'<|im_start|>system\n{system_prompt}<|im_end|>\n'
|
f'<|im_start|>system\n{system_prompt}<|im_end|>\n'
|
||||||
f'<|im_start|>user\n{instruction}<|im_end|>\n'
|
f'<|im_start|>user\n{instruction}<|im_end|>\n'
|
||||||
@@ -745,6 +748,14 @@ def build_planning_prompt(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _prompt_section_without_trailing_newline(text: str) -> str:
|
||||||
|
return str(text or '').rstrip('\n')
|
||||||
|
|
||||||
|
|
||||||
|
def _prompt_section_with_one_trailing_newline(text: str) -> str:
|
||||||
|
return _prompt_section_without_trailing_newline(text) + '\n'
|
||||||
|
|
||||||
|
|
||||||
def _training_jsonl_line(
|
def _training_jsonl_line(
|
||||||
record: dict[str, Any],
|
record: dict[str, Any],
|
||||||
*,
|
*,
|
||||||
@@ -754,7 +765,7 @@ def _training_jsonl_line(
|
|||||||
system_prompt: str,
|
system_prompt: str,
|
||||||
) -> str:
|
) -> str:
|
||||||
payload = {
|
payload = {
|
||||||
'system': system_prompt,
|
'system': _prompt_section_without_trailing_newline(system_prompt),
|
||||||
'instruction': build_training_instruction(
|
'instruction': build_training_instruction(
|
||||||
record,
|
record,
|
||||||
session_num=session_num,
|
session_num=session_num,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from pathlib import Path
|
|||||||
from src.agent_tools import build_tool_context, default_tool_registry, execute_tool
|
from src.agent_tools import build_tool_context, default_tool_registry, execute_tool
|
||||||
from src.agent_types import AgentRuntimeConfig
|
from src.agent_types import AgentRuntimeConfig
|
||||||
from src.data_agent_records import (
|
from src.data_agent_records import (
|
||||||
|
build_planning_prompt,
|
||||||
confirm_generation_goal,
|
confirm_generation_goal,
|
||||||
confirm_generation_plan,
|
confirm_generation_plan,
|
||||||
export_dataset_records,
|
export_dataset_records,
|
||||||
@@ -262,6 +263,47 @@ target: Agent(tag='地图导航')
|
|||||||
self.assertIn('[function]', rows[0]['newPrompt'])
|
self.assertIn('[function]', rows[0]['newPrompt'])
|
||||||
self.assertTrue(rows[0]['newPrompt'].endswith('<|im_start|>assistant\n'))
|
self.assertTrue(rows[0]['newPrompt'].endswith('<|im_start|>assistant\n'))
|
||||||
|
|
||||||
|
def test_build_planning_prompt_matches_standard_chat_template(self) -> None:
|
||||||
|
records = normalize_dataset_draft(
|
||||||
|
'''
|
||||||
|
# dataset_label: 地图导航边界数据
|
||||||
|
### case: 多轮顺路停车场
|
||||||
|
用户: 帮我查一下最近的停车场
|
||||||
|
小爱: 最近的停车场离你27米
|
||||||
|
用户: 帮我找一个最顺路的停车场
|
||||||
|
target: Agent(tag='地图导航')
|
||||||
|
'''.strip(),
|
||||||
|
batch_id='demo',
|
||||||
|
base_timestamp=1_755_567_930_500,
|
||||||
|
timestamp_step_ms=60_000,
|
||||||
|
)['records']
|
||||||
|
records[0]['context'] = {'location': '', 'rag': ''}
|
||||||
|
|
||||||
|
prompt = build_planning_prompt(records[0], system_prompt='你是小爱同学,中文智能语音助手。\n')
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
prompt,
|
||||||
|
'<|im_start|>system\n'
|
||||||
|
'你是小爱同学,中文智能语音助手。<|im_end|>\n'
|
||||||
|
'<|im_start|>user\n'
|
||||||
|
'请参考用户的[当前query]、[对话历史]、[知识注入]、[系统状态]识别出[当前query]的[function]结果,[function]是python的code形式。\n'
|
||||||
|
'[知识注入]\n'
|
||||||
|
'{\n'
|
||||||
|
'"location": "",\n'
|
||||||
|
'"rag": ""\n'
|
||||||
|
'}\n'
|
||||||
|
'[系统状态]\n'
|
||||||
|
'{}\n'
|
||||||
|
'[对话历史]\n'
|
||||||
|
'用户: 帮我查一下最近的停车场\n'
|
||||||
|
'小爱: 最近的停车场离你27米\n'
|
||||||
|
'[当前query]\n'
|
||||||
|
'用户: 帮我找一个最顺路的停车场\n'
|
||||||
|
'[function]\n'
|
||||||
|
'<|im_end|>\n'
|
||||||
|
'<|im_start|>assistant\n',
|
||||||
|
)
|
||||||
|
|
||||||
def test_export_planning_eval_csv_writes_file(self) -> None:
|
def test_export_planning_eval_csv_writes_file(self) -> None:
|
||||||
records = normalize_dataset_draft(
|
records = normalize_dataset_draft(
|
||||||
'''
|
'''
|
||||||
|
|||||||
Reference in New Issue
Block a user