Gate combat extraction on scene descriptions
This commit is contained in:
@@ -9,17 +9,21 @@ import (
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
|
||||
sceneregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/scenedescriptions/registry"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
|
||||
)
|
||||
|
||||
const (
|
||||
Key = "dnd/combat-turns"
|
||||
mappingPolicy = "dnd.combat_turns.extract_mapping.v2"
|
||||
Key = "dnd/combat-turns"
|
||||
mappingPolicy = "dnd.combat_turns.extract_mapping.v2"
|
||||
sceneGatePolicy = "dnd.combat_turns.scene_gate.v1"
|
||||
)
|
||||
|
||||
const (
|
||||
NPCRegistryReferenceSlot = npcregistry.ReferenceSlot
|
||||
NPCRegistryMaxBytes = npcregistry.MaxBytes
|
||||
NPCRegistryReferenceSlot = npcregistry.ReferenceSlot
|
||||
NPCRegistryMaxBytes = npcregistry.MaxBytes
|
||||
SceneDescriptionReferenceSlot = sceneregistry.ReferenceSlot
|
||||
SceneDescriptionMaxBytes = sceneregistry.MaxBytes
|
||||
)
|
||||
|
||||
var requiredCapabilities = []string{
|
||||
@@ -47,6 +51,14 @@ func referenceSlots() []contracts.ReferenceSlot {
|
||||
AcceptedArtifactKinds: []contracts.ArtifactKind{dnd.NPCListKind},
|
||||
MaxBytes: NPCRegistryMaxBytes,
|
||||
})
|
||||
slots = append(slots, contracts.ReferenceSlot{
|
||||
Name: SceneDescriptionReferenceSlot,
|
||||
Description: "Required scene descriptions that gate combat eligibility and are not combat evidence.",
|
||||
Required: true,
|
||||
AcceptedMediaTypes: []string{"application/json"},
|
||||
AcceptedArtifactKinds: []contracts.ArtifactKind{dnd.SceneDescriptionListKind},
|
||||
MaxBytes: SceneDescriptionMaxBytes,
|
||||
})
|
||||
sort.Slice(slots, func(i, j int) bool { return slots[i].Name < slots[j].Name })
|
||||
return slots
|
||||
}
|
||||
@@ -60,6 +72,7 @@ type Options struct{}
|
||||
type Extractor struct {
|
||||
llm contracts.StructuredLLMClient
|
||||
npcResolver *npcregistry.Resolver
|
||||
sceneResolver *sceneregistry.Resolver
|
||||
promptSHA string
|
||||
responseSchemaSHA string
|
||||
}
|
||||
@@ -79,6 +92,10 @@ func New(llmClient contracts.StructuredLLMClient, _ Options, references ...contr
|
||||
if err != nil {
|
||||
return nil, extractorErrorf("prepare NPC registry prompt input: %w", err)
|
||||
}
|
||||
sceneResolver, err := sceneregistry.NewResolver(referenceSet)
|
||||
if err != nil {
|
||||
return nil, extractorErrorf("prepare scene eligibility: %w", err)
|
||||
}
|
||||
promptSHA, err := scriptoriumPromptMetadata()
|
||||
if err != nil {
|
||||
return nil, extractorErrorf("load prompt metadata: %w", err)
|
||||
@@ -90,6 +107,7 @@ func New(llmClient contracts.StructuredLLMClient, _ Options, references ...contr
|
||||
return &Extractor{
|
||||
llm: llmClient,
|
||||
npcResolver: npcResolver,
|
||||
sceneResolver: sceneResolver,
|
||||
promptSHA: promptSHA,
|
||||
responseSchemaSHA: responseSchema.SHA256,
|
||||
}, nil
|
||||
@@ -108,6 +126,7 @@ func (e *Extractor) ManifestMetadata() map[string]any {
|
||||
"prompt_version": SchemaVersion,
|
||||
"prompt_sha256": e.promptSHA,
|
||||
"mapping_policy": mappingPolicy,
|
||||
"scene_gate_policy": sceneGatePolicy,
|
||||
"response_schema_key": string(ResponseSchemaKey),
|
||||
"response_schema_id": ResponseSchemaID,
|
||||
"response_schema_name": ResponseSchemaName,
|
||||
@@ -119,6 +138,11 @@ func (e *Extractor) ManifestMetadata() map[string]any {
|
||||
metadata["npc_registry_digest"] = seeded.Digest()
|
||||
metadata["npc_count"] = seeded.Count()
|
||||
}
|
||||
seededScenes := e.sceneResolver.Seeded()
|
||||
if seededScenes.Bound() {
|
||||
metadata["scene_eligibility_digest"] = seededScenes.EligibilityDigest()
|
||||
metadata["scene_description_count"] = seededScenes.Count()
|
||||
}
|
||||
return metadata
|
||||
}
|
||||
|
||||
@@ -130,9 +154,12 @@ func (e *Extractor) CheckpointFingerprints() []pipeline.CheckpointFingerprint {
|
||||
{Name: "prompt", Value: e.promptSHA},
|
||||
{Name: "response_schema", Value: e.responseSchemaSHA},
|
||||
{Name: "mapping_policy", Value: mappingPolicy},
|
||||
{Name: "scene_gate_policy", Value: sceneGatePolicy},
|
||||
}
|
||||
seeded := e.npcResolver.Seeded()
|
||||
fingerprints = append(fingerprints, pipeline.CheckpointFingerprint{Name: "npc_registry", Value: seeded.ProjectionDigest()})
|
||||
seededScenes := e.sceneResolver.Seeded()
|
||||
fingerprints = append(fingerprints, pipeline.CheckpointFingerprint{Name: "scene_eligibility", Value: seededScenes.EligibilityDigest()})
|
||||
return fingerprints
|
||||
}
|
||||
|
||||
@@ -147,6 +174,29 @@ func (e *Extractor) Extract(ctx context.Context, req contracts.TypedExtractionRe
|
||||
if err != nil {
|
||||
return contracts.TypedExtractionResult[dnd.CombatTurnList]{}, extractorErrorf("%w", err)
|
||||
}
|
||||
if _, ok := req.References.Slots[SceneDescriptionReferenceSlot]; !ok {
|
||||
return contracts.TypedExtractionResult[dnd.CombatTurnList]{}, extractorErrorf("required scene descriptions reference is missing")
|
||||
}
|
||||
scenes, err := e.sceneResolver.Resolve(req.References)
|
||||
if err != nil {
|
||||
return contracts.TypedExtractionResult[dnd.CombatTurnList]{}, extractorErrorf("resolve scene eligibility: %w", err)
|
||||
}
|
||||
match := scenes.Match(req.Chunk)
|
||||
switch match.State {
|
||||
case sceneregistry.MatchExact:
|
||||
if match.Kind != dnd.SceneKindCombat {
|
||||
return contracts.TypedExtractionResult[dnd.CombatTurnList]{Value: dnd.CombatTurnList{CombatTurns: []dnd.CombatTurn{}}}, nil
|
||||
}
|
||||
case sceneregistry.MatchMissing, sceneregistry.MatchMismatched:
|
||||
return contracts.TypedExtractionResult[dnd.CombatTurnList]{
|
||||
Value: dnd.CombatTurnList{CombatTurns: []dnd.CombatTurn{}},
|
||||
Warnings: []contracts.Warning{{
|
||||
Scope: SceneDescriptionReferenceSlot,
|
||||
ReasonCode: "scene_classification_unavailable",
|
||||
Message: "No exact scene classification was available; combat extraction was skipped.",
|
||||
}},
|
||||
}, nil
|
||||
}
|
||||
order := shared.NewSourceRefOrder(req.Source)
|
||||
npcRegistry, err := e.npcResolver.Resolve(req.References)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user