Expose conversation coordination through the engine
This commit is contained in:
73
coordinate.go
Normal file
73
coordinate.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package promptkit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go.maximumdirect.net/promptkit/internal/domain"
|
||||
"go.maximumdirect.net/promptkit/internal/usecase"
|
||||
)
|
||||
|
||||
// ConversationTurn is delivered before each requested continuation. Result is
|
||||
// nil on the initial callback; RemainingTurns counts additional runs allowed.
|
||||
type ConversationTurn struct {
|
||||
Exchange *Exchange
|
||||
Result *RunResult
|
||||
RemainingTurns int
|
||||
}
|
||||
|
||||
// CoordinateRequest selects a starting exchange, turn budget and callback.
|
||||
// Observer selects continuation runs only, independently of callback calls.
|
||||
type CoordinateRequest struct {
|
||||
Start *Exchange
|
||||
AdditionalTurns int
|
||||
Next func(context.Context, ConversationTurn) (*RunRequest, error)
|
||||
Observer ExecutionObserver
|
||||
}
|
||||
|
||||
// CoordinationOutcome distinguishes caller-directed stopping from exhaustion.
|
||||
type CoordinationOutcome string
|
||||
|
||||
const (
|
||||
// CoordinationStopped means the callback chose not to continue.
|
||||
CoordinationStopped CoordinationOutcome = "stopped"
|
||||
// CoordinationExhausted means another run was requested without budget.
|
||||
CoordinationExhausted CoordinationOutcome = "exhausted"
|
||||
)
|
||||
|
||||
// CoordinationResult contains the normal outcome or a response withheld by
|
||||
// cancellation before delivery to the callback.
|
||||
type CoordinationResult struct {
|
||||
Outcome CoordinationOutcome `json:"outcome,omitempty"`
|
||||
Undelivered *RunResult `json:"undelivered,omitempty"`
|
||||
}
|
||||
|
||||
// Coordinate advances one branch from Start using ordinary runs. Next is called
|
||||
// initially even for a zero budget and receives each completed result once.
|
||||
// A nil request stops; requesting a run without remaining budget exhausts it.
|
||||
// Callback and execution errors retain their identities. Cancellation observed
|
||||
// before delivery returns the single completed response as Undelivered;
|
||||
// cancellation after callback invocation does not replay that response.
|
||||
func (e *Engine) Coordinate(ctx context.Context, req CoordinateRequest) (CoordinationResult, error) {
|
||||
if e == nil || e.internal == nil {
|
||||
return CoordinationResult{}, fmt.Errorf("%w: engine is nil", ErrInvalidConfig)
|
||||
}
|
||||
if req.Start == nil || req.Start.internal == nil || req.Next == nil || req.AdditionalTurns < 0 {
|
||||
return CoordinationResult{}, fmt.Errorf("%w: invalid coordination request", ErrInvalidRequest)
|
||||
}
|
||||
outcome, err := usecase.Coordinate(ctx, req.Start.internal, req.AdditionalTurns,
|
||||
func(ctx context.Context, turn usecase.ConversationTurn[RunResult]) (*RunRequest, error) {
|
||||
return req.Next(ctx, ConversationTurn{Exchange: &Exchange{internal: turn.Exchange}, Result: turn.Result, RemainingTurns: turn.RemainingTurns})
|
||||
},
|
||||
func(ctx context.Context, current *domain.Exchange, selected *RunRequest) (*RunResult, *domain.Exchange, error) {
|
||||
request := *selected
|
||||
request.ContinueFrom = &Exchange{internal: current}
|
||||
request.Observer = ComposeObservers(request.Observer, req.Observer)
|
||||
result, runErr := e.Run(ctx, request)
|
||||
if runErr != nil {
|
||||
return nil, nil, runErr
|
||||
}
|
||||
return result, result.Exchange.internal, nil
|
||||
})
|
||||
return CoordinationResult{Outcome: CoordinationOutcome(outcome.Outcome), Undelivered: outcome.Undelivered}, err
|
||||
}
|
||||
Reference in New Issue
Block a user