Fix product data eval export format
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from product_data_portable import (
|
||||
DEFAULT_SYSTEM_PROMPT,
|
||||
default_planning_eval_output_path,
|
||||
emit_error,
|
||||
emit_success,
|
||||
@@ -20,6 +21,7 @@ def main() -> int:
|
||||
session_num=int(payload.get("session_num", 5)),
|
||||
session_time_minutes=int(payload.get("session_time_minutes", 5)),
|
||||
context_fields=payload.get("context_fields"),
|
||||
system_prompt=str(payload.get("system_prompt") or DEFAULT_SYSTEM_PROMPT),
|
||||
complex_default=bool(payload.get("complex_default", False)),
|
||||
require_validation_ok=bool(payload.get("require_validation_ok", True)),
|
||||
overwrite=bool(payload.get("overwrite", True)),
|
||||
|
||||
@@ -159,7 +159,7 @@ def parse_draft_text(draft_text: str) -> dict[str, Any]:
|
||||
continue
|
||||
|
||||
field_match = re.match(
|
||||
r"^(用户|小爱|target|notes|dataset_label|request_id|timestamp)\s*[::]\s*(.*)$",
|
||||
r"^(用户|小爱|target|complex|notes|dataset_label|request_id|timestamp)\s*[::]\s*(.*)$",
|
||||
line,
|
||||
re.I,
|
||||
)
|
||||
@@ -207,9 +207,14 @@ def case_to_record(
|
||||
if not current_query:
|
||||
raise ProductDataError(f"case {index} current 用户 line must be non-empty")
|
||||
|
||||
target = canonical_target(str(case.get("target") or "").strip())
|
||||
raw_target, prefixed_complex = split_complex_prefixed_target(str(case.get("target") or "").strip())
|
||||
target = canonical_target(raw_target)
|
||||
if not target:
|
||||
raise ProductDataError(f"case {index} target is required")
|
||||
complex_value = parse_complex(
|
||||
case.get("complex"),
|
||||
default=False if prefixed_complex is None else prefixed_complex,
|
||||
)
|
||||
|
||||
prev_session = build_prev_session(turns[:final_user_index], case_index=index)
|
||||
if len(prev_session) > 10:
|
||||
@@ -248,6 +253,9 @@ def case_to_record(
|
||||
"target": target,
|
||||
"target_type": target_type(target),
|
||||
},
|
||||
"dimensions": {
|
||||
"complex": complex_value,
|
||||
},
|
||||
"meta": {
|
||||
"case_name": str(case.get("case_name") or "").strip(),
|
||||
"notes": str(case.get("notes") or "").strip(),
|
||||
@@ -364,6 +372,14 @@ def validate_dataset_records(records: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
if label.get("target_type") not in {"agent", "function", "unknown"}:
|
||||
errors.append(issue(f"{prefix}.label.target_type", "label.target_type is invalid"))
|
||||
|
||||
dimensions = record.get("dimensions")
|
||||
if not isinstance(dimensions, dict):
|
||||
warnings.append(issue(f"{prefix}.dimensions", "dimensions.complex is missing; false will be used as fallback"))
|
||||
elif "complex" not in dimensions:
|
||||
warnings.append(issue(f"{prefix}.dimensions.complex", "complex is missing; false will be used as fallback"))
|
||||
elif not isinstance(dimensions.get("complex"), bool):
|
||||
errors.append(issue(f"{prefix}.dimensions.complex", "complex must be a boolean"))
|
||||
|
||||
return {
|
||||
"ok": not errors,
|
||||
"error_count": len(errors),
|
||||
@@ -500,6 +516,7 @@ def export_planning_eval_csv(
|
||||
session_num: int = 5,
|
||||
session_time_minutes: int = 5,
|
||||
context_fields: list[str] | None = None,
|
||||
system_prompt: str = DEFAULT_SYSTEM_PROMPT,
|
||||
complex_default: bool = False,
|
||||
require_validation_ok: bool = True,
|
||||
overwrite: bool = True,
|
||||
@@ -520,6 +537,7 @@ def export_planning_eval_csv(
|
||||
session_num=session_num,
|
||||
session_time_minutes=session_time_minutes,
|
||||
context_fields=context_fields,
|
||||
system_prompt=system_prompt,
|
||||
complex_default=complex_default,
|
||||
)
|
||||
path.write_text(content, encoding="utf-8-sig")
|
||||
@@ -547,6 +565,7 @@ def render_planning_eval_csv(
|
||||
session_num: int = 5,
|
||||
session_time_minutes: int = 5,
|
||||
context_fields: list[str] | None = None,
|
||||
system_prompt: str = DEFAULT_SYSTEM_PROMPT,
|
||||
complex_default: bool = False,
|
||||
) -> str:
|
||||
fields = normalize_context_fields(context_fields)
|
||||
@@ -560,21 +579,21 @@ def render_planning_eval_csv(
|
||||
for record in records:
|
||||
source = record.get("source") if isinstance(record.get("source"), dict) else {}
|
||||
turn = record.get("turn") if isinstance(record.get("turn"), dict) else {}
|
||||
label = record.get("label") if isinstance(record.get("label"), dict) else {}
|
||||
target = str(label.get("target") or "")
|
||||
target = record_target(record)
|
||||
writer.writerow(
|
||||
{
|
||||
"request_id": str(source.get("request_id") or ""),
|
||||
"newPrompt": build_training_instruction(
|
||||
"newPrompt": build_planning_prompt(
|
||||
record,
|
||||
session_num=session_num,
|
||||
session_time_minutes=session_time_minutes,
|
||||
context_fields=fields,
|
||||
system_prompt=system_prompt,
|
||||
),
|
||||
"query": str(turn.get("query") or ""),
|
||||
"类别真实标签": category_label_from_target(target),
|
||||
"code标签": target,
|
||||
"complex": "true" if complex_default else "false",
|
||||
"complex": eval_complex_literal(record_complex(record, default=complex_default)),
|
||||
}
|
||||
)
|
||||
return output.getvalue()
|
||||
@@ -621,6 +640,29 @@ def build_training_instruction(
|
||||
return instruction
|
||||
|
||||
|
||||
def build_planning_prompt(
|
||||
record: dict[str, Any],
|
||||
*,
|
||||
session_num: int = 5,
|
||||
session_time_minutes: int = 5,
|
||||
context_fields: list[str] | None = None,
|
||||
system_prompt: str = DEFAULT_SYSTEM_PROMPT,
|
||||
) -> str:
|
||||
"""把训练 instruction 包成评测侧使用的 chat template。"""
|
||||
|
||||
instruction = build_training_instruction(
|
||||
record,
|
||||
session_num=session_num,
|
||||
session_time_minutes=session_time_minutes,
|
||||
context_fields=context_fields,
|
||||
)
|
||||
return (
|
||||
f"<|im_start|>system\n{system_prompt}<|im_end|>\n"
|
||||
f"<|im_start|>user\n{instruction}<|im_end|>\n"
|
||||
"<|im_start|>assistant\n"
|
||||
)
|
||||
|
||||
|
||||
def training_jsonl_line(
|
||||
record: dict[str, Any],
|
||||
*,
|
||||
@@ -629,7 +671,6 @@ def training_jsonl_line(
|
||||
context_fields: list[str],
|
||||
system_prompt: str,
|
||||
) -> str:
|
||||
label = record.get("label") if isinstance(record.get("label"), dict) else {}
|
||||
payload = {
|
||||
"system": system_prompt,
|
||||
"instruction": build_training_instruction(
|
||||
@@ -638,7 +679,7 @@ def training_jsonl_line(
|
||||
session_time_minutes=session_time_minutes,
|
||||
context_fields=context_fields,
|
||||
),
|
||||
"output": str(label.get("target") or ""),
|
||||
"output": combined_function_label(record),
|
||||
}
|
||||
return json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
|
||||
|
||||
@@ -711,6 +752,7 @@ def optional_int_for_export(value: Any) -> int | None:
|
||||
|
||||
|
||||
def category_label_from_target(target: str) -> str:
|
||||
target = strip_complex_prefix(target)
|
||||
agent_match = re.match(r'''^Agent\s*\(\s*tag\s*=\s*["']([^"']+)["']\s*\)$''', target.strip())
|
||||
if agent_match:
|
||||
return agent_match.group(1)
|
||||
@@ -749,7 +791,7 @@ def dataset_record_table_row(record: dict[str, Any]) -> dict[str, str]:
|
||||
"context": json.dumps(context, ensure_ascii=False, separators=(",", ":")),
|
||||
"label": str(label.get("dataset_label") or ""),
|
||||
"是否迁移Function": "",
|
||||
"function": str(label.get("target") or ""),
|
||||
"function": combined_function_label(record),
|
||||
}
|
||||
|
||||
|
||||
@@ -808,7 +850,75 @@ def default_planning_eval_output_path(payload: dict[str, Any]) -> str:
|
||||
return str(Path("output") / "eval_planning.csv")
|
||||
|
||||
|
||||
def normalize_target_expression(target: str) -> str:
|
||||
"""把可能带 complex 前缀的标签表达式收敛为纯 target。"""
|
||||
|
||||
return canonical_target(strip_complex_prefix(target))
|
||||
|
||||
|
||||
def strip_complex_prefix(target: str) -> str:
|
||||
stripped_target, _complex_value = split_complex_prefixed_target(target)
|
||||
return stripped_target
|
||||
|
||||
|
||||
def split_complex_prefixed_target(target: str) -> tuple[str, bool | None]:
|
||||
lines = target.strip().splitlines()
|
||||
if not lines:
|
||||
return "", None
|
||||
first_line = lines[0].strip()
|
||||
match = re.fullmatch(r"complex\s*=\s*(.+)", first_line, flags=re.I)
|
||||
if not match:
|
||||
return target.strip(), None
|
||||
complex_value = parse_complex(match.group(1), default=False)
|
||||
return "\n".join(lines[1:]).strip(), complex_value
|
||||
|
||||
|
||||
def parse_complex(value: Any, *, default: bool) -> bool:
|
||||
if value is None or value == "":
|
||||
return default
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
text = str(value).strip().lower()
|
||||
if text in {"true", "1", "yes", "y", "是", "复杂", "complex"}:
|
||||
return True
|
||||
if text in {"false", "0", "no", "n", "否", "不复杂", "简单", "simple"}:
|
||||
return False
|
||||
raise ProductDataError("complex must be a boolean value such as true/false")
|
||||
|
||||
|
||||
def complex_literal(value: bool) -> str:
|
||||
return "true" if value else "false"
|
||||
|
||||
|
||||
def eval_complex_literal(value: bool) -> str:
|
||||
return "TRUE" if value else "FALSE"
|
||||
|
||||
|
||||
def record_target(record: dict[str, Any]) -> str:
|
||||
label = record.get("label") if isinstance(record.get("label"), dict) else {}
|
||||
return normalize_target_expression(str(label.get("target") or ""))
|
||||
|
||||
|
||||
def record_complex(record: dict[str, Any], *, default: bool = False) -> bool:
|
||||
dimensions = record.get("dimensions")
|
||||
if isinstance(dimensions, dict) and "complex" in dimensions:
|
||||
return parse_complex(dimensions.get("complex"), default=default)
|
||||
label = record.get("label") if isinstance(record.get("label"), dict) else {}
|
||||
if "complex" in label:
|
||||
return parse_complex(label.get("complex"), default=default)
|
||||
_target, prefixed_complex = split_complex_prefixed_target(str(label.get("target") or ""))
|
||||
if prefixed_complex is not None:
|
||||
return prefixed_complex
|
||||
return default
|
||||
|
||||
|
||||
def combined_function_label(record: dict[str, Any], *, default_complex: bool = False) -> str:
|
||||
target = record_target(record)
|
||||
return f"complex={complex_literal(record_complex(record, default=default_complex))}\n{target}"
|
||||
|
||||
|
||||
def target_type(target: str) -> str:
|
||||
target = strip_complex_prefix(target)
|
||||
if re.match(r"^Agent\s*\(\s*tag\s*=", target):
|
||||
return "agent"
|
||||
if target:
|
||||
@@ -817,6 +927,7 @@ def target_type(target: str) -> str:
|
||||
|
||||
|
||||
def canonical_target(target: str) -> str:
|
||||
target = strip_complex_prefix(target)
|
||||
agent_match = re.match(r'''^Agent\s*\(\s*tag\s*=\s*["']([^"']+)["']\s*\)$''', target)
|
||||
if agent_match:
|
||||
return f'Agent(tag="{agent_match.group(1)}")'
|
||||
|
||||
Reference in New Issue
Block a user