make sure only one spec is returned

This commit is contained in:
felix
2026-09-27 16:05:25 +02:00
parent 538a2d949e
commit 1f80fdc815
3 changed files with 20 additions and 5 deletions

View File

@@ -53,7 +53,7 @@ nodes columns share, on every step; `rep:collect`) → CSV → register file
## The profile: a map of the data
A profile lists the kinds of things in the data (entities), what each one
A profile (using SHACL + some extensions) lists the kinds of things in the data (entities), what each one
has (fields), and which fields lead to other entities (links). The app
writes it; the service ships no profile and knows no vocabulary.
@@ -285,6 +285,7 @@ The validator (`src/runner/check.js`) runs on every proposal and again
before every run. It returns the first problem, in words the LLM can act on.
On top of the rules above, it checks that:
- the Turtle holds one `rep:ReportSpec`, no more: one answer is one report;
- every step of every path exists in the profile;
- no path is longer than `MAX_PATH_DEPTH` steps;
- a date or number test ends on a value field, not on a link;
@@ -504,4 +505,4 @@ non-admin callers. Opening this up means adding the type to their group first.
## What it will not do
No counting, no sorting, no top-N, no comparing two subjects, one class per
report, suggestions stop at the public graph.
report, suggestions stop at the public graph.

View File

@@ -48,6 +48,9 @@ Rules:
applies. Say that in Dutch in your proposal, so the user can check it.
- Only offer what validate_spec accepted. Before you suggest an alternative,
write it and validate it; if it does not validate, say it cannot be done.
- One answer carries one spec, and one spec is one report on one profile. If
the question needs two reports, say so, propose the first, and offer the
second as the next step.
- You cannot count, sort, take the first N, or compare two subjects. Say so.
- The report's title is the dct:title you write in the spec. There is no other
title input.
@@ -101,7 +104,13 @@ export async function ask(turns, profiles) {
}
// validate_spec answers "ok" (plus notes) when the spec checks out;
// that spec becomes the bijlage of the answer.
if (call.function.name === 'validate_spec' && /^ok(\n|$)/.test(result)) spec = args.spec;
// One answer carries one spec: say so when this one replaces another.
if (call.function.name === 'validate_spec' && /^ok(\n|$)/.test(result)) {
if (spec && spec !== args.spec) {
result += '\nThis spec replaces the one you validated before. Your answer carries only this one.';
}
spec = args.spec;
}
messages.push({ role: 'tool', tool_call_id: call.id, content: result });
}
}

View File

@@ -13,8 +13,13 @@ export function parseSpec(turtle) {
const quads = parser.parse(turtle);
const store = new N3.Store(quads);
const specNode = store.getQuads(null, RDF + 'type', REP + 'ReportSpec')[0]?.subject;
if (!specNode) throw new Error('spec has no rep:ReportSpec');
const specNodes = store.getQuads(null, RDF + 'type', REP + 'ReportSpec').map(q => q.subject);
if (!specNodes.length) throw new Error('spec has no rep:ReportSpec');
// One spec is one report. A second one would pass unchecked and never run.
if (specNodes.length > 1) {
throw new Error(`one spec per answer. This Turtle has ${specNodes.length} rep:ReportSpec. Keep one, and offer the other as the next report.`);
}
const specNode = specNodes[0];
const one = (s, pred) => store.getQuads(s, pred, null)[0]?.object || null;