Fix joining active inference
This commit is contained in:
@@ -155,6 +155,7 @@ type EventType string
|
||||
const (
|
||||
EventAssistantUpdate EventType = "assistant_update"
|
||||
EventDone EventType = "done"
|
||||
EventInferenceActive EventType = "inference_active"
|
||||
EventMessageDeleted EventType = "message_deleted"
|
||||
EventMessageSaved EventType = "message_saved"
|
||||
)
|
||||
@@ -197,6 +198,22 @@ type Done struct {
|
||||
|
||||
func (Done) Type() EventType { return EventDone }
|
||||
|
||||
// InferenceActive reports that an [Inference] is reserved or in progress.
|
||||
// [Conversation.Subscribe] sends this event one time, after the cache diff
|
||||
// and before the live events of the run.
|
||||
//
|
||||
// Without this event, two cases look the same to the client:
|
||||
// - No run is active. The stream closes after the diff.
|
||||
// - A run is active, but it sends no events yet. For example, the model
|
||||
// has not sent its first token, or a slow tool is running.
|
||||
//
|
||||
// A client that did not start the run uses this event to show the stop
|
||||
// control. The client also uses this event to separate saved history
|
||||
// (before the event) from live events (after the event).
|
||||
type InferenceActive struct{}
|
||||
|
||||
func (InferenceActive) Type() EventType { return EventInferenceActive }
|
||||
|
||||
// Inference is one reserved inference call and its events.
|
||||
// [Conversation.Reserve] creates it, and [Inference.Run] runs it.
|
||||
// [Inference.Events] and [Conversation.Subscribe] use its events to send
|
||||
@@ -323,11 +340,11 @@ func (inf *Inference) follow(
|
||||
// 1. Cache diff: [MessageSaved] for each saved message missing from or
|
||||
// newer than manifest, followed by [MessageDeleted] for each ID in
|
||||
// manifest that is no longer saved.
|
||||
// 2. Active inference: if an [Inference] is reserved or running when
|
||||
// Subscribe loads the database, Subscribe streams its
|
||||
// [MessageSaved] and [AssistantUpdate] events, then ends with [Done].
|
||||
// If no inference is reserved or running, Subscribe sends no [Done]
|
||||
// event.
|
||||
// 2. Active inference: if an [Inference] is reserved or in progress when
|
||||
// this method reads the database, the stream sends [InferenceActive],
|
||||
// then the [MessageSaved] and [AssistantUpdate] events of the run, and
|
||||
// then [Done]. If no run is active, the stream closes after the diff and
|
||||
// does not send [InferenceActive] or [Done].
|
||||
//
|
||||
// The channel closes when delivery finishes or ctx is canceled. Canceling
|
||||
// ctx stops delivery to this subscriber; it does not stop an ongoing
|
||||
@@ -372,6 +389,9 @@ func (c *Conversation) Subscribe(
|
||||
if inf == nil {
|
||||
return
|
||||
}
|
||||
if !send(ctx, ch, InferenceActive{}) {
|
||||
return
|
||||
}
|
||||
// Skip events for messages that the initial diff already
|
||||
// sent. If the client's manifest already had a message from
|
||||
// this call, it is not in sent and will be re-sent here; the
|
||||
|
||||
@@ -1474,17 +1474,21 @@ func TestSubscribeInference(t *testing.T) {
|
||||
close(release)
|
||||
got := collectEvents(t, ch)
|
||||
|
||||
// The diff delivers the user message first.
|
||||
// The cache diff sends the user message first. Then, the active marker
|
||||
// shows that the live events of the run start.
|
||||
saved, ok := got[0].(MessageSaved)
|
||||
if !ok || saved.Message.Role != db.MessageRoleUser {
|
||||
t.Fatalf("first event = %v, want the user MessageSaved", got[0])
|
||||
}
|
||||
if _, ok := got[1].(InferenceActive); !ok {
|
||||
t.Fatalf("second event = %v, want InferenceActive", got[1])
|
||||
}
|
||||
var (
|
||||
chunkTexts []string
|
||||
chunkIDs []string
|
||||
asst *MessageSaved
|
||||
)
|
||||
for _, e := range got[1:] {
|
||||
for _, e := range got[2:] {
|
||||
switch e := e.(type) {
|
||||
case AssistantUpdate:
|
||||
if asst != nil {
|
||||
@@ -1589,6 +1593,10 @@ func TestSubscribeInferenceError(t *testing.T) {
|
||||
close(release)
|
||||
got := collectEvents(t, ch)
|
||||
|
||||
// The active marker comes after the user message from the cache diff.
|
||||
if _, ok := got[1].(InferenceActive); !ok {
|
||||
t.Fatalf("second event = %v, want InferenceActive", got[1])
|
||||
}
|
||||
done, ok := got[len(got)-1].(Done)
|
||||
if !ok {
|
||||
t.Fatalf("last event = %v, want Done", got[len(got)-1])
|
||||
@@ -1709,12 +1717,18 @@ func TestSubscribeToolResult(t *testing.T) {
|
||||
t.Errorf("event %d role = %q, want the user first", i, e.Message.Role)
|
||||
}
|
||||
}
|
||||
case InferenceActive:
|
||||
// This event separates the cache diff from the live events
|
||||
// of the run.
|
||||
case Done:
|
||||
if e.Error != "" {
|
||||
t.Errorf("Done.Error = %q, want empty", e.Error)
|
||||
}
|
||||
default:
|
||||
t.Errorf("event %d = %v, want MessageSaved or Done", i, e)
|
||||
t.Errorf(
|
||||
"event %d = %v, want MessageSaved, InferenceActive, or Done",
|
||||
i, e,
|
||||
)
|
||||
}
|
||||
}
|
||||
if aId == "" {
|
||||
|
||||
Reference in New Issue
Block a user