Keep observer composition storage proportional to selected callbacks
This commit is contained in:
@@ -140,7 +140,13 @@ statically. Do not add public allocation guarantees or exact-allocation tests.
|
||||
|
||||
## 3. Size Observer Storage For Selected Callbacks
|
||||
|
||||
**Priority:** P3. **Status:** For consideration. **Plan stage:** 9.
|
||||
**Priority:** P3. **Status:** Complete. **Plan stage:** 9.
|
||||
|
||||
**Completion:** Observer composition counts selected callbacks before allocating,
|
||||
sizes retained storage to that count, and handles zero/single selections without
|
||||
a fan-out closure. Sparse-selection ownership and duplicate-delivery tests pass.
|
||||
The complete offline race suite, maintained examples, vet, and diff checks passed
|
||||
with Go 1.27.3 after all remediations.
|
||||
|
||||
**Location:** [`Compose`](../../internal/observation/compose.go).
|
||||
|
||||
|
||||
@@ -2,15 +2,26 @@ package observation
|
||||
|
||||
// Compose copies and synchronously invokes non-nil observers in order.
|
||||
func Compose[T any, F ~func(T)](observers ...F) F {
|
||||
selected := make([]F, 0, len(observers))
|
||||
count := 0
|
||||
var single F
|
||||
for _, observer := range observers {
|
||||
if observer != nil {
|
||||
count++
|
||||
single = observer
|
||||
}
|
||||
}
|
||||
if count == 0 {
|
||||
return nil
|
||||
}
|
||||
if count == 1 {
|
||||
return single
|
||||
}
|
||||
selected := make([]F, 0, count)
|
||||
for _, observer := range observers {
|
||||
if observer != nil {
|
||||
selected = append(selected, observer)
|
||||
}
|
||||
}
|
||||
if len(selected) == 0 {
|
||||
return nil
|
||||
}
|
||||
return func(event T) {
|
||||
for _, observer := range selected {
|
||||
observer(event)
|
||||
|
||||
@@ -31,6 +31,31 @@ func TestComposeObserversCopiesAndOrders(t *testing.T) {
|
||||
promptkit.ComposeObservers(func(promptkit.ExecutionEvent) { panic("panic") }, second)(promptkit.ExecutionEvent{})
|
||||
}
|
||||
|
||||
func TestComposeObserversSparseSelection(t *testing.T) {
|
||||
for _, selected := range []int{0, 1, 2} {
|
||||
calls := 0
|
||||
observers := make([]promptkit.ExecutionObserver, 1024)
|
||||
callback := func(promptkit.ExecutionEvent) { calls++ }
|
||||
for index := 0; index < selected; index++ {
|
||||
observers[len(observers)-1-index] = callback
|
||||
}
|
||||
composed := promptkit.ComposeObservers(observers...)
|
||||
for index := range observers {
|
||||
observers[index] = func(promptkit.ExecutionEvent) { calls += 100 }
|
||||
}
|
||||
if selected == 0 {
|
||||
if composed != nil {
|
||||
t.Fatal("all-nil composition must be nil")
|
||||
}
|
||||
continue
|
||||
}
|
||||
composed(promptkit.ExecutionEvent{})
|
||||
if calls != selected {
|
||||
t.Fatalf("selected %d callbacks, got %d calls", selected, calls)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoordinateObserverCompositionAndNestedSelection(t *testing.T) {
|
||||
engine := newSimpleEngine(t, "question", promptkit.WithLLMClient(&fakeLLMClient{response: &promptkit.GenerateResponse{Content: "answer"}}))
|
||||
ctx := context.Background()
|
||||
|
||||
Reference in New Issue
Block a user