feat(prompt): decouple prompt-facing choice ids from canonical catego… - #8
Merged
Conversation
added 3 commits
August 19, 2026 13:32
…ry_id Prompt-facing identity is now a compact choice protocol, never the canonical category_id: - New PromptChoiceRegistry: deterministic choice ids "1".."N" following LeafRegistry.categories order, plus shortest unique path suffix display names (duplicate leaf names are parent-qualified until unique). Strict PromptChoiceError on unknown ids / wrong counts / ambiguous names; no fuzzy fallback. - Stage 1 catalog renders [choice_id, display_name] pairs; the model answers with global choice ids. Stage 2 renders the 5 candidates with local bundle ids "1".."5"; the model answers with the local id. - evaluate_stage1_choices / evaluate_stage2_choices decode choice ids back to canonical category_id BEFORE delegating to the existing canonical evaluate_stage1/2 (which consume the unified parser's check_stage1/2_output shared with the RL reward adapter). No check logic is duplicated; reward/evaluation semantics stay canonical. - SFT exporter/validator keep ground_truth and candidates columns as canonical category_id; only messages speak choice ids. The RL exporter reuses the same prompt builders, so RL prompts now speak choice ids too while reward_model.ground_truth and extra_info.candidates stay canonical; the RL validator's stage-1 catalog-coverage check was adapted to the [choice_id, display_name] format (canonical ids never leak into prompts). canonical category_id, registry universe and ground-truth semantics are unchanged (no data/artifacts/corpus modifications, no token stats recomputation, no retraining, no RL reward integration). SFT/RL parquet re-export and prompt token stats are deferred to the next stage.
…racts
The Stage 1 contract showcased {"candidates":["1","2","3","4","5"]} and the
Stage 2 contract showcased {"answer":"1"} as example shapes. These concrete
legal answers create position bias in the model. Replace them with
position-agnostic descriptions:
- Stage 1: "Return exactly one JSON object with key 'candidates'. The value
must contain exactly five unique choice ids from the catalog."
- Stage 2: "Return exactly one JSON object with key 'answer'. Its value must
be one of the five candidate ids '1' through '5'."
Tests updated to assert the new wording and to guard against future
reintroduction of real candidate ids in the system contracts.
With local Stage 2 ids, the old GT-first fixture made every Stage 2 gold
{"answer":"1"}. Keep the baseline no-hard-negative policy but permute the
bundle deterministically from the stable source_id:
- build_candidates(ground_truth, registry, *, source_id) in training.common
(shared by SFT and RL): GT + first four registry negatives, then a
deterministic Fisher-Yates shuffle keyed by a sha256 digest of source_id.
No runtime randomness (hash() is per-process randomized and never used).
- Same source_id always yields the same bundle (reproducible across runs and
exporters); GT always present, exactly 5 unique, GT not fixed at position 1.
- Stage 1 and Stage 2 of a sample share the same permuted bundle; the SFT
validator re-derives it from the row source_id.
Tests: same-id determinism, id-varied orderings, GT membership, 5-unique,
GT position spread (all five positions appear over a synthetic id set), and
the SFT Stage 2 assistant answers are no longer all {"answer":"1"}.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Decouple the canonical
category_idfrom the model-visible prompt/action identity.Canonical
category_id(e.g.finance:业务.账户信息.基本信息) is needed for registry / corpus lookup, canonical ground truth, evaluation/reward and data joins — but it should not appear in prompts or be generated by the LLM. This PR introduces a prompt-facing choice protocol while keeping the canonical contract completely unchanged.Design
Boundary:
choice_id, internal boundary:category_idChanges
New:
src/agent/task/prompt_choices.pyPromptChoice(choice_id/category_id/display_name)PromptChoiceRegistry: bidirectional mapping, full registry coverage, unique choice ids, deterministic ("1".."N"by registry order),encode/decode_candidates基本信息→账户信息 / 基本信息,合约协议 / 基本信息,营销服务 / 基本信息)encode/decode_stage2_answer: positional local ids1..5PromptChoiceErrorfor strict failures — no fuzzy/name fallbackPrompts:
src/agent/task/prompts.py[choice_id, display_name]pairs; system contract requires 5 unique choice ids, forbids canonical ids"id":"1".."5"+ display_name + corpus description/examplesstage1_answer/stage2_answeremit choice idsEvaluation:
src/agent/evaluation/classification.pyevaluate_stage1_choices/evaluate_stage2_choicesdecode choice ids before delegating to the unchanged canonicalevaluate_stage1/2(which consume the unified parsercheck_stage1/2_output, shared with the RL reward adapter). No check logic duplicated; reward/evaluation stay canonical.SFT:
src/agent/training/sft/dataset.pyground_truth/candidatescolumns keep canonicalcategory_id; onlymessagesspeak choice idsRL:
src/agent/training/rl/dataset.pyreward_model.ground_truth/extra_info.candidatesstay canonical[choice_id, display_name]format; canonical ids must not leak into promptsExamples
Stage 1 (finance)
Stage 2
Invariants
category_idunchangedtarget.category_idis the only label)training.commonhelpers reused (no duplication ofcanonical_target/build_candidates/ corpus checks)Tests
python -m pytest -q→ 181 passed, 2 skippedverlmodule not installed locally (environment, unrelated)Deferred