Prepare requests with captured conversation history
This commit is contained in:
@@ -60,6 +60,8 @@ type CacheControl struct {
|
||||
|
||||
// RunRequest represents a request to generate a single artifact.
|
||||
type RunRequest struct {
|
||||
CaptureExchange bool
|
||||
ContinueFrom *Exchange
|
||||
PromptID string
|
||||
ProfileID string
|
||||
SessionID string
|
||||
@@ -98,6 +100,7 @@ type RunResult struct {
|
||||
// PreparedRun contains pre-LLM execution state from the prepare/render phase.
|
||||
// It must never include resolved API key values, model output, or validation data.
|
||||
type PreparedRun struct {
|
||||
ParentRunID string
|
||||
PromptID string
|
||||
PromptHash string
|
||||
SelectedProfileID string
|
||||
|
||||
@@ -66,6 +66,7 @@ type ObservedTokenUsage struct {
|
||||
type ExecutionEvent struct {
|
||||
Kind ExecutionEventKind
|
||||
RunID string
|
||||
ParentRunID string
|
||||
Time time.Time
|
||||
PromptID string
|
||||
ProfileID string
|
||||
|
||||
@@ -79,6 +79,8 @@ type preparationState struct {
|
||||
type preparedOperation struct {
|
||||
run *domain.PreparedRun
|
||||
validation validate.PreparedValidation
|
||||
capture bool
|
||||
parent *domain.Exchange
|
||||
}
|
||||
|
||||
func NewRunner(
|
||||
@@ -399,7 +401,7 @@ func (r *Runner) completePreparation(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &preparedOperation{run: prepared, validation: validationPlan}, nil
|
||||
return &preparedOperation{run: prepared, validation: validationPlan, capture: req.CaptureExchange, parent: req.ContinueFrom}, nil
|
||||
}
|
||||
|
||||
func (r *Runner) prepareValidation(
|
||||
@@ -473,11 +475,15 @@ func (r *Runner) completePreparationWithStructuredOutput(
|
||||
effectivePrompt.SessionID = state.directSessionID
|
||||
}
|
||||
effectivePrompt.Messages = domain.ConcatRenderedMessages(renderedPrompt.Messages, req.AppendedMessages)
|
||||
if req.ContinueFrom != nil {
|
||||
effectivePrompt.Messages = domain.ConcatRenderedMessages(req.ContinueFrom.Messages(), effectivePrompt.Messages)
|
||||
}
|
||||
|
||||
end := time.Now().UTC()
|
||||
effectiveModel := state.effectiveModel
|
||||
effectiveModel.APIKey = ""
|
||||
return &domain.PreparedRun{
|
||||
ParentRunID: req.ContinueFrom.RunID(),
|
||||
PromptID: state.definition.ID,
|
||||
PromptHash: state.promptDefinitionHash,
|
||||
SelectedProfileID: state.selectedProfileID,
|
||||
|
||||
@@ -1310,6 +1310,39 @@ func TestRunnerComposesAppendedMessagesIntoEveryRenderedPrompt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunnerPreparesContinuationHistory(t *testing.T) {
|
||||
renderer := &fakeRenderer{rendered: &domain.RenderedPrompt{SessionID: "session", Messages: []domain.RenderedMessage{{Role: domain.RoleUser, Content: "new"}}}}
|
||||
client := &fakeLLM{resp: &domain.GenerateResponse{Content: "output"}}
|
||||
runner := NewRunner(&fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)}, &fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}}, defaultArtifactReader(), renderer, client, nil, nil)
|
||||
request := domain.RunRequest{PromptID: "p", ProfileID: "exec", Inputs: singleInputRef(), AppendedMessages: []domain.RenderedMessage{{Role: domain.RoleDeveloper, Content: "suffix"}}}
|
||||
plain, err := runner.Prepare(context.Background(), request)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
request.ContinueFrom = domain.CaptureExchange(nil, "parent", []domain.RenderedMessage{{Role: domain.RoleSystem, Content: "old", CacheControl: &domain.CacheControl{Type: domain.CacheControlEphemeral, TTL: "1h"}}}, "prior")
|
||||
prepared, err := runner.Prepare(context.Background(), request)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := []domain.RenderedMessage{{Role: domain.RoleSystem, Content: "old", CacheControl: &domain.CacheControl{Type: domain.CacheControlEphemeral, TTL: "1h"}}, {Role: domain.RoleAssistant, Content: "prior"}, {Role: domain.RoleUser, Content: "new"}, {Role: domain.RoleDeveloper, Content: "suffix"}}
|
||||
if !reflect.DeepEqual(prepared.Messages, want) || prepared.ParentRunID != "parent" || prepared.PromptHash != plain.PromptHash || prepared.RenderedPromptHash == plain.RenderedPromptHash {
|
||||
t.Fatalf("prepared = %#v, plain = %#v", prepared, plain)
|
||||
}
|
||||
if client.calls != 0 {
|
||||
t.Fatal("preparation called the model")
|
||||
}
|
||||
execution, err := runner.PrepareExecution(context.Background(), request)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if execution.Details().ParentRunID != "parent" || !reflect.DeepEqual(execution.Details().Messages, want) {
|
||||
t.Fatalf("prepared execution = %#v", execution.Details())
|
||||
}
|
||||
if client.calls != 0 {
|
||||
t.Fatal("prepared execution called the model")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunnerRunSuccessful(t *testing.T) {
|
||||
promptRepo := &fakePromptRepo{def: promptDef(domain.FormatMarkdown, domain.ValidationBasic, 0)}
|
||||
execRepo := &fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}}
|
||||
|
||||
Reference in New Issue
Block a user