Files
Maven/internal/router/semantic/transform.go
T
claude b481792c02 router/semantic: contrastive safety transforms and split-by-family (slice 12)
Six deterministic transforms (negation, question, reported speech,
quotation, hypothetical, capability question) applied to action-route
seeds. Each transform determines the expected class explicitly — no
model guessing labels. SplitByFamily uses hash-based bucketing to keep
paraphrases in the same split.
2026-09-07 01:42:27 +04:00

227 lines
6.8 KiB
Go

package semantic
import "fmt"
// ContrastivePair — one base utterance and a derived transformation that
// should land in a different (or same) coarse route. The transform tag
// identifies the operation so the eval can report which transformations are
// easy vs hard.
type ContrastivePair struct {
BaseID string `json:"base_id"`
BaseText string `json:"base_text"`
BaseRoute SemanticRoute `json:"base_route"`
Transform string `json:"transform"`
Text string `json:"text"`
Route SemanticRoute `json:"route"`
}
// ContrastiveTransform — a function that takes a base utterance and returns
// a list of (text, expected_route) pairs. The mapping is deterministic and
// does not consult any model.
type ContrastiveTransform struct {
Name string
Fn func(baseID, baseText string, baseRoute SemanticRoute) []ContrastivePair
}
// StandardTransforms is the ordered set of contrastive safety transformations
// applied to the action-route seeds. Each base utterance gets all transforms;
// the expected route depends on the transformation semantics and the current
// routing contract.
var StandardTransforms = []ContrastiveTransform{
{Name: "negation", Fn: negationTransform},
{Name: "question", Fn: questionTransform},
{Name: "reported_speech", Fn: reportedSpeechTransform},
{Name: "quotation", Fn: quotationTransform},
{Name: "hypothetical", Fn: hypotheticalTransform},
{Name: "capability_question", Fn: capabilityQuestionTransform},
}
// negationTransform — turns an imperative into a negated one. A negated
// command is not an executable action; current semantics route it to
// uncertain.
//
// "выключи свет" → "не выключай свет" → uncertain
func negationTransform(baseID, baseText string, baseRoute SemanticRoute) []ContrastivePair {
// Only transform action-route examples: other routes are unaffected.
if baseRoute != RouteAction {
return nil
}
ruNegations := []struct{ prefix, suffix string }{
{"не ", ""},
{"ни ", ""},
}
var pairs []ContrastivePair
for _, n := range ruNegations {
text := n.prefix + baseText + n.suffix
pairs = append(pairs, ContrastivePair{
BaseID: baseID,
BaseText: baseText,
BaseRoute: baseRoute,
Transform: "negation",
Text: text,
Route: RouteUncertain,
})
}
return pairs
}
// questionTransform — turns a statement into a question. A question about an
// action is not the action itself; it becomes knowledge.
//
// "выключи свет" → "ты выключила свет?" → knowledge
func questionTransform(baseID, baseText string, baseRoute SemanticRoute) []ContrastivePair {
if baseRoute != RouteAction {
return nil
}
// Russian question forms for actions
suffixes := []string{
"?", // direct question
", верно?", // tag question
}
var pairs []ContrastivePair
for _, s := range suffixes {
text := baseText + s
pairs = append(pairs, ContrastivePair{
BaseID: baseID,
BaseText: baseText,
BaseRoute: baseRoute,
Transform: "question",
Text: text,
Route: RouteKnowledge,
})
}
return pairs
}
// reportedSpeechTransform — wraps the utterance in reported speech. An action
// reported as speech is not an action.
//
// "выключи свет" → "он сказал выключи свет" → uncertain
func reportedSpeechTransform(baseID, baseText string, baseRoute SemanticRoute) []ContrastivePair {
if baseRoute != RouteAction {
return nil
}
prefixes := []string{
"он сказал: ",
"она сказала: ",
"он сказал «",
}
suffixes := []string{
"",
"",
"»",
}
var pairs []ContrastivePair
for i, p := range prefixes {
text := p + baseText + suffixes[i]
pairs = append(pairs, ContrastivePair{
BaseID: baseID,
BaseText: baseText,
BaseRoute: baseRoute,
Transform: "reported_speech",
Text: text,
Route: RouteUncertain,
})
}
return pairs
}
// quotationTransform — mentions the utterance as a phrase/quote, not as a
// command. Quoting an action is not doing it.
//
// "выключи свет" → "фраза «выключи свет»" → uncertain
func quotationTransform(baseID, baseText string, baseRoute SemanticRoute) []ContrastivePair {
if baseRoute != RouteAction {
return nil
}
patterns := []struct{ pre, post string }{
{"фраза «", "»"},
{"«", "»"},
{"цитата: \"", "\""},
}
var pairs []ContrastivePair
for _, p := range patterns {
text := p.pre + baseText + p.post
pairs = append(pairs, ContrastivePair{
BaseID: baseID,
BaseText: baseText,
BaseRoute: baseRoute,
Transform: "quotation",
Text: text,
Route: RouteUncertain,
})
}
return pairs
}
// hypotheticalTransform — puts the action in a hypothetical frame. An
// "if..." clause is not an executable command.
//
// "выключи свет" → "если выключить свет..." → uncertain
func hypotheticalTransform(baseID, baseText string, baseRoute SemanticRoute) []ContrastivePair {
if baseRoute != RouteAction {
return nil
}
frames := []struct{ pre, post string }{
{"если ", "..."},
{"когда ", ", будет проще"},
{"если бы я сказал: ", ", что бы ты сделала?"},
}
var pairs []ContrastivePair
for _, f := range frames {
text := f.pre + baseText + f.post
pairs = append(pairs, ContrastivePair{
BaseID: baseID,
BaseText: baseText,
BaseRoute: baseRoute,
Transform: "hypothetical",
Text: text,
Route: RouteUncertain,
})
}
return pairs
}
// capabilityQuestionTransform — asks whether the system CAN do the action.
// A capability question is knowledge, not a direct executable command.
//
// "выключи свет" → "ты можешь выключить свет?" → knowledge
func capabilityQuestionTransform(baseID, baseText string, baseRoute SemanticRoute) []ContrastivePair {
if baseRoute != RouteAction {
return nil
}
templates := []string{
"ты можешь %s?",
"умеешь ли %s?",
"способен ли ты %s?",
}
// Extract the verb phrase for templates that need infinitive.
// For Russian, we use the base text as-is since the template
// handles the grammar.
var pairs []ContrastivePair
for _, t := range templates {
text := fmt.Sprintf(t, baseText)
pairs = append(pairs, ContrastivePair{
BaseID: baseID,
BaseText: baseText,
BaseRoute: baseRoute,
Transform: "capability_question",
Text: text,
Route: RouteKnowledge,
})
}
return pairs
}
// GenerateContrastivePairs applies all standard transforms to a slice of
// base examples and returns the full set of contrastive pairs.
func GenerateContrastivePairs(bases []RouteExample) []ContrastivePair {
var all []ContrastivePair
for _, b := range bases {
for _, t := range StandardTransforms {
all = append(all, t.Fn(b.SourceID, b.Text, b.Route)...)
}
}
return all
}