Propagate structured repair requests

This commit is contained in:
2026-08-25 19:39:04 +00:00
parent ab9b743df6
commit e00cc45c6b
12 changed files with 154 additions and 134 deletions

View File

@@ -427,7 +427,7 @@ git diff --check
match the target semantics. match the target semantics.
- No PromptKit type crosses the LLM package boundary. - No PromptKit type crosses the LLM package boundary.
## Stage 6: Propagate Repair Policy Through Framework Requests ## Stage 6: Propagate Repair Policy Through Framework Requests
### Goal ### Goal

View File

@@ -136,11 +136,12 @@ func (set LLMInputSet) Clone() LLMInputSet {
} }
type ParseRequest struct { type ParseRequest struct {
SourceID string `json:"source_id,omitempty"` SourceID string `json:"source_id,omitempty"`
Path string `json:"path,omitempty"` Path string `json:"path,omitempty"`
Raw []byte `json:"-"` Raw []byte `json:"-"`
LLMProfile string `json:"llm_profile,omitempty"` LLMProfile string `json:"llm_profile,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"` StructuredOutputRepairAttempts *int `json:"structured_output_repair_attempts,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
} }
type InputAdapter interface { type InputAdapter interface {
@@ -149,12 +150,13 @@ type InputAdapter interface {
} }
type ChunkRequest struct { type ChunkRequest struct {
Source *source.SourceDocument `json:"-"` Source *source.SourceDocument `json:"-"`
SourceInput LLMInputMaterial `json:"source_input,omitempty"` SourceInput LLMInputMaterial `json:"source_input,omitempty"`
SessionID string `json:"session_id,omitempty"` SessionID string `json:"session_id,omitempty"`
References ReferenceSet `json:"references,omitempty"` References ReferenceSet `json:"references,omitempty"`
LLMProfile string `json:"llm_profile,omitempty"` LLMProfile string `json:"llm_profile,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"` StructuredOutputRepairAttempts *int `json:"structured_output_repair_attempts,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
} }
type ChunkPlanResult struct { type ChunkPlanResult struct {
@@ -292,14 +294,15 @@ type Warning struct {
} }
type OutputRequest struct { type OutputRequest struct {
Manifest artifacts.RunManifest `json:"manifest"` Manifest artifacts.RunManifest `json:"manifest"`
NormalizeOutputs []SerializedOutput `json:"normalize_outputs,omitempty"` NormalizeOutputs []SerializedOutput `json:"normalize_outputs,omitempty"`
Rejected []RejectedOutput `json:"rejected,omitempty"` Rejected []RejectedOutput `json:"rejected,omitempty"`
Warnings []Warning `json:"warnings,omitempty"` Warnings []Warning `json:"warnings,omitempty"`
LLMProfile string `json:"llm_profile,omitempty"` LLMProfile string `json:"llm_profile,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"` StructuredOutputRepairAttempts *int `json:"structured_output_repair_attempts,omitempty"`
ChunkMap *SerializedArtifact `json:"chunk_map,omitempty"` Metadata map[string]any `json:"metadata,omitempty"`
EvidenceContext *SerializedArtifact `json:"evidence_context,omitempty"` ChunkMap *SerializedArtifact `json:"chunk_map,omitempty"`
EvidenceContext *SerializedArtifact `json:"evidence_context,omitempty"`
} }
type OutputFile struct { type OutputFile struct {

View File

@@ -34,14 +34,15 @@ type NormalizeArtifact[T any] struct {
} }
type TypedExtractionRequest struct { type TypedExtractionRequest struct {
Source *source.SourceDocument Source *source.SourceDocument
Chunk *source.Chunk Chunk *source.Chunk
AmbientContext map[string]any AmbientContext map[string]any
SourceInput LLMInputMaterial SourceInput LLMInputMaterial
SessionID string SessionID string
References ReferenceSet References ReferenceSet
LLMProfile string LLMProfile string
Metadata map[string]any StructuredOutputRepairAttempts *int
Metadata map[string]any
} }
type TypedExtractionResult[T any] struct { type TypedExtractionResult[T any] struct {
@@ -56,14 +57,15 @@ type Extractor[T any] interface {
} }
type TypedMergeRequest[T any] struct { type TypedMergeRequest[T any] struct {
Source *source.SourceDocument Source *source.SourceDocument
LaneID string LaneID string
ExtractOutputs []ExtractArtifact[T] ExtractOutputs []ExtractArtifact[T]
SourceInput LLMInputMaterial SourceInput LLMInputMaterial
SessionID string SessionID string
References ReferenceSet References ReferenceSet
LLMProfile string LLMProfile string
Metadata map[string]any StructuredOutputRepairAttempts *int
Metadata map[string]any
} }
type TypedMergeResult[T any] struct { type TypedMergeResult[T any] struct {
@@ -77,14 +79,15 @@ type Merger[T any] interface {
} }
type TypedNormalizeRequest[T any] struct { type TypedNormalizeRequest[T any] struct {
Source *source.SourceDocument Source *source.SourceDocument
LaneID string LaneID string
MergeOutput MergeArtifact[T] MergeOutput MergeArtifact[T]
SourceInput LLMInputMaterial SourceInput LLMInputMaterial
SessionID string SessionID string
References ReferenceSet References ReferenceSet
LLMProfile string LLMProfile string
Metadata map[string]any StructuredOutputRepairAttempts *int
Metadata map[string]any
} }
type TypedNormalizeResult[T any] struct { type TypedNormalizeResult[T any] struct {
@@ -115,20 +118,21 @@ type Normalizer[T any] interface {
} }
type TypedValidationRequest[T any] struct { type TypedValidationRequest[T any] struct {
Stage string Stage string
LaneID string LaneID string
ModuleKey string ModuleKey string
Source *source.SourceDocument Source *source.SourceDocument
SourceID string SourceID string
SourceInput LLMInputMaterial SourceInput LLMInputMaterial
SessionID string SessionID string
References ReferenceSet References ReferenceSet
LLMProfile string LLMProfile string
Metadata map[string]any StructuredOutputRepairAttempts *int
Chunk *source.Chunk Metadata map[string]any
Chunks []source.Chunk Chunk *source.Chunk
Ref source.SourceRef Chunks []source.Chunk
Value T Ref source.SourceRef
Value T
} }
type TypedValidator[T any] interface { type TypedValidator[T any] interface {
@@ -138,15 +142,16 @@ type TypedValidator[T any] interface {
} }
type ChunkValidationRequest struct { type ChunkValidationRequest struct {
ModuleKey string ModuleKey string
Source *source.SourceDocument Source *source.SourceDocument
SourceID string SourceID string
SourceInput LLMInputMaterial SourceInput LLMInputMaterial
SessionID string SessionID string
References ReferenceSet References ReferenceSet
LLMProfile string LLMProfile string
Metadata map[string]any StructuredOutputRepairAttempts *int
Chunks []source.Chunk Metadata map[string]any
Chunks []source.Chunk
} }
type ChunkValidator interface { type ChunkValidator interface {
@@ -156,21 +161,22 @@ type ChunkValidator interface {
} }
type SerializedValidationRequest struct { type SerializedValidationRequest struct {
Stage string Stage string
LaneID string LaneID string
ModuleKey string ModuleKey string
Source *source.SourceDocument Source *source.SourceDocument
SourceID string SourceID string
SourceInput LLMInputMaterial SourceInput LLMInputMaterial
SessionID string SessionID string
References ReferenceSet References ReferenceSet
LLMProfile string LLMProfile string
Metadata map[string]any StructuredOutputRepairAttempts *int
Chunk *source.Chunk Metadata map[string]any
Chunks []source.Chunk Chunk *source.Chunk
Schema ArtifactSchema Chunks []source.Chunk
MediaType string Schema ArtifactSchema
Content []byte MediaType string
Content []byte
} }
type SerializedValidator interface { type SerializedValidator interface {

View File

@@ -21,12 +21,13 @@ const (
) )
type ModuleBinding struct { type ModuleBinding struct {
Module string `json:"module"` Module string `json:"module"`
LLMProfile string `json:"llm_profile,omitempty"` LLMProfile string `json:"llm_profile,omitempty"`
Retries int `json:"retries,omitempty"` StructuredOutputRepairAttempts *int `json:"structured_output_repair_attempts,omitempty"`
Options map[string]any `json:"options,omitempty"` Retries int `json:"retries,omitempty"`
References map[string]ReferenceSource `json:"references,omitempty"` Options map[string]any `json:"options,omitempty"`
Validators ValidatorOverride `json:"validators,omitempty"` References map[string]ReferenceSource `json:"references,omitempty"`
Validators ValidatorOverride `json:"validators,omitempty"`
} }
// ArtifactReference identifies a normalized artifact produced by an earlier // ArtifactReference identifies a normalized artifact produced by an earlier

View File

@@ -173,11 +173,12 @@ func (r *Runner) Run(ctx context.Context, input RunInput) (output RunOutput, err
return failOutput(output), err return failOutput(output), err
} }
doc, err = adapter.Parse(ctx, contracts.ParseRequest{ doc, err = adapter.Parse(ctx, contracts.ParseRequest{
SourceID: input.SourceID, SourceID: input.SourceID,
Path: input.Path, Path: input.Path,
Raw: input.RawInput, Raw: input.RawInput,
LLMProfile: input.pipeline.Input.LLMProfile, LLMProfile: input.pipeline.Input.LLMProfile,
Metadata: requestMetadata, StructuredOutputRepairAttempts: input.pipeline.Input.StructuredOutputRepairAttempts,
Metadata: requestMetadata,
}) })
if ctxErr := ctx.Err(); ctxErr != nil { if ctxErr := ctx.Err(); ctxErr != nil {
_ = checkpoints.SourceFailed(adapter.Key(), ctxErr) _ = checkpoints.SourceFailed(adapter.Key(), ctxErr)
@@ -360,14 +361,15 @@ func (r *Runner) Run(ctx context.Context, input RunInput) (output RunOutput, err
return failOutput(output), err return failOutput(output), err
} }
encoded, err := encoder.Encode(ctx, contracts.OutputRequest{ encoded, err := encoder.Encode(ctx, contracts.OutputRequest{
Manifest: output.Manifest, Manifest: output.Manifest,
NormalizeOutputs: cloneSerializedOutputs(output.NormalizeOutputs), NormalizeOutputs: cloneSerializedOutputs(output.NormalizeOutputs),
Rejected: cloneRejectedOutputs(output.Rejected), Rejected: cloneRejectedOutputs(output.Rejected),
Warnings: output.Warnings, Warnings: output.Warnings,
LLMProfile: input.pipeline.Output.LLMProfile, LLMProfile: input.pipeline.Output.LLMProfile,
Metadata: outputMetadata, StructuredOutputRepairAttempts: input.pipeline.Output.StructuredOutputRepairAttempts,
ChunkMap: contracts.CloneSerializedArtifactPointer(acceptedChunkMap), Metadata: outputMetadata,
EvidenceContext: contracts.CloneSerializedArtifactPointer(evidenceArtifact), ChunkMap: contracts.CloneSerializedArtifactPointer(acceptedChunkMap),
EvidenceContext: contracts.CloneSerializedArtifactPointer(evidenceArtifact),
}) })
if ctxErr := ctx.Err(); ctxErr != nil { if ctxErr := ctx.Err(); ctxErr != nil {
return failOutput(output), ctxErr return failOutput(output), ctxErr

View File

@@ -96,7 +96,7 @@ func (r *Runner) runChunkPlan(ctx context.Context, input RunInput, doc *source.S
chunkResult, callErr := chunker.Plan(attemptCtx, contracts.ChunkRequest{ chunkResult, callErr := chunker.Plan(attemptCtx, contracts.ChunkRequest{
Source: doc, SourceInput: sourceInput.Clone(), SessionID: sessionID, Source: doc, SourceInput: sourceInput.Clone(), SessionID: sessionID,
References: CloneReferenceSet(input.pipeline.ChunkReferences.ReferenceSet), References: CloneReferenceSet(input.pipeline.ChunkReferences.ReferenceSet),
LLMProfile: input.pipeline.Chunk.LLMProfile, Metadata: requestMetadata, LLMProfile: input.pipeline.Chunk.LLMProfile, StructuredOutputRepairAttempts: input.pipeline.Chunk.StructuredOutputRepairAttempts, Metadata: requestMetadata,
}) })
if callErr != nil { if callErr != nil {
return retryAttemptResult{}, terminal.record(nil, fmt.Errorf("chunk source with chunker %q: %w", chunker.Key(), callErr)) return retryAttemptResult{}, terminal.record(nil, fmt.Errorf("chunk source with chunker %q: %w", chunker.Key(), callErr))

View File

@@ -420,7 +420,7 @@ func (r *Runner) runExtractJob(ctx context.Context, input RunInput, doc *source.
return retryAttemptResult{}, terminal.record(nil, fmt.Errorf("clone extract request metadata: %w", metadataErr)) return retryAttemptResult{}, terminal.record(nil, fmt.Errorf("clone extract request metadata: %w", metadataErr))
} }
extractReferences := operationReferenceSet(input, lane.ExtractReferences) extractReferences := operationReferenceSet(input, lane.ExtractReferences)
extracted, callErr := typed.extract(attemptCtx, typed.extractor, contracts.TypedExtractionRequest{Source: doc, Chunk: &chunk, SourceInput: chunkInputMaterial(sourceInput, chunk), SessionID: sessionID, References: CloneReferenceSet(extractReferences), LLMProfile: lane.Extract.LLMProfile, Metadata: requestMetadata}) extracted, callErr := typed.extract(attemptCtx, typed.extractor, contracts.TypedExtractionRequest{Source: doc, Chunk: &chunk, SourceInput: chunkInputMaterial(sourceInput, chunk), SessionID: sessionID, References: CloneReferenceSet(extractReferences), LLMProfile: lane.Extract.LLMProfile, StructuredOutputRepairAttempts: lane.Extract.StructuredOutputRepairAttempts, Metadata: requestMetadata})
if callErr != nil { if callErr != nil {
attemptErr := fmt.Errorf("extract lane %q chunk %q with extractor %q: %w", lane.ID, chunk.ID, lane.Extract.Module, callErr) attemptErr := fmt.Errorf("extract lane %q chunk %q with extractor %q: %w", lane.ID, chunk.ID, lane.Extract.Module, callErr)
return retryAttemptResult{}, terminal.record(nil, attemptErr) return retryAttemptResult{}, terminal.record(nil, attemptErr)

View File

@@ -258,7 +258,7 @@ func (r *Runner) runMergeStage(ctx context.Context, input RunInput, checkpoints
if metadataErr != nil { if metadataErr != nil {
return retryAttemptResult{}, terminal.record(nil, fmt.Errorf("clone merge request metadata: %w", metadataErr)) return retryAttemptResult{}, terminal.record(nil, fmt.Errorf("clone merge request metadata: %w", metadataErr))
} }
result, callErr := typed.merge(attemptCtx, typed.merger, contracts.TypedMergeRequest[any]{Source: doc, LaneID: lane.ID, ExtractOutputs: mergeInputs, SourceInput: sourceInput.Clone(), SessionID: sessionID, References: CloneReferenceSet(mergeReferences), LLMProfile: lane.Merge.LLMProfile, Metadata: requestMetadata}) result, callErr := typed.merge(attemptCtx, typed.merger, contracts.TypedMergeRequest[any]{Source: doc, LaneID: lane.ID, ExtractOutputs: mergeInputs, SourceInput: sourceInput.Clone(), SessionID: sessionID, References: CloneReferenceSet(mergeReferences), LLMProfile: lane.Merge.LLMProfile, StructuredOutputRepairAttempts: lane.Merge.StructuredOutputRepairAttempts, Metadata: requestMetadata})
if callErr != nil { if callErr != nil {
attemptErr := fmt.Errorf("merge lane %q with merger %q: %w", lane.ID, lane.Merge.Module, callErr) attemptErr := fmt.Errorf("merge lane %q with merger %q: %w", lane.ID, lane.Merge.Module, callErr)
return retryAttemptResult{}, terminal.record(nil, attemptErr) return retryAttemptResult{}, terminal.record(nil, attemptErr)
@@ -360,7 +360,7 @@ func (r *Runner) runNormalizeStage(ctx context.Context, input RunInput, checkpoi
if metadataErr != nil { if metadataErr != nil {
return retryAttemptResult{}, terminal.record(nil, fmt.Errorf("clone normalize request metadata: %w", metadataErr)) return retryAttemptResult{}, terminal.record(nil, fmt.Errorf("clone normalize request metadata: %w", metadataErr))
} }
result, callErr := typed.normalize(attemptCtx, typed.normalizer, contracts.TypedNormalizeRequest[any]{Source: doc, LaneID: lane.ID, MergeOutput: contracts.MergeArtifact[any]{LaneID: lane.ID, MergerKey: lane.Merge.Module, SourceID: doc.ID, Value: merged.Value}, SourceInput: sourceInput.Clone(), SessionID: sessionID, References: CloneReferenceSet(normalizeReferences), LLMProfile: lane.Normalize.LLMProfile, Metadata: requestMetadata}) result, callErr := typed.normalize(attemptCtx, typed.normalizer, contracts.TypedNormalizeRequest[any]{Source: doc, LaneID: lane.ID, MergeOutput: contracts.MergeArtifact[any]{LaneID: lane.ID, MergerKey: lane.Merge.Module, SourceID: doc.ID, Value: merged.Value}, SourceInput: sourceInput.Clone(), SessionID: sessionID, References: CloneReferenceSet(normalizeReferences), LLMProfile: lane.Normalize.LLMProfile, StructuredOutputRepairAttempts: lane.Normalize.StructuredOutputRepairAttempts, Metadata: requestMetadata})
if callErr != nil { if callErr != nil {
attemptErr := fmt.Errorf("normalize lane %q with normalizer %q: %w", lane.ID, lane.Normalize.Module, callErr) attemptErr := fmt.Errorf("normalize lane %q with normalizer %q: %w", lane.ID, lane.Normalize.Module, callErr)
return retryAttemptResult{}, terminal.record(nil, attemptErr) return retryAttemptResult{}, terminal.record(nil, attemptErr)
@@ -511,6 +511,7 @@ func (r *Runner) validateTypedArtifact(ctx context.Context, codec artifactCodecE
} }
requestTarget.value = candidateValue requestTarget.value = candidateValue
requestTarget.llmProfile = binding.LLMProfile requestTarget.llmProfile = binding.LLMProfile
requestTarget.structuredOutputRepairAttempts = binding.StructuredOutputRepairAttempts
result, err = item.typedValidate(validatorCtx, item.typed, requestTarget) result, err = item.typedValidate(validatorCtx, item.typed, requestTarget)
case ValidatorTargetSerialized: case ValidatorTargetSerialized:
artifact, encodeErr := validationCandidateArtifact(codec, target) artifact, encodeErr := validationCandidateArtifact(codec, target)
@@ -518,7 +519,7 @@ func (r *Runner) validateTypedArtifact(ctx context.Context, codec artifactCodecE
err = encodeErr err = encodeErr
break break
} }
result, err = item.serialized.Validate(validatorCtx, contracts.SerializedValidationRequest{Stage: string(target.stage), LaneID: target.laneID, ModuleKey: target.moduleKey, Source: target.source, SourceID: target.sourceID, SourceInput: requestTarget.sourceInput, SessionID: target.sessionID, References: requestTarget.references, LLMProfile: binding.LLMProfile, Metadata: requestTarget.metadata, Chunk: requestTarget.chunk, Chunks: requestTarget.chunks, Schema: contracts.CloneArtifactSchema(artifact.Artifact.Schema), MediaType: artifact.Artifact.MediaType, Content: append([]byte(nil), artifact.Artifact.Content...)}) result, err = item.serialized.Validate(validatorCtx, contracts.SerializedValidationRequest{Stage: string(target.stage), LaneID: target.laneID, ModuleKey: target.moduleKey, Source: target.source, SourceID: target.sourceID, SourceInput: requestTarget.sourceInput, SessionID: target.sessionID, References: requestTarget.references, LLMProfile: binding.LLMProfile, StructuredOutputRepairAttempts: binding.StructuredOutputRepairAttempts, Metadata: requestTarget.metadata, Chunk: requestTarget.chunk, Chunks: requestTarget.chunks, Schema: contracts.CloneArtifactSchema(artifact.Artifact.Schema), MediaType: artifact.Artifact.MediaType, Content: append([]byte(nil), artifact.Artifact.Content...)})
default: default:
return nil, nil, fmt.Errorf("validator %q is incompatible with typed artifact validation", binding.Module) return nil, nil, fmt.Errorf("validator %q is incompatible with typed artifact validation", binding.Module)
} }

View File

@@ -28,22 +28,23 @@ type erasedTypedResult struct {
} }
type typedValidationTarget struct { type typedValidationTarget struct {
stage ModuleStage stage ModuleStage
stepID string stepID string
laneID string laneID string
moduleKey string moduleKey string
source *source.SourceDocument source *source.SourceDocument
sourceID string sourceID string
sourceInput contracts.LLMInputMaterial sourceInput contracts.LLMInputMaterial
sessionID string sessionID string
references contracts.ReferenceSet references contracts.ReferenceSet
llmProfile string llmProfile string
metadata map[string]any structuredOutputRepairAttempts *int
chunk *source.Chunk metadata map[string]any
chunks []source.Chunk chunk *source.Chunk
ref source.SourceRef chunks []source.Chunk
value any ref source.SourceRef
candidate *CheckpointArtifact value any
candidate *CheckpointArtifact
} }
func exactTypedValue[T any](operation string, value any) (T, error) { func exactTypedValue[T any](operation string, value any) (T, error) {

View File

@@ -94,6 +94,10 @@ func cloneModuleBindings(bindings []ModuleBinding) []ModuleBinding {
func cloneModuleBinding(binding ModuleBinding) ModuleBinding { func cloneModuleBinding(binding ModuleBinding) ModuleBinding {
binding.Module = strings.TrimSpace(binding.Module) binding.Module = strings.TrimSpace(binding.Module)
binding.LLMProfile = strings.TrimSpace(binding.LLMProfile) binding.LLMProfile = strings.TrimSpace(binding.LLMProfile)
if binding.StructuredOutputRepairAttempts != nil {
value := *binding.StructuredOutputRepairAttempts
binding.StructuredOutputRepairAttempts = &value
}
binding.Options = cloneOptions(binding.Options) binding.Options = cloneOptions(binding.Options)
if len(binding.References) > 0 { if len(binding.References) > 0 {
references := make(map[string]ReferenceSource, len(binding.References)) references := make(map[string]ReferenceSource, len(binding.References))

View File

@@ -115,7 +115,7 @@ func RegisterTypedValidatorBuilder[T any](registry *ValidatorRegistry, kind cont
if err != nil { if err != nil {
return contracts.ValidationResult{}, err return contracts.ValidationResult{}, err
} }
return validator.Validate(ctx, contracts.TypedValidationRequest[T]{Stage: string(target.stage), LaneID: target.laneID, ModuleKey: target.moduleKey, Source: target.source, SourceID: target.sourceID, SourceInput: target.sourceInput, SessionID: target.sessionID, References: target.references, LLMProfile: target.llmProfile, Metadata: target.metadata, Chunk: target.chunk, Chunks: target.chunks, Ref: target.ref, Value: value}) return validator.Validate(ctx, contracts.TypedValidationRequest[T]{Stage: string(target.stage), LaneID: target.laneID, ModuleKey: target.moduleKey, Source: target.source, SourceID: target.sourceID, SourceInput: target.sourceInput, SessionID: target.sessionID, References: target.references, LLMProfile: target.llmProfile, StructuredOutputRepairAttempts: target.structuredOutputRepairAttempts, Metadata: target.metadata, Chunk: target.chunk, Chunks: target.chunks, Ref: target.ref, Value: value})
}, },
} }
return nil return nil

View File

@@ -43,11 +43,12 @@ func DefaultPromptSpec() (PromptSpec, error) {
// Request contains one typed owner's source-backed reconciliation input. // Request contains one typed owner's source-backed reconciliation input.
type Request struct { type Request struct {
StageName string StageName string
Source *source.SourceDocument Source *source.SourceDocument
Candidates []Candidate Candidates []Candidate
ProfileID string ProfileID string
SessionID string StructuredOutputRepairAttempts *int
SessionID string
} }
// ResultDisposition classifies a provider-neutral reconciliation outcome. // ResultDisposition classifies a provider-neutral reconciliation outcome.
@@ -163,12 +164,13 @@ func (engine *Engine) Reconcile(ctx context.Context, request Request) (Result, e
} }
var response ProposalResponse var response ProposalResponse
_, err = engine.client.CompleteStructured(ctx, contracts.StructuredCompletionRequest{ _, err = engine.client.CompleteStructured(ctx, contracts.StructuredCompletionRequest{
StageName: request.StageName, StageName: request.StageName,
PromptID: engine.prompt.ID, PromptID: engine.prompt.ID,
PromptVersion: engine.prompt.Version, PromptVersion: engine.prompt.Version,
ProfileID: request.ProfileID, ProfileID: request.ProfileID,
SessionID: request.SessionID, SessionID: request.SessionID,
Inputs: preparation.Materials(), StructuredOutputRepairAttempts: request.StructuredOutputRepairAttempts,
Inputs: preparation.Materials(),
}, &response) }, &response)
if err != nil { if err != nil {
if errors.Is(err, contracts.ErrInvalidStructuredOutput) { if errors.Is(err, contracts.ErrInvalidStructuredOutput) {