Align item occurrence evidence fields
This commit is contained in:
@@ -22,10 +22,10 @@
|
|||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": ["start_segment", "end_segment"],
|
"required": ["start_unit_id", "end_unit_id"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"start_segment": {"type": "integer"},
|
"start_unit_id": {"type": "integer"},
|
||||||
"end_segment": {"type": "integer"}
|
"end_unit_id": {"type": "integer"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -330,7 +330,7 @@ func (client *enemyEventLLMClient) CompleteStructured(ctx context.Context, reque
|
|||||||
if len(registry.Items) != 1 || registry.Items[0].Name != "Moonblade" {
|
if len(registry.Items) != 1 || registry.Items[0].Name != "Moonblade" {
|
||||||
return contracts.StructuredCompletionResponse{}, fmt.Errorf("generated item registry has %d items, want 1", len(registry.Items))
|
return contracts.StructuredCompletionResponse{}, fmt.Errorf("generated item registry has %d items, want 1", len(registry.Items))
|
||||||
}
|
}
|
||||||
content = []byte(`{"occurrences":[{"name":"Moonblade","kind":"discovered","quantity":null,"from":null,"to":null,"source_refs":[{"start_segment":5,"end_segment":5}]}]}`)
|
content = []byte(`{"occurrences":[{"name":"Moonblade","kind":"discovered","quantity":null,"from":null,"to":null,"source_refs":[{"start_unit_id":5,"end_unit_id":5}]}]}`)
|
||||||
}
|
}
|
||||||
case combat.PromptID:
|
case combat.PromptID:
|
||||||
content = []byte(`{"combat_turns":[{"actor":"Kesh","turn_kind":"turn","source_refs":[{"start_unit_id":8,"end_unit_id":8}]}]}`)
|
content = []byte(`{"combat_turns":[{"actor":"Kesh","turn_kind":"turn","source_refs":[{"start_unit_id":8,"end_unit_id":8}]}]}`)
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ func itemOccurrenceSourceRefs(refs []itemOccurrenceSourceRefResponse, sourceID s
|
|||||||
}
|
}
|
||||||
values := make([]source.SourceRef, len(refs))
|
values := make([]source.SourceRef, len(refs))
|
||||||
for index, ref := range refs {
|
for index, ref := range refs {
|
||||||
values[index] = source.SourceRef{SourceID: sourceID, StartUnitID: ref.StartSegment, EndUnitID: ref.EndSegment}
|
values[index] = source.SourceRef{SourceID: sourceID, StartUnitID: ref.StartUnitID, EndUnitID: ref.EndUnitID}
|
||||||
}
|
}
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ func TestExtractGroundsOccurrencesInRequiredRegistry(t *testing.T) {
|
|||||||
if len(result.Value.Occurrences) != 1 || result.Value.Occurrences[0].ItemID != id || result.Value.Occurrences[0].Name != "Torch" {
|
if len(result.Value.Occurrences) != 1 || result.Value.Occurrences[0].ItemID != id || result.Value.Occurrences[0].Name != "Torch" {
|
||||||
t.Fatalf("occurrences = %#v", result.Value.Occurrences)
|
t.Fatalf("occurrences = %#v", result.Value.Occurrences)
|
||||||
}
|
}
|
||||||
|
if refs := result.Value.Occurrences[0].SourceRefs; len(refs) != 1 || refs[0].SourceID != req.Source.ID || refs[0].StartUnitID != 1 || refs[0].EndUnitID != 1 {
|
||||||
|
t.Fatalf("occurrence evidence = %#v, want current-source unit range", refs)
|
||||||
|
}
|
||||||
input := client.requests[0].Inputs[ItemRegistryReferenceSlot]
|
input := client.requests[0].Inputs[ItemRegistryReferenceSlot]
|
||||||
if input.Name != ItemRegistryReferenceSlot || string(input.Content) != `{"items":[{"name":"Torch"}]}` || strings.Contains(string(input.Content), "item:sha256:") {
|
if input.Name != ItemRegistryReferenceSlot || string(input.Content) != `{"items":[{"name":"Torch"}]}` || strings.Contains(string(input.Content), "item:sha256:") {
|
||||||
t.Fatalf("registry prompt input = %#v, want names-only projection", input)
|
t.Fatalf("registry prompt input = %#v, want names-only projection", input)
|
||||||
@@ -98,7 +101,7 @@ func TestExtractAcceptsOnlyEmptyResponseForEmptyRegistry(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractPreservesNullableFields(t *testing.T) {
|
func TestExtractPreservesNullableFields(t *testing.T) {
|
||||||
client := &fakeItemOccurrencesLLMClient{content: []byte(`{"occurrences":[{"name":"Torch","kind":"discovered","quantity":null,"from":null,"to":null,"source_refs":[{"start_segment":1,"end_segment":1}]}]}`)}
|
client := &fakeItemOccurrencesLLMClient{content: []byte(`{"occurrences":[{"name":"Torch","kind":"discovered","quantity":null,"from":null,"to":null,"source_refs":[{"start_unit_id":1,"end_unit_id":1}]}]}`)}
|
||||||
req := extractionRequest()
|
req := extractionRequest()
|
||||||
req.References = itemRegistryReferences(t)
|
req.References = itemRegistryReferences(t)
|
||||||
result, err := newExtractor(t, client, req.References).Extract(context.Background(), req)
|
result, err := newExtractor(t, client, req.References).Extract(context.Background(), req)
|
||||||
|
|||||||
@@ -14,6 +14,6 @@ type itemOccurrenceResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type itemOccurrenceSourceRefResponse struct {
|
type itemOccurrenceSourceRefResponse struct {
|
||||||
StartSegment int `json:"start_segment"`
|
StartUnitID int `json:"start_unit_id"`
|
||||||
EndSegment int `json:"end_segment"`
|
EndUnitID int `json:"end_unit_id"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,19 @@ func TestPromptAssetsPrepareItemOccurrencePrompt(t *testing.T) {
|
|||||||
if prepared.PromptID != PromptID || prepared.OutputContract.SchemaPath != "dnd_item_occurrences_llm.v1.json" {
|
if prepared.PromptID != PromptID || prepared.OutputContract.SchemaPath != "dnd_item_occurrences_llm.v1.json" {
|
||||||
t.Fatalf("prepared prompt = %#v", prepared)
|
t.Fatalf("prepared prompt = %#v", prepared)
|
||||||
}
|
}
|
||||||
|
rendered := make([]string, len(prepared.Messages))
|
||||||
|
for index, message := range prepared.Messages {
|
||||||
|
rendered[index] = message.Content
|
||||||
|
}
|
||||||
|
content := strings.Join(rendered, "\n")
|
||||||
|
for _, field := range []string{"start_unit_id", "end_unit_id", "source_id"} {
|
||||||
|
if !strings.Contains(content, field) {
|
||||||
|
t.Fatalf("prepared prompt does not include shared evidence field %q", field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.Contains(content, "start_segment") || strings.Contains(content, "end_segment") {
|
||||||
|
t.Fatalf("prepared prompt contains obsolete segment evidence fields: %s", content)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPromptAssetsDoNotLeakIntoMetadata(t *testing.T) {
|
func TestPromptAssetsDoNotLeakIntoMetadata(t *testing.T) {
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ func TestResponseSchemaIsStrictlyStructuralAndPrivate(t *testing.T) {
|
|||||||
valid := map[string]any{"occurrences": []any{
|
valid := map[string]any{"occurrences": []any{
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"name": "", "kind": "unsupported", "quantity": 0, "from": "party", "to": "Party",
|
"name": "", "kind": "unsupported", "quantity": 0, "from": "party", "to": "Party",
|
||||||
"source_refs": []any{map[string]any{"start_segment": 0, "end_segment": -1}},
|
"source_refs": []any{map[string]any{"start_unit_id": 0, "end_unit_id": -1}},
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"name": "Hidden Cache", "kind": "discovered", "quantity": nil, "from": nil, "to": nil,
|
"name": "Hidden Cache", "kind": "discovered", "quantity": nil, "from": nil, "to": nil,
|
||||||
"source_refs": []any{map[string]any{"start_segment": 1, "end_segment": 1}},
|
"source_refs": []any{map[string]any{"start_unit_id": 1, "end_unit_id": 1}},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
content, err := json.Marshal(valid)
|
content, err := json.Marshal(valid)
|
||||||
@@ -43,8 +43,10 @@ func TestResponseSchemaIsStrictlyStructuralAndPrivate(t *testing.T) {
|
|||||||
{"missing nullable field", map[string]any{"occurrences": []any{withoutField(responseOccurrence(), "quantity")}}},
|
{"missing nullable field", map[string]any{"occurrences": []any{withoutField(responseOccurrence(), "quantity")}}},
|
||||||
{"opaque item identifier", map[string]any{"occurrences": []any{withField(responseOccurrence(), "item_id", "item:sha256:opaque")}}},
|
{"opaque item identifier", map[string]any{"occurrences": []any{withField(responseOccurrence(), "item_id", "item:sha256:opaque")}}},
|
||||||
{"unknown occurrence field", map[string]any{"occurrences": []any{withField(responseOccurrence(), "extra", true)}}},
|
{"unknown occurrence field", map[string]any{"occurrences": []any{withField(responseOccurrence(), "extra", true)}}},
|
||||||
{"unknown reference field", map[string]any{"occurrences": []any{withField(responseOccurrence(), "source_refs", []any{map[string]any{"start_segment": 1, "end_segment": 1, "extra": true}})}}},
|
{"segment-named range", map[string]any{"occurrences": []any{withField(responseOccurrence(), "source_refs", []any{map[string]any{"start_segment": 1, "end_segment": 1}})}}},
|
||||||
{"noninteger range", map[string]any{"occurrences": []any{withField(responseOccurrence(), "source_refs", []any{map[string]any{"start_segment": 1.5, "end_segment": 1}})}}},
|
{"opaque source identifier", map[string]any{"occurrences": []any{withField(responseOccurrence(), "source_refs", []any{map[string]any{"start_unit_id": 1, "end_unit_id": 1, "source_id": "session-alpha"}})}}},
|
||||||
|
{"unknown reference field", map[string]any{"occurrences": []any{withField(responseOccurrence(), "source_refs", []any{map[string]any{"start_unit_id": 1, "end_unit_id": 1, "extra": true}})}}},
|
||||||
|
{"noninteger range", map[string]any{"occurrences": []any{withField(responseOccurrence(), "source_refs", []any{map[string]any{"start_unit_id": 1.5, "end_unit_id": 1}})}}},
|
||||||
} {
|
} {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
content, err := json.Marshal(test.value)
|
content, err := json.Marshal(test.value)
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func sourceDocument() *source.SourceDocument {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func responseRefs(start, end int) []itemOccurrenceSourceRefResponse {
|
func responseRefs(start, end int) []itemOccurrenceSourceRefResponse {
|
||||||
return []itemOccurrenceSourceRefResponse{{StartSegment: start, EndSegment: end}}
|
return []itemOccurrenceSourceRefResponse{{StartUnitID: start, EndUnitID: end}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newExtractor(t *testing.T, client contracts.StructuredLLMClient, references ...contracts.ReferenceSet) *Extractor {
|
func newExtractor(t *testing.T, client contracts.StructuredLLMClient, references ...contracts.ReferenceSet) *Extractor {
|
||||||
|
|||||||
Reference in New Issue
Block a user