Include conversation ancestry in execution observations

This commit is contained in:
2026-11-14 01:20:06 +00:00
parent 04569e44df
commit e5c42de06d
6 changed files with 79 additions and 2 deletions

View File

@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"testing"
@@ -15,6 +16,72 @@ func observationEngine(t *testing.T, client promptkit.LLMClient) *promptkit.Engi
return newSimpleEngine(t, "hello", promptkit.WithLLMClient(client))
}
func TestContinuationObservationAncestry(t *testing.T) {
client := &fakeLLMClient{response: &promptkit.GenerateResponse{Content: "private answer"}}
engine := observationEngine(t, client)
ctx := context.Background()
initial, err := engine.Run(ctx, promptkit.RunRequest{PromptID: "prompt", CaptureExchange: true})
if err != nil {
t.Fatal(err)
}
var events []promptkit.ExecutionEvent
observer := func(event promptkit.ExecutionEvent) { events = append(events, event) }
req := promptkit.RunRequest{PromptID: "prompt", ContinueFrom: initial.Exchange, Observer: observer}
prepared, err := engine.PrepareExecution(ctx, req)
if err != nil || len(events) != 0 {
t.Fatalf("preparation events=%#v error=%v", events, err)
}
check := func(result *promptkit.RunResult, runErr error) {
t.Helper()
if runErr != nil || result == nil || len(events) != 3 {
t.Fatalf("result=%#v error=%v events=%#v", result, runErr, events)
}
for _, event := range events {
if event.ParentRunID != initial.RunID || event.RunID != result.RunID || strings.Contains(fmt.Sprintf("%+v", event), "private answer") {
t.Fatalf("event = %#v", event)
}
}
events = nil
}
result, err := engine.Run(ctx, req)
check(result, err)
result, err = engine.RunPrepared(ctx, prepared)
check(result, err)
client.err = errors.New("failure")
if result, err := engine.Run(ctx, req); result != nil || err == nil || len(events) != 3 {
t.Fatalf("failure result=%#v error=%v events=%#v", result, err, events)
}
for _, event := range events {
if event.ParentRunID != initial.RunID {
t.Fatalf("failure event = %#v", event)
}
}
}
func TestContinuationNestedRunHasIndependentAncestry(t *testing.T) {
engine := observationEngine(t, &fakeLLMClient{response: &promptkit.GenerateResponse{Content: "ok"}})
ctx := context.Background()
initial, err := engine.Run(ctx, promptkit.RunRequest{PromptID: "prompt", CaptureExchange: true})
if err != nil {
t.Fatal(err)
}
var nested []promptkit.ExecutionEvent
var nestedErr error
_, err = engine.Run(ctx, promptkit.RunRequest{PromptID: "prompt", ContinueFrom: initial.Exchange, Observer: func(event promptkit.ExecutionEvent) {
if event.Kind == promptkit.ExecutionFinished {
_, nestedErr = engine.Run(ctx, promptkit.RunRequest{PromptID: "prompt", Observer: func(value promptkit.ExecutionEvent) { nested = append(nested, value) }})
}
}})
if err != nil || nestedErr != nil || len(nested) == 0 {
t.Fatalf("errors=%v, %v; events=%#v", err, nestedErr, nested)
}
for _, event := range nested {
if event.ParentRunID != "" {
t.Fatalf("nested event = %#v", event)
}
}
}
func TestRunObservationCoversFacadeErrors(t *testing.T) {
for _, tc := range []struct {
name string