Deliver references to eligible runtime targets
This commit is contained in:
@@ -60,10 +60,11 @@ Reference flags are resolved against selected chunk, extractor, and normalizer
|
||||
targets before the run starts. Flat slot names are accepted only when exactly
|
||||
one selected target declares that slot. Bound reference files are read before
|
||||
pipeline work starts, validated as UTF-8 text, and recorded as provenance for
|
||||
the target that declares the slot. Runtime reference content is currently passed
|
||||
only to lane extractors. Notarius infers reference media types from file
|
||||
extensions for provenance and for optional slot checks. Reference content is not
|
||||
written to diagnostics, logs, errors, or manifests.
|
||||
the target that declares the slot. Runtime reference content is passed to the
|
||||
chunker, extractor, or normalizer target that declares the slot. Notarius infers
|
||||
reference media types from file extensions for provenance and for optional slot
|
||||
checks. Reference content is not written to diagnostics, logs, errors, or
|
||||
manifests.
|
||||
|
||||
Reference binding precedence is:
|
||||
|
||||
|
||||
@@ -149,10 +149,10 @@ compatibility bindings, and run-time `--reference` or `--without-reference`
|
||||
overrides are applied. Config-relative paths are resolved relative to the config
|
||||
file; CLI reference paths are resolved relative to the current working
|
||||
directory. Materialized bound files must be UTF-8 text. Materialized reference
|
||||
provenance is recorded for chunk, extractor, and normalizer targets; runtime
|
||||
reference content is currently passed only to lane extractors that declare the
|
||||
slot. Reference media types are inferred from file extensions, recorded as
|
||||
canonical base media types, and checked only when a module declares
|
||||
provenance is recorded for chunk, extractor, and normalizer targets, and runtime
|
||||
reference content is passed to the target that declares the slot. Reference
|
||||
media types are inferred from file extensions, recorded as canonical base media
|
||||
types, and checked only when a module declares
|
||||
`AcceptedMediaTypes`; unknown extensions are recorded as
|
||||
`application/octet-stream`. Reference content is not written to diagnostics,
|
||||
logs, errors, or manifests.
|
||||
|
||||
@@ -32,14 +32,15 @@ compares the canonical base media type inferred from the file extension,
|
||||
case-insensitively and without parameters.
|
||||
|
||||
The resolver materializes reference content for chunk, extractor, and
|
||||
normalizer targets. Runtime delivery is currently implemented only for lane
|
||||
extractors through `contracts.ExtractionRequest.References`. Reference material
|
||||
is not source evidence and must not be converted into `SourceRef` values. If a
|
||||
module prompt uses references, load the prompt bundle with the same declared
|
||||
slots and render with `RenderUserSystemWithReferences`. Prompt templates may use
|
||||
the `reference` function for content and the `hasreference` function for
|
||||
conditional sections. Prompt metadata hashes remain based on template source,
|
||||
not rendered reference bytes.
|
||||
normalizer targets. Runtime delivery uses `contracts.ChunkRequest.References`,
|
||||
`contracts.ExtractionRequest.References`, and
|
||||
`contracts.NormalizeRequest.References`. Reference material is not source
|
||||
evidence and must not be converted into `SourceRef` values. If a module prompt
|
||||
uses references, load the prompt bundle with the same declared slots and render
|
||||
with `RenderUserSystemWithReferences`. Prompt templates may use the `reference`
|
||||
function for content and the `hasreference` function for conditional sections.
|
||||
Prompt metadata hashes remain based on template source, not rendered reference
|
||||
bytes.
|
||||
|
||||
Chunk modules receive the structured LLM client through `contracts.ChunkRequest`
|
||||
when they need model-backed chunking. The pipeline runner validates generic
|
||||
|
||||
@@ -53,8 +53,8 @@ empty bound files. Media-type acceptance is checked only when a slot declares
|
||||
`application/octet-stream`. Reference content is omitted from diagnostics and
|
||||
manifests. The CLI writes provenance-only resolved reference diagnostics, and
|
||||
the run manifest records target-stage reference provenance separately from
|
||||
source digests. Runtime reference content is currently passed only to the
|
||||
matching lane extractor through `ExtractionRequest`.
|
||||
source digests. Runtime reference content is passed to the matching chunker,
|
||||
extractor, or normalizer request.
|
||||
|
||||
Prompt bundles can declare reference slots and use `reference` and
|
||||
`hasreference` template functions. Bundle loading validates string-literal slot
|
||||
|
||||
@@ -95,6 +95,7 @@ func (r *Runner) Run(ctx context.Context, input RunInput) (RunOutput, error) {
|
||||
attachModuleManifestMetadata(&output, "chunker", chunker)
|
||||
chunkResult, err := chunker.Chunk(ctx, contracts.ChunkRequest{
|
||||
Source: doc,
|
||||
References: CloneReferenceSet(input.Pipeline.ChunkReferences.ReferenceSet),
|
||||
LLMClient: input.LLMClient,
|
||||
LLMProfile: input.Pipeline.Chunk.LLMProfile,
|
||||
Options: cloneOptions(input.Pipeline.Chunk.Options),
|
||||
@@ -224,6 +225,7 @@ func (r *Runner) runLane(ctx context.Context, input RunInput, doc *source.Source
|
||||
Source: doc,
|
||||
LaneID: lane.ID,
|
||||
Candidates: mergeResult.Candidates,
|
||||
References: CloneReferenceSet(lane.NormalizeReferences.ReferenceSet),
|
||||
LLMClient: input.LLMClient,
|
||||
LLMProfile: lane.Normalize.LLMProfile,
|
||||
Options: cloneOptions(lane.Normalize.Options),
|
||||
|
||||
@@ -566,24 +566,7 @@ func TestRunPassesModuleBindingConfigToStageRequests(t *testing.T) {
|
||||
func TestRunPassesLaneReferencesToExtractorRequests(t *testing.T) {
|
||||
modules := defaultRunnerModules()
|
||||
pipeline := resolvedPipeline()
|
||||
pipeline.ArtifactLanes[0].ExtractReferences.ReferenceSet = contracts.ReferenceSet{
|
||||
Slots: map[string]contracts.ResolvedReferenceSlot{
|
||||
"roster": {
|
||||
Slot: contracts.ReferenceSlot{Name: "roster"},
|
||||
Items: []contracts.ReferenceItem{
|
||||
{
|
||||
SlotName: "roster",
|
||||
MediaType: "text/plain; charset=utf-8",
|
||||
Content: []byte("reference text"),
|
||||
Digest: "sha256:test",
|
||||
Origin: contracts.ReferenceOrigin{Type: "file", URI: "file:///tmp/reference.txt"},
|
||||
SizeBytes: int64(len("reference text")),
|
||||
BindingSource: contracts.ReferenceBindingSourceConfig,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
pipeline.ArtifactLanes[0].ExtractReferences.ReferenceSet = testReferenceSet("roster", "reference text")
|
||||
|
||||
_, err := New(newRunnerRegistries(t, modules)).Run(context.Background(), RunInput{Pipeline: pipeline})
|
||||
if err != nil {
|
||||
@@ -601,6 +584,55 @@ func TestRunPassesLaneReferencesToExtractorRequests(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunPassesChunkReferencesToChunkerRequest(t *testing.T) {
|
||||
modules := defaultRunnerModules()
|
||||
pipeline := resolvedPipeline()
|
||||
pipeline.ChunkReferences.ReferenceSet = testReferenceSet("scene_guide", "chunk reference text")
|
||||
|
||||
_, err := New(newRunnerRegistries(t, modules)).Run(context.Background(), RunInput{Pipeline: pipeline})
|
||||
if err != nil {
|
||||
t.Fatalf("Run() error = %v, want nil", err)
|
||||
}
|
||||
|
||||
req := modules.chunker.requests[0]
|
||||
item := req.References.Slots["scene_guide"].Items[0]
|
||||
if string(item.Content) != "chunk reference text" {
|
||||
t.Fatalf("chunk reference content = %q, want chunk reference text", item.Content)
|
||||
}
|
||||
item.Content[0] = 'C'
|
||||
if got := string(pipeline.ChunkReferences.ReferenceSet.Slots["scene_guide"].Items[0].Content); got != "chunk reference text" {
|
||||
t.Fatalf("runner mutated chunk reference set content = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunPassesNormalizeReferencesToNormalizerRequest(t *testing.T) {
|
||||
modules := defaultRunnerModules()
|
||||
pipeline := resolvedPipeline()
|
||||
pipeline.ArtifactLanes[0].NormalizeReferences.ReferenceSet = testReferenceSet("normalization_notes", "normalize reference text")
|
||||
|
||||
_, err := New(newRunnerRegistries(t, modules)).Run(context.Background(), RunInput{Pipeline: pipeline})
|
||||
if err != nil {
|
||||
t.Fatalf("Run() error = %v, want nil", err)
|
||||
}
|
||||
|
||||
req := modules.normalizers["normalize"].requests[0]
|
||||
item := req.References.Slots["normalization_notes"].Items[0]
|
||||
if string(item.Content) != "normalize reference text" {
|
||||
t.Fatalf("normalize reference content = %q, want normalize reference text", item.Content)
|
||||
}
|
||||
item.Content[0] = 'N'
|
||||
if got := string(pipeline.ArtifactLanes[0].NormalizeReferences.ReferenceSet.Slots["normalization_notes"].Items[0].Content); got != "normalize reference text" {
|
||||
t.Fatalf("runner mutated normalize reference set content = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunAllowsNilLLMClientWhenModulesDoNotUseIt(t *testing.T) {
|
||||
_, err := New(newRunnerRegistries(t, defaultRunnerModules())).Run(context.Background(), RunInput{Pipeline: resolvedPipeline()})
|
||||
if err != nil {
|
||||
t.Fatalf("Run() error = %v, want nil with nil LLM client when modules do not use it", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunIncludesInputWarnings(t *testing.T) {
|
||||
modules := defaultRunnerModules()
|
||||
warning := contracts.Warning{Scope: "reference", ReasonCode: "empty_reference", Message: "empty reference"}
|
||||
@@ -1235,6 +1267,27 @@ func resolvedPipelineWithValidators(validators ...string) ResolvedPipeline {
|
||||
return pipeline
|
||||
}
|
||||
|
||||
func testReferenceSet(slotName string, content string) contracts.ReferenceSet {
|
||||
return contracts.ReferenceSet{
|
||||
Slots: map[string]contracts.ResolvedReferenceSlot{
|
||||
slotName: {
|
||||
Slot: contracts.ReferenceSlot{Name: slotName},
|
||||
Items: []contracts.ReferenceItem{
|
||||
{
|
||||
SlotName: slotName,
|
||||
MediaType: "text/plain; charset=utf-8",
|
||||
Content: []byte(content),
|
||||
Digest: "sha256:test",
|
||||
Origin: contracts.ReferenceOrigin{Type: "file", URI: "file:///tmp/reference.txt"},
|
||||
SizeBytes: int64(len(content)),
|
||||
BindingSource: contracts.ReferenceBindingSourceConfig,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type runnerModules struct {
|
||||
input *runnerInputAdapter
|
||||
chunker *runnerChunker
|
||||
|
||||
@@ -43,8 +43,8 @@ func TestWalkingSkeletonFixture(t *testing.T) {
|
||||
t.Fatalf("ContentType = %q, want application/json", output.OutputFiles[0].ContentType)
|
||||
}
|
||||
assertStructuralJSONEqual(t, output.OutputFiles[0].Bytes, expectedBytes)
|
||||
if llmClient.calls != 2 {
|
||||
t.Fatalf("LLM calls = %d, want chunk count 2", llmClient.calls)
|
||||
if llmClient.calls != 3 {
|
||||
t.Fatalf("LLM calls = %d, want extractor calls plus normalizer call", llmClient.calls)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,6 +345,15 @@ func (normalizer walkingSkeletonNormalizer) ReferenceSlots() []contracts.Referen
|
||||
}
|
||||
|
||||
func (normalizer walkingSkeletonNormalizer) Normalize(ctx context.Context, req contracts.NormalizeRequest) (contracts.NormalizeResult, error) {
|
||||
var response struct {
|
||||
Call int `json:"call"`
|
||||
}
|
||||
if _, err := req.LLMClient.CompleteStructured(ctx, contracts.StructuredCompletionRequest{
|
||||
StageName: "fake/normalize",
|
||||
ResponseSchemaName: "fake_normalize",
|
||||
}, &response); err != nil {
|
||||
return contracts.NormalizeResult{}, err
|
||||
}
|
||||
return contracts.NormalizeResult{Candidates: req.Candidates}, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user