Skip to content

Commit 5ceaec0

Browse files
authored
fix: streaming e2e failures (vllm-project#1640)
Signed-off-by: xunzhuo <xunzhuo@vllm-semantic-router.ai>
1 parent c9816e0 commit 5ceaec0

File tree

2 files changed

+108
-2
lines changed

2 files changed

+108
-2
lines changed

src/semantic-router/pkg/decision/engine.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,13 +239,16 @@ func signalConfidence(confidences map[string]float64, signalType string, name st
239239
return 1.0
240240
}
241241

242-
// evalAND returns true only when every child matches; confidence is the average.
242+
// evalAND returns true only when every child matches.
243+
// An empty conjunction acts as a catch-all/default route with zero confidence,
244+
// so it can serve as a fallback without outranking signal-backed decisions when
245+
// confidence-based selection is enabled.
243246
func (e *DecisionEngine) evalAND(
244247
children []config.RuleNode,
245248
signals *SignalMatches,
246249
) (matched bool, confidence float64, matchedRules []string) {
247250
if len(children) == 0 {
248-
return false, 0, nil
251+
return true, 0, nil
249252
}
250253
totalConf := 0.0
251254
for _, child := range children {
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package decision
2+
3+
import (
4+
"testing"
5+
6+
"github.com/vllm-project/semantic-router/src/semantic-router/pkg/config"
7+
)
8+
9+
func TestDecisionEngine_EmptyANDActsAsCatchAll(t *testing.T) {
10+
engine := NewDecisionEngine(
11+
nil,
12+
nil,
13+
nil,
14+
[]config.Decision{
15+
{
16+
Name: "default-route",
17+
Priority: 10,
18+
Rules: config.RuleCombination{
19+
Operator: "AND",
20+
Conditions: []config.RuleCondition{},
21+
},
22+
},
23+
},
24+
"priority",
25+
)
26+
27+
result, err := engine.EvaluateDecisionsWithSignals(&SignalMatches{})
28+
if err != nil {
29+
t.Fatalf("Unexpected error: %v", err)
30+
}
31+
if result == nil {
32+
t.Fatal("Expected result but got nil")
33+
}
34+
if result.Decision.Name != "default-route" {
35+
t.Fatalf("Expected default-route, got %s", result.Decision.Name)
36+
}
37+
if result.Confidence != 0.0 {
38+
t.Fatalf("Expected zero confidence for catch-all route, got %f", result.Confidence)
39+
}
40+
}
41+
42+
func TestDecisionEngine_EmptyANDActsAsFallbackInConfidenceMode(t *testing.T) {
43+
engine := NewDecisionEngine(
44+
nil,
45+
nil,
46+
nil,
47+
[]config.Decision{
48+
{
49+
Name: "specific-route",
50+
Priority: 200,
51+
Rules: config.RuleCombination{
52+
Operator: "AND",
53+
Conditions: []config.RuleCondition{
54+
{Type: "keyword", Name: "urgent"},
55+
},
56+
},
57+
},
58+
{
59+
Name: "default-route",
60+
Priority: 100,
61+
Rules: config.RuleCombination{
62+
Operator: "AND",
63+
Conditions: []config.RuleCondition{},
64+
},
65+
},
66+
},
67+
"confidence",
68+
)
69+
70+
t.Run("signal-backed route outranks catch-all", func(t *testing.T) {
71+
result, err := engine.EvaluateDecisionsWithSignals(&SignalMatches{
72+
KeywordRules: []string{"urgent"},
73+
SignalConfidences: map[string]float64{
74+
"keyword:urgent": 0.72,
75+
},
76+
})
77+
if err != nil {
78+
t.Fatalf("Unexpected error: %v", err)
79+
}
80+
if result == nil {
81+
t.Fatal("Expected result but got nil")
82+
}
83+
if result.Decision.Name != "specific-route" {
84+
t.Fatalf("Expected specific-route, got %s", result.Decision.Name)
85+
}
86+
})
87+
88+
t.Run("catch-all still matches without signals", func(t *testing.T) {
89+
result, err := engine.EvaluateDecisionsWithSignals(&SignalMatches{})
90+
if err != nil {
91+
t.Fatalf("Unexpected error: %v", err)
92+
}
93+
if result == nil {
94+
t.Fatal("Expected result but got nil")
95+
}
96+
if result.Decision.Name != "default-route" {
97+
t.Fatalf("Expected default-route, got %s", result.Decision.Name)
98+
}
99+
if result.Confidence != 0.0 {
100+
t.Fatalf("Expected zero confidence for catch-all route, got %f", result.Confidence)
101+
}
102+
})
103+
}

0 commit comments

Comments
 (0)