Move NPC occurrences to their canonical namespace
This commit is contained in:
@@ -4,7 +4,7 @@ Notarius is a Go CLI for turning source material into structured artifacts with
|
||||
configured extraction pipelines. The implemented D&D workflow reads Seriatim
|
||||
transcript JSON and can produce location registries and occurrences, scene
|
||||
descriptions, item and currency events, NPC identities, combat turns, NPC
|
||||
interactions, enemy events, and spell casts.
|
||||
occurrences, enemy events, and spell casts.
|
||||
|
||||
## Quickstart
|
||||
|
||||
|
||||
@@ -9,4 +9,4 @@ Combat-turn grounding:
|
||||
|
||||
Named combat-opponent grounding:
|
||||
|
||||
{{ input "npc_interactions" }}
|
||||
{{ input "npc_occurrences" }}
|
||||
|
||||
@@ -20,7 +20,7 @@ inputs:
|
||||
- name: combat_turns
|
||||
required: true
|
||||
content_type: application/json
|
||||
- name: npc_interactions
|
||||
- name: npc_occurrences
|
||||
required: true
|
||||
content_type: application/json
|
||||
messages:
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Extract Dungeons & Dragons NPC interaction occurrences from the supplied
|
||||
Extract Dungeons & Dragons NPC occurrence occurrences from the supplied
|
||||
transcript. Include an occurrence only when the transcript establishes one
|
||||
supplied NPC, one interaction kind, and a coherent passage supporting both.
|
||||
supplied NPC, one occurrence kind, and a coherent passage supporting both.
|
||||
Use the exact `npc_id` and matching `name` pair from the supplied NPC registry;
|
||||
never invent an ID or substitute a similar name.
|
||||
|
||||
Do not summarize, infer relationships, sentiment, factions, motives, aliases,
|
||||
or persistent state. Do not identify player characters, anonymous groups, or
|
||||
invented NPCs. Split records when an NPC's interaction kind changes, when
|
||||
invented NPCs. Split records when an NPC's occurrence kind changes, when
|
||||
combat alignment changes, or when an NPC is first mentioned and later becomes
|
||||
present.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
id: dnd.npc_interactions
|
||||
id: dnd.npc_occurrences
|
||||
version: "v1"
|
||||
default_profile: dnd-extraction
|
||||
inputs:
|
||||
@@ -371,10 +371,10 @@ selected target declares them:
|
||||
| **glossary** | Optional text campaign glossary. |
|
||||
| **spell_catalog** | Optional JSON spell-catalog overlay for spell extraction and normalization. See [spell-catalog overlays](integrations/dnd-spell-catalog-overlays.md). |
|
||||
| **locations** | Required normalized location registry for location-occurrence extraction and normalization. |
|
||||
| **npc_registry** | Normalized NPC registry. Optional for spells and combat turns; required for NPC interactions and enemy-event extraction and normalization. |
|
||||
| **npc_registry** | Normalized NPC registry. Optional for spells and combat turns; required for NPC occurrences and enemy-event extraction and normalization. |
|
||||
| **scene_descriptions** | Required normalized scene-description artifact for combat-turn and enemy-event extraction. |
|
||||
| **combat_turns** | Required normalized combat-turn artifact for enemy-event extraction. |
|
||||
| **npc_interactions** | Required normalized NPC-interaction artifact for enemy-event extraction. |
|
||||
| **npc_occurrences** | Required normalized NPC-occurrence artifact for enemy-event extraction. |
|
||||
|
||||
Location-occurrence and enemy-event artifact slots have the following exact
|
||||
binding contracts. Durable semantics and wire shapes remain in their
|
||||
@@ -386,11 +386,11 @@ binding contracts. Durable semantics and wire shapes remain in their
|
||||
| `npc_registry` | `dnd/npc-registry` | `application/json` | 1,048,576 bytes | extract and normalize |
|
||||
| `scene_descriptions` | `dnd/scene-description-list` | `application/json` | 1,048,576 bytes | extract only |
|
||||
| `combat_turns` | `dnd/combat-turn-list` | `application/json` | 1,048,576 bytes | extract only |
|
||||
| `npc_interactions` | `dnd/npc-interaction-list` | `application/json` | 1,048,576 bytes | extract only |
|
||||
| `npc_occurrences` | `dnd/npc-occurrence-list` | `application/json` | 1,048,576 bytes | extract only |
|
||||
| `locations` | `dnd/location-list` | `application/json` | 1,048,576 bytes | location-occurrence extract and normalize |
|
||||
|
||||
Scene descriptions accept **party**, **players**, and **glossary**, but not
|
||||
**roster**. NPC interactions require **npc_registry** for both extraction and
|
||||
**roster**. NPC occurrences require **npc_registry** for both extraction and
|
||||
normalization. Combat turns require **scene_descriptions** for extraction; the
|
||||
normalized combat-turn module may use optional **npc_registry**. Location occurrences
|
||||
require **locations** for extraction and normalization. Enemy-event extraction
|
||||
@@ -404,9 +404,9 @@ generated bindings.
|
||||
| --- | --- |
|
||||
| Input | **seriatim** |
|
||||
| Chunk | **generic**, **dnd/scenes** |
|
||||
| Extract | **dnd/spells**, **dnd/npc-registry**, **dnd/combat-turns**, **dnd/item-events**, **dnd/npc-interactions**, **dnd/scene-descriptions**, **dnd/enemy-events**, **dnd/locations**, **dnd/location-occurrences** |
|
||||
| Extract | **dnd/spells**, **dnd/npc-registry**, **dnd/combat-turns**, **dnd/item-events**, **dnd/npc-occurrences**, **dnd/scene-descriptions**, **dnd/enemy-events**, **dnd/locations**, **dnd/location-occurrences** |
|
||||
| Merge | **appendorder** |
|
||||
| Normalize | **noop**, **dnd/spells**, **dnd/npc-registry**, **dnd/combat-turns**, **dnd/item-events**, **dnd/npc-interactions**, **dnd/scene-descriptions**, **dnd/enemy-events**, **dnd/locations**, **dnd/location-occurrences** |
|
||||
| Normalize | **noop**, **dnd/spells**, **dnd/npc-registry**, **dnd/combat-turns**, **dnd/item-events**, **dnd/npc-occurrences**, **dnd/scene-descriptions**, **dnd/enemy-events**, **dnd/locations**, **dnd/location-occurrences** |
|
||||
| Output | **json** |
|
||||
|
||||
`dnd/locations` extraction and normalization are `llm_backed`; location
|
||||
@@ -418,7 +418,7 @@ step and the occurrence lane in the next.
|
||||
The D&D artifact contracts define each emitted schema:
|
||||
[spells](integrations/dnd-spell-artifacts.md),
|
||||
[NPC registry](integrations/dnd-npc-registry-artifacts.md),
|
||||
[NPC interactions](integrations/dnd-npc-interaction-artifacts.md),
|
||||
[NPC occurrences](integrations/dnd-npc-occurrence-artifacts.md),
|
||||
[combat turns](integrations/dnd-combat-turn-artifacts.md),
|
||||
[item events](integrations/dnd-item-event-artifacts.md),
|
||||
[scene descriptions](integrations/dnd-scene-description-artifacts.md), and
|
||||
@@ -437,7 +437,7 @@ Available validator keys are:
|
||||
| NPC registry | **extract/dnd/npc-registry/shape**, **extract/dnd/npc-registry/source_refs**, **extract/dnd/npc-registry/source_relatedness**, **normalize/dnd/npc-registry/identity** |
|
||||
| Combat turns | **extract/dnd/combat-turns/shape**, **extract/dnd/combat-turns/source_refs**, **extract/dnd/combat-turns/source_relatedness**, **normalize/dnd/combat-turns/invariants** |
|
||||
| Item events | **extract/dnd/item-events/shape**, **extract/dnd/item-events/source_refs**, **extract/dnd/item-events/source_relatedness**, **normalize/dnd/item-events/invariants** |
|
||||
| NPC interactions | **extract/dnd/npc-interactions/shape**, **extract/dnd/npc-interactions/registry**, **extract/dnd/npc-interactions/source_refs**, **extract/dnd/npc-interactions/source_relatedness**, **normalize/dnd/npc-interactions/invariants** |
|
||||
| NPC occurrences | **extract/dnd/npc-occurrences/shape**, **extract/dnd/npc-occurrences/registry**, **extract/dnd/npc-occurrences/source_refs**, **extract/dnd/npc-occurrences/source_relatedness**, **normalize/dnd/npc-occurrences/invariants** |
|
||||
| Scene descriptions | **extract/dnd/scene-descriptions/shape**, **extract/dnd/scene-descriptions/source_refs**, **extract/dnd/scene-descriptions/source_relatedness**, **normalize/dnd/scene-descriptions/invariants** |
|
||||
| Enemy events | **extract/dnd/enemy-events/shape**, **extract/dnd/enemy-events/engagements**, **extract/dnd/enemy-events/source_refs**, **extract/dnd/enemy-events/source_relatedness**, **normalize/dnd/enemy-events/invariants** |
|
||||
| Locations | **extract/dnd/locations/shape**, **extract/dnd/locations/source_refs**, **extract/dnd/locations/source_relatedness**, **normalize/dnd/locations/identity** |
|
||||
@@ -453,7 +453,7 @@ same at both stages.
|
||||
| NPC registry | generic/valid_json, extract/dnd/npc-registry/shape, extract/dnd/npc-registry/source_refs, generic/valid_json_schema, extract/dnd/npc-registry/source_relatedness | generic/valid_json, extract/dnd/npc-registry/shape, normalize/dnd/npc-registry/identity, extract/dnd/npc-registry/source_refs, generic/valid_json_schema, extract/dnd/npc-registry/source_relatedness |
|
||||
| Combat turns | generic/valid_json, extract/dnd/combat-turns/shape, extract/dnd/combat-turns/source_refs, generic/valid_json_schema, extract/dnd/combat-turns/source_relatedness | generic/valid_json, extract/dnd/combat-turns/shape, normalize/dnd/combat-turns/invariants, extract/dnd/combat-turns/source_refs, generic/valid_json_schema, extract/dnd/combat-turns/source_relatedness |
|
||||
| Item events | generic/valid_json, extract/dnd/item-events/shape, extract/dnd/item-events/source_refs, generic/valid_json_schema, extract/dnd/item-events/source_relatedness | generic/valid_json, extract/dnd/item-events/shape, normalize/dnd/item-events/invariants, extract/dnd/item-events/source_refs, generic/valid_json_schema, extract/dnd/item-events/source_relatedness |
|
||||
| NPC interactions | generic/valid_json, extract/dnd/npc-interactions/shape, extract/dnd/npc-interactions/registry, extract/dnd/npc-interactions/source_refs, generic/valid_json_schema, extract/dnd/npc-interactions/source_relatedness | generic/valid_json, extract/dnd/npc-interactions/shape, extract/dnd/npc-interactions/registry, normalize/dnd/npc-interactions/invariants, extract/dnd/npc-interactions/source_refs, generic/valid_json_schema, extract/dnd/npc-interactions/source_relatedness |
|
||||
| NPC occurrences | generic/valid_json, extract/dnd/npc-occurrences/shape, extract/dnd/npc-occurrences/registry, extract/dnd/npc-occurrences/source_refs, generic/valid_json_schema, extract/dnd/npc-occurrences/source_relatedness | generic/valid_json, extract/dnd/npc-occurrences/shape, extract/dnd/npc-occurrences/registry, normalize/dnd/npc-occurrences/invariants, extract/dnd/npc-occurrences/source_refs, generic/valid_json_schema, extract/dnd/npc-occurrences/source_relatedness |
|
||||
| Scene descriptions | generic/valid_json, extract/dnd/scene-descriptions/shape, extract/dnd/scene-descriptions/source_refs, generic/valid_json_schema, extract/dnd/scene-descriptions/source_relatedness | generic/valid_json, extract/dnd/scene-descriptions/shape, normalize/dnd/scene-descriptions/invariants, extract/dnd/scene-descriptions/source_refs, generic/valid_json_schema, extract/dnd/scene-descriptions/source_relatedness |
|
||||
| Enemy events | generic/valid_json, extract/dnd/enemy-events/shape, extract/dnd/enemy-events/engagements, extract/dnd/enemy-events/source_refs, generic/valid_json_schema, extract/dnd/enemy-events/source_relatedness | generic/valid_json, extract/dnd/enemy-events/shape, normalize/dnd/enemy-events/invariants, extract/dnd/enemy-events/source_refs, generic/valid_json_schema, extract/dnd/enemy-events/source_relatedness |
|
||||
| Locations | generic/valid_json, extract/dnd/locations/shape, extract/dnd/locations/source_refs, generic/valid_json_schema, extract/dnd/locations/source_relatedness | generic/valid_json, extract/dnd/locations/shape, normalize/dnd/locations/identity, extract/dnd/locations/source_refs, generic/valid_json_schema, extract/dnd/locations/source_relatedness |
|
||||
|
||||
@@ -63,7 +63,7 @@ chronology; and collapses only duplicates with the same actor identity, turn
|
||||
kind, and complete valid evidence. It does not infer turns, initiative, or
|
||||
actions from registry or scene data.
|
||||
|
||||
The [NPC-interaction artifact](dnd-npc-interaction-artifacts.md) records
|
||||
The [NPC-occurrence artifact](dnd-npc-occurrence-artifacts.md) records
|
||||
broader NPC occurrences. The [enemy-event artifact](dnd-enemy-event-artifacts.md)
|
||||
uses combat turns as grounding only; turns do not establish an enemy event or
|
||||
its outcome. The [JSON output contract](json-output.md) defines publication,
|
||||
|
||||
@@ -66,7 +66,7 @@ are positive and the start may not follow the end.
|
||||
| `incapacitated` | The subject is explicitly rendered unable to continue acting without being established as killed or captured. A missed turn is insufficient. |
|
||||
|
||||
The current transcript is the only event evidence. Campaign context and
|
||||
normalized NPC, scene-description, combat-turn, and NPC-interaction artifacts
|
||||
normalized NPC, scene-description, combat-turn, and NPC-occurrence artifacts
|
||||
can ground names or control combat eligibility, but none may supply event
|
||||
evidence. An outcome may share evidence with an engagement, in which case both
|
||||
events are retained.
|
||||
|
||||
@@ -74,5 +74,5 @@ valid evidence.
|
||||
|
||||
The [JSON output contract](json-output.md) defines publication. See
|
||||
[D&D module internals](../internal/dnd.md) for implementation details and the
|
||||
[NPC-interaction artifact](dnd-npc-interaction-artifacts.md) for a distinct
|
||||
[NPC-occurrence artifact](dnd-npc-occurrence-artifacts.md) for a distinct
|
||||
kind of occurrence.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# D&D NPC Interaction Artifact
|
||||
# D&D NPC Occurrence Artifact
|
||||
|
||||
This contract defines the durable occurrence list produced by
|
||||
`dnd/npc-interactions`. It records discrete, source-grounded interactions with
|
||||
`dnd/npc-occurrences`. It records discrete, source-grounded occurrences with
|
||||
NPCs already present in a normalized registry; it does not extend that registry
|
||||
or summarize the session.
|
||||
|
||||
@@ -27,13 +27,13 @@ Each occurrence has these required fields:
|
||||
| --- | --- |
|
||||
| `npc_id` | Exact durable ID from the required NPC registry. |
|
||||
| `name` | Non-empty canonical display name from the required NPC registry. |
|
||||
| `kind` | One of the interaction categories below. |
|
||||
| `kind` | One of the occurrence categories below. |
|
||||
| `source_refs` | One or more transcript evidence ranges. |
|
||||
|
||||
Each source reference has exactly `source_id`, `start_unit_id`, and
|
||||
`end_unit_id`. It identifies an inclusive range in the current transcript;
|
||||
unit IDs are positive and the start may not follow the end. Extraction evidence
|
||||
for an interaction is confined to its accepted chunk.
|
||||
for an occurrence is confined to its accepted chunk.
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -50,7 +50,7 @@ for an interaction is confined to its accepted chunk.
|
||||
}
|
||||
```
|
||||
|
||||
## Interaction categories
|
||||
## Occurrence categories
|
||||
|
||||
| Kind | Meaning |
|
||||
| --- | --- |
|
||||
@@ -70,16 +70,16 @@ for uncertain classification.
|
||||
The required normalized [NPC registry artifact](dnd-npc-registry-artifacts.md) resolves
|
||||
the exact `{npc_id, name}` pair. Unknown IDs and names that do not match their
|
||||
ID are rejected; normalization does not repair names by similarity. Registry
|
||||
references are provenance only and never replace an interaction's own evidence.
|
||||
references are provenance only and never replace an occurrence's own evidence.
|
||||
Normalization validates the exact pair, orders and
|
||||
deduplicates exact source references, then orders interactions by valid source
|
||||
deduplicates exact source references, then orders occurrences by valid source
|
||||
chronology, NPC comparison identity, display name, kind, and reference sequence.
|
||||
Only entries with the same NPC ID, canonical name, kind, and complete valid evidence
|
||||
sequence are collapsed; distinct categories or evidence remain separate.
|
||||
|
||||
See the [combat-turn artifact](dnd-combat-turn-artifacts.md) for combat-action
|
||||
occurrences. The [enemy-event artifact](dnd-enemy-event-artifacts.md) consumes
|
||||
only `combat_opponent` interactions as grounding; they never establish an enemy
|
||||
only `combat_opponent` occurrences as grounding; they never establish an enemy
|
||||
event or outcome. The [JSON output contract](json-output.md) defines
|
||||
publication. Pipeline mechanics are described in
|
||||
[D&D module internals](../internal/dnd.md).
|
||||
@@ -70,12 +70,12 @@ descriptor.
|
||||
Only individually identifiable NPC names with transcript evidence belong in
|
||||
this artifact. Groups, generic roles, invented labels, and descriptive
|
||||
enrichment are excluded. Its source references prove registry provenance; they
|
||||
do not become evidence for a spell, interaction, combat, or enemy-event
|
||||
do not become evidence for a spell, occurrence, combat, or enemy-event
|
||||
occurrence.
|
||||
|
||||
This registry can ground actor or caster names in the [spell](dnd-spell-artifacts.md)
|
||||
and [combat-turn](dnd-combat-turn-artifacts.md) artifacts. It is required to
|
||||
resolve the canonical `name` in an [NPC interaction](dnd-npc-interaction-artifacts.md).
|
||||
resolve the canonical `name` in an [NPC occurrence](dnd-npc-occurrence-artifacts.md).
|
||||
The [enemy-event artifact](dnd-enemy-event-artifacts.md) also uses it only for
|
||||
subject grounding and canonical display names.
|
||||
The [JSON output contract](json-output.md) defines publication, and
|
||||
|
||||
@@ -72,7 +72,7 @@ Each `lanes/<safe-lane-id>.json` file is the codec-owned normalized JSON for
|
||||
that lane. Consumers should use the index descriptor’s schema identity rather
|
||||
than infer a lane schema from its name. The current D&D payload contracts are
|
||||
[spells](dnd-spell-artifacts.md), [NPC registry](dnd-npc-registry-artifacts.md),
|
||||
[NPC interactions](dnd-npc-interaction-artifacts.md),
|
||||
[NPC occurrences](dnd-npc-occurrence-artifacts.md),
|
||||
[combat turns](dnd-combat-turn-artifacts.md),
|
||||
[item events](dnd-item-event-artifacts.md),
|
||||
[scene descriptions](dnd-scene-description-artifacts.md), and
|
||||
|
||||
@@ -16,7 +16,7 @@ does not repeat their JSON shapes or schemas.
|
||||
| NPC registry | [NPC registry artifacts](../integrations/dnd-npc-registry-artifacts.md) |
|
||||
| Combat turns | [combat-turn artifacts](../integrations/dnd-combat-turn-artifacts.md) |
|
||||
| Item events | [item-event artifacts](../integrations/dnd-item-event-artifacts.md) |
|
||||
| NPC interactions | [NPC-interaction artifacts](../integrations/dnd-npc-interaction-artifacts.md) |
|
||||
| NPC occurrences | [NPC-occurrence artifacts](../integrations/dnd-npc-occurrence-artifacts.md) |
|
||||
| Scene descriptions | [scene-description artifacts](../integrations/dnd-scene-description-artifacts.md) |
|
||||
| Enemy events | [enemy-event artifacts](../integrations/dnd-enemy-event-artifacts.md) |
|
||||
| Locations | [location artifacts](../integrations/dnd-location-artifacts.md) |
|
||||
@@ -123,7 +123,7 @@ combine results from distinct scenes, so it intentionally does not apply that
|
||||
rule. Configuration owns the exact validator key and chain position.
|
||||
|
||||
Normalizers are deterministic for spells, combat turns, item events, NPC
|
||||
interactions, scene descriptions, enemy events, and location occurrences. They canonicalize display
|
||||
occurrences, scene descriptions, enemy events, and location occurrences. They canonicalize display
|
||||
values and evidence, use source-document order for stable output, and issue
|
||||
bounded warnings for changes or collapsed duplicates. The NPC and location
|
||||
normalizers are intentional exceptions: each first produces a deterministic
|
||||
@@ -141,12 +141,12 @@ immutable, validated projection for each operation. External files are checked
|
||||
during preparation, while generated artifacts are resolved at the handoff.
|
||||
|
||||
NPC registries are names-only grounding projections: they may canonicalize
|
||||
actors for spells and combat turns and are required for NPC interactions, but
|
||||
actors for spells and combat turns and are required for NPC occurrences, but
|
||||
they do not supply evidence. Scene-description registries are eligibility-only
|
||||
projections: they retain the current chunk’s classification data, not scene
|
||||
prose or evidence, and exist to route combat extraction. Enemy-event extraction
|
||||
also projects combat turns to `actor` and `turn_kind` and filters NPC
|
||||
interactions to `combat_opponent` names and kinds. Location registries project
|
||||
occurrences to `combat_opponent` names and kinds. Location registries project
|
||||
ordered `{id, name}` pairs to location-occurrence extraction and normalization;
|
||||
exact ID/name matching keeps same-name locations distinguishable. These compact
|
||||
projections, like NPC grounding, are source-free guidance and never event
|
||||
@@ -163,9 +163,9 @@ shared helper changes.
|
||||
| NPCs | Does not consume an NPC registry. Its normalizer is the LLM-assisted reconciliation exception described above. |
|
||||
| Combat turns | Requires a scene-description artifact. It calls the LLM only for an exact `combat` classification; exact non-combat classifications return an accepted empty result, while missing or mismatched classifications return an empty result with a bounded warning. Optional NPC grounding never becomes evidence. |
|
||||
| Item events | Uses campaign context for disambiguation but has no NPC-registry or scene-description dependency. |
|
||||
| NPC interactions | Requires the normalized NPC registry at extraction and normalization, using it for canonical actor grounding only. |
|
||||
| NPC occurrences | Requires the normalized NPC registry at extraction and normalization, using it for canonical actor grounding only. |
|
||||
| Scene descriptions | Produces the classifications consumed by combat routing; it does not consume an NPC registry or provide evidence for combat artifacts. |
|
||||
| Enemy events | Requires NPC, scene-description, combat-turn, and NPC-interaction artifacts. It calls the LLM only for an exact `combat` classification, records ordered observations rather than terminal state, and normalizes recognized names through the NPC registry while preserving grounded collective labels. |
|
||||
| Enemy events | Requires NPC, scene-description, combat-turn, and NPC-occurrence artifacts. It calls the LLM only for an exact `combat` classification, records ordered observations rather than terminal state, and normalizes recognized names through the NPC registry while preserving grounded collective labels. |
|
||||
| Locations | Produces a source-anchored, session-scoped registry. Its LLM-assisted reconciliation is proposal-only and never collapses same-name places without validated identity and evidence rules. |
|
||||
| Location occurrences | Requires the normalized location registry for both extraction and normalization. Its [durable occurrence categories](../integrations/dnd-location-occurrence-artifacts.md#occurrence-categories) distinguish explicit speculation from unsupported inference; the deterministic normalizer enforces exact registry grounding and never turns registry provenance into occurrence evidence. |
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ pipelines:
|
||||
- npc_registry
|
||||
- spells
|
||||
- combat-turns
|
||||
- npc-interactions
|
||||
- npc-occurrences
|
||||
- enemy-events
|
||||
steps:
|
||||
# Establish session-wide reference artifacts alongside independent item events.
|
||||
@@ -110,12 +110,12 @@ pipelines:
|
||||
retries: 2
|
||||
merge: appendorder
|
||||
normalize: dnd/combat-turns
|
||||
npc-interactions:
|
||||
npc-occurrences:
|
||||
extract:
|
||||
module: dnd/npc-interactions
|
||||
module: dnd/npc-occurrences
|
||||
retries: 2
|
||||
merge: appendorder
|
||||
normalize: dnd/npc-interactions
|
||||
normalize: dnd/npc-occurrences
|
||||
location-occurrences:
|
||||
extract:
|
||||
module: dnd/location-occurrences
|
||||
@@ -136,10 +136,10 @@ pipelines:
|
||||
artifact:
|
||||
step: extract-events
|
||||
lane: combat-turns
|
||||
npc_interactions:
|
||||
npc_occurrences:
|
||||
artifact:
|
||||
step: extract-events
|
||||
lane: npc-interactions
|
||||
lane: npc-occurrences
|
||||
artifacts:
|
||||
enemy-events:
|
||||
extract:
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
itemevents "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/itemevents"
|
||||
locationoccurrences "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationoccurrences"
|
||||
locations "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locations"
|
||||
npcinteractions "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcinteractions"
|
||||
npcoccurrences "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcoccurrences"
|
||||
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
||||
scenedescriptions "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/scenedescriptions"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/spells"
|
||||
@@ -58,7 +58,7 @@ func TestProductionEnemyEventConfigurationResolvesGeneratedHandoffs(t *testing.T
|
||||
"npc_registry": {step: "describe-session", lane: "npc_registry"},
|
||||
"scene_descriptions": {step: "describe-session", lane: "scene-descriptions"},
|
||||
"combat_turns": {step: "extract-events", lane: "combat-turns"},
|
||||
"npc_interactions": {step: "extract-events", lane: "npc-interactions"},
|
||||
"npc_occurrences": {step: "extract-events", lane: "npc-occurrences"},
|
||||
} {
|
||||
binding, found := generatedReferenceBinding(lane.ExtractReferences.Bindings, slot)
|
||||
if !found || binding.Artifact.Step != want.step || binding.Artifact.Lane != want.lane {
|
||||
@@ -78,7 +78,7 @@ func TestProductionEnemyEventConfigurationResolvesGeneratedHandoffs(t *testing.T
|
||||
if !ok || !reflect.DeepEqual(normalizeSpec.Requires, []string{"merged"}) || !reflect.DeepEqual(normalizeSpec.Provides, []string{"normalized"}) {
|
||||
t.Fatalf("enemy event normalizer spec = %#v, want merged/normalized capabilities", normalizeSpec)
|
||||
}
|
||||
for _, slot := range []string{"npc_registry", "scene_descriptions", "combat_turns", "npc_interactions"} {
|
||||
for _, slot := range []string{"npc_registry", "scene_descriptions", "combat_turns", "npc_occurrences"} {
|
||||
if !hasReferenceSlot(extractSpec.ReferenceSlots, slot) {
|
||||
t.Fatalf("enemy event extractor slots = %#v, want %q", extractSpec.ReferenceSlots, slot)
|
||||
}
|
||||
@@ -171,9 +171,9 @@ func TestMaintainedCompleteExampleProducesEnemyEventsThroughGeneratedHandoffs(t
|
||||
t.Fatalf("enemy event session = %q, want shared session", request.SessionID)
|
||||
}
|
||||
for slot, required := range map[string]string{
|
||||
"npc_registry": "Kesh",
|
||||
"combat_turns": "Kesh",
|
||||
"npc_interactions": "Kesh",
|
||||
"npc_registry": "Kesh",
|
||||
"combat_turns": "Kesh",
|
||||
"npc_occurrences": "Kesh",
|
||||
} {
|
||||
input, ok := request.Inputs[slot]
|
||||
if !ok || !strings.Contains(string(input.Content), required) || strings.Contains(string(input.Content), "source_refs") || strings.Contains(string(input.Content), "start_unit_id") {
|
||||
@@ -257,7 +257,7 @@ func (client *enemyEventLLMClient) CompleteStructured(ctx context.Context, reque
|
||||
content = []byte(`{"events":[]}`)
|
||||
case combat.PromptID:
|
||||
content = []byte(`{"combat_turns":[{"actor":"Kesh","turn_kind":"turn","source_refs":[{"start_unit_id":8,"end_unit_id":8}]}]}`)
|
||||
case npcinteractions.PromptID:
|
||||
case npcoccurrences.PromptID:
|
||||
if combatScene {
|
||||
var registry struct {
|
||||
NPCs []struct {
|
||||
|
||||
@@ -8,16 +8,16 @@ import (
|
||||
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||
interactioncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcinteractions"
|
||||
interactionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcinteractions"
|
||||
occurrencecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcoccurrences"
|
||||
occurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcoccurrences"
|
||||
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
||||
interactionnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcinteractions"
|
||||
occurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcoccurrences"
|
||||
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcregistry"
|
||||
)
|
||||
|
||||
func TestProductionNPCInteractionPipelineResolvesAndPrepares(t *testing.T) {
|
||||
func TestProductionNPCOccurrencePipelineResolvesAndPrepares(t *testing.T) {
|
||||
components := productionTestComponents(t)
|
||||
resolved, err := pipeline.ResolvePipeline(npcInteractionProfile(pipeline.GeneratedReference("npc-registry", "npc_registry")), pipeline.ResolveOptions{}, catalogFromRegistries(components.registries))
|
||||
resolved, err := pipeline.ResolvePipeline(npcOccurrenceProfile(pipeline.GeneratedReference("npc-registry", "npc_registry")), pipeline.ResolveOptions{}, catalogFromRegistries(components.registries))
|
||||
if err != nil {
|
||||
t.Fatalf("ResolvePipeline() error = %v", err)
|
||||
}
|
||||
@@ -25,8 +25,8 @@ func TestProductionNPCInteractionPipelineResolvesAndPrepares(t *testing.T) {
|
||||
t.Fatalf("resolved pipeline = %#v", resolved)
|
||||
}
|
||||
lane := resolved.Steps[1].ArtifactLanes[0]
|
||||
if lane.ArtifactKind != dnd.NPCOccurrenceListKind || lane.Extract.Module != interactionextract.Key || lane.Normalize.Module != interactionnormalize.Key {
|
||||
t.Fatalf("interaction lane = %#v", lane)
|
||||
if lane.ArtifactKind != dnd.NPCOccurrenceListKind || lane.Extract.Module != occurrenceextract.Key || lane.Normalize.Module != occurrencenormalize.Key {
|
||||
t.Fatalf("occurrence lane = %#v", lane)
|
||||
}
|
||||
for _, bindings := range [][]pipeline.ReferenceBinding{lane.ExtractReferences.Bindings, lane.NormalizeReferences.Bindings} {
|
||||
if len(bindings) != 1 || bindings[0].SlotName != "npc_registry" || bindings[0].Artifact == nil || bindings[0].Artifact.Step != "npc-registry" || bindings[0].Artifact.Lane != "npc_registry" {
|
||||
@@ -39,15 +39,15 @@ func TestProductionNPCInteractionPipelineResolvesAndPrepares(t *testing.T) {
|
||||
|
||||
catalog := catalogFromRegistries(components.registries)
|
||||
codecSpec, ok := catalog.ArtifactCodecs.Spec(dnd.NPCOccurrenceListKind)
|
||||
if !ok || codecSpec.Schema.ID != interactioncodec.SchemaID || codecSpec.Schema.Version != interactioncodec.SchemaVersion {
|
||||
t.Fatalf("NPC interaction codec spec = %#v", codecSpec)
|
||||
if !ok || codecSpec.Schema.ID != occurrencecodec.SchemaID || codecSpec.Schema.Version != occurrencecodec.SchemaVersion {
|
||||
t.Fatalf("NPC occurrence codec spec = %#v", codecSpec)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProductionNPCInteractionReferencesRequireEarlierCompatibleProducer(t *testing.T) {
|
||||
func TestProductionNPCOccurrenceReferencesRequireEarlierCompatibleProducer(t *testing.T) {
|
||||
components := productionTestComponents(t)
|
||||
catalog := catalogFromRegistries(components.registries)
|
||||
laterProfile := npcInteractionProfile(pipeline.GeneratedReference("npc-registry", "npc_registry"))
|
||||
laterProfile := npcOccurrenceProfile(pipeline.GeneratedReference("npc-registry", "npc_registry"))
|
||||
laterProfile.Steps[0].ID = "seed"
|
||||
laterProfile.Steps[0].Artifacts["seed"] = laterProfile.Steps[0].Artifacts["npc_registry"]
|
||||
delete(laterProfile.Steps[0].Artifacts, "npc_registry")
|
||||
@@ -60,10 +60,10 @@ func TestProductionNPCInteractionReferencesRequireEarlierCompatibleProducer(t *t
|
||||
profile pipeline.PipelineProfile
|
||||
want string
|
||||
}{
|
||||
{name: "missing", profile: npcInteractionProfile(pipeline.ReferenceSource{}), want: "source must not be empty"},
|
||||
{name: "same step", profile: npcInteractionProfile(pipeline.GeneratedReference("interactions", "interactions")), want: "earlier step"},
|
||||
{name: "missing", profile: npcOccurrenceProfile(pipeline.ReferenceSource{}), want: "source must not be empty"},
|
||||
{name: "same step", profile: npcOccurrenceProfile(pipeline.GeneratedReference("occurrences", "occurrences")), want: "earlier step"},
|
||||
{name: "later step", profile: laterProfile, want: "earlier step"},
|
||||
{name: "wrong artifact kind", profile: npcInteractionProfile(pipeline.GeneratedReference("npc-registry", "npc_registry")), want: "does not accept artifact kind"},
|
||||
{name: "wrong artifact kind", profile: npcOccurrenceProfile(pipeline.GeneratedReference("npc-registry", "npc_registry")), want: "does not accept artifact kind"},
|
||||
}
|
||||
tests[3].profile.Steps[0].Artifacts["npc_registry"] = pipeline.ArtifactLaneProfile{Extract: pipeline.Binding("dnd/spells")}
|
||||
for _, test := range tests {
|
||||
@@ -76,7 +76,7 @@ func TestProductionNPCInteractionReferencesRequireEarlierCompatibleProducer(t *t
|
||||
}
|
||||
}
|
||||
|
||||
func TestProductionNPCInteractionReferencesRejectIncompatibleExternalRegistries(t *testing.T) {
|
||||
func TestProductionNPCOccurrenceReferencesRejectIncompatibleExternalRegistries(t *testing.T) {
|
||||
components := productionTestComponents(t)
|
||||
catalog := catalogFromRegistries(components.registries)
|
||||
root := t.TempDir()
|
||||
@@ -95,7 +95,7 @@ func TestProductionNPCInteractionReferencesRejectIncompatibleExternalRegistries(
|
||||
if err := os.WriteFile(path, []byte(test.content), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resolved, err := pipeline.ResolvePipeline(npcInteractionProfile(pipeline.ExternalReference(path)), pipeline.ResolveOptions{}, catalog)
|
||||
resolved, err := pipeline.ResolvePipeline(npcOccurrenceProfile(pipeline.ExternalReference(path)), pipeline.ResolveOptions{}, catalog)
|
||||
if err != nil {
|
||||
t.Fatalf("ResolvePipeline() error = %v", err)
|
||||
}
|
||||
@@ -116,9 +116,9 @@ func TestProductionNPCInteractionReferencesRejectIncompatibleExternalRegistries(
|
||||
}
|
||||
}
|
||||
|
||||
func npcInteractionProfile(reference pipeline.ReferenceSource) pipeline.PipelineProfile {
|
||||
func npcOccurrenceProfile(reference pipeline.ReferenceSource) pipeline.PipelineProfile {
|
||||
profile := pipeline.PipelineProfile{
|
||||
ID: "dnd-npc-interactions",
|
||||
ID: "dnd-npc-occurrences",
|
||||
Input: pipeline.Binding("seriatim"),
|
||||
Chunk: pipeline.ModuleBinding{Module: "generic", Options: map[string]any{"max_units": 1}},
|
||||
Output: pipeline.Binding("json"),
|
||||
@@ -126,8 +126,8 @@ func npcInteractionProfile(reference pipeline.ReferenceSource) pipeline.Pipeline
|
||||
{ID: "npc-registry", Artifacts: map[string]pipeline.ArtifactLaneProfile{
|
||||
"npc_registry": {Extract: pipeline.Binding(npcextract.Key), Normalize: pipeline.Binding(npcnormalize.Key)},
|
||||
}},
|
||||
{ID: "interactions", References: map[string]pipeline.ReferenceSource{"npc_registry": reference}, Artifacts: map[string]pipeline.ArtifactLaneProfile{
|
||||
"interactions": {Extract: pipeline.Binding(interactionextract.Key), Normalize: pipeline.Binding(interactionnormalize.Key)},
|
||||
{ID: "occurrences", References: map[string]pipeline.ReferenceSource{"npc_registry": reference}, Artifacts: map[string]pipeline.ArtifactLaneProfile{
|
||||
"occurrences": {Extract: pipeline.Binding(occurrenceextract.Key), Normalize: pipeline.Binding(occurrencenormalize.Key)},
|
||||
}},
|
||||
},
|
||||
}
|
||||
@@ -58,7 +58,7 @@ func TestMaintainedExamplesLoadResolveAndList(t *testing.T) {
|
||||
t.Fatalf("materialize maintained example references for %q: %v", pipelineID, err)
|
||||
}
|
||||
if example.name == "complete" {
|
||||
if got := exampleStepLaneIDs(materialized); strings.Join(got, "|") != "describe-session:item-events,locations,npc_registry,scene-descriptions|extract-events:combat-turns,location-occurrences,npc-interactions,spells|track-enemies:enemy-events" {
|
||||
if got := exampleStepLaneIDs(materialized); strings.Join(got, "|") != "describe-session:item-events,locations,npc_registry,scene-descriptions|extract-events:combat-turns,location-occurrences,npc-occurrences,spells|track-enemies:enemy-events" {
|
||||
t.Fatalf("complete example steps and lanes = %v, want the documented D&D extractor composition", got)
|
||||
}
|
||||
locationLane := referenceContractLane(t, materialized, "locations")
|
||||
@@ -102,7 +102,7 @@ func TestMaintainedExamplesLoadResolveAndList(t *testing.T) {
|
||||
"npc_registry": {step: "describe-session", lane: "npc_registry"},
|
||||
"scene_descriptions": {step: "describe-session", lane: "scene-descriptions"},
|
||||
"combat_turns": {step: "extract-events", lane: "combat-turns"},
|
||||
"npc_interactions": {step: "extract-events", lane: "npc-interactions"},
|
||||
"npc_occurrences": {step: "extract-events", lane: "npc-occurrences"},
|
||||
} {
|
||||
binding, found := generatedReferenceBinding(enemyEventLane.ExtractReferences.Bindings, slot)
|
||||
if !found || binding.Artifact.Step != want.step || binding.Artifact.Lane != want.lane {
|
||||
|
||||
@@ -165,7 +165,7 @@ func TestProductionCatalogCoversMaintainedConfigurations(t *testing.T) {
|
||||
{stage: pipeline.StageExtract, key: "dnd/npc-registry", want: contracts.ExecutionClassLLMBacked},
|
||||
{stage: pipeline.StageExtract, key: "dnd/combat-turns", want: contracts.ExecutionClassLLMBacked},
|
||||
{stage: pipeline.StageExtract, key: "dnd/item-events", want: contracts.ExecutionClassLLMBacked},
|
||||
{stage: pipeline.StageExtract, key: "dnd/npc-interactions", want: contracts.ExecutionClassLLMBacked},
|
||||
{stage: pipeline.StageExtract, key: "dnd/npc-occurrences", want: contracts.ExecutionClassLLMBacked},
|
||||
{stage: pipeline.StageExtract, key: "dnd/scene-descriptions", want: contracts.ExecutionClassLLMBacked},
|
||||
{stage: pipeline.StageExtract, key: enemyeventextract.Key, want: contracts.ExecutionClassLLMBacked},
|
||||
{stage: pipeline.StageMerge, key: "appendorder", want: contracts.ExecutionClassDeterministic},
|
||||
@@ -174,7 +174,7 @@ func TestProductionCatalogCoversMaintainedConfigurations(t *testing.T) {
|
||||
{stage: pipeline.StageNormalize, key: "dnd/npc-registry", want: contracts.ExecutionClassLLMBacked},
|
||||
{stage: pipeline.StageNormalize, key: "dnd/combat-turns", want: contracts.ExecutionClassDeterministic},
|
||||
{stage: pipeline.StageNormalize, key: "dnd/item-events", want: contracts.ExecutionClassDeterministic},
|
||||
{stage: pipeline.StageNormalize, key: "dnd/npc-interactions", want: contracts.ExecutionClassDeterministic},
|
||||
{stage: pipeline.StageNormalize, key: "dnd/npc-occurrences", want: contracts.ExecutionClassDeterministic},
|
||||
{stage: pipeline.StageNormalize, key: "dnd/scene-descriptions", want: contracts.ExecutionClassDeterministic},
|
||||
{stage: pipeline.StageNormalize, key: enemyeventnormalize.Key, want: contracts.ExecutionClassDeterministic},
|
||||
{stage: pipeline.StageOutput, key: "json", want: contracts.ExecutionClassDeterministic},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Package npcinteractions encodes durable D&D NPC occurrence artifacts.
|
||||
package npcinteractions
|
||||
// Package npcoccurrences encodes durable D&D NPC occurrence artifacts.
|
||||
package npcoccurrences
|
||||
|
||||
import (
|
||||
"embed"
|
||||
@@ -1,4 +1,4 @@
|
||||
package npcinteractions
|
||||
package npcoccurrences
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -111,7 +111,7 @@ func TestCodecStrictlyRejectsMalformedUnknownAndTrailingJSON(t *testing.T) {
|
||||
for _, test := range []struct{ name, raw, want string }{
|
||||
{"malformed", `{`, "decode dnd npc occurrence list"},
|
||||
{"unknown top-level", `{"occurrences":[],"unexpected":true}`, "unknown field"},
|
||||
{"unknown interaction field", strings.Replace(validJSON, `"kind":"dialogue"`, `"kind":"dialogue","unexpected":true`, 1), "unknown field"},
|
||||
{"unknown occurrence field", strings.Replace(validJSON, `"kind":"dialogue"`, `"kind":"dialogue","unexpected":true`, 1), "unknown field"},
|
||||
{"unknown source reference field", strings.Replace(validJSON, `"end_unit_id":1`, `"end_unit_id":1,"unexpected":true`, 1), "unknown field"},
|
||||
{"trailing", `{"occurrences":[]} {}`, "multiple JSON values"},
|
||||
} {
|
||||
@@ -147,7 +147,7 @@ func TestCodecRejectsRequiredShapeEnumAndReferenceBoundaries(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodecAcceptsEveryInteractionKind(t *testing.T) {
|
||||
func TestCodecAcceptsEveryOccurrenceKind(t *testing.T) {
|
||||
for _, kind := range []dnd.NPCOccurrenceKind{
|
||||
dnd.NPCOccurrenceKindMentioned,
|
||||
dnd.NPCOccurrenceKindNoncombatPresence,
|
||||
@@ -49,8 +49,8 @@ func TestExtractMapsEnemyEventsInSourceOrder(t *testing.T) {
|
||||
if got := string(request.Inputs[CombatTurnReferenceSlot].Content); !strings.Contains(got, `"actor":"Ashfang"`) || strings.Contains(got, "source_ref") {
|
||||
t.Fatalf("combat grounding = %s", got)
|
||||
}
|
||||
if got := string(request.Inputs[NPCInteractionReferenceSlot].Content); !strings.Contains(got, `"kind":"combat_opponent"`) || strings.Contains(got, "Aria") {
|
||||
t.Fatalf("interaction grounding = %s", got)
|
||||
if got := string(request.Inputs[NPCOccurrenceReferenceSlot].Content); !strings.Contains(got, `"kind":"combat_opponent"`) || strings.Contains(got, "Aria") {
|
||||
t.Fatalf("occurrence grounding = %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||
combatturncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/combatturns"
|
||||
interactioncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcinteractions"
|
||||
occurrencecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcoccurrences"
|
||||
npccodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcregistry"
|
||||
scenecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/scenedescriptions"
|
||||
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
|
||||
@@ -27,7 +27,7 @@ const (
|
||||
NPCRegistryReferenceSlot = npcregistry.ReferenceSlot
|
||||
SceneDescriptionReferenceSlot = sceneregistry.ReferenceSlot
|
||||
CombatTurnReferenceSlot = "combat_turns"
|
||||
NPCInteractionReferenceSlot = "npc_interactions"
|
||||
NPCOccurrenceReferenceSlot = "npc_occurrences"
|
||||
ReferenceMaxBytes = 1048576
|
||||
promptProjectionMediaType = "application/json"
|
||||
)
|
||||
@@ -67,10 +67,10 @@ func referenceSlots() []contracts.ReferenceSlot {
|
||||
MaxBytes: ReferenceMaxBytes,
|
||||
},
|
||||
contracts.ReferenceSlot{
|
||||
Name: NPCInteractionReferenceSlot,
|
||||
Description: "Required NPC-interaction artifact used only as source-free enemy-event grounding.",
|
||||
Name: NPCOccurrenceReferenceSlot,
|
||||
Description: "Required NPC-occurrence artifact used only as source-free enemy-event grounding.",
|
||||
Required: true,
|
||||
AcceptedMediaTypes: []string{interactioncodec.MediaType},
|
||||
AcceptedMediaTypes: []string{occurrencecodec.MediaType},
|
||||
AcceptedArtifactKinds: []contracts.ArtifactKind{dnd.NPCOccurrenceListKind},
|
||||
MaxBytes: ReferenceMaxBytes,
|
||||
},
|
||||
@@ -86,14 +86,14 @@ type groundingResolver struct {
|
||||
npcs *npcregistry.Resolver
|
||||
scenes *sceneregistry.Resolver
|
||||
|
||||
combatTurns *contracts.LLMInputMaterial
|
||||
npcInteractions *contracts.LLMInputMaterial
|
||||
combatTurns *contracts.LLMInputMaterial
|
||||
npcOccurrences *contracts.LLMInputMaterial
|
||||
}
|
||||
|
||||
type grounding struct {
|
||||
npcInput contracts.LLMInputMaterial
|
||||
combatTurnInput contracts.LLMInputMaterial
|
||||
npcInteractionInput contracts.LLMInputMaterial
|
||||
npcInput contracts.LLMInputMaterial
|
||||
combatTurnInput contracts.LLMInputMaterial
|
||||
npcOccurrenceInput contracts.LLMInputMaterial
|
||||
}
|
||||
|
||||
func newGroundingResolver(references contracts.ReferenceSet) (*groundingResolver, error) {
|
||||
@@ -109,15 +109,15 @@ func newGroundingResolver(references contracts.ReferenceSet) (*groundingResolver
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
npcInteractions, err := prepareNPCInteractionInput(references)
|
||||
npcOccurrences, err := prepareNPCOccurrenceInput(references)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &groundingResolver{
|
||||
npcs: npcs,
|
||||
scenes: scenes,
|
||||
combatTurns: combatTurns,
|
||||
npcInteractions: npcInteractions,
|
||||
npcs: npcs,
|
||||
scenes: scenes,
|
||||
combatTurns: combatTurns,
|
||||
npcOccurrences: npcOccurrences,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -136,14 +136,14 @@ func (r *groundingResolver) Resolve(references contracts.ReferenceSet) (groundin
|
||||
if err != nil {
|
||||
return grounding{}, err
|
||||
}
|
||||
npcInteractions, err := resolveInput(references, NPCInteractionReferenceSlot, r.npcInteractions, prepareNPCInteractionInput)
|
||||
npcOccurrences, err := resolveInput(references, NPCOccurrenceReferenceSlot, r.npcOccurrences, prepareNPCOccurrenceInput)
|
||||
if err != nil {
|
||||
return grounding{}, err
|
||||
}
|
||||
return grounding{
|
||||
npcInput: npcs.PromptInput(),
|
||||
combatTurnInput: combatTurns,
|
||||
npcInteractionInput: npcInteractions,
|
||||
npcInput: npcs.PromptInput(),
|
||||
combatTurnInput: combatTurns,
|
||||
npcOccurrenceInput: npcOccurrences,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -188,9 +188,9 @@ func resolveInput(references contracts.ReferenceSet, slot string, seeded *contra
|
||||
|
||||
func (g grounding) PromptInputs() contracts.LLMInputSet {
|
||||
return contracts.LLMInputSet{
|
||||
NPCRegistryReferenceSlot: g.npcInput.Clone(),
|
||||
CombatTurnReferenceSlot: g.combatTurnInput.Clone(),
|
||||
NPCInteractionReferenceSlot: g.npcInteractionInput.Clone(),
|
||||
NPCRegistryReferenceSlot: g.npcInput.Clone(),
|
||||
CombatTurnReferenceSlot: g.combatTurnInput.Clone(),
|
||||
NPCOccurrenceReferenceSlot: g.npcOccurrenceInput.Clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,22 +212,22 @@ func prepareCombatTurnInput(references contracts.ReferenceSet) (*contracts.LLMIn
|
||||
return newPromptInput(CombatTurnReferenceSlot, content), nil
|
||||
}
|
||||
|
||||
func prepareNPCInteractionInput(references contracts.ReferenceSet) (*contracts.LLMInputMaterial, error) {
|
||||
item, ok, err := referenceItem(references, NPCInteractionReferenceSlot, interactioncodec.MediaType)
|
||||
func prepareNPCOccurrenceInput(references contracts.ReferenceSet) (*contracts.LLMInputMaterial, error) {
|
||||
item, ok, err := referenceItem(references, NPCOccurrenceReferenceSlot, occurrencecodec.MediaType)
|
||||
if err != nil || !ok {
|
||||
return nil, err
|
||||
}
|
||||
value, err := interactioncodec.New().Decode(item.Content)
|
||||
value, err := occurrencecodec.New().Decode(item.Content)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode NPC-interaction grounding: invalid approved NPC-interaction JSON")
|
||||
return nil, fmt.Errorf("decode NPC-occurrence grounding: invalid approved NPC-occurrence JSON")
|
||||
}
|
||||
content, err := json.Marshal(struct {
|
||||
Interactions []npcInteractionProjection `json:"npc_interactions"`
|
||||
}{Interactions: projectNPCInteractions(value.Occurrences)})
|
||||
Occurrences []npcOccurrenceProjection `json:"npc_occurrences"`
|
||||
}{Occurrences: projectNPCOccurrences(value.Occurrences)})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encode NPC-interaction grounding: %w", err)
|
||||
return nil, fmt.Errorf("encode NPC-occurrence grounding: %w", err)
|
||||
}
|
||||
return newPromptInput(NPCInteractionReferenceSlot, content), nil
|
||||
return newPromptInput(NPCOccurrenceReferenceSlot, content), nil
|
||||
}
|
||||
|
||||
func referenceItem(references contracts.ReferenceSet, slotName, expectedMediaType string) (contracts.ReferenceItem, bool, error) {
|
||||
@@ -271,16 +271,16 @@ func projectCombatTurns(turns []dnd.CombatTurn) []combatTurnProjection {
|
||||
return projection
|
||||
}
|
||||
|
||||
type npcInteractionProjection struct {
|
||||
type npcOccurrenceProjection struct {
|
||||
Name string `json:"name"`
|
||||
Kind dnd.NPCOccurrenceKind `json:"kind"`
|
||||
}
|
||||
|
||||
func projectNPCInteractions(occurrences []dnd.NPCOccurrence) []npcInteractionProjection {
|
||||
projection := make([]npcInteractionProjection, 0, len(occurrences))
|
||||
func projectNPCOccurrences(occurrences []dnd.NPCOccurrence) []npcOccurrenceProjection {
|
||||
projection := make([]npcOccurrenceProjection, 0, len(occurrences))
|
||||
for _, occurrence := range occurrences {
|
||||
if occurrence.Kind == dnd.NPCOccurrenceKindCombatOpponent {
|
||||
projection = append(projection, npcInteractionProjection{Name: occurrence.Name, Kind: occurrence.Kind})
|
||||
projection = append(projection, npcOccurrenceProjection{Name: occurrence.Name, Kind: occurrence.Kind})
|
||||
}
|
||||
}
|
||||
return projection
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||
combatturncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/combatturns"
|
||||
interactioncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcinteractions"
|
||||
occurrencecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcoccurrences"
|
||||
npccodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcregistry"
|
||||
scenecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/scenedescriptions"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/identity"
|
||||
@@ -36,7 +36,7 @@ func TestReferenceSlotsDescribeRequiredTypedArtifacts(t *testing.T) {
|
||||
{NPCRegistryReferenceSlot, dnd.NPCRegistryKind},
|
||||
{SceneDescriptionReferenceSlot, dnd.SceneDescriptionListKind},
|
||||
{CombatTurnReferenceSlot, dnd.CombatTurnListKind},
|
||||
{NPCInteractionReferenceSlot, dnd.NPCOccurrenceListKind},
|
||||
{NPCOccurrenceReferenceSlot, dnd.NPCOccurrenceListKind},
|
||||
} {
|
||||
slot, ok := byName[want.name]
|
||||
if !ok || !slot.Required || slot.MaxBytes != ReferenceMaxBytes || len(slot.AcceptedMediaTypes) != 1 || slot.AcceptedMediaTypes[0] != "application/json" || len(slot.AcceptedArtifactKinds) != 1 || slot.AcceptedArtifactKinds[0] != want.kind {
|
||||
@@ -61,9 +61,9 @@ func TestGroundingProducesExactSourceFreePromptInputs(t *testing.T) {
|
||||
}
|
||||
inputs := resolved.PromptInputs()
|
||||
want := map[string]string{
|
||||
NPCRegistryReferenceSlot: `{"npcs":[{"name":"Ashfang"}]}`,
|
||||
CombatTurnReferenceSlot: `{"combat_turns":[{"actor":"Ashfang","turn_kind":"turn"},{"actor":"Aria","turn_kind":"reaction"}]}`,
|
||||
NPCInteractionReferenceSlot: `{"npc_interactions":[{"name":"Ashfang","kind":"combat_opponent"}]}`,
|
||||
NPCRegistryReferenceSlot: `{"npcs":[{"name":"Ashfang"}]}`,
|
||||
CombatTurnReferenceSlot: `{"combat_turns":[{"actor":"Ashfang","turn_kind":"turn"},{"actor":"Aria","turn_kind":"reaction"}]}`,
|
||||
NPCOccurrenceReferenceSlot: `{"npc_occurrences":[{"name":"Ashfang","kind":"combat_opponent"}]}`,
|
||||
}
|
||||
if len(inputs) != len(want) {
|
||||
t.Fatalf("PromptInputs() = %#v", inputs)
|
||||
@@ -153,7 +153,7 @@ func TestGroundingRejectsMissingAndInvalidReferences(t *testing.T) {
|
||||
{NPCRegistryReferenceSlot, "NPC registry"},
|
||||
{SceneDescriptionReferenceSlot, "scene descriptions"},
|
||||
{CombatTurnReferenceSlot, CombatTurnReferenceSlot},
|
||||
{NPCInteractionReferenceSlot, NPCInteractionReferenceSlot},
|
||||
{NPCOccurrenceReferenceSlot, NPCOccurrenceReferenceSlot},
|
||||
} {
|
||||
t.Run("missing "+test.slot, func(t *testing.T) {
|
||||
resolver, err := newGroundingResolver(withoutSlot(valid, test.slot))
|
||||
@@ -176,7 +176,7 @@ func TestGroundingRejectsMissingAndInvalidReferences(t *testing.T) {
|
||||
set contracts.ReferenceSet
|
||||
}{
|
||||
{"multiple items", replaceSlot(valid, CombatTurnReferenceSlot, contracts.ResolvedReferenceSlot{Items: []contracts.ReferenceItem{{MediaType: "application/json"}, {MediaType: "application/json"}}})},
|
||||
{"malformed durable content", replaceItem(valid, NPCInteractionReferenceSlot, contracts.ReferenceItem{MediaType: "application/json", Content: []byte(`{}`)})},
|
||||
{"malformed durable content", replaceItem(valid, NPCOccurrenceReferenceSlot, contracts.ReferenceItem{MediaType: "application/json", Content: []byte(`{}`)})},
|
||||
{"wrong media type", replaceItem(valid, CombatTurnReferenceSlot, contracts.ReferenceItem{MediaType: "text/plain", Content: valid.Slots[CombatTurnReferenceSlot].Items[0].Content})},
|
||||
{"oversize", replaceItem(valid, CombatTurnReferenceSlot, contracts.ReferenceItem{MediaType: "application/json", Content: make([]byte, ReferenceMaxBytes+1)})},
|
||||
}
|
||||
@@ -220,7 +220,7 @@ func groundingReferences(t *testing.T, enemy string, sceneKind dnd.SceneKind) co
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
interactionContent, err := interactioncodec.New().Encode(dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{
|
||||
interactionContent, err := occurrencecodec.New().Encode(dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{
|
||||
{NPCID: identity.DeriveID(enemy), Name: enemy, Kind: dnd.NPCOccurrenceKindCombatOpponent, SourceRefs: []source.SourceRef{{SourceID: "combat-session", StartUnitID: 1, EndUnitID: 1}}},
|
||||
{NPCID: identity.DeriveID("Aria"), Name: "Aria", Kind: dnd.NPCOccurrenceKindCombatAlly, SourceRefs: []source.SourceRef{{SourceID: "combat-session", StartUnitID: 2, EndUnitID: 2}}},
|
||||
}})
|
||||
@@ -231,7 +231,7 @@ func groundingReferences(t *testing.T, enemy string, sceneKind dnd.SceneKind) co
|
||||
NPCRegistryReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(NPCRegistryReferenceSlot, npcContent)}},
|
||||
SceneDescriptionReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(SceneDescriptionReferenceSlot, sceneContent)}},
|
||||
CombatTurnReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(CombatTurnReferenceSlot, turnContent)}},
|
||||
NPCInteractionReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(NPCInteractionReferenceSlot, interactionContent)}},
|
||||
NPCOccurrenceReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(NPCOccurrenceReferenceSlot, interactionContent)}},
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ func TestRegisterPromptAssetsAndPrepareEnemyEventPrompt(t *testing.T) {
|
||||
|
||||
func TestEnemyEventPromptRequiresGroundingInputs(t *testing.T) {
|
||||
engine := newEnemyEventPromptEngine(t)
|
||||
for _, inputName := range []string{"npc_registry", "combat_turns", "npc_interactions"} {
|
||||
for _, inputName := range []string{"npc_registry", "combat_turns", "npc_occurrences"} {
|
||||
t.Run(inputName, func(t *testing.T) {
|
||||
inputs := enemyEventPromptInputs()
|
||||
delete(inputs, inputName)
|
||||
@@ -79,12 +79,12 @@ func newEnemyEventPromptEngine(t *testing.T) *promptkit.Engine {
|
||||
|
||||
func enemyEventPromptInputs() map[string]promptkit.ArtifactRef {
|
||||
return map[string]promptkit.ArtifactRef{
|
||||
"transcript": promptkit.Inline(`{"value":"enemy-transcript"}`),
|
||||
"players": promptkit.Inline("enemy-player"),
|
||||
"party": promptkit.Inline("enemy-party"),
|
||||
"glossary": promptkit.Inline("enemy-glossary"),
|
||||
"npc_registry": promptkit.Inline(`{"npcs":[{"name":"enemy-npc"}]}`),
|
||||
"combat_turns": promptkit.Inline(`{"combat_turns":[{"actor":"enemy-turn","turn_kind":"turn"}]}`),
|
||||
"npc_interactions": promptkit.Inline(`{"npc_interactions":[{"name":"enemy-opponent","kind":"combat_opponent"}]}`),
|
||||
"transcript": promptkit.Inline(`{"value":"enemy-transcript"}`),
|
||||
"players": promptkit.Inline("enemy-player"),
|
||||
"party": promptkit.Inline("enemy-party"),
|
||||
"glossary": promptkit.Inline("enemy-glossary"),
|
||||
"npc_registry": promptkit.Inline(`{"npcs":[{"name":"enemy-npc"}]}`),
|
||||
"combat_turns": promptkit.Inline(`{"combat_turns":[{"actor":"enemy-turn","turn_kind":"turn"}]}`),
|
||||
"npc_occurrences": promptkit.Inline(`{"npc_occurrences":[{"name":"enemy-opponent","kind":"combat_opponent"}]}`),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package npcinteractions
|
||||
|
||||
type extractionResponse struct {
|
||||
Occurrences []occurrenceResponse `json:"occurrences"`
|
||||
}
|
||||
|
||||
type occurrenceResponse struct {
|
||||
NPCID string `json:"npc_id"`
|
||||
Name string `json:"name"`
|
||||
Kind string `json:"kind"`
|
||||
SourceRefs []interactionSourceRefResponse `json:"source_refs"`
|
||||
}
|
||||
|
||||
type interactionSourceRefResponse struct {
|
||||
StartUnitID int `json:"start_unit_id"`
|
||||
EndUnitID int `json:"end_unit_id"`
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package npcinteractions
|
||||
package npcoccurrences
|
||||
|
||||
import (
|
||||
"sort"
|
||||
@@ -66,7 +66,7 @@ func canonicalOccurrenceList(response extractionResponse, sourceID string) dnd.N
|
||||
return dnd.NPCOccurrenceList{Occurrences: occurrences}
|
||||
}
|
||||
|
||||
func canonicalSourceRefs(refs []interactionSourceRefResponse, sourceID string) []source.SourceRef {
|
||||
func canonicalSourceRefs(refs []occurrenceSourceRefResponse, sourceID string) []source.SourceRef {
|
||||
if refs == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -77,13 +77,13 @@ func canonicalSourceRefs(refs []interactionSourceRefResponse, sourceID string) [
|
||||
return out
|
||||
}
|
||||
|
||||
func occurrenceResponseRefs(refs []source.SourceRef) []interactionSourceRefResponse {
|
||||
func occurrenceResponseRefs(refs []source.SourceRef) []occurrenceSourceRefResponse {
|
||||
if refs == nil {
|
||||
return nil
|
||||
}
|
||||
values := make([]interactionSourceRefResponse, len(refs))
|
||||
values := make([]occurrenceSourceRefResponse, len(refs))
|
||||
for index, ref := range refs {
|
||||
values[index] = interactionSourceRefResponse{StartUnitID: ref.StartUnitID, EndUnitID: ref.EndUnitID}
|
||||
values[index] = occurrenceSourceRefResponse{StartUnitID: ref.StartUnitID, EndUnitID: ref.EndUnitID}
|
||||
}
|
||||
return values
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package npcinteractions
|
||||
package npcoccurrences
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -13,8 +13,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
Key = "dnd/npc-interactions"
|
||||
mappingPolicy = "dnd.npc_interactions.extract_mapping.v2"
|
||||
Key = "dnd/npc-occurrences"
|
||||
mappingPolicy = "dnd.npc_occurrences.extract_mapping.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -28,21 +28,21 @@ var requiredCapabilities = []string{
|
||||
}
|
||||
|
||||
var providedCapabilities = []string{
|
||||
"dnd.npc_interactions",
|
||||
"dnd.npc_occurrences",
|
||||
}
|
||||
|
||||
var referenceSlotDescriptions = shared.ReferenceSlotDescriptions{
|
||||
Glossary: "Optional campaign glossary reference material used only for interaction disambiguation.",
|
||||
Party: "Optional party roster reference material used only for interaction disambiguation.",
|
||||
Players: "Optional player list reference material used only for interaction disambiguation.",
|
||||
Roster: "Deprecated alias for party roster reference material used only for interaction disambiguation.",
|
||||
Glossary: "Optional campaign glossary reference material used only for occurrence disambiguation.",
|
||||
Party: "Optional party roster reference material used only for occurrence disambiguation.",
|
||||
Players: "Optional player list reference material used only for occurrence disambiguation.",
|
||||
Roster: "Deprecated alias for party roster reference material used only for occurrence disambiguation.",
|
||||
}
|
||||
|
||||
func referenceSlots() []contracts.ReferenceSlot {
|
||||
slots := shared.ReferenceSlots(referenceSlotDescriptions)
|
||||
slots = append(slots, contracts.ReferenceSlot{
|
||||
Name: NPCRegistryReferenceSlot,
|
||||
Description: "Required normalized NPC registry used only for interaction identity grounding, never as interaction evidence.",
|
||||
Description: "Required normalized NPC registry used only for occurrence identity grounding, never as occurrence evidence.",
|
||||
Required: true,
|
||||
AcceptedMediaTypes: []string{"application/json"},
|
||||
AcceptedArtifactKinds: []contracts.ArtifactKind{dnd.NPCRegistryKind},
|
||||
@@ -225,5 +225,5 @@ func DecodeOptions(options map[string]any) (Options, error) {
|
||||
}
|
||||
|
||||
func extractorErrorf(format string, args ...any) error {
|
||||
return fmt.Errorf("dnd NPC interactions extractor: "+format, args...)
|
||||
return fmt.Errorf("dnd NPC occurrences extractor: "+format, args...)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package npcinteractions
|
||||
package npcoccurrences
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -17,14 +17,14 @@ import (
|
||||
)
|
||||
|
||||
func TestExtractMapsEveryKindAndOrdersBySourcePosition(t *testing.T) {
|
||||
client := &fakeInteractionsLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{
|
||||
{NPCID: identity.DeriveID("Other"), Name: "Other", Kind: "other", SourceRefs: interactionRefs(30, 30)},
|
||||
{NPCID: identity.DeriveID("Opponent"), Name: "Opponent", Kind: "combat_opponent", SourceRefs: interactionRefs(20, 20)},
|
||||
{NPCID: identity.DeriveID("Ally"), Name: "Ally", Kind: "combat_ally", SourceRefs: interactionRefs(5, 5)},
|
||||
{NPCID: identity.DeriveID("Speaker"), Name: "Speaker", Kind: "dialogue", SourceRefs: append(interactionRefs(2, 2), interactionRefs(2, 2)...)},
|
||||
{NPCID: identity.DeriveID("Present"), Name: "Present", Kind: "noncombat_presence", SourceRefs: interactionRefs(7, 7)},
|
||||
{NPCID: identity.DeriveID("Mentioned"), Name: "Mentioned", Kind: "mentioned", SourceRefs: interactionRefs(10, 10)},
|
||||
{NPCID: identity.DeriveID("Invalid"), Name: "Invalid", Kind: "unsupported", SourceRefs: interactionRefs(0, 0)},
|
||||
client := &fakeOccurrencesLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{
|
||||
{NPCID: identity.DeriveID("Other"), Name: "Other", Kind: "other", SourceRefs: occurrenceRefs(30, 30)},
|
||||
{NPCID: identity.DeriveID("Opponent"), Name: "Opponent", Kind: "combat_opponent", SourceRefs: occurrenceRefs(20, 20)},
|
||||
{NPCID: identity.DeriveID("Ally"), Name: "Ally", Kind: "combat_ally", SourceRefs: occurrenceRefs(5, 5)},
|
||||
{NPCID: identity.DeriveID("Speaker"), Name: "Speaker", Kind: "dialogue", SourceRefs: append(occurrenceRefs(2, 2), occurrenceRefs(2, 2)...)},
|
||||
{NPCID: identity.DeriveID("Present"), Name: "Present", Kind: "noncombat_presence", SourceRefs: occurrenceRefs(7, 7)},
|
||||
{NPCID: identity.DeriveID("Mentioned"), Name: "Mentioned", Kind: "mentioned", SourceRefs: occurrenceRefs(10, 10)},
|
||||
{NPCID: identity.DeriveID("Invalid"), Name: "Invalid", Kind: "unsupported", SourceRefs: occurrenceRefs(0, 0)},
|
||||
}}}
|
||||
references := requiredRegistryReferences(t, "Mentioned", "Speaker", "Present", "Ally", "Opponent", "Other", "Invalid")
|
||||
req := extractionRequest()
|
||||
@@ -34,10 +34,10 @@ func TestExtractMapsEveryKindAndOrdersBySourcePosition(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Extract() error = %v", err)
|
||||
}
|
||||
if got := interactionNames(result.Value); !reflect.DeepEqual(got, []string{"Mentioned", "Speaker", "Present", "Ally", "Opponent", "Other", "Invalid"}) {
|
||||
t.Fatalf("interaction order = %#v", got)
|
||||
if got := occurrenceNames(result.Value); !reflect.DeepEqual(got, []string{"Mentioned", "Speaker", "Present", "Ally", "Opponent", "Other", "Invalid"}) {
|
||||
t.Fatalf("occurrence order = %#v", got)
|
||||
}
|
||||
if got := interactionKinds(result.Value); !reflect.DeepEqual(got, []dnd.NPCOccurrenceKind{
|
||||
if got := occurrenceKinds(result.Value); !reflect.DeepEqual(got, []dnd.NPCOccurrenceKind{
|
||||
dnd.NPCOccurrenceKindMentioned,
|
||||
dnd.NPCOccurrenceKindDialogue,
|
||||
dnd.NPCOccurrenceKindNoncombatPresence,
|
||||
@@ -46,7 +46,7 @@ func TestExtractMapsEveryKindAndOrdersBySourcePosition(t *testing.T) {
|
||||
dnd.NPCOccurrenceKindOther,
|
||||
"unsupported",
|
||||
}) {
|
||||
t.Fatalf("interaction kinds = %#v", got)
|
||||
t.Fatalf("occurrence kinds = %#v", got)
|
||||
}
|
||||
if refs := result.Value.Occurrences[1].SourceRefs; !reflect.DeepEqual(refs, []source.SourceRef{{SourceID: "session-alpha", StartUnitID: 2, EndUnitID: 2}}) {
|
||||
t.Fatalf("canonical source refs = %#v", refs)
|
||||
@@ -59,16 +59,16 @@ func TestExtractMapsEveryKindAndOrdersBySourcePosition(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractUsesDocumentOrderForReferencesAndInteractions(t *testing.T) {
|
||||
client := &fakeInteractionsLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{
|
||||
{NPCID: identity.DeriveID("Later"), Name: "Later", Kind: "dialogue", SourceRefs: interactionRefs(10, 10)},
|
||||
{NPCID: identity.DeriveID("First"), Name: "First", Kind: "mentioned", SourceRefs: []interactionSourceRefResponse{
|
||||
func TestExtractUsesDocumentOrderForReferencesAndOccurrences(t *testing.T) {
|
||||
client := &fakeOccurrencesLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{
|
||||
{NPCID: identity.DeriveID("Later"), Name: "Later", Kind: "dialogue", SourceRefs: occurrenceRefs(10, 10)},
|
||||
{NPCID: identity.DeriveID("First"), Name: "First", Kind: "mentioned", SourceRefs: []occurrenceSourceRefResponse{
|
||||
{StartUnitID: 10, EndUnitID: 10},
|
||||
{StartUnitID: 30, EndUnitID: 30},
|
||||
{StartUnitID: 30, EndUnitID: 30},
|
||||
{StartUnitID: 999, EndUnitID: 0},
|
||||
}},
|
||||
{NPCID: identity.DeriveID("Second"), Name: "Second", Kind: "other", SourceRefs: interactionRefs(30, 30)},
|
||||
{NPCID: identity.DeriveID("Second"), Name: "Second", Kind: "other", SourceRefs: occurrenceRefs(30, 30)},
|
||||
}}}
|
||||
references := requiredRegistryReferences(t, "Later", "First", "Second")
|
||||
req := extractionRequest()
|
||||
@@ -81,16 +81,16 @@ func TestExtractUsesDocumentOrderForReferencesAndInteractions(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Extract() error = %v", err)
|
||||
}
|
||||
if got := interactionNames(result.Value); !reflect.DeepEqual(got, []string{"First", "Second", "Later"}) {
|
||||
t.Fatalf("interaction order = %#v, want document chronology with stable equal-evidence ties", got)
|
||||
if got := occurrenceNames(result.Value); !reflect.DeepEqual(got, []string{"First", "Second", "Later"}) {
|
||||
t.Fatalf("occurrence order = %#v, want document chronology with stable equal-evidence ties", got)
|
||||
}
|
||||
refs := result.Value.Occurrences[0].SourceRefs
|
||||
if got := []int{refs[0].StartUnitID, refs[1].StartUnitID, refs[2].StartUnitID}; !reflect.DeepEqual(got, []int{30, 10, 999}) {
|
||||
t.Fatalf("source refs = %#v, want document order with exact duplicate removed", refs)
|
||||
}
|
||||
refs[0].StartUnitID = 777
|
||||
for _, interaction := range client.response.Occurrences {
|
||||
for _, ref := range interaction.SourceRefs {
|
||||
for _, occurrence := range client.response.Occurrences {
|
||||
for _, ref := range occurrence.SourceRefs {
|
||||
if ref.StartUnitID == 777 {
|
||||
t.Fatal("result source references alias the model response")
|
||||
}
|
||||
@@ -102,14 +102,14 @@ func TestNewRequiresLLMAndRejectsAmbiguousReferenceSets(t *testing.T) {
|
||||
if _, err := New(nil, Options{}); err == nil || !strings.Contains(err.Error(), "LLM client") {
|
||||
t.Fatalf("New(nil) error = %v", err)
|
||||
}
|
||||
if _, err := New(&fakeInteractionsLLMClient{}, Options{}, contracts.ReferenceSet{}, contracts.ReferenceSet{}); err == nil || !strings.Contains(err.Error(), "at most one reference set") {
|
||||
if _, err := New(&fakeOccurrencesLLMClient{}, Options{}, contracts.ReferenceSet{}, contracts.ReferenceSet{}); err == nil || !strings.Contains(err.Error(), "at most one reference set") {
|
||||
t.Fatalf("New() error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractUsesRegistryIDsAndCurrentTranscriptEvidence(t *testing.T) {
|
||||
client := &fakeInteractionsLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{{
|
||||
NPCID: identity.DeriveID("Mira Thorn"), Name: "Mira Thorn", Kind: "dialogue", SourceRefs: interactionRefs(10, 10),
|
||||
client := &fakeOccurrencesLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{{
|
||||
NPCID: identity.DeriveID("Mira Thorn"), Name: "Mira Thorn", Kind: "dialogue", SourceRefs: occurrenceRefs(10, 10),
|
||||
}}}}
|
||||
references := requiredRegistryReferences(t, "Mira Thorn", "Hooded Guard")
|
||||
req := extractionRequest()
|
||||
@@ -130,7 +130,7 @@ func TestExtractUsesRegistryIDsAndCurrentTranscriptEvidence(t *testing.T) {
|
||||
if strings.Contains(string(request.Inputs["transcript"].Content), "other-session") {
|
||||
t.Fatal("transcript input contains registry evidence")
|
||||
}
|
||||
metadata, err := json.Marshal(newExtractor(t, &fakeInteractionsLLMClient{}, references).ManifestMetadata())
|
||||
metadata, err := json.Marshal(newExtractor(t, &fakeOccurrencesLLMClient{}, references).ManifestMetadata())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -140,7 +140,7 @@ func TestExtractUsesRegistryIDsAndCurrentTranscriptEvidence(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestExtractRequiresBoundRegistryBeforeLLMCall(t *testing.T) {
|
||||
client := &fakeInteractionsLLMClient{}
|
||||
client := &fakeOccurrencesLLMClient{}
|
||||
if _, err := newExtractor(t, client).Extract(context.Background(), extractionRequest()); err == nil || !strings.Contains(err.Error(), "NPC registry reference is required") {
|
||||
t.Fatalf("Extract() error = %v, want required registry failure", err)
|
||||
}
|
||||
@@ -169,11 +169,11 @@ func TestExtractRejectsUnknownIDsAndMismatchedNames(t *testing.T) {
|
||||
occurrence occurrenceResponse
|
||||
want string
|
||||
}{
|
||||
{"unknown ID", occurrenceResponse{NPCID: "npc:unknown", Name: "Mira Thorn", Kind: "dialogue", SourceRefs: interactionRefs(10, 10)}, "npc_id is not in the NPC registry"},
|
||||
{"mismatched name", occurrenceResponse{NPCID: identity.DeriveID("Mira Thorn"), Name: "Hooded Guard", Kind: "dialogue", SourceRefs: interactionRefs(10, 10)}, "name does not match npc_id"},
|
||||
{"unknown ID", occurrenceResponse{NPCID: "npc:unknown", Name: "Mira Thorn", Kind: "dialogue", SourceRefs: occurrenceRefs(10, 10)}, "npc_id is not in the NPC registry"},
|
||||
{"mismatched name", occurrenceResponse{NPCID: identity.DeriveID("Mira Thorn"), Name: "Hooded Guard", Kind: "dialogue", SourceRefs: occurrenceRefs(10, 10)}, "name does not match npc_id"},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
client := &fakeInteractionsLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{test.occurrence}}}
|
||||
client := &fakeOccurrencesLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{test.occurrence}}}
|
||||
req := extractionRequest()
|
||||
req.References = references
|
||||
if _, err := newExtractor(t, client, references).Extract(context.Background(), req); err == nil || !strings.Contains(err.Error(), test.want) {
|
||||
@@ -184,7 +184,7 @@ func TestExtractRejectsUnknownIDsAndMismatchedNames(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestExtractResolvesGeneratedRegistryAtOperationTime(t *testing.T) {
|
||||
client := &fakeInteractionsLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{}}}
|
||||
client := &fakeOccurrencesLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{}}}
|
||||
references := requiredRegistryReferences(t, "Mira Thorn")
|
||||
req := extractionRequest()
|
||||
req.References = references
|
||||
@@ -212,12 +212,12 @@ func TestExtractAcceptsEmptyBoundRegistryAndEmptyResponse(t *testing.T) {
|
||||
Items: []contracts.ReferenceItem{{SlotName: NPCRegistryReferenceSlot, MediaType: npccodec.MediaType, Content: content}},
|
||||
},
|
||||
}}
|
||||
client := &fakeInteractionsLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{}}}
|
||||
client := &fakeOccurrencesLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{}}}
|
||||
req := extractionRequest()
|
||||
req.References = references
|
||||
result, err := newExtractor(t, client, references).Extract(context.Background(), req)
|
||||
if err != nil || result.Value.Occurrences == nil || len(result.Value.Occurrences) != 0 {
|
||||
t.Fatalf("Extract() = %#v, %v; want present empty interactions", result, err)
|
||||
t.Fatalf("Extract() = %#v, %v; want present empty occurrences", result, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ func TestExtractRejectsInvalidRequestsAndProviderFailures(t *testing.T) {
|
||||
references := requiredRegistryReferences(t, "Mira Thorn")
|
||||
valid := extractionRequest()
|
||||
valid.References = references
|
||||
extractor := newExtractor(t, &fakeInteractionsLLMClient{}, references)
|
||||
extractor := newExtractor(t, &fakeOccurrencesLLMClient{}, references)
|
||||
var nilExtractor *Extractor
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
@@ -244,14 +244,14 @@ func TestExtractRejectsInvalidRequestsAndProviderFailures(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
if _, err := newExtractor(t, &fakeInteractionsLLMClient{err: errors.New("provider unavailable")}, references).Extract(context.Background(), valid); err == nil || !strings.Contains(err.Error(), "provider unavailable") {
|
||||
if _, err := newExtractor(t, &fakeOccurrencesLLMClient{err: errors.New("provider unavailable")}, references).Extract(context.Background(), valid); err == nil || !strings.Contains(err.Error(), "provider unavailable") {
|
||||
t.Fatalf("provider error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModuleSpecRegistrationMetadataAndFingerprints(t *testing.T) {
|
||||
got := ModuleSpec()
|
||||
if got.Key != Key || got.Stage != pipeline.StageExtract || got.ArtifactKind != dnd.NPCOccurrenceListKind || !reflect.DeepEqual(got.Requires, []string{"chunks", "source.transcript"}) || !reflect.DeepEqual(got.Provides, []string{"dnd.npc_interactions"}) {
|
||||
if got.Key != Key || got.Stage != pipeline.StageExtract || got.ArtifactKind != dnd.NPCOccurrenceListKind || !reflect.DeepEqual(got.Requires, []string{"chunks", "source.transcript"}) || !reflect.DeepEqual(got.Provides, []string{"dnd.npc_occurrences"}) {
|
||||
t.Fatalf("ModuleSpec() = %#v", got)
|
||||
}
|
||||
var registrySlot contracts.ReferenceSlot
|
||||
@@ -267,9 +267,9 @@ func TestModuleSpecRegistrationMetadataAndFingerprints(t *testing.T) {
|
||||
if ModuleSpec().ReferenceSlots[0].AcceptedMediaTypes[0] == "changed" {
|
||||
t.Fatal("ModuleSpec() returned mutable reference slots")
|
||||
}
|
||||
extractorSlots := newExtractor(t, &fakeInteractionsLLMClient{}).ReferenceSlots()
|
||||
extractorSlots := newExtractor(t, &fakeOccurrencesLLMClient{}).ReferenceSlots()
|
||||
extractorSlots[0].AcceptedMediaTypes[0] = "changed"
|
||||
if newExtractor(t, &fakeInteractionsLLMClient{}).ReferenceSlots()[0].AcceptedMediaTypes[0] == "changed" {
|
||||
if newExtractor(t, &fakeOccurrencesLLMClient{}).ReferenceSlots()[0].AcceptedMediaTypes[0] == "changed" {
|
||||
t.Fatal("ReferenceSlots() returned mutable reference slots")
|
||||
}
|
||||
registry := pipeline.NewExtractorRegistry()
|
||||
@@ -284,7 +284,7 @@ func TestModuleSpecRegistrationMetadataAndFingerprints(t *testing.T) {
|
||||
}
|
||||
|
||||
references := requiredRegistryReferences(t, "Mira Thorn")
|
||||
extractor := newExtractor(t, &fakeInteractionsLLMClient{}, references)
|
||||
extractor := newExtractor(t, &fakeOccurrencesLLMClient{}, references)
|
||||
metadata := extractor.ManifestMetadata()
|
||||
for key, want := range map[string]string{
|
||||
"prompt_id": PromptID, "prompt_version": SchemaVersion, "mapping_policy": mappingPolicy,
|
||||
@@ -320,7 +320,7 @@ func extractionRequest() contracts.TypedExtractionRequest {
|
||||
return contracts.TypedExtractionRequest{
|
||||
Source: doc, Chunk: chunk,
|
||||
SourceInput: contracts.NewLLMInputMaterial("source", chunk.MediaType, chunk.Content, "sha256:chunk", "file:///session-alpha.json"),
|
||||
SessionID: "session-123", LLMProfile: "profile-npc-interactions",
|
||||
SessionID: "session-123", LLMProfile: "profile-npc-occurrences",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,22 +359,22 @@ func requiredRegistryReferences(t *testing.T, names ...string) contracts.Referen
|
||||
}}
|
||||
}
|
||||
|
||||
func interactionRefs(start, end int) []interactionSourceRefResponse {
|
||||
return []interactionSourceRefResponse{{StartUnitID: start, EndUnitID: end}}
|
||||
func occurrenceRefs(start, end int) []occurrenceSourceRefResponse {
|
||||
return []occurrenceSourceRefResponse{{StartUnitID: start, EndUnitID: end}}
|
||||
}
|
||||
|
||||
func interactionNames(value dnd.NPCOccurrenceList) []string {
|
||||
func occurrenceNames(value dnd.NPCOccurrenceList) []string {
|
||||
names := make([]string, len(value.Occurrences))
|
||||
for index, interaction := range value.Occurrences {
|
||||
names[index] = interaction.Name
|
||||
for index, occurrence := range value.Occurrences {
|
||||
names[index] = occurrence.Name
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func interactionKinds(value dnd.NPCOccurrenceList) []dnd.NPCOccurrenceKind {
|
||||
func occurrenceKinds(value dnd.NPCOccurrenceList) []dnd.NPCOccurrenceKind {
|
||||
kinds := make([]dnd.NPCOccurrenceKind, len(value.Occurrences))
|
||||
for index, interaction := range value.Occurrences {
|
||||
kinds[index] = interaction.Kind
|
||||
for index, occurrence := range value.Occurrences {
|
||||
kinds[index] = occurrence.Kind
|
||||
}
|
||||
return kinds
|
||||
}
|
||||
@@ -393,13 +393,13 @@ func newExtractor(t *testing.T, client contracts.StructuredLLMClient, references
|
||||
return extractor
|
||||
}
|
||||
|
||||
type fakeInteractionsLLMClient struct {
|
||||
type fakeOccurrencesLLMClient struct {
|
||||
response extractionResponse
|
||||
err error
|
||||
requests []contracts.StructuredCompletionRequest
|
||||
}
|
||||
|
||||
func (client *fakeInteractionsLLMClient) CompleteStructured(_ context.Context, req contracts.StructuredCompletionRequest, out any) (contracts.StructuredCompletionResponse, error) {
|
||||
func (client *fakeOccurrencesLLMClient) CompleteStructured(_ context.Context, req contracts.StructuredCompletionRequest, out any) (contracts.StructuredCompletionResponse, error) {
|
||||
client.requests = append(client.requests, cloneStructuredCompletionRequest(req))
|
||||
if client.err != nil {
|
||||
return contracts.StructuredCompletionResponse{}, client.err
|
||||
17
internal/modules/dnd/extract/npcoccurrences/model.go
Normal file
17
internal/modules/dnd/extract/npcoccurrences/model.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package npcoccurrences
|
||||
|
||||
type extractionResponse struct {
|
||||
Occurrences []occurrenceResponse `json:"occurrences"`
|
||||
}
|
||||
|
||||
type occurrenceResponse struct {
|
||||
NPCID string `json:"npc_id"`
|
||||
Name string `json:"name"`
|
||||
Kind string `json:"kind"`
|
||||
SourceRefs []occurrenceSourceRefResponse `json:"source_refs"`
|
||||
}
|
||||
|
||||
type occurrenceSourceRefResponse struct {
|
||||
StartUnitID int `json:"start_unit_id"`
|
||||
EndUnitID int `json:"end_unit_id"`
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package npcinteractions
|
||||
package npcoccurrences
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -12,7 +12,7 @@ func TestExtractionResponsePreservesValidatorOwnedSemantics(t *testing.T) {
|
||||
t.Fatalf("json.Unmarshal() error = %v", err)
|
||||
}
|
||||
occurrence := response.Occurrences[0]
|
||||
if occurrence.NPCID != "" || occurrence.Name != "" || occurrence.Kind != "unsupported" || occurrence.SourceRefs[0] != (interactionSourceRefResponse{StartUnitID: 0, EndUnitID: -1}) {
|
||||
if occurrence.NPCID != "" || occurrence.Name != "" || occurrence.Kind != "unsupported" || occurrence.SourceRefs[0] != (occurrenceSourceRefResponse{StartUnitID: 0, EndUnitID: -1}) {
|
||||
t.Fatalf("decoded response = %#v", occurrence)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package npcinteractions
|
||||
package npcoccurrences
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
const promptAssetRoot = "assets/prompts"
|
||||
|
||||
var promptAssetManifest = shared.PromptAssetManifest{
|
||||
ModuleDir: "dnd.npc_interactions",
|
||||
ModuleDir: "dnd.npc_occurrences",
|
||||
ModuleFiles: []promptfs.ModulePromptFile{
|
||||
{Name: "prompt.yaml", Path: "prompts/prompt.yaml"},
|
||||
{Name: "instructions.md", Path: "prompts/instructions.md"},
|
||||
@@ -30,9 +30,9 @@ var promptAssetManifest = shared.PromptAssetManifest{
|
||||
}
|
||||
|
||||
func moduleAssetFS() (fs.FS, error) {
|
||||
assets, err := fs.Sub(rootassets.FS(), "dnd/npc-interactions")
|
||||
assets, err := fs.Sub(rootassets.FS(), "dnd/npc-occurrences")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("scope NPC-interaction assets: %w", err)
|
||||
return nil, fmt.Errorf("scope NPC-occurrence assets: %w", err)
|
||||
}
|
||||
return assets, nil
|
||||
}
|
||||
@@ -44,14 +44,14 @@ func RegisterPromptAssets(registry *llm.AssetRegistry) error {
|
||||
}
|
||||
promptFS, err := promptAssetManifest.PromptFS(assets)
|
||||
if err != nil {
|
||||
return fmt.Errorf("prepare NPC-interaction prompt assets: %w", err)
|
||||
return fmt.Errorf("prepare NPC-occurrence prompt assets: %w", err)
|
||||
}
|
||||
if err := registry.RegisterPromptFS(promptFS, promptAssetRoot); err != nil {
|
||||
return err
|
||||
}
|
||||
schemas, err := fs.Sub(assets, "schemas")
|
||||
if err != nil {
|
||||
return fmt.Errorf("scope NPC-interaction schemas: %w", err)
|
||||
return fmt.Errorf("scope NPC-occurrence schemas: %w", err)
|
||||
}
|
||||
return registry.RegisterSchemaFS(schemas, ".")
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package npcinteractions
|
||||
package npcoccurrences
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"gitea.maximumdirect.net/eric/promptkit"
|
||||
)
|
||||
|
||||
func TestRegisterPromptAssetsAndPrepareInteractionPrompt(t *testing.T) {
|
||||
func TestRegisterPromptAssetsAndPrepareOccurrencePrompt(t *testing.T) {
|
||||
registry := llm.NewAssetRegistry()
|
||||
if err := RegisterPromptAssets(registry); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -28,21 +28,21 @@ func TestRegisterPromptAssetsAndPrepareInteractionPrompt(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
options = append(options, promptkit.WithProfiles(promptkit.OpenAICompatibleProfile(promptkit.OpenAICompatibleProfileConfig{
|
||||
ID: "npc-interactions-test-profile", Endpoint: "http://127.0.0.1:1/v1", Model: "npc-interactions-test-model",
|
||||
ID: "npc-occurrences-test-profile", Endpoint: "http://127.0.0.1:1/v1", Model: "npc-occurrences-test-model",
|
||||
})))
|
||||
engine, err := promptkit.NewEngine(promptkit.Config{Timeout: time.Second}, options...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
transcript := `{"units":[{"sentinel":"interaction-transcript"}]}`
|
||||
transcript := `{"units":[{"sentinel":"occurrence-transcript"}]}`
|
||||
prepared, err := engine.Prepare(context.Background(), promptkit.RunRequest{
|
||||
PromptID: PromptID, PromptVersion: SchemaVersion, ProfileID: "npc-interactions-test-profile",
|
||||
PromptID: PromptID, PromptVersion: SchemaVersion, ProfileID: "npc-occurrences-test-profile",
|
||||
Inputs: map[string]promptkit.ArtifactRef{
|
||||
"transcript": promptkit.InlineWithURI("file:///session.json", transcript),
|
||||
"players": promptkit.Inline("interaction-player"),
|
||||
"players": promptkit.Inline("occurrence-player"),
|
||||
"party": promptkit.Inline("Mira: ranger"),
|
||||
"glossary": promptkit.Inline("Greencloak: title"),
|
||||
"npc_registry": promptkit.Inline(`{"npcs":[{"id":"npc:sha256:test","name":"interaction-npc"}]}`),
|
||||
"npc_registry": promptkit.Inline(`{"npcs":[{"id":"npc:sha256:test","name":"occurrence-npc"}]}`),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -58,7 +58,7 @@ func TestPromptMetadataDoesNotExposeAssetContent(t *testing.T) {
|
||||
if err != nil || !strings.HasPrefix(hash, "sha256:") {
|
||||
t.Fatalf("promptAssetMetadata() = %q, %v", hash, err)
|
||||
}
|
||||
metadata := newExtractor(t, &fakeInteractionsLLMClient{}).ManifestMetadata()
|
||||
metadata := newExtractor(t, &fakeOccurrencesLLMClient{}).ManifestMetadata()
|
||||
for _, forbidden := range []string{"common-dnd-system", "dnd_npc_occurrences_llm.v1.json"} {
|
||||
if strings.Contains(strings.Join(mapValues(metadata), " "), forbidden) {
|
||||
t.Fatalf("metadata leaked prompt or schema content %q: %#v", forbidden, metadata)
|
||||
@@ -1,9 +1,9 @@
|
||||
package npcinteractions
|
||||
package npcoccurrences
|
||||
|
||||
import "gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
||||
|
||||
const (
|
||||
PromptID = "dnd.npc_interactions"
|
||||
PromptID = "dnd.npc_occurrences"
|
||||
ResponseSchemaKey = llm.ResponseSchemaKey("dnd_npc_occurrences_llm")
|
||||
ResponseSchemaID = "notarius.dnd.npc_occurrences.llm"
|
||||
ResponseSchemaName = "notarius_dnd_npc_occurrences_llm_v1"
|
||||
@@ -1,4 +1,4 @@
|
||||
package npcinteractions
|
||||
package npcoccurrences
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -17,7 +17,7 @@ func TestResponseSchemaOwnsOnlyPrivateStructuralContract(t *testing.T) {
|
||||
if schema.Key != ResponseSchemaKey || schema.ID != ResponseSchemaID || schema.Name != ResponseSchemaName || schema.Version != SchemaVersion || !strings.HasPrefix(schema.SHA256, "sha256:") || !json.Valid(schema.JSONSchema) {
|
||||
t.Fatalf("schema = %#v", schema)
|
||||
}
|
||||
valid := validInteractionResponse()
|
||||
valid := validOccurrenceResponse()
|
||||
content, err := json.Marshal(valid)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -26,11 +26,11 @@ func TestResponseSchemaOwnsOnlyPrivateStructuralContract(t *testing.T) {
|
||||
t.Fatalf("valid response rejected: %v", err)
|
||||
}
|
||||
|
||||
semanticCandidate := validInteractionResponse()
|
||||
interaction := semanticCandidate["occurrences"].([]any)[0].(map[string]any)
|
||||
interaction["name"] = ""
|
||||
interaction["kind"] = "unsupported"
|
||||
ref := interaction["source_refs"].([]any)[0].(map[string]any)
|
||||
semanticCandidate := validOccurrenceResponse()
|
||||
occurrence := semanticCandidate["occurrences"].([]any)[0].(map[string]any)
|
||||
occurrence["name"] = ""
|
||||
occurrence["kind"] = "unsupported"
|
||||
ref := occurrence["source_refs"].([]any)[0].(map[string]any)
|
||||
ref["start_unit_id"] = 0
|
||||
ref["end_unit_id"] = -1
|
||||
content, err = json.Marshal(semanticCandidate)
|
||||
@@ -50,7 +50,7 @@ func TestResponseSchemaOwnsOnlyPrivateStructuralContract(t *testing.T) {
|
||||
record["source_refs"].([]any)[0].(map[string]any)["source_id"] = "assigned later"
|
||||
},
|
||||
} {
|
||||
candidate := validInteractionResponse()
|
||||
candidate := validOccurrenceResponse()
|
||||
mutate(candidate["occurrences"].([]any)[0].(map[string]any))
|
||||
content, err := json.Marshal(candidate)
|
||||
if err != nil {
|
||||
@@ -79,7 +79,7 @@ func TestResponseSchemaIsDefensiveAndContentSafe(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func validInteractionResponse() map[string]any {
|
||||
func validOccurrenceResponse() map[string]any {
|
||||
return map[string]any{"occurrences": []any{map[string]any{
|
||||
"npc_id": "npc:sha256:test", "name": "Mira Thorn", "kind": "dialogue",
|
||||
"source_refs": []any{map[string]any{"start_unit_id": 1, "end_unit_id": 2}},
|
||||
@@ -1,5 +1,5 @@
|
||||
// Package npcinteractions normalizes merged D&D NPC interaction candidates.
|
||||
package npcinteractions
|
||||
// Package npcoccurrences normalizes merged D&D NPC occurrence candidates.
|
||||
package npcoccurrences
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -10,21 +10,21 @@ import (
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||
interactionmodel "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcinteractions"
|
||||
occurrencemodel "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcoccurrences"
|
||||
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
|
||||
)
|
||||
|
||||
const (
|
||||
Key = "dnd/npc-interactions"
|
||||
normalizationPolicy = "dnd.npc_interactions.normalize.v2"
|
||||
Key = "dnd/npc-occurrences"
|
||||
normalizationPolicy = "dnd.npc_occurrences.normalize.v2"
|
||||
NormalizationPolicy = normalizationPolicy
|
||||
|
||||
ReasonCodeSourceRefsNormalized = "source_references_normalized"
|
||||
ReasonCodeInteractionsReordered = "npc_interactions_reordered"
|
||||
ReasonCodeDuplicateCollapsed = "duplicate_npc_interaction_collapsed"
|
||||
ReasonCodeWarningsOmitted = "npc_interaction_normalization_warnings_omitted"
|
||||
ReasonCodeSourceRefsNormalized = "source_references_normalized"
|
||||
ReasonCodeOccurrencesReordered = "npc_occurrences_reordered"
|
||||
ReasonCodeDuplicateCollapsed = "duplicate_npc_occurrence_collapsed"
|
||||
ReasonCodeWarningsOmitted = "npc_occurrence_normalization_warnings_omitted"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -36,10 +36,10 @@ var requiredCapabilities = []string{"merged"}
|
||||
var providedCapabilities = []string{"normalized"}
|
||||
|
||||
var referenceSlotDescriptions = shared.ReferenceSlotDescriptions{
|
||||
Glossary: "Optional campaign glossary reference material used only for interaction disambiguation.",
|
||||
Party: "Optional party roster reference material used only for interaction disambiguation.",
|
||||
Players: "Optional player list reference material used only for interaction disambiguation.",
|
||||
Roster: "Deprecated alias for party roster reference material used only for interaction disambiguation.",
|
||||
Glossary: "Optional campaign glossary reference material used only for occurrence disambiguation.",
|
||||
Party: "Optional party roster reference material used only for occurrence disambiguation.",
|
||||
Players: "Optional player list reference material used only for occurrence disambiguation.",
|
||||
Roster: "Deprecated alias for party roster reference material used only for occurrence disambiguation.",
|
||||
}
|
||||
|
||||
var _ contracts.Normalizer[dnd.NPCOccurrenceList] = (*Normalizer)(nil)
|
||||
@@ -152,7 +152,7 @@ func normalizeList(input dnd.NPCOccurrenceList, documentIndex source.DocumentInd
|
||||
}
|
||||
|
||||
sort.SliceStable(records, func(left, right int) bool {
|
||||
return interactionmodel.Less(order, records[left].occurrence, records[right].occurrence)
|
||||
return occurrencemodel.Less(order, records[left].occurrence, records[right].occurrence)
|
||||
})
|
||||
for position, record := range records {
|
||||
if position == record.inputIndex {
|
||||
@@ -160,7 +160,7 @@ func normalizeList(input dnd.NPCOccurrenceList, documentIndex source.DocumentInd
|
||||
}
|
||||
warnings = append(warnings, contracts.Warning{
|
||||
Scope: occurrenceScope(record.inputIndex),
|
||||
ReasonCode: ReasonCodeInteractionsReordered,
|
||||
ReasonCode: ReasonCodeOccurrencesReordered,
|
||||
Message: fmt.Sprintf("input index %d moved to normalized position %d by source chronology", record.inputIndex, position),
|
||||
})
|
||||
}
|
||||
@@ -168,7 +168,7 @@ func normalizeList(input dnd.NPCOccurrenceList, documentIndex source.DocumentInd
|
||||
output, duplicateWarnings := collapseDuplicates(records, documentIndex)
|
||||
warnings = append(warnings, duplicateWarnings...)
|
||||
return dnd.NPCOccurrenceList{Occurrences: output},
|
||||
diagnostics.LimitWarnings(warnings, "npc_interactions", ReasonCodeWarningsOmitted), nil
|
||||
diagnostics.LimitWarnings(warnings, "npc_occurrences", ReasonCodeWarningsOmitted), nil
|
||||
}
|
||||
|
||||
func normalizeOccurrence(input dnd.NPCOccurrence, order shared.SourceRefOrder, registry *npcregistry.Registry) (dnd.NPCOccurrence, bool, error) {
|
||||
@@ -181,7 +181,7 @@ func normalizeOccurrence(input dnd.NPCOccurrence, order shared.SourceRefOrder, r
|
||||
}
|
||||
output := cloneOccurrence(input)
|
||||
output.SourceRefs = order.Canonicalize(input.SourceRefs)
|
||||
return output, !interactionmodel.SourceRefsEqual(input.SourceRefs, output.SourceRefs), nil
|
||||
return output, !occurrencemodel.SourceRefsEqual(input.SourceRefs, output.SourceRefs), nil
|
||||
}
|
||||
|
||||
func cloneOccurrence(input dnd.NPCOccurrence) dnd.NPCOccurrence {
|
||||
@@ -205,11 +205,11 @@ func collapseDuplicates(records []normalizedRecord, documentIndex source.Documen
|
||||
groups := make([]duplicateGroup, 0)
|
||||
groupByKey := make(map[string]int)
|
||||
for index, record := range records {
|
||||
if !interactionmodel.ValidSourceRefs(documentIndex, record.occurrence.SourceRefs) {
|
||||
if !occurrencemodel.ValidSourceRefs(documentIndex, record.occurrence.SourceRefs) {
|
||||
keep[index] = true
|
||||
continue
|
||||
}
|
||||
key := interactionmodel.ExactIdentity(record.occurrence)
|
||||
key := occurrencemodel.ExactIdentity(record.occurrence)
|
||||
groupIndex, exists := groupByKey[key]
|
||||
if !exists {
|
||||
groupByKey[key] = len(groups)
|
||||
@@ -243,7 +243,7 @@ func duplicateWarning(retainedIndex int, removed []int) contracts.Warning {
|
||||
Scope: occurrenceScope(retainedIndex),
|
||||
ReasonCode: ReasonCodeDuplicateCollapsed,
|
||||
Message: diagnostics.Aggregate(
|
||||
fmt.Sprintf("duplicate NPC interaction collapsed; retained input index %d", retainedIndex), issues),
|
||||
fmt.Sprintf("duplicate NPC occurrence collapsed; retained input index %d", retainedIndex), issues),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,7 +253,7 @@ func referenceSlots() []contracts.ReferenceSlot {
|
||||
slots := shared.ReferenceSlots(referenceSlotDescriptions)
|
||||
slots = append(slots, contracts.ReferenceSlot{
|
||||
Name: NPCRegistryReferenceSlot,
|
||||
Description: "Required normalized NPC registry used only for interaction identity grounding, never as interaction evidence.",
|
||||
Description: "Required normalized NPC registry used only for occurrence identity grounding, never as occurrence evidence.",
|
||||
Required: true,
|
||||
AcceptedMediaTypes: []string{"application/json"},
|
||||
AcceptedArtifactKinds: []contracts.ArtifactKind{dnd.NPCRegistryKind},
|
||||
@@ -295,5 +295,5 @@ func DecodeOptions(options map[string]any) (Options, error) {
|
||||
func validateOptions(options map[string]any) error { _, err := DecodeOptions(options); return err }
|
||||
|
||||
func normalizerErrorf(format string, args ...any) error {
|
||||
return fmt.Errorf("dnd NPC interactions normalizer: "+format, args...)
|
||||
return fmt.Errorf("dnd NPC occurrences normalizer: "+format, args...)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package npcinteractions
|
||||
package npcoccurrences
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -31,7 +31,7 @@ func TestNormalizeValidatesPairsAndClones(t *testing.T) {
|
||||
}
|
||||
got := result.Value.Occurrences[0]
|
||||
if got.NPCID != identity.DeriveID("Ária") || got.Name != "Ária" || !reflect.DeepEqual(got.SourceRefs, []source.SourceRef{{SourceID: doc.ID, StartUnitID: 30, EndUnitID: 30}, {SourceID: doc.ID, StartUnitID: 10, EndUnitID: 10}}) {
|
||||
t.Fatalf("normalized interaction = %#v", got)
|
||||
t.Fatalf("normalized occurrence = %#v", got)
|
||||
}
|
||||
if !hasWarning(result.Warnings, ReasonCodeSourceRefsNormalized) {
|
||||
t.Fatalf("warnings = %#v", result.Warnings)
|
||||
@@ -77,7 +77,7 @@ func TestNormalizeRejectsUnknownIDsAndMismatchedNames(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, occurrence := range []dnd.NPCOccurrence{
|
||||
interaction("Unknown NPC", dnd.NPCOccurrenceKindOther, source.SourceRef{SourceID: doc.ID, StartUnitID: 10, EndUnitID: 10}),
|
||||
occurrence("Unknown NPC", dnd.NPCOccurrenceKindOther, source.SourceRef{SourceID: doc.ID, StartUnitID: 10, EndUnitID: 10}),
|
||||
{NPCID: identity.DeriveID("Ária"), Name: "Borin", Kind: dnd.NPCOccurrenceKindOther, SourceRefs: []source.SourceRef{{SourceID: doc.ID, StartUnitID: 10, EndUnitID: 10}}},
|
||||
} {
|
||||
input := dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{occurrence}}
|
||||
@@ -92,14 +92,14 @@ func TestNormalizeOrdersAndCollapsesExactDuplicatesOnly(t *testing.T) {
|
||||
ref := func(unit int) source.SourceRef {
|
||||
return source.SourceRef{SourceID: doc.ID, StartUnitID: unit, EndUnitID: unit}
|
||||
}
|
||||
first := interaction("Ária", dnd.NPCOccurrenceKindDialogue, ref(50))
|
||||
first := occurrence("Ária", dnd.NPCOccurrenceKindDialogue, ref(50))
|
||||
input := dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{
|
||||
interaction("Borin", dnd.NPCOccurrenceKindMentioned, ref(90)),
|
||||
occurrence("Borin", dnd.NPCOccurrenceKindMentioned, ref(90)),
|
||||
first,
|
||||
first,
|
||||
interaction("Ária", dnd.NPCOccurrenceKindCombatAlly, ref(50)),
|
||||
interaction("Ária", dnd.NPCOccurrenceKindDialogue, ref(10)),
|
||||
interaction("Ária", dnd.NPCOccurrenceKindDialogue, ref(999)),
|
||||
occurrence("Ária", dnd.NPCOccurrenceKindCombatAlly, ref(50)),
|
||||
occurrence("Ária", dnd.NPCOccurrenceKindDialogue, ref(10)),
|
||||
occurrence("Ária", dnd.NPCOccurrenceKindDialogue, ref(999)),
|
||||
}}
|
||||
normalizer, err := New(Options{}, npcReferences(t))
|
||||
if err != nil {
|
||||
@@ -111,12 +111,12 @@ func TestNormalizeOrdersAndCollapsesExactDuplicatesOnly(t *testing.T) {
|
||||
}
|
||||
got := result.Value.Occurrences
|
||||
if len(got) != 5 {
|
||||
t.Fatalf("interaction count = %d, want 5: %#v", len(got), got)
|
||||
t.Fatalf("occurrence count = %d, want 5: %#v", len(got), got)
|
||||
}
|
||||
if got[0].Kind != dnd.NPCOccurrenceKindCombatAlly || got[0].SourceRefs[0].StartUnitID != 50 || got[1].SourceRefs[0].StartUnitID != 50 || got[2].SourceRefs[0].StartUnitID != 10 || got[3].SourceRefs[0].StartUnitID != 90 || got[4].SourceRefs[0].StartUnitID != 999 {
|
||||
t.Fatalf("canonical order = %#v", got)
|
||||
}
|
||||
if !hasWarning(result.Warnings, ReasonCodeInteractionsReordered) || !hasWarning(result.Warnings, ReasonCodeDuplicateCollapsed) {
|
||||
if !hasWarning(result.Warnings, ReasonCodeOccurrencesReordered) || !hasWarning(result.Warnings, ReasonCodeDuplicateCollapsed) {
|
||||
t.Fatalf("warnings = %#v", result.Warnings)
|
||||
}
|
||||
}
|
||||
@@ -147,7 +147,7 @@ func TestNormalizeBoundsWarnings(t *testing.T) {
|
||||
for index := range doc.Units {
|
||||
doc.Units[index].ID = index + 1
|
||||
unitID := count - index
|
||||
input.Occurrences[index] = interaction(
|
||||
input.Occurrences[index] = occurrence(
|
||||
"Ária",
|
||||
dnd.NPCOccurrenceKindDialogue,
|
||||
source.SourceRef{SourceID: doc.ID, StartUnitID: unitID, EndUnitID: unitID},
|
||||
@@ -169,7 +169,7 @@ func TestNormalizeBoundsWarnings(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func interaction(name string, kind dnd.NPCOccurrenceKind, ref source.SourceRef) dnd.NPCOccurrence {
|
||||
func occurrence(name string, kind dnd.NPCOccurrenceKind, ref source.SourceRef) dnd.NPCOccurrence {
|
||||
return dnd.NPCOccurrence{NPCID: identity.DeriveID(name), Name: name, Kind: kind, SourceRefs: []source.SourceRef{ref}}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Package npcinteractions owns canonical ordering and exact-identity rules for
|
||||
// D&D NPC interaction artifacts.
|
||||
package npcinteractions
|
||||
// Package npcoccurrences owns canonical ordering and exact-identity rules for
|
||||
// D&D NPC occurrence artifacts.
|
||||
package npcoccurrences
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
itemeventextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/itemevents"
|
||||
locationoccurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationoccurrences"
|
||||
locationextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locations"
|
||||
interactionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcinteractions"
|
||||
occurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcoccurrences"
|
||||
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
||||
scenedescriptionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/scenedescriptions"
|
||||
spellextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/spells"
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
itemeventnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/itemevents"
|
||||
locationoccurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locationoccurrences"
|
||||
locationnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locations"
|
||||
interactionnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcinteractions"
|
||||
occurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcoccurrences"
|
||||
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcregistry"
|
||||
scenedescriptionnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/scenedescriptions"
|
||||
spellnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/spells"
|
||||
@@ -33,20 +33,20 @@ import (
|
||||
itemeventshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/itemevents/shape"
|
||||
itemeventrefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/itemevents/source_refs"
|
||||
itemeventrelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/itemevents/source_relatedness"
|
||||
occurrenceinvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/invariants"
|
||||
occurrenceregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/registry"
|
||||
occurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/shape"
|
||||
occurrencerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/source_refs"
|
||||
occurrencerelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/source_relatedness"
|
||||
locationoccurrenceinvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/invariants"
|
||||
locationoccurrenceregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/registry"
|
||||
locationoccurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/shape"
|
||||
locationoccurrencerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/source_refs"
|
||||
locationoccurrencerelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/source_relatedness"
|
||||
locationidentity "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/identity"
|
||||
locationshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/shape"
|
||||
locationrefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/source_refs"
|
||||
locationrelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/source_relatedness"
|
||||
interactioninvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/invariants"
|
||||
interactionregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/registry"
|
||||
interactionshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/shape"
|
||||
interactionrefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/source_refs"
|
||||
interactionrelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/source_relatedness"
|
||||
npcoccurrenceinvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/invariants"
|
||||
npcoccurrenceregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/registry"
|
||||
npcoccurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/shape"
|
||||
npcoccurrencerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/source_refs"
|
||||
npcoccurrencerelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/source_relatedness"
|
||||
npcidentity "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcregistry/identity"
|
||||
npcshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcregistry/shape"
|
||||
npcsourcerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcregistry/source_refs"
|
||||
@@ -202,32 +202,32 @@ func registerDefaultChains(registry *pipeline.ValidatorChainRegistry) error {
|
||||
},
|
||||
})
|
||||
}},
|
||||
{name: "npc interactions validator chain", register: func() error {
|
||||
{name: "npc occurrences validator chain", register: func() error {
|
||||
return registry.Register(pipeline.ValidatorChainMapping{
|
||||
Stage: pipeline.StageExtract,
|
||||
Module: interactionextract.Key,
|
||||
Module: occurrenceextract.Key,
|
||||
Validators: []pipeline.ModuleBinding{
|
||||
pipeline.Binding(validjson.Key),
|
||||
pipeline.Binding(interactionshape.Key),
|
||||
pipeline.Binding(interactionregistry.Key),
|
||||
pipeline.Binding(interactionrefs.Key),
|
||||
pipeline.Binding(npcoccurrenceshape.Key),
|
||||
pipeline.Binding(npcoccurrenceregistry.Key),
|
||||
pipeline.Binding(npcoccurrencerefs.Key),
|
||||
pipeline.Binding(validjsonschema.Key),
|
||||
pipeline.Binding(interactionrelatedness.Key),
|
||||
pipeline.Binding(npcoccurrencerelatedness.Key),
|
||||
},
|
||||
})
|
||||
}},
|
||||
{name: "npc interactions normalize validator chain", register: func() error {
|
||||
{name: "npc occurrences normalize validator chain", register: func() error {
|
||||
return registry.Register(pipeline.ValidatorChainMapping{
|
||||
Stage: pipeline.StageNormalize,
|
||||
Module: interactionnormalize.Key,
|
||||
Module: occurrencenormalize.Key,
|
||||
Validators: []pipeline.ModuleBinding{
|
||||
pipeline.Binding(validjson.Key),
|
||||
pipeline.Binding(interactionshape.Key),
|
||||
pipeline.Binding(interactionregistry.Key),
|
||||
pipeline.Binding(interactioninvariants.Key),
|
||||
pipeline.Binding(interactionrefs.Key),
|
||||
pipeline.Binding(npcoccurrenceshape.Key),
|
||||
pipeline.Binding(npcoccurrenceregistry.Key),
|
||||
pipeline.Binding(npcoccurrenceinvariants.Key),
|
||||
pipeline.Binding(npcoccurrencerefs.Key),
|
||||
pipeline.Binding(validjsonschema.Key),
|
||||
pipeline.Binding(interactionrelatedness.Key),
|
||||
pipeline.Binding(npcoccurrencerelatedness.Key),
|
||||
},
|
||||
})
|
||||
}},
|
||||
@@ -280,8 +280,8 @@ func registerDefaultChains(registry *pipeline.ValidatorChainRegistry) error {
|
||||
return registry.Register(pipeline.ValidatorChainMapping{
|
||||
Stage: pipeline.StageExtract, Module: locationoccurrenceextract.Key,
|
||||
Validators: []pipeline.ModuleBinding{
|
||||
pipeline.Binding(validjson.Key), pipeline.Binding(occurrenceshape.Key), pipeline.Binding(occurrenceregistry.Key),
|
||||
pipeline.Binding(occurrencerefs.Key), pipeline.Binding(validjsonschema.Key), pipeline.Binding(occurrencerelatedness.Key),
|
||||
pipeline.Binding(validjson.Key), pipeline.Binding(locationoccurrenceshape.Key), pipeline.Binding(locationoccurrenceregistry.Key),
|
||||
pipeline.Binding(locationoccurrencerefs.Key), pipeline.Binding(validjsonschema.Key), pipeline.Binding(locationoccurrencerelatedness.Key),
|
||||
},
|
||||
})
|
||||
}},
|
||||
@@ -289,9 +289,9 @@ func registerDefaultChains(registry *pipeline.ValidatorChainRegistry) error {
|
||||
return registry.Register(pipeline.ValidatorChainMapping{
|
||||
Stage: pipeline.StageNormalize, Module: locationoccurrencenormalize.Key,
|
||||
Validators: []pipeline.ModuleBinding{
|
||||
pipeline.Binding(validjson.Key), pipeline.Binding(occurrenceshape.Key), pipeline.Binding(occurrenceregistry.Key),
|
||||
pipeline.Binding(occurrenceinvariants.Key), pipeline.Binding(occurrencerefs.Key), pipeline.Binding(validjsonschema.Key),
|
||||
pipeline.Binding(occurrencerelatedness.Key),
|
||||
pipeline.Binding(validjson.Key), pipeline.Binding(locationoccurrenceshape.Key), pipeline.Binding(locationoccurrenceregistry.Key),
|
||||
pipeline.Binding(locationoccurrenceinvariants.Key), pipeline.Binding(locationoccurrencerefs.Key), pipeline.Binding(validjsonschema.Key),
|
||||
pipeline.Binding(locationoccurrencerelatedness.Key),
|
||||
},
|
||||
})
|
||||
}},
|
||||
|
||||
@@ -19,8 +19,8 @@ func registerEvidence(registry *pipeline.ArtifactEvidenceRegistry) error {
|
||||
{name: "item events evidence", register: func() error {
|
||||
return pipeline.RegisterArtifactEvidence(registry, dnd.ItemEventListKind, itemEventEvidence)
|
||||
}},
|
||||
{name: "npc interactions evidence", register: func() error {
|
||||
return pipeline.RegisterArtifactEvidence(registry, dnd.NPCOccurrenceListKind, npcInteractionEvidence)
|
||||
{name: "npc occurrences evidence", register: func() error {
|
||||
return pipeline.RegisterArtifactEvidence(registry, dnd.NPCOccurrenceListKind, npcOccurrenceEvidence)
|
||||
}},
|
||||
{name: "scene descriptions evidence", register: func() error {
|
||||
return pipeline.RegisterArtifactEvidence(registry, dnd.SceneDescriptionListKind, sceneDescriptionEvidence)
|
||||
@@ -74,7 +74,7 @@ func itemEventEvidence(value dnd.ItemEventList) []source.SourceRef {
|
||||
return append([]source.SourceRef(nil), refs...)
|
||||
}
|
||||
|
||||
func npcInteractionEvidence(value dnd.NPCOccurrenceList) []source.SourceRef {
|
||||
func npcOccurrenceEvidence(value dnd.NPCOccurrenceList) []source.SourceRef {
|
||||
var refs []source.SourceRef
|
||||
for _, record := range value.Occurrences {
|
||||
refs = append(refs, record.SourceRefs...)
|
||||
|
||||
@@ -112,7 +112,7 @@ func appendItemEventLists(values []dnd.ItemEventList) (dnd.ItemEventList, error)
|
||||
return combined, nil
|
||||
}
|
||||
|
||||
func appendNPCInteractionLists(values []dnd.NPCOccurrenceList) (dnd.NPCOccurrenceList, error) {
|
||||
func appendNPCOccurrenceLists(values []dnd.NPCOccurrenceList) (dnd.NPCOccurrenceList, error) {
|
||||
count := 0
|
||||
present := false
|
||||
for _, value := range values {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
itemeventcodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/itemevents"
|
||||
locationoccurrencecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/locationoccurrences"
|
||||
locationcodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/locations"
|
||||
interactioncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcinteractions"
|
||||
occurrencecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcoccurrences"
|
||||
npccodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcregistry"
|
||||
scenedescriptioncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/scenedescriptions"
|
||||
spellcodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/spells"
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
itemeventextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/itemevents"
|
||||
locationoccurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationoccurrences"
|
||||
locationextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locations"
|
||||
interactionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcinteractions"
|
||||
occurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcoccurrences"
|
||||
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
||||
scenedescriptionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/scenedescriptions"
|
||||
spellextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/spells"
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
itemeventnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/itemevents"
|
||||
locationoccurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locationoccurrences"
|
||||
locationnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locations"
|
||||
interactionnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcinteractions"
|
||||
occurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcoccurrences"
|
||||
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcregistry"
|
||||
scenedescriptionnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/scenedescriptions"
|
||||
spellnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/spells"
|
||||
@@ -45,7 +45,7 @@ func registerModules(registries pipeline.Registries) error {
|
||||
{name: "combat turns codec", register: func() error { return pipeline.RegisterArtifactCodec(registries.ArtifactCodecs, combatcodec.New()) }},
|
||||
{name: "enemy events codec", register: func() error { return pipeline.RegisterArtifactCodec(registries.ArtifactCodecs, enemyeventcodec.New()) }},
|
||||
{name: "item events codec", register: func() error { return pipeline.RegisterArtifactCodec(registries.ArtifactCodecs, itemeventcodec.New()) }},
|
||||
{name: "npc interactions codec", register: func() error { return pipeline.RegisterArtifactCodec(registries.ArtifactCodecs, interactioncodec.New()) }},
|
||||
{name: "npc occurrences codec", register: func() error { return pipeline.RegisterArtifactCodec(registries.ArtifactCodecs, occurrencecodec.New()) }},
|
||||
{name: "scene descriptions codec", register: func() error {
|
||||
return pipeline.RegisterArtifactCodec(registries.ArtifactCodecs, scenedescriptioncodec.New())
|
||||
}},
|
||||
@@ -59,7 +59,7 @@ func registerModules(registries pipeline.Registries) error {
|
||||
{name: "combat turns extractor", register: func() error { return combatextract.Register(registries.Extractors) }},
|
||||
{name: "enemy events extractor", register: func() error { return enemyeventextract.Register(registries.Extractors) }},
|
||||
{name: "item events extractor", register: func() error { return itemeventextract.Register(registries.Extractors) }},
|
||||
{name: "npc interactions extractor", register: func() error { return interactionextract.Register(registries.Extractors) }},
|
||||
{name: "npc occurrences extractor", register: func() error { return occurrenceextract.Register(registries.Extractors) }},
|
||||
{name: "scene descriptions extractor", register: func() error { return scenedescriptionextract.Register(registries.Extractors) }},
|
||||
{name: "locations extractor", register: func() error { return locationextract.Register(registries.Extractors) }},
|
||||
{name: "location occurrences extractor", register: func() error { return locationoccurrenceextract.Register(registries.Extractors) }},
|
||||
@@ -78,8 +78,8 @@ func registerModules(registries pipeline.Registries) error {
|
||||
{name: "item-event-list appendorder merger", register: func() error {
|
||||
return appendorder.RegisterTyped(registries.Mergers, dnd.ItemEventListKind, appendItemEventLists)
|
||||
}},
|
||||
{name: "npc-interaction-list appendorder merger", register: func() error {
|
||||
return appendorder.RegisterTyped(registries.Mergers, dnd.NPCOccurrenceListKind, appendNPCInteractionLists)
|
||||
{name: "npc-occurrence-list appendorder merger", register: func() error {
|
||||
return appendorder.RegisterTyped(registries.Mergers, dnd.NPCOccurrenceListKind, appendNPCOccurrenceLists)
|
||||
}},
|
||||
{name: "scene-description-list appendorder merger", register: func() error {
|
||||
return appendorder.RegisterTyped(registries.Mergers, dnd.SceneDescriptionListKind, appendSceneDescriptionLists)
|
||||
@@ -95,7 +95,7 @@ func registerModules(registries pipeline.Registries) error {
|
||||
{name: "combat turns normalizer", register: func() error { return combatnormalize.Register(registries.Normalizers) }},
|
||||
{name: "enemy events normalizer", register: func() error { return enemyeventnormalize.Register(registries.Normalizers) }},
|
||||
{name: "item events normalizer", register: func() error { return itemeventnormalize.Register(registries.Normalizers) }},
|
||||
{name: "npc interactions normalizer", register: func() error { return interactionnormalize.Register(registries.Normalizers) }},
|
||||
{name: "npc occurrences normalizer", register: func() error { return occurrencenormalize.Register(registries.Normalizers) }},
|
||||
{name: "scene descriptions normalizer", register: func() error { return scenedescriptionnormalize.Register(registries.Normalizers) }},
|
||||
{name: "locations normalizer", register: func() error { return locationnormalize.Register(registries.Normalizers) }},
|
||||
{name: "location occurrences normalizer", register: func() error { return locationoccurrencenormalize.Register(registries.Normalizers) }},
|
||||
@@ -114,7 +114,7 @@ func registerModules(registries pipeline.Registries) error {
|
||||
{name: "item-event-list noop normalizer", register: func() error {
|
||||
return noop.RegisterTyped[dnd.ItemEventList](registries.Normalizers, dnd.ItemEventListKind)
|
||||
}},
|
||||
{name: "npc-interaction-list noop normalizer", register: func() error {
|
||||
{name: "npc-occurrence-list noop normalizer", register: func() error {
|
||||
return noop.RegisterTyped[dnd.NPCOccurrenceList](registries.Normalizers, dnd.NPCOccurrenceListKind)
|
||||
}},
|
||||
{name: "scene-description-list noop normalizer", register: func() error {
|
||||
@@ -139,7 +139,7 @@ func registerPromptAssets(assets *llm.AssetRegistry) error {
|
||||
{name: "combat turns prompt assets", register: func() error { return combatextract.RegisterPromptAssets(assets) }},
|
||||
{name: "enemy events prompt assets", register: func() error { return enemyeventextract.RegisterPromptAssets(assets) }},
|
||||
{name: "item events prompt assets", register: func() error { return itemeventextract.RegisterPromptAssets(assets) }},
|
||||
{name: "npc interactions prompt assets", register: func() error { return interactionextract.RegisterPromptAssets(assets) }},
|
||||
{name: "npc occurrences prompt assets", register: func() error { return occurrenceextract.RegisterPromptAssets(assets) }},
|
||||
{name: "scene descriptions prompt assets", register: func() error { return scenedescriptionextract.RegisterPromptAssets(assets) }},
|
||||
{name: "locations prompt assets", register: func() error { return locationextract.RegisterPromptAssets(assets) }},
|
||||
{name: "location normalization prompt assets", register: func() error { return locationnormalize.RegisterPromptAssets(assets) }},
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
itemeventextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/itemevents"
|
||||
locationoccurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationoccurrences"
|
||||
locationextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locations"
|
||||
interactionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcinteractions"
|
||||
occurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcoccurrences"
|
||||
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
||||
scenedescriptionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/scenedescriptions"
|
||||
spellextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/spells"
|
||||
@@ -66,20 +66,20 @@ func TestExtractionPromptComposition(t *testing.T) {
|
||||
promptID: enemyeventextract.PromptID,
|
||||
promptVersion: enemyeventextract.SchemaVersion,
|
||||
inputs: withPromptInputs(commonInputs, map[string]promptkit.ArtifactRef{
|
||||
"npc_registry": promptkit.Inline(`{"sentinel":"` + npcSentinel + `"}`),
|
||||
"combat_turns": promptkit.Inline(`{"sentinel":"combat-turns-sentinel"}`),
|
||||
"npc_interactions": promptkit.Inline(`{"sentinel":"npc-interactions-sentinel"}`),
|
||||
"npc_registry": promptkit.Inline(`{"sentinel":"` + npcSentinel + `"}`),
|
||||
"combat_turns": promptkit.Inline(`{"sentinel":"combat-turns-sentinel"}`),
|
||||
"npc_occurrences": promptkit.Inline(`{"sentinel":"npc-occurrences-sentinel"}`),
|
||||
}),
|
||||
suffixGroups: [][]string{
|
||||
{evidenceSentinel},
|
||||
{npcSentinel},
|
||||
{"combat-turns-sentinel", "npc-interactions-sentinel"},
|
||||
{"combat-turns-sentinel", "npc-occurrences-sentinel"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "npc interactions",
|
||||
promptID: interactionextract.PromptID,
|
||||
promptVersion: interactionextract.SchemaVersion,
|
||||
name: "npc occurrences",
|
||||
promptID: occurrenceextract.PromptID,
|
||||
promptVersion: occurrenceextract.SchemaVersion,
|
||||
inputs: withPromptInputs(commonInputs, map[string]promptkit.ArtifactRef{
|
||||
"npc_registry": promptkit.Inline(`{"sentinel":"` + npcSentinel + `"}`),
|
||||
}),
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
itemeventextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/itemevents"
|
||||
locationoccurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationoccurrences"
|
||||
locationextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locations"
|
||||
interactionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcinteractions"
|
||||
occurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcoccurrences"
|
||||
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
||||
scenedescriptionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/scenedescriptions"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/spells"
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
itemeventnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/itemevents"
|
||||
locationoccurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locationoccurrences"
|
||||
locationnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locations"
|
||||
interactionnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcinteractions"
|
||||
occurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcoccurrences"
|
||||
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcregistry"
|
||||
scenedescriptionnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/scenedescriptions"
|
||||
spellnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/spells"
|
||||
@@ -52,7 +52,7 @@ func TestRegisterAddsDNDFamily(t *testing.T) {
|
||||
"dnd.combat_turns/prompt.yaml",
|
||||
"dnd.enemy_events/prompt.yaml",
|
||||
"dnd.item_events/prompt.yaml",
|
||||
"dnd.npc_interactions/prompt.yaml",
|
||||
"dnd.npc_occurrences/prompt.yaml",
|
||||
"dnd.scene_descriptions/prompt.yaml",
|
||||
"dnd.npc_registry.normalize/prompt.yaml",
|
||||
"dnd.locations/prompt.yaml",
|
||||
@@ -82,8 +82,8 @@ func TestRegisterAddsDNDFamily(t *testing.T) {
|
||||
t.Fatalf("entity reconciliation schema asset = %v, want registered shared schema", err)
|
||||
}
|
||||
assertContainsKeys(t, "chunkers", registries.Chunkers.RegisteredKeys(), []string{"dnd/scenes"})
|
||||
assertContainsKeys(t, "extractors", registries.Extractors.RegisteredKeys(), []string{"dnd/spells", npcextract.Key, combatextract.Key, enemyeventextract.Key, itemeventextract.Key, interactionextract.Key, scenedescriptionextract.Key, locationextract.Key, locationoccurrenceextract.Key})
|
||||
assertContainsKeys(t, "normalizers", registries.Normalizers.RegisteredKeys(), []string{spellnormalize.Key, npcnormalize.Key, combatnormalize.Key, enemyeventnormalize.Key, itemeventnormalize.Key, interactionnormalize.Key, scenedescriptionnormalize.Key, locationnormalize.Key, locationoccurrencenormalize.Key, pipeline.DefaultNormalizeModule})
|
||||
assertContainsKeys(t, "extractors", registries.Extractors.RegisteredKeys(), []string{"dnd/spells", npcextract.Key, combatextract.Key, enemyeventextract.Key, itemeventextract.Key, occurrenceextract.Key, scenedescriptionextract.Key, locationextract.Key, locationoccurrenceextract.Key})
|
||||
assertContainsKeys(t, "normalizers", registries.Normalizers.RegisteredKeys(), []string{spellnormalize.Key, npcnormalize.Key, combatnormalize.Key, enemyeventnormalize.Key, itemeventnormalize.Key, occurrencenormalize.Key, scenedescriptionnormalize.Key, locationnormalize.Key, locationoccurrencenormalize.Key, pipeline.DefaultNormalizeModule})
|
||||
assertContainsArtifactKinds(t, registries.ArtifactCodecs.RegisteredKinds(), []contracts.ArtifactKind{dnd.SpellListKind, dnd.NPCRegistryKind, dnd.CombatTurnListKind, dnd.EnemyEventListKind, dnd.ItemEventListKind, dnd.NPCOccurrenceListKind, dnd.SceneDescriptionListKind, dnd.LocationListKind, dnd.LocationOccurrenceListKind})
|
||||
assertContainsArtifactKinds(t, registries.ArtifactEvidence.RegisteredKinds(), []contracts.ArtifactKind{dnd.SpellListKind, dnd.NPCRegistryKind, dnd.CombatTurnListKind, dnd.EnemyEventListKind, dnd.ItemEventListKind, dnd.NPCOccurrenceListKind, dnd.SceneDescriptionListKind, dnd.LocationListKind, dnd.LocationOccurrenceListKind})
|
||||
assertContainsArtifactKinds(t, registries.Mergers.RegisteredArtifactKinds(pipeline.DefaultMergeModule), []contracts.ArtifactKind{dnd.SpellListKind, dnd.NPCRegistryKind, dnd.CombatTurnListKind, dnd.EnemyEventListKind, dnd.ItemEventListKind, dnd.NPCOccurrenceListKind, dnd.SceneDescriptionListKind, dnd.LocationListKind, dnd.LocationOccurrenceListKind})
|
||||
@@ -92,7 +92,7 @@ func TestRegisterAddsDNDFamily(t *testing.T) {
|
||||
assertContainsArtifactKinds(t, registries.Normalizers.RegisteredArtifactKinds(combatnormalize.Key), []contracts.ArtifactKind{dnd.CombatTurnListKind})
|
||||
assertContainsArtifactKinds(t, registries.Normalizers.RegisteredArtifactKinds(enemyeventnormalize.Key), []contracts.ArtifactKind{dnd.EnemyEventListKind})
|
||||
assertContainsArtifactKinds(t, registries.Normalizers.RegisteredArtifactKinds(itemeventnormalize.Key), []contracts.ArtifactKind{dnd.ItemEventListKind})
|
||||
assertContainsArtifactKinds(t, registries.Normalizers.RegisteredArtifactKinds(interactionnormalize.Key), []contracts.ArtifactKind{dnd.NPCOccurrenceListKind})
|
||||
assertContainsArtifactKinds(t, registries.Normalizers.RegisteredArtifactKinds(occurrencenormalize.Key), []contracts.ArtifactKind{dnd.NPCOccurrenceListKind})
|
||||
assertContainsArtifactKinds(t, registries.Normalizers.RegisteredArtifactKinds(scenedescriptionnormalize.Key), []contracts.ArtifactKind{dnd.SceneDescriptionListKind})
|
||||
assertContainsKeys(t, "validators", registries.Validators.RegisteredKeys(), []string{
|
||||
"extract/dnd/locations/shape", "normalize/dnd/locations/identity", "extract/dnd/locations/source_refs", "extract/dnd/locations/source_relatedness",
|
||||
@@ -118,11 +118,11 @@ func TestRegisterAddsDNDFamily(t *testing.T) {
|
||||
"extract/dnd/item-events/source_refs",
|
||||
"extract/dnd/item-events/source_relatedness",
|
||||
"normalize/dnd/item-events/invariants",
|
||||
"extract/dnd/npc-interactions/shape",
|
||||
"extract/dnd/npc-interactions/registry",
|
||||
"extract/dnd/npc-interactions/source_refs",
|
||||
"extract/dnd/npc-interactions/source_relatedness",
|
||||
"normalize/dnd/npc-interactions/invariants",
|
||||
"extract/dnd/npc-occurrences/shape",
|
||||
"extract/dnd/npc-occurrences/registry",
|
||||
"extract/dnd/npc-occurrences/source_refs",
|
||||
"extract/dnd/npc-occurrences/source_relatedness",
|
||||
"normalize/dnd/npc-occurrences/invariants",
|
||||
"extract/dnd/scene-descriptions/shape",
|
||||
"extract/dnd/scene-descriptions/source_refs",
|
||||
"extract/dnd/scene-descriptions/source_relatedness",
|
||||
@@ -245,28 +245,28 @@ func TestRegisterAddsDNDFamily(t *testing.T) {
|
||||
if got := registries.ValidatorChains.Validators(pipeline.StageNormalize, itemeventnormalize.Key); !reflect.DeepEqual(got, itemEventNormalizeChain) {
|
||||
t.Fatalf("item event normalize validator chain = %#v, want %#v", got, itemEventNormalizeChain)
|
||||
}
|
||||
interactionExtractChain := []pipeline.ModuleBinding{
|
||||
npcOccurrenceExtractChain := []pipeline.ModuleBinding{
|
||||
pipeline.Binding("generic/valid_json"),
|
||||
pipeline.Binding("extract/dnd/npc-interactions/shape"),
|
||||
pipeline.Binding("extract/dnd/npc-interactions/registry"),
|
||||
pipeline.Binding("extract/dnd/npc-interactions/source_refs"),
|
||||
pipeline.Binding("extract/dnd/npc-occurrences/shape"),
|
||||
pipeline.Binding("extract/dnd/npc-occurrences/registry"),
|
||||
pipeline.Binding("extract/dnd/npc-occurrences/source_refs"),
|
||||
pipeline.Binding("generic/valid_json_schema"),
|
||||
pipeline.Binding("extract/dnd/npc-interactions/source_relatedness"),
|
||||
pipeline.Binding("extract/dnd/npc-occurrences/source_relatedness"),
|
||||
}
|
||||
interactionNormalizeChain := []pipeline.ModuleBinding{
|
||||
npcOccurrenceNormalizeChain := []pipeline.ModuleBinding{
|
||||
pipeline.Binding("generic/valid_json"),
|
||||
pipeline.Binding("extract/dnd/npc-interactions/shape"),
|
||||
pipeline.Binding("extract/dnd/npc-interactions/registry"),
|
||||
pipeline.Binding("normalize/dnd/npc-interactions/invariants"),
|
||||
pipeline.Binding("extract/dnd/npc-interactions/source_refs"),
|
||||
pipeline.Binding("extract/dnd/npc-occurrences/shape"),
|
||||
pipeline.Binding("extract/dnd/npc-occurrences/registry"),
|
||||
pipeline.Binding("normalize/dnd/npc-occurrences/invariants"),
|
||||
pipeline.Binding("extract/dnd/npc-occurrences/source_refs"),
|
||||
pipeline.Binding("generic/valid_json_schema"),
|
||||
pipeline.Binding("extract/dnd/npc-interactions/source_relatedness"),
|
||||
pipeline.Binding("extract/dnd/npc-occurrences/source_relatedness"),
|
||||
}
|
||||
if got := registries.ValidatorChains.Validators(pipeline.StageExtract, interactionextract.Key); !reflect.DeepEqual(got, interactionExtractChain) {
|
||||
t.Fatalf("NPC interaction extract validator chain = %#v, want %#v", got, interactionExtractChain)
|
||||
if got := registries.ValidatorChains.Validators(pipeline.StageExtract, occurrenceextract.Key); !reflect.DeepEqual(got, npcOccurrenceExtractChain) {
|
||||
t.Fatalf("NPC occurrence extract validator chain = %#v, want %#v", got, npcOccurrenceExtractChain)
|
||||
}
|
||||
if got := registries.ValidatorChains.Validators(pipeline.StageNormalize, interactionnormalize.Key); !reflect.DeepEqual(got, interactionNormalizeChain) {
|
||||
t.Fatalf("NPC interaction normalize validator chain = %#v, want %#v", got, interactionNormalizeChain)
|
||||
if got := registries.ValidatorChains.Validators(pipeline.StageNormalize, occurrencenormalize.Key); !reflect.DeepEqual(got, npcOccurrenceNormalizeChain) {
|
||||
t.Fatalf("NPC occurrence normalize validator chain = %#v, want %#v", got, npcOccurrenceNormalizeChain)
|
||||
}
|
||||
sceneExtractChain := []pipeline.ModuleBinding{
|
||||
pipeline.Binding("generic/valid_json"),
|
||||
@@ -292,8 +292,8 @@ func TestRegisterAddsDNDFamily(t *testing.T) {
|
||||
if got := registries.ValidatorChains.Validators(pipeline.StageMerge, npcextract.Key); got != nil {
|
||||
t.Fatalf("NPC merge validator chain = %#v, want absent", got)
|
||||
}
|
||||
if got := registries.ValidatorChains.Validators(pipeline.StageMerge, interactionextract.Key); got != nil {
|
||||
t.Fatalf("NPC interaction merge validator chain = %#v, want absent", got)
|
||||
if got := registries.ValidatorChains.Validators(pipeline.StageMerge, occurrenceextract.Key); got != nil {
|
||||
t.Fatalf("NPC occurrence merge validator chain = %#v, want absent", got)
|
||||
}
|
||||
assertAssetNamesContain(t, assets.SchemaFS, []string{
|
||||
"dnd_scenes_llm.v1.json",
|
||||
@@ -351,31 +351,31 @@ func TestRegisterAddsDNDFamily(t *testing.T) {
|
||||
t.Fatalf("item event extractor has a generated-reference dependency: %#v", slot)
|
||||
}
|
||||
}
|
||||
interactionExtractSpec, extractOK := registries.Extractors.Spec(interactionextract.Key)
|
||||
interactionNormalizeSpec, normalizeOK := registries.Normalizers.Spec(interactionnormalize.Key)
|
||||
if !extractOK || interactionExtractSpec.ArtifactKind != dnd.NPCOccurrenceListKind || !normalizeOK || interactionNormalizeSpec.ArtifactKind != dnd.NPCOccurrenceListKind || interactionNormalizeSpec.Stage != pipeline.StageNormalize {
|
||||
t.Fatalf("NPC interaction specs = %#v / %#v, present = %t / %t", interactionExtractSpec, interactionNormalizeSpec, extractOK, normalizeOK)
|
||||
occurrenceExtractSpec, extractOK := registries.Extractors.Spec(occurrenceextract.Key)
|
||||
occurrenceNormalizeSpec, normalizeOK := registries.Normalizers.Spec(occurrencenormalize.Key)
|
||||
if !extractOK || occurrenceExtractSpec.ArtifactKind != dnd.NPCOccurrenceListKind || !normalizeOK || occurrenceNormalizeSpec.ArtifactKind != dnd.NPCOccurrenceListKind || occurrenceNormalizeSpec.Stage != pipeline.StageNormalize {
|
||||
t.Fatalf("NPC occurrence specs = %#v / %#v, present = %t / %t", occurrenceExtractSpec, occurrenceNormalizeSpec, extractOK, normalizeOK)
|
||||
}
|
||||
locationExtractSpec, locationExtractOK := registries.Extractors.Spec(locationextract.Key)
|
||||
locationNormalizeSpec, locationNormalizeOK := registries.Normalizers.Spec(locationnormalize.Key)
|
||||
if !locationExtractOK || locationExtractSpec.ArtifactKind != dnd.LocationListKind || locationExtractSpec.ExecutionClass != contracts.ExecutionClassLLMBacked || !locationNormalizeOK || locationNormalizeSpec.ArtifactKind != dnd.LocationListKind || locationNormalizeSpec.ExecutionClass != contracts.ExecutionClassLLMBacked {
|
||||
t.Fatalf("location specs = %#v / %#v", locationExtractSpec, locationNormalizeSpec)
|
||||
}
|
||||
occurrenceExtractSpec, occurrenceExtractOK := registries.Extractors.Spec(locationoccurrenceextract.Key)
|
||||
occurrenceNormalizeSpec, occurrenceNormalizeOK := registries.Normalizers.Spec(locationoccurrencenormalize.Key)
|
||||
if !occurrenceExtractOK || occurrenceExtractSpec.ArtifactKind != dnd.LocationOccurrenceListKind || occurrenceExtractSpec.ExecutionClass != contracts.ExecutionClassLLMBacked || !occurrenceNormalizeOK || occurrenceNormalizeSpec.ArtifactKind != dnd.LocationOccurrenceListKind || occurrenceNormalizeSpec.ExecutionClass != contracts.ExecutionClassDeterministic {
|
||||
t.Fatalf("location occurrence specs = %#v / %#v", occurrenceExtractSpec, occurrenceNormalizeSpec)
|
||||
locationOccurrenceExtractSpec, locationOccurrenceExtractOK := registries.Extractors.Spec(locationoccurrenceextract.Key)
|
||||
locationOccurrenceNormalizeSpec, locationOccurrenceNormalizeOK := registries.Normalizers.Spec(locationoccurrencenormalize.Key)
|
||||
if !locationOccurrenceExtractOK || locationOccurrenceExtractSpec.ArtifactKind != dnd.LocationOccurrenceListKind || locationOccurrenceExtractSpec.ExecutionClass != contracts.ExecutionClassLLMBacked || !locationOccurrenceNormalizeOK || locationOccurrenceNormalizeSpec.ArtifactKind != dnd.LocationOccurrenceListKind || locationOccurrenceNormalizeSpec.ExecutionClass != contracts.ExecutionClassDeterministic {
|
||||
t.Fatalf("location occurrence specs = %#v / %#v", locationOccurrenceExtractSpec, locationOccurrenceNormalizeSpec)
|
||||
}
|
||||
locationRegistrySlot := referenceSlot(occurrenceExtractSpec.ReferenceSlots, "locations")
|
||||
occurrenceNormalizeRegistrySlot := referenceSlot(occurrenceNormalizeSpec.ReferenceSlots, "locations")
|
||||
if len(occurrenceExtractSpec.ReferenceSlots) != 5 || len(occurrenceNormalizeSpec.ReferenceSlots) != 1 {
|
||||
t.Fatalf("location occurrence reference slots = %#v / %#v, want extractor campaign context and normalizer registry only", occurrenceExtractSpec.ReferenceSlots, occurrenceNormalizeSpec.ReferenceSlots)
|
||||
locationRegistrySlot := referenceSlot(locationOccurrenceExtractSpec.ReferenceSlots, "locations")
|
||||
occurrenceNormalizeRegistrySlot := referenceSlot(locationOccurrenceNormalizeSpec.ReferenceSlots, "locations")
|
||||
if len(locationOccurrenceExtractSpec.ReferenceSlots) != 5 || len(locationOccurrenceNormalizeSpec.ReferenceSlots) != 1 {
|
||||
t.Fatalf("location occurrence reference slots = %#v / %#v, want extractor campaign context and normalizer registry only", locationOccurrenceExtractSpec.ReferenceSlots, locationOccurrenceNormalizeSpec.ReferenceSlots)
|
||||
}
|
||||
if !locationRegistrySlot.Required || !reflect.DeepEqual(locationRegistrySlot.AcceptedMediaTypes, []string{"application/json"}) || !reflect.DeepEqual(locationRegistrySlot.AcceptedArtifactKinds, []contracts.ArtifactKind{dnd.LocationListKind}) || locationRegistrySlot.MaxBytes != 1048576 || !sameReferenceSlotContract(locationRegistrySlot, occurrenceNormalizeRegistrySlot) {
|
||||
t.Fatalf("location registry slots disagree: %#v / %#v", occurrenceExtractSpec.ReferenceSlots, occurrenceNormalizeSpec.ReferenceSlots)
|
||||
}
|
||||
for _, name := range []string{"party", "roster", "players", "glossary"} {
|
||||
slot := referenceSlot(occurrenceExtractSpec.ReferenceSlots, name)
|
||||
slot := referenceSlot(locationOccurrenceExtractSpec.ReferenceSlots, name)
|
||||
if slot.Name != name || slot.Required || len(slot.AcceptedArtifactKinds) != 0 {
|
||||
t.Fatalf("location occurrence extractor campaign slot %q = %#v, want optional text context", name, slot)
|
||||
}
|
||||
@@ -388,10 +388,10 @@ func TestRegisterAddsDNDFamily(t *testing.T) {
|
||||
if len(sceneExtractSpec.ReferenceSlots) != 3 || len(sceneNormalizeSpec.ReferenceSlots) != 0 {
|
||||
t.Fatalf("scene description reference slots = %#v / %#v, want extractor campaign slots only", sceneExtractSpec.ReferenceSlots, sceneNormalizeSpec.ReferenceSlots)
|
||||
}
|
||||
extractRegistrySlot := referenceSlot(interactionExtractSpec.ReferenceSlots, "npc_registry")
|
||||
normalizeRegistrySlot := referenceSlot(interactionNormalizeSpec.ReferenceSlots, "npc_registry")
|
||||
extractRegistrySlot := referenceSlot(occurrenceExtractSpec.ReferenceSlots, "npc_registry")
|
||||
normalizeRegistrySlot := referenceSlot(occurrenceNormalizeSpec.ReferenceSlots, "npc_registry")
|
||||
if !extractRegistrySlot.Required || !reflect.DeepEqual(extractRegistrySlot.AcceptedArtifactKinds, []contracts.ArtifactKind{dnd.NPCRegistryKind}) || !sameReferenceSlotContract(extractRegistrySlot, normalizeRegistrySlot) {
|
||||
t.Fatalf("NPC interaction registry slots disagree: %#v / %#v", interactionExtractSpec.ReferenceSlots, interactionNormalizeSpec.ReferenceSlots)
|
||||
t.Fatalf("NPC occurrence registry slots disagree: %#v / %#v", occurrenceExtractSpec.ReferenceSlots, occurrenceNormalizeSpec.ReferenceSlots)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,8 +418,8 @@ func TestEvidenceProjectorsPreserveDirectReferencesWithIndependentStorage(t *tes
|
||||
{name: "item events", project: func() []source.SourceRef {
|
||||
return itemEventEvidence(dnd.ItemEventList{Events: []dnd.ItemEvent{{SourceRefs: []source.SourceRef{first, second}}}})
|
||||
}, want: []source.SourceRef{first, second}},
|
||||
{name: "npc interactions", project: func() []source.SourceRef {
|
||||
return npcInteractionEvidence(dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{{SourceRefs: []source.SourceRef{first, second}}}})
|
||||
{name: "npc occurrences", project: func() []source.SourceRef {
|
||||
return npcOccurrenceEvidence(dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{{SourceRefs: []source.SourceRef{first, second}}}})
|
||||
}, want: []source.SourceRef{first, second}},
|
||||
{name: "scene descriptions", project: func() []source.SourceRef {
|
||||
return sceneDescriptionEvidence(dnd.SceneDescriptionList{Scenes: []dnd.SceneDescription{{SourceRef: first}, {SourceRef: second}}})
|
||||
@@ -559,7 +559,7 @@ func TestAppendSpellListsPreservesOrderPresenceAndOwnership(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendNPCInteractionListsPreservesOrderPresenceAndOwnership(t *testing.T) {
|
||||
func TestAppendNPCOccurrenceListsPreservesOrderPresenceAndOwnership(t *testing.T) {
|
||||
refs := []source.SourceRef{{SourceID: "session", StartUnitID: 10, EndUnitID: 10}}
|
||||
input := []dnd.NPCOccurrenceList{
|
||||
{},
|
||||
@@ -567,21 +567,21 @@ func TestAppendNPCInteractionListsPreservesOrderPresenceAndOwnership(t *testing.
|
||||
{Occurrences: []dnd.NPCOccurrence{{Name: "Aria", Kind: dnd.NPCOccurrenceKindDialogue, SourceRefs: refs}}},
|
||||
{Occurrences: []dnd.NPCOccurrence{{Name: "Borin", Kind: dnd.NPCOccurrenceKindMentioned, SourceRefs: []source.SourceRef{{SourceID: "session", StartUnitID: 20, EndUnitID: 20}}}}},
|
||||
}
|
||||
got, err := appendNPCInteractionLists(input)
|
||||
got, err := appendNPCOccurrenceLists(input)
|
||||
if err != nil {
|
||||
t.Fatalf("appendNPCInteractionLists() error = %v", err)
|
||||
t.Fatalf("appendNPCOccurrenceLists() error = %v", err)
|
||||
}
|
||||
if got.Occurrences == nil || !reflect.DeepEqual([]string{got.Occurrences[0].Name, got.Occurrences[1].Name}, []string{"Aria", "Borin"}) {
|
||||
t.Fatalf("combined interactions = %#v", got)
|
||||
t.Fatalf("combined occurrences = %#v", got)
|
||||
}
|
||||
got.Occurrences[0].SourceRefs[0].StartUnitID = 999
|
||||
if input[2].Occurrences[0].SourceRefs[0].StartUnitID == 999 {
|
||||
t.Fatal("merged interactions share source reference storage")
|
||||
t.Fatal("merged occurrences share source reference storage")
|
||||
}
|
||||
for _, values := range [][]dnd.NPCOccurrenceList{nil, []dnd.NPCOccurrenceList{{}, {}}} {
|
||||
result, err := appendNPCInteractionLists(values)
|
||||
result, err := appendNPCOccurrenceLists(values)
|
||||
if err != nil || result.Occurrences != nil {
|
||||
t.Fatalf("nil-only merge = %#v, %v; want nil interactions", result, err)
|
||||
t.Fatalf("nil-only merge = %#v, %v; want nil occurrences", result, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -711,9 +711,9 @@ func TestAppendListsPreserveNestedSourceReferencePresence(t *testing.T) {
|
||||
t.Fatalf("appendEnemyEventLists() = %#v, %v; want present-empty source refs", enemyEvents, err)
|
||||
}
|
||||
|
||||
interactions, err := appendNPCInteractionLists([]dnd.NPCOccurrenceList{{Occurrences: []dnd.NPCOccurrence{{SourceRefs: []source.SourceRef{}}}}})
|
||||
if err != nil || interactions.Occurrences[0].SourceRefs == nil {
|
||||
t.Fatalf("appendNPCInteractionLists() = %#v, %v; want present-empty source refs", interactions, err)
|
||||
occurrences, err := appendNPCOccurrenceLists([]dnd.NPCOccurrenceList{{Occurrences: []dnd.NPCOccurrence{{SourceRefs: []source.SourceRef{}}}}})
|
||||
if err != nil || occurrences.Occurrences[0].SourceRefs == nil {
|
||||
t.Fatalf("appendNPCOccurrenceLists() = %#v, %v; want present-empty source refs", occurrences, err)
|
||||
}
|
||||
|
||||
events, err := appendItemEventLists([]dnd.ItemEventList{{Events: []dnd.ItemEvent{{SourceRefs: []source.SourceRef{}}}}})
|
||||
|
||||
@@ -16,20 +16,20 @@ import (
|
||||
itemeventshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/itemevents/shape"
|
||||
itemeventrefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/itemevents/source_refs"
|
||||
itemeventrelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/itemevents/source_relatedness"
|
||||
occurrenceinvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/invariants"
|
||||
occurrenceregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/registry"
|
||||
occurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/shape"
|
||||
occurrencerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/source_refs"
|
||||
occurrencerelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/source_relatedness"
|
||||
locationoccurrenceinvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/invariants"
|
||||
locationoccurrenceregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/registry"
|
||||
locationoccurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/shape"
|
||||
locationoccurrencerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/source_refs"
|
||||
locationoccurrencerelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/source_relatedness"
|
||||
locationidentity "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/identity"
|
||||
locationshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/shape"
|
||||
locationrefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/source_refs"
|
||||
locationrelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/source_relatedness"
|
||||
interactioninvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/invariants"
|
||||
interactionregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/registry"
|
||||
interactionshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/shape"
|
||||
interactionrefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/source_refs"
|
||||
interactionrelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/source_relatedness"
|
||||
npcoccurrenceinvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/invariants"
|
||||
npcoccurrenceregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/registry"
|
||||
npcoccurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/shape"
|
||||
npcoccurrencerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/source_refs"
|
||||
npcoccurrencerelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/source_relatedness"
|
||||
npcidentity "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcregistry/identity"
|
||||
npcshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcregistry/shape"
|
||||
npcsourcerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcregistry/source_refs"
|
||||
@@ -69,11 +69,11 @@ func registerValidators(registries pipeline.Registries) error {
|
||||
{name: "item event source references validator", register: func() error { return itemeventrefs.Register(registries.Validators) }},
|
||||
{name: "item event source relatedness validator", register: func() error { return itemeventrelatedness.Register(registries.Validators) }},
|
||||
{name: "item event normalized invariants validator", register: func() error { return itemeventinvariants.Register(registries.Validators) }},
|
||||
{name: "npc interaction shape validator", register: func() error { return interactionshape.Register(registries.Validators) }},
|
||||
{name: "npc interaction registry validator", register: func() error { return interactionregistry.Register(registries.Validators) }},
|
||||
{name: "npc interaction source references validator", register: func() error { return interactionrefs.Register(registries.Validators) }},
|
||||
{name: "npc interaction source relatedness validator", register: func() error { return interactionrelatedness.Register(registries.Validators) }},
|
||||
{name: "npc interaction normalized invariants validator", register: func() error { return interactioninvariants.Register(registries.Validators) }},
|
||||
{name: "npc occurrence shape validator", register: func() error { return npcoccurrenceshape.Register(registries.Validators) }},
|
||||
{name: "npc occurrence registry validator", register: func() error { return npcoccurrenceregistry.Register(registries.Validators) }},
|
||||
{name: "npc occurrence source references validator", register: func() error { return npcoccurrencerefs.Register(registries.Validators) }},
|
||||
{name: "npc occurrence source relatedness validator", register: func() error { return npcoccurrencerelatedness.Register(registries.Validators) }},
|
||||
{name: "npc occurrence normalized invariants validator", register: func() error { return npcoccurrenceinvariants.Register(registries.Validators) }},
|
||||
{name: "scene description shape validator", register: func() error { return sceneshape.Register(registries.Validators) }},
|
||||
{name: "scene description source references validator", register: func() error { return scenerefs.Register(registries.Validators) }},
|
||||
{name: "scene description source relatedness validator", register: func() error { return scenerelatedness.Register(registries.Validators) }},
|
||||
@@ -82,11 +82,11 @@ func registerValidators(registries pipeline.Registries) error {
|
||||
{name: "location identity validator", register: func() error { return locationidentity.Register(registries.Validators) }},
|
||||
{name: "location source references validator", register: func() error { return locationrefs.Register(registries.Validators) }},
|
||||
{name: "location source relatedness validator", register: func() error { return locationrelatedness.Register(registries.Validators) }},
|
||||
{name: "location occurrence shape validator", register: func() error { return occurrenceshape.Register(registries.Validators) }},
|
||||
{name: "location occurrence registry validator", register: func() error { return occurrenceregistry.Register(registries.Validators) }},
|
||||
{name: "location occurrence normalized invariants validator", register: func() error { return occurrenceinvariants.Register(registries.Validators) }},
|
||||
{name: "location occurrence source references validator", register: func() error { return occurrencerefs.Register(registries.Validators) }},
|
||||
{name: "location occurrence source relatedness validator", register: func() error { return occurrencerelatedness.Register(registries.Validators) }},
|
||||
{name: "location occurrence shape validator", register: func() error { return locationoccurrenceshape.Register(registries.Validators) }},
|
||||
{name: "location occurrence registry validator", register: func() error { return locationoccurrenceregistry.Register(registries.Validators) }},
|
||||
{name: "location occurrence normalized invariants validator", register: func() error { return locationoccurrenceinvariants.Register(registries.Validators) }},
|
||||
{name: "location occurrence source references validator", register: func() error { return locationoccurrencerefs.Register(registries.Validators) }},
|
||||
{name: "location occurrence source relatedness validator", register: func() error { return locationoccurrencerelatedness.Register(registries.Validators) }},
|
||||
{name: "spell-list always accept validator", register: func() error {
|
||||
return alwaysaccept.RegisterTyped[dnd.SpellList](registries.Validators, dnd.SpellListKind)
|
||||
}},
|
||||
@@ -117,10 +117,10 @@ func registerValidators(registries pipeline.Registries) error {
|
||||
{name: "item-event-list always reject validator", register: func() error {
|
||||
return alwaysreject.RegisterTyped[dnd.ItemEventList](registries.Validators, dnd.ItemEventListKind)
|
||||
}},
|
||||
{name: "npc-interaction-list always accept validator", register: func() error {
|
||||
{name: "npc-occurrence-list always accept validator", register: func() error {
|
||||
return alwaysaccept.RegisterTyped[dnd.NPCOccurrenceList](registries.Validators, dnd.NPCOccurrenceListKind)
|
||||
}},
|
||||
{name: "npc-interaction-list always reject validator", register: func() error {
|
||||
{name: "npc-occurrence-list always reject validator", register: func() error {
|
||||
return alwaysreject.RegisterTyped[dnd.NPCOccurrenceList](registries.Validators, dnd.NPCOccurrenceListKind)
|
||||
}},
|
||||
{name: "scene-description-list always accept validator", register: func() error {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Package invariants validates normalized D&D NPC interaction artifacts.
|
||||
// Package invariants validates normalized D&D NPC occurrence artifacts.
|
||||
package invariants
|
||||
|
||||
import (
|
||||
@@ -10,17 +10,17 @@ import (
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||
interactionmodel "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcinteractions"
|
||||
interactionmodel "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcoccurrences"
|
||||
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
|
||||
interactionshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/shape"
|
||||
occurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/shape"
|
||||
)
|
||||
|
||||
const (
|
||||
Key = "normalize/dnd/npc-interactions/invariants"
|
||||
Key = "normalize/dnd/npc-occurrences/invariants"
|
||||
ReasonCode = "invalid_npc_interaction_normalization"
|
||||
policy = "dnd.npc_interactions.validator.normalized.v1"
|
||||
policy = "dnd.npc_occurrences.validator.normalized.v1"
|
||||
)
|
||||
|
||||
type Options struct{}
|
||||
@@ -35,7 +35,7 @@ var _ pipeline.CheckpointFingerprintProvider = (*Validator)(nil)
|
||||
|
||||
func New(_ Options, references ...contracts.ReferenceSet) (*Validator, error) {
|
||||
if len(references) > 1 {
|
||||
return nil, fmt.Errorf("NPC interaction invariants validator accepts at most one reference set")
|
||||
return nil, fmt.Errorf("NPC occurrence invariants validator accepts at most one reference set")
|
||||
}
|
||||
var referenceSet contracts.ReferenceSet
|
||||
if len(references) == 1 {
|
||||
@@ -77,7 +77,7 @@ func (v *Validator) CheckpointFingerprints() []pipeline.CheckpointFingerprint {
|
||||
}
|
||||
|
||||
func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationRequest[dnd.NPCOccurrenceList]) (contracts.ValidationResult, error) {
|
||||
if interactionshape.Validate(req.Value) != nil {
|
||||
if occurrenceshape.Validate(req.Value) != nil {
|
||||
return contracts.ValidationResult{Approved: true}, nil
|
||||
}
|
||||
index := source.NewDocumentIndex(req.Source)
|
||||
@@ -85,14 +85,14 @@ func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationReq
|
||||
return contracts.ValidationResult{Approved: true}, nil
|
||||
}
|
||||
if v == nil || v.npcResolver == nil {
|
||||
return contracts.ValidationResult{}, fmt.Errorf("NPC interaction invariants validator must not be nil")
|
||||
return contracts.ValidationResult{}, fmt.Errorf("NPC occurrence invariants validator must not be nil")
|
||||
}
|
||||
npcRegistry, err := v.npcResolver.Resolve(req.References)
|
||||
if err != nil {
|
||||
return contracts.ValidationResult{}, fmt.Errorf("resolve NPC registry: %w", err)
|
||||
}
|
||||
if !npcRegistry.Bound() {
|
||||
return contracts.ValidationResult{Approved: false, ReasonCode: ReasonCode, Message: "invalid NPC interaction normalization: NPC registry reference is required"}, nil
|
||||
return contracts.ValidationResult{Approved: false, ReasonCode: ReasonCode, Message: "invalid NPC occurrence normalization: NPC registry reference is required"}, nil
|
||||
}
|
||||
issues := issuesFor(shared.NewSourceRefOrderFromIndex(index), req.Value, npcRegistry)
|
||||
if len(issues) == 0 {
|
||||
@@ -101,7 +101,7 @@ func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationReq
|
||||
return contracts.ValidationResult{
|
||||
Approved: false,
|
||||
ReasonCode: ReasonCode,
|
||||
Message: diagnostics.Aggregate("invalid NPC interaction normalization", issues),
|
||||
Message: diagnostics.Aggregate("invalid NPC occurrence normalization", issues),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
|
||||
)
|
||||
|
||||
func TestValidatorApprovesCanonicalNormalizedInteractions(t *testing.T) {
|
||||
func TestValidatorApprovesCanonicalNormalizedOccurrences(t *testing.T) {
|
||||
references := registryReferences(t, "Aria", "Borin")
|
||||
result, err := newValidator(t, references).Validate(context.Background(), request(references, normalizedList()))
|
||||
if err != nil || !result.Approved {
|
||||
@@ -143,9 +143,9 @@ func normalizedList() dnd.NPCOccurrenceList {
|
||||
|
||||
func cloneList(value dnd.NPCOccurrenceList) dnd.NPCOccurrenceList {
|
||||
copyValue := dnd.NPCOccurrenceList{Occurrences: make([]dnd.NPCOccurrence, len(value.Occurrences))}
|
||||
for index, interaction := range value.Occurrences {
|
||||
copyValue.Occurrences[index] = interaction
|
||||
copyValue.Occurrences[index].SourceRefs = append([]source.SourceRef(nil), interaction.SourceRefs...)
|
||||
for index, occurrence := range value.Occurrences {
|
||||
copyValue.Occurrences[index] = occurrence
|
||||
copyValue.Occurrences[index].SourceRefs = append([]source.SourceRef(nil), occurrence.SourceRefs...)
|
||||
}
|
||||
return copyValue
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Package registry validates D&D NPC interaction names against NPC grounding.
|
||||
// Package registry validates D&D NPC occurrence names against NPC grounding.
|
||||
package registry
|
||||
|
||||
import (
|
||||
@@ -10,13 +10,13 @@ import (
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
|
||||
interactionshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/shape"
|
||||
occurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/shape"
|
||||
)
|
||||
|
||||
const (
|
||||
Key = "extract/dnd/npc-interactions/registry"
|
||||
Key = "extract/dnd/npc-occurrences/registry"
|
||||
ReasonCode = "invalid_npc_interaction_registry"
|
||||
policy = "dnd.npc_interactions.validator.registry.v1"
|
||||
policy = "dnd.npc_occurrences.validator.registry.v1"
|
||||
)
|
||||
|
||||
type Options struct{}
|
||||
@@ -31,7 +31,7 @@ var _ pipeline.CheckpointFingerprintProvider = (*Validator)(nil)
|
||||
|
||||
func New(_ Options, references ...contracts.ReferenceSet) (*Validator, error) {
|
||||
if len(references) > 1 {
|
||||
return nil, fmt.Errorf("NPC interaction registry validator accepts at most one reference set")
|
||||
return nil, fmt.Errorf("NPC occurrence registry validator accepts at most one reference set")
|
||||
}
|
||||
var referenceSet contracts.ReferenceSet
|
||||
if len(references) == 1 {
|
||||
@@ -73,11 +73,11 @@ func (v *Validator) CheckpointFingerprints() []pipeline.CheckpointFingerprint {
|
||||
}
|
||||
|
||||
func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationRequest[dnd.NPCOccurrenceList]) (contracts.ValidationResult, error) {
|
||||
if interactionshape.Validate(req.Value) != nil {
|
||||
if occurrenceshape.Validate(req.Value) != nil {
|
||||
return contracts.ValidationResult{Approved: true}, nil
|
||||
}
|
||||
if v == nil || v.npcResolver == nil {
|
||||
return contracts.ValidationResult{}, fmt.Errorf("NPC interaction registry validator must not be nil")
|
||||
return contracts.ValidationResult{}, fmt.Errorf("NPC occurrence registry validator must not be nil")
|
||||
}
|
||||
npcRegistry, err := v.npcResolver.Resolve(req.References)
|
||||
if err != nil {
|
||||
@@ -107,7 +107,7 @@ func rejection(issues []string) contracts.ValidationResult {
|
||||
return contracts.ValidationResult{
|
||||
Approved: false,
|
||||
ReasonCode: ReasonCode,
|
||||
Message: diagnostics.Aggregate("invalid NPC interaction registry", issues),
|
||||
Message: diagnostics.Aggregate("invalid NPC occurrence registry", issues),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Package shape validates required D&D NPC interaction candidate fields.
|
||||
// Package shape validates required D&D NPC occurrence candidate fields.
|
||||
package shape
|
||||
|
||||
import (
|
||||
@@ -13,9 +13,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
Key = "extract/dnd/npc-interactions/shape"
|
||||
Key = "extract/dnd/npc-occurrences/shape"
|
||||
ReasonCode = "invalid_npc_interaction_shape"
|
||||
policy = "dnd.npc_interactions.validator.shape.v1"
|
||||
policy = "dnd.npc_occurrences.validator.shape.v1"
|
||||
)
|
||||
|
||||
type Options struct{}
|
||||
@@ -45,7 +45,7 @@ func Validate(value dnd.NPCOccurrenceList) error {
|
||||
if len(issues) == 0 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%s", diagnostics.Aggregate("invalid NPC interaction shape", issues))
|
||||
return fmt.Errorf("%s", diagnostics.Aggregate("invalid NPC occurrence shape", issues))
|
||||
}
|
||||
|
||||
func issuesFor(value dnd.NPCOccurrenceList) []string {
|
||||
@@ -1,4 +1,4 @@
|
||||
// Package sourcerefs validates D&D NPC interaction transcript evidence.
|
||||
// Package sourcerefs validates D&D NPC occurrence transcript evidence.
|
||||
package sourcerefs
|
||||
|
||||
import (
|
||||
@@ -10,13 +10,13 @@ import (
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
|
||||
interactionshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/shape"
|
||||
occurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/shape"
|
||||
)
|
||||
|
||||
const (
|
||||
Key = "extract/dnd/npc-interactions/source_refs"
|
||||
Key = "extract/dnd/npc-occurrences/source_refs"
|
||||
ReasonCode = "invalid_npc_interaction_source_refs"
|
||||
policy = "dnd.npc_interactions.validator.source_refs.v2"
|
||||
policy = "dnd.npc_occurrences.validator.source_refs.v2"
|
||||
)
|
||||
|
||||
type Options struct{}
|
||||
@@ -36,9 +36,9 @@ func (v *Validator) CheckpointFingerprints() []pipeline.CheckpointFingerprint {
|
||||
|
||||
func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationRequest[dnd.NPCOccurrenceList]) (contracts.ValidationResult, error) {
|
||||
if req.Stage == string(pipeline.StageExtract) && req.Chunk == nil {
|
||||
return contracts.ValidationResult{}, fmt.Errorf("NPC interaction source-reference validator requires the current extraction chunk")
|
||||
return contracts.ValidationResult{}, fmt.Errorf("NPC occurrence source-reference validator requires the current extraction chunk")
|
||||
}
|
||||
if interactionshape.Validate(req.Value) != nil {
|
||||
if occurrenceshape.Validate(req.Value) != nil {
|
||||
return contracts.ValidationResult{Approved: true}, nil
|
||||
}
|
||||
index := source.NewDocumentIndex(req.Source)
|
||||
@@ -63,7 +63,7 @@ func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationReq
|
||||
return contracts.ValidationResult{
|
||||
Approved: false,
|
||||
ReasonCode: ReasonCode,
|
||||
Message: diagnostics.Aggregate("invalid NPC interaction source references", issues),
|
||||
Message: diagnostics.Aggregate("invalid NPC occurrence source references", issues),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Package sourcerelatedness warns about NPC interaction evidence unrelated to its NPC.
|
||||
// Package sourcerelatedness warns about NPC occurrence evidence unrelated to its NPC.
|
||||
package sourcerelatedness
|
||||
|
||||
import (
|
||||
@@ -10,14 +10,14 @@ import (
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
|
||||
interactionshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/shape"
|
||||
occurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/shape"
|
||||
)
|
||||
|
||||
const (
|
||||
Key = "extract/dnd/npc-interactions/source_relatedness"
|
||||
Key = "extract/dnd/npc-occurrences/source_relatedness"
|
||||
WarningReasonCode = "npc_interaction_not_near_source"
|
||||
OmittedReasonCode = "npc_interaction_relatedness_warnings_omitted"
|
||||
policy = "dnd.npc_interactions.validator.source_relatedness.v2"
|
||||
policy = "dnd.npc_occurrences.validator.source_relatedness.v2"
|
||||
)
|
||||
|
||||
type Options struct{}
|
||||
@@ -36,7 +36,7 @@ func (v *Validator) CheckpointFingerprints() []pipeline.CheckpointFingerprint {
|
||||
}
|
||||
|
||||
func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationRequest[dnd.NPCOccurrenceList]) (contracts.ValidationResult, error) {
|
||||
if interactionshape.Validate(req.Value) != nil {
|
||||
if occurrenceshape.Validate(req.Value) != nil {
|
||||
return contracts.ValidationResult{Approved: true}, nil
|
||||
}
|
||||
resolver, err := shared.NewCitationResolver(req.Source)
|
||||
@@ -59,12 +59,12 @@ func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationReq
|
||||
warnings = append(warnings, contracts.Warning{
|
||||
Scope: fmt.Sprintf("occurrences[%d]", index),
|
||||
ReasonCode: WarningReasonCode,
|
||||
Message: fmt.Sprintf("NPC interaction name %s was not found in cited source text", diagnostics.Quote(occurrence.Name)),
|
||||
Message: fmt.Sprintf("NPC occurrence name %s was not found in cited source text", diagnostics.Quote(occurrence.Name)),
|
||||
})
|
||||
}
|
||||
return contracts.ValidationResult{
|
||||
Approved: true,
|
||||
Warnings: diagnostics.LimitWarnings(warnings, "npc_interactions", OmittedReasonCode),
|
||||
Warnings: diagnostics.LimitWarnings(warnings, "npc_occurrences", OmittedReasonCode),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
|
||||
)
|
||||
|
||||
func TestValidatorUsesOnlyCurrentTranscriptAndWarnsOncePerInteraction(t *testing.T) {
|
||||
func TestValidatorUsesOnlyCurrentTranscriptAndWarnsOncePerOccurrence(t *testing.T) {
|
||||
value := dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{
|
||||
{NPCID: "npc:test", Name: "O'Rin Thorn", Kind: dnd.NPCOccurrenceKindDialogue, SourceRefs: []source.SourceRef{{SourceID: "session", StartUnitID: 1, EndUnitID: 1}}},
|
||||
{NPCID: "npc:test", Name: "Missing\nNPC", Kind: dnd.NPCOccurrenceKindMentioned, SourceRefs: []source.SourceRef{{SourceID: "session", StartUnitID: 2, EndUnitID: 2}, {SourceID: "session", StartUnitID: 2, EndUnitID: 2}}},
|
||||
@@ -53,9 +53,9 @@ func TestValidatorDefersMalformedShapeAndInvalidRanges(t *testing.T) {
|
||||
|
||||
func TestValidatorBoundsWarnings(t *testing.T) {
|
||||
count := diagnostics.MaxWarnings + 5
|
||||
interactions := make([]dnd.NPCOccurrence, count)
|
||||
for index := range interactions {
|
||||
interactions[index] = dnd.NPCOccurrence{
|
||||
occurrences := make([]dnd.NPCOccurrence, count)
|
||||
for index := range occurrences {
|
||||
occurrences[index] = dnd.NPCOccurrence{
|
||||
NPCID: "npc:test",
|
||||
Name: "Missing NPC",
|
||||
Kind: dnd.NPCOccurrenceKindMentioned,
|
||||
@@ -64,7 +64,7 @@ func TestValidatorBoundsWarnings(t *testing.T) {
|
||||
}
|
||||
result, err := New(Options{}).Validate(context.Background(), contracts.TypedValidationRequest[dnd.NPCOccurrenceList]{
|
||||
Source: &source.SourceDocument{ID: "session", Units: []source.SourceUnit{{ID: 1, Text: "The party waits."}}},
|
||||
Value: dnd.NPCOccurrenceList{Occurrences: interactions},
|
||||
Value: dnd.NPCOccurrenceList{Occurrences: occurrences},
|
||||
})
|
||||
if err != nil || !result.Approved {
|
||||
t.Fatalf("Validate() = %#v, %v", result, err)
|
||||
@@ -14,73 +14,73 @@ import (
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||
interactioncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcinteractions"
|
||||
occurrencecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcoccurrences"
|
||||
npccodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcregistry"
|
||||
interactionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcinteractions"
|
||||
occurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcoccurrences"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
||||
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcregistry"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/identity"
|
||||
)
|
||||
|
||||
func TestNPCInteractionPipelineUsesAcceptedRegistryAndCurrentEvidence(t *testing.T) {
|
||||
func TestNPCOccurrencePipelineUsesAcceptedRegistryAndCurrentEvidence(t *testing.T) {
|
||||
registries := productionNPCRegistries(t)
|
||||
resolved := resolveNPCInteractionPipeline(t, registries)
|
||||
client := &npcInteractionLLMClient{}
|
||||
resolved := resolveNPCOccurrencePipeline(t, registries)
|
||||
client := &npcOccurrenceLLMClient{}
|
||||
|
||||
output, err := runPreparedPipeline(t, registries, resolved, client, pipeline.RunInput{RawInput: readNPCFixture(t)})
|
||||
if err != nil {
|
||||
t.Fatalf("Run() error = %v", err)
|
||||
}
|
||||
if len(output.Rejected) != 0 || len(output.NormalizeOutputs) != 2 {
|
||||
t.Fatalf("run outputs = %#v rejected = %#v, want NPC and interaction artifacts", output.NormalizeOutputs, output.Rejected)
|
||||
t.Fatalf("run outputs = %#v rejected = %#v, want NPC and occurrence artifacts", output.NormalizeOutputs, output.Rejected)
|
||||
}
|
||||
|
||||
request := client.requestFor(t, interactionextract.PromptID)
|
||||
request := client.requestFor(t, occurrenceextract.PromptID)
|
||||
wantRegistry := `{"npcs":[{"id":"` + identity.DeriveID("Mira Thorn") + `","name":"Mira Thorn"},{"id":"` + identity.DeriveID("Hooded Guard") + `","name":"Hooded Guard"}]}`
|
||||
if got := string(request.Inputs["npc_registry"].Content); got != wantRegistry {
|
||||
t.Fatalf("interaction registry input = %s, want names-only projection %s", got, wantRegistry)
|
||||
t.Fatalf("occurrence registry input = %s, want names-only projection %s", got, wantRegistry)
|
||||
}
|
||||
if request.Inputs["npc_registry"].MediaType != npccodec.MediaType {
|
||||
t.Fatalf("interaction registry media type = %q, want %q", request.Inputs["npc_registry"].MediaType, npccodec.MediaType)
|
||||
t.Fatalf("occurrence registry media type = %q, want %q", request.Inputs["npc_registry"].MediaType, npccodec.MediaType)
|
||||
}
|
||||
|
||||
serialized := normalizedLane(t, output, "interactions")
|
||||
if serialized.Artifact.Schema.ID != interactioncodec.SchemaID || serialized.Artifact.Schema.Version != interactioncodec.SchemaVersion {
|
||||
t.Fatalf("interaction artifact schema = %#v", serialized.Artifact.Schema)
|
||||
serialized := normalizedLane(t, output, "occurrences")
|
||||
if serialized.Artifact.Schema.ID != occurrencecodec.SchemaID || serialized.Artifact.Schema.Version != occurrencecodec.SchemaVersion {
|
||||
t.Fatalf("occurrence artifact schema = %#v", serialized.Artifact.Schema)
|
||||
}
|
||||
interactions, err := interactioncodec.New().Decode(serialized.Artifact.Content)
|
||||
occurrences, err := occurrencecodec.New().Decode(serialized.Artifact.Content)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode(interaction output) error = %v", err)
|
||||
t.Fatalf("Decode(occurrence output) error = %v", err)
|
||||
}
|
||||
if len(interactions.Occurrences) != 2 {
|
||||
t.Fatalf("interactions = %#v, want two occurrences", interactions)
|
||||
if len(occurrences.Occurrences) != 2 {
|
||||
t.Fatalf("occurrences = %#v, want two occurrences", occurrences)
|
||||
}
|
||||
first, second := interactions.Occurrences[0], interactions.Occurrences[1]
|
||||
first, second := occurrences.Occurrences[0], occurrences.Occurrences[1]
|
||||
if first.Name != "Mira Thorn" || string(first.Kind) != "dialogue" || second.Name != "Hooded Guard" || string(second.Kind) != "noncombat_presence" {
|
||||
t.Fatalf("interactions = %#v, want canonical names, kinds, and source chronology", interactions)
|
||||
t.Fatalf("occurrences = %#v, want canonical names, kinds, and source chronology", occurrences)
|
||||
}
|
||||
assertInteractionEvidence(t, first.SourceRefs)
|
||||
assertInteractionEvidence(t, second.SourceRefs)
|
||||
assertOccurrenceEvidence(t, first.SourceRefs)
|
||||
assertOccurrenceEvidence(t, second.SourceRefs)
|
||||
if first.SourceRefs[0].StartUnitID >= second.SourceRefs[0].StartUnitID {
|
||||
t.Fatalf("interaction chronology = %#v, want source order", interactions.Occurrences)
|
||||
t.Fatalf("occurrence chronology = %#v, want source order", occurrences.Occurrences)
|
||||
}
|
||||
|
||||
var durable map[string]json.RawMessage
|
||||
if err := json.Unmarshal(serialized.Artifact.Content, &durable); err != nil {
|
||||
t.Fatalf("unmarshal durable interaction payload: %v", err)
|
||||
t.Fatalf("unmarshal durable occurrence payload: %v", err)
|
||||
}
|
||||
if len(durable) != 1 || durable["occurrences"] == nil {
|
||||
t.Fatalf("durable interaction payload = %#v, want only occurrences", durable)
|
||||
t.Fatalf("durable occurrence payload = %#v, want only occurrences", durable)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemanticNPCNormalizationCrossesOrderedRegistryHandoff(t *testing.T) {
|
||||
registries := productionNPCRegistries(t)
|
||||
cfg := loadNPCInteractionPipelineConfig(t)
|
||||
profile := cfg.Pipelines["dnd-npc-interactions-fixture"]
|
||||
cfg := loadNPCOccurrencePipelineConfig(t)
|
||||
profile := cfg.Pipelines["dnd-npc-occurrences-fixture"]
|
||||
profile.Chunk = pipeline.ModuleBinding{Module: "generic", Options: map[string]any{"max_units": 1}}
|
||||
cfg.Pipelines["dnd-npc-interactions-fixture"] = profile
|
||||
effective, err := cfg.Resolve(config.ResolveInput{PipelineID: "dnd-npc-interactions-fixture", Catalog: moduleCatalog(registries)})
|
||||
cfg.Pipelines["dnd-npc-occurrences-fixture"] = profile
|
||||
effective, err := cfg.Resolve(config.ResolveInput{PipelineID: "dnd-npc-occurrences-fixture", Catalog: moduleCatalog(registries)})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -88,7 +88,7 @@ func TestSemanticNPCNormalizationCrossesOrderedRegistryHandoff(t *testing.T) {
|
||||
if err != nil || len(warnings) != 0 {
|
||||
t.Fatalf("MaterializeReferences() error = %v warnings = %#v", err, warnings)
|
||||
}
|
||||
client := &semanticNPCInteractionClient{}
|
||||
client := &semanticNPCOccurrenceClient{}
|
||||
raw := []byte(`{"metadata":{"id":"semantic-session","title":"Semantic NPC session"},"segments":[{"id":1,"start":0,"end":1,"speaker":"DM","text":"Mira Thorn enters."},{"id":2,"start":1,"end":2,"speaker":"DM","text":"Mira Thorn, the Greencloak, waves."}]}`)
|
||||
output, err := runPreparedPipeline(t, registries, resolved, client, pipeline.RunInput{RawInput: raw})
|
||||
if err != nil {
|
||||
@@ -108,11 +108,11 @@ func TestSemanticNPCNormalizationCrossesOrderedRegistryHandoff(t *testing.T) {
|
||||
if refs := npcsValue.NPCs[0].SourceRefs; !reflect.DeepEqual(refs, []source.SourceRef{{SourceID: "semantic-session", StartUnitID: 1, EndUnitID: 1}, {SourceID: "semantic-session", StartUnitID: 2, EndUnitID: 2}}) {
|
||||
t.Fatalf("NPC evidence = %#v, want original extraction evidence union", refs)
|
||||
}
|
||||
interactionOutput := normalizedLane(t, output, "interactions")
|
||||
if interactionOutput.StepID != "extract-interactions" {
|
||||
t.Fatalf("interaction output step = %q, want ordered downstream step", interactionOutput.StepID)
|
||||
occurrenceOutput := normalizedLane(t, output, "occurrences")
|
||||
if occurrenceOutput.StepID != "extract-occurrences" {
|
||||
t.Fatalf("occurrence output step = %q, want ordered downstream step", occurrenceOutput.StepID)
|
||||
}
|
||||
registryRequest := client.requestFor(t, interactionextract.PromptID)
|
||||
registryRequest := client.requestFor(t, occurrenceextract.PromptID)
|
||||
if got := string(registryRequest.Inputs["npc_registry"].Content); got != `{"npcs":[{"id":"`+identity.DeriveID("Mira Thorn")+`","name":"Mira Thorn"}]}` {
|
||||
t.Fatalf("downstream registry = %s, want one canonical NPC identity", got)
|
||||
}
|
||||
@@ -127,10 +127,10 @@ func TestSemanticNPCNormalizationCrossesOrderedRegistryHandoff(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNPCInteractionPipelineSkipsConsumerWhenNPCProducerIsRejected(t *testing.T) {
|
||||
func TestNPCOccurrencePipelineSkipsConsumerWhenNPCProducerIsRejected(t *testing.T) {
|
||||
registries := productionNPCRegistries(t)
|
||||
resolved := resolveNPCInteractionPipeline(t, registries)
|
||||
client := &npcInteractionLLMClient{rejectNPCs: true}
|
||||
resolved := resolveNPCOccurrencePipeline(t, registries)
|
||||
client := &npcOccurrenceLLMClient{rejectNPCs: true}
|
||||
|
||||
output, err := runPreparedPipeline(t, registries, resolved, client, pipeline.RunInput{RawInput: readNPCFixture(t)})
|
||||
if err == nil {
|
||||
@@ -139,15 +139,15 @@ func TestNPCInteractionPipelineSkipsConsumerWhenNPCProducerIsRejected(t *testing
|
||||
if client.requestCount(npcregistry.PromptID) != 1 {
|
||||
t.Fatalf("NPC requests = %d, want rejected producer", client.requestCount(npcregistry.PromptID))
|
||||
}
|
||||
if client.requestCount(interactionextract.PromptID) != 0 {
|
||||
t.Fatalf("interaction requests = %d, want none after rejected producer", client.requestCount(interactionextract.PromptID))
|
||||
if client.requestCount(occurrenceextract.PromptID) != 0 {
|
||||
t.Fatalf("occurrence requests = %d, want none after rejected producer", client.requestCount(occurrenceextract.PromptID))
|
||||
}
|
||||
}
|
||||
|
||||
func resolveNPCInteractionPipeline(t *testing.T, registries pipeline.Registries) pipeline.ResolvedPipeline {
|
||||
func resolveNPCOccurrencePipeline(t *testing.T, registries pipeline.Registries) pipeline.ResolvedPipeline {
|
||||
t.Helper()
|
||||
configValue := loadNPCInteractionPipelineConfig(t)
|
||||
effective, err := configValue.Resolve(config.ResolveInput{PipelineID: "dnd-npc-interactions-fixture", Catalog: moduleCatalog(registries)})
|
||||
configValue := loadNPCOccurrencePipelineConfig(t)
|
||||
effective, err := configValue.Resolve(config.ResolveInput{PipelineID: "dnd-npc-occurrences-fixture", Catalog: moduleCatalog(registries)})
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve() error = %v", err)
|
||||
}
|
||||
@@ -158,11 +158,11 @@ func resolveNPCInteractionPipeline(t *testing.T, registries pipeline.Registries)
|
||||
return resolved
|
||||
}
|
||||
|
||||
func loadNPCInteractionPipelineConfig(t *testing.T) config.Config {
|
||||
func loadNPCOccurrencePipelineConfig(t *testing.T) config.Config {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile("testdata/dnd_npc_interactions_pipeline.yml")
|
||||
data, err := os.ReadFile("testdata/dnd_npc_occurrences_pipeline.yml")
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile(dnd_npc_interactions_pipeline.yml) error = %v", err)
|
||||
t.Fatalf("ReadFile(dnd_npc_occurrences_pipeline.yml) error = %v", err)
|
||||
}
|
||||
fileConfig, err := config.ParseFileConfigYAML(data)
|
||||
if err != nil {
|
||||
@@ -186,25 +186,25 @@ func normalizedLane(t *testing.T, output pipeline.RunOutput, laneID string) cont
|
||||
return contracts.SerializedOutput{}
|
||||
}
|
||||
|
||||
func assertInteractionEvidence(t *testing.T, references []source.SourceRef) {
|
||||
func assertOccurrenceEvidence(t *testing.T, references []source.SourceRef) {
|
||||
t.Helper()
|
||||
if len(references) == 0 {
|
||||
t.Fatal("interaction has no current-source evidence")
|
||||
t.Fatal("occurrence has no current-source evidence")
|
||||
}
|
||||
for _, reference := range references {
|
||||
if reference.SourceID != "npc-session" {
|
||||
t.Fatalf("interaction evidence = %#v, want current source only", reference)
|
||||
t.Fatalf("occurrence evidence = %#v, want current source only", reference)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type npcInteractionLLMClient struct {
|
||||
type npcOccurrenceLLMClient struct {
|
||||
mu sync.Mutex
|
||||
requests []contracts.StructuredCompletionRequest
|
||||
rejectNPCs bool
|
||||
}
|
||||
|
||||
func (client *npcInteractionLLMClient) CompleteStructured(ctx context.Context, request contracts.StructuredCompletionRequest, out any) (contracts.StructuredCompletionResponse, error) {
|
||||
func (client *npcOccurrenceLLMClient) CompleteStructured(ctx context.Context, request contracts.StructuredCompletionRequest, out any) (contracts.StructuredCompletionResponse, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return contracts.StructuredCompletionResponse{}, err
|
||||
}
|
||||
@@ -227,25 +227,25 @@ func (client *npcInteractionLLMClient) CompleteStructured(ctx context.Context, r
|
||||
}
|
||||
case npcnormalize.PromptID:
|
||||
payload = map[string]any{"duplicate_groups": []any{}}
|
||||
case interactionextract.PromptID:
|
||||
case occurrenceextract.PromptID:
|
||||
payload = map[string]any{"occurrences": []any{
|
||||
map[string]any{"npc_id": identity.DeriveID("Hooded Guard"), "name": "Hooded Guard", "kind": "noncombat_presence", "source_refs": []any{map[string]int{"start_unit_id": 3, "end_unit_id": 3}}},
|
||||
map[string]any{"npc_id": identity.DeriveID("Mira Thorn"), "name": "Mira Thorn", "kind": "dialogue", "source_refs": []any{map[string]int{"start_unit_id": 2, "end_unit_id": 2}}},
|
||||
}}
|
||||
default:
|
||||
return contracts.StructuredCompletionResponse{}, fmt.Errorf("unexpected interaction prompt %q", request.PromptID)
|
||||
return contracts.StructuredCompletionResponse{}, fmt.Errorf("unexpected occurrence prompt %q", request.PromptID)
|
||||
}
|
||||
content, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return contracts.StructuredCompletionResponse{}, err
|
||||
}
|
||||
if err := json.Unmarshal(content, out); err != nil {
|
||||
return contracts.StructuredCompletionResponse{}, fmt.Errorf("populate interaction response: %w", err)
|
||||
return contracts.StructuredCompletionResponse{}, fmt.Errorf("populate occurrence response: %w", err)
|
||||
}
|
||||
return contracts.StructuredCompletionResponse{Content: content, Provider: "test", Model: "interaction-fake"}, nil
|
||||
return contracts.StructuredCompletionResponse{Content: content, Provider: "test", Model: "occurrence-fake"}, nil
|
||||
}
|
||||
|
||||
func (client *npcInteractionLLMClient) requestFor(t *testing.T, promptID string) contracts.StructuredCompletionRequest {
|
||||
func (client *npcOccurrenceLLMClient) requestFor(t *testing.T, promptID string) contracts.StructuredCompletionRequest {
|
||||
t.Helper()
|
||||
client.mu.Lock()
|
||||
defer client.mu.Unlock()
|
||||
@@ -258,7 +258,7 @@ func (client *npcInteractionLLMClient) requestFor(t *testing.T, promptID string)
|
||||
return contracts.StructuredCompletionRequest{}
|
||||
}
|
||||
|
||||
func (client *npcInteractionLLMClient) requestCount(promptID string) int {
|
||||
func (client *npcOccurrenceLLMClient) requestCount(promptID string) int {
|
||||
client.mu.Lock()
|
||||
defer client.mu.Unlock()
|
||||
count := 0
|
||||
@@ -270,14 +270,14 @@ func (client *npcInteractionLLMClient) requestCount(promptID string) int {
|
||||
return count
|
||||
}
|
||||
|
||||
var _ contracts.StructuredLLMClient = (*npcInteractionLLMClient)(nil)
|
||||
var _ contracts.StructuredLLMClient = (*npcOccurrenceLLMClient)(nil)
|
||||
|
||||
type semanticNPCInteractionClient struct {
|
||||
type semanticNPCOccurrenceClient struct {
|
||||
requests []contracts.StructuredCompletionRequest
|
||||
npcCalls int
|
||||
}
|
||||
|
||||
func (client *semanticNPCInteractionClient) CompleteStructured(_ context.Context, request contracts.StructuredCompletionRequest, out any) (contracts.StructuredCompletionResponse, error) {
|
||||
func (client *semanticNPCOccurrenceClient) CompleteStructured(_ context.Context, request contracts.StructuredCompletionRequest, out any) (contracts.StructuredCompletionResponse, error) {
|
||||
client.requests = append(client.requests, cloneStructuredCompletionRequest(request))
|
||||
var payload any
|
||||
switch request.PromptID {
|
||||
@@ -290,7 +290,7 @@ func (client *semanticNPCInteractionClient) CompleteStructured(_ context.Context
|
||||
payload = map[string]any{"npcs": []any{map[string]any{"name": name, "source_refs": []any{map[string]int{"start_unit_id": client.npcCalls, "end_unit_id": client.npcCalls}}}}}
|
||||
case npcnormalize.PromptID:
|
||||
payload = map[string]any{"duplicate_groups": []any{map[string]any{"members": []string{"candidate-000001", "candidate-000002"}, "canonical": "candidate-000001"}}}
|
||||
case interactionextract.PromptID:
|
||||
case occurrenceextract.PromptID:
|
||||
payload = map[string]any{"occurrences": []any{map[string]any{"npc_id": identity.DeriveID("Mira Thorn"), "name": "Mira Thorn", "kind": "dialogue", "source_refs": []any{map[string]int{"start_unit_id": 1, "end_unit_id": 1}}}}}
|
||||
default:
|
||||
return contracts.StructuredCompletionResponse{}, fmt.Errorf("unexpected semantic pipeline prompt %q", request.PromptID)
|
||||
@@ -305,7 +305,7 @@ func (client *semanticNPCInteractionClient) CompleteStructured(_ context.Context
|
||||
return contracts.StructuredCompletionResponse{Content: content}, nil
|
||||
}
|
||||
|
||||
func (client *semanticNPCInteractionClient) requestCount(promptID string) int {
|
||||
func (client *semanticNPCOccurrenceClient) requestCount(promptID string) int {
|
||||
count := 0
|
||||
for _, request := range client.requests {
|
||||
if request.PromptID == promptID {
|
||||
@@ -315,7 +315,7 @@ func (client *semanticNPCInteractionClient) requestCount(promptID string) int {
|
||||
return count
|
||||
}
|
||||
|
||||
func (client *semanticNPCInteractionClient) requestFor(t *testing.T, promptID string) contracts.StructuredCompletionRequest {
|
||||
func (client *semanticNPCOccurrenceClient) requestFor(t *testing.T, promptID string) contracts.StructuredCompletionRequest {
|
||||
t.Helper()
|
||||
for _, request := range client.requests {
|
||||
if request.PromptID == promptID {
|
||||
@@ -326,4 +326,4 @@ func (client *semanticNPCInteractionClient) requestFor(t *testing.T, promptID st
|
||||
return contracts.StructuredCompletionRequest{}
|
||||
}
|
||||
|
||||
var _ contracts.StructuredLLMClient = (*semanticNPCInteractionClient)(nil)
|
||||
var _ contracts.StructuredLLMClient = (*semanticNPCOccurrenceClient)(nil)
|
||||
@@ -8,7 +8,7 @@ cache:
|
||||
debug:
|
||||
directory: ./notarius-debug
|
||||
pipelines:
|
||||
dnd-npc-interactions-fixture:
|
||||
dnd-npc-occurrences-fixture:
|
||||
input: seriatim
|
||||
steps:
|
||||
- id: identify-npcs
|
||||
@@ -16,13 +16,13 @@ pipelines:
|
||||
npc_registry:
|
||||
extract: dnd/npc-registry
|
||||
normalize: dnd/npc-registry
|
||||
- id: extract-interactions
|
||||
- id: extract-occurrences
|
||||
references:
|
||||
npc_registry:
|
||||
artifact:
|
||||
step: identify-npcs
|
||||
lane: npc_registry
|
||||
artifacts:
|
||||
interactions:
|
||||
extract: dnd/npc-interactions
|
||||
normalize: dnd/npc-interactions
|
||||
occurrences:
|
||||
extract: dnd/npc-occurrences
|
||||
normalize: dnd/npc-occurrences
|
||||
Reference in New Issue
Block a user