@metaobjectsdev/sdk 0.23.2 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-context/skills/metaobjects-audit/SKILL.md +20 -7
- package/agent-context/skills/metaobjects-audit/references/capability-checklist.md +64 -18
- package/agent-context/skills/metaobjects-audit/references/csharp.md +2 -1
- package/agent-context/skills/metaobjects-audit/references/java.md +3 -2
- package/agent-context/skills/metaobjects-audit/references/kotlin.md +2 -1
- package/agent-context/skills/metaobjects-audit/references/requirements.md +3 -4
- package/agent-context/skills/metaobjects-audit/references/typescript.md +1 -1
- package/agent-context/skills/metaobjects-authoring/SKILL.md +28 -3
- package/agent-context/skills/metaobjects-authoring/references/requirements.md +49 -23
- package/agent-context/skills/metaobjects-codegen/SKILL.md +4 -3
- package/agent-context/skills/metaobjects-codegen/references/csharp.md +2 -2
- package/agent-context/skills/metaobjects-codegen/references/java.md +3 -3
- package/agent-context/skills/metaobjects-codegen/references/kotlin.md +4 -4
- package/agent-context/skills/metaobjects-codegen/references/python.md +2 -2
- package/agent-context/skills/metaobjects-codegen/references/typescript.md +3 -3
- package/agent-context/skills/metaobjects-fit-assessment/SKILL.md +4 -4
- package/agent-context/skills/metaobjects-prompts/SKILL.md +53 -32
- package/agent-context/skills/metaobjects-prompts/references/csharp.md +26 -21
- package/agent-context/skills/metaobjects-prompts/references/java.md +24 -18
- package/agent-context/skills/metaobjects-prompts/references/kotlin.md +25 -20
- package/agent-context/skills/metaobjects-prompts/references/python.md +68 -26
- package/agent-context/skills/metaobjects-prompts/references/typescript.md +29 -23
- package/agent-context/skills/metaobjects-verify/references/migration.md +1 -1
- package/agent-context/skills/metaobjects-verify/references/requirements.md +36 -22
- package/agent-context/templates/always-on.md.mustache +3 -1
- package/dist/agent-docs/body.d.ts +1 -1
- package/dist/agent-docs/body.d.ts.map +1 -1
- package/dist/agent-docs/body.js +24 -2
- package/dist/agent-docs/body.js.map +1 -1
- package/dist/collection.d.ts +71 -0
- package/dist/collection.d.ts.map +1 -0
- package/dist/collection.js +121 -0
- package/dist/collection.js.map +1 -0
- package/dist/config.d.ts +51 -21
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +76 -10
- package/dist/config.js.map +1 -1
- package/dist/discovery.d.ts +43 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +100 -0
- package/dist/discovery.js.map +1 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -4
- package/dist/index.js.map +1 -1
- package/dist/memory.d.ts +42 -19
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +38 -95
- package/dist/memory.js.map +1 -1
- package/dist/metadata-files.d.ts +67 -0
- package/dist/metadata-files.d.ts.map +1 -0
- package/dist/metadata-files.js +155 -0
- package/dist/metadata-files.js.map +1 -0
- package/dist/records/any.d.ts +8 -8
- package/dist/records/decision.d.ts +2 -2
- package/dist/records/glossary.d.ts +4 -4
- package/dist/records/principle.d.ts +2 -2
- package/dist/scope.d.ts +16 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +75 -0
- package/dist/scope.js.map +1 -0
- package/dist/sources.d.ts +86 -0
- package/dist/sources.d.ts.map +1 -0
- package/dist/sources.js +130 -0
- package/dist/sources.js.map +1 -0
- package/package.json +2 -2
- package/src/agent-docs/body.ts +24 -2
- package/src/collection.ts +183 -0
- package/src/config.ts +80 -12
- package/src/discovery.ts +110 -0
- package/src/index.ts +26 -9
- package/src/memory.ts +65 -99
- package/src/metadata-files.ts +158 -0
- package/src/scope.ts +95 -0
- package/src/sources.ts +160 -0
|
@@ -28,7 +28,7 @@ cite. (3) Whether anyone fills the ledger in is itself untested; advertising it
|
|
|
28
28
|
is the brochure failure this skill exists to avoid.
|
|
29
29
|
|
|
30
30
|
TRIGGER to revisit — the release carrying `requirement.*` has shipped, AND either:
|
|
31
|
-
Arm A a dogfooded project's
|
|
31
|
+
Arm A a dogfooded project's retired capabilities are shown, retrospectively,
|
|
32
32
|
to have been discoverable PRE-adoption from repo evidence at file:line standard
|
|
33
33
|
(removal commit, dead flag, do-not-reintroduce comment);
|
|
34
34
|
Arm B a team that adopted via this assessment hits a resurrection the ledger would have
|
|
@@ -267,8 +267,8 @@ signature class. Hunt all ten classes:
|
|
|
267
267
|
@agg/@of/@via`, row-scoped with `@filter`), a predicate quantifier (`origin.aggregate
|
|
268
268
|
@agg: any|all`), an array rollup (`origin.aggregate @agg: collect`), a non-aggregate
|
|
269
269
|
derived scalar (`origin.computed @expr`), an argmax-style "one related row's column"
|
|
270
|
-
pick (`origin.first @via` — covers the common `DISTINCT ON` / lateral-join shape),
|
|
271
|
-
|
|
270
|
+
pick (`origin.first @via` — covers the common `DISTINCT ON` / lateral-join shape),
|
|
271
|
+
a soft-delete/status/type row-scope (an
|
|
272
272
|
object-level `@filter` on `object.projection`), or `extends`-borrowed — and joins
|
|
273
273
|
follow declared relationships/`identity.reference` FKs. Expressible → projection
|
|
274
274
|
candidate (note: an unmodeled hand view is *unmanaged* — invisible to `verify --db`;
|
|
@@ -407,7 +407,7 @@ promising it or counting it in benefits:
|
|
|
407
407
|
- hand `COUNT/SUM` subqueries, read-model SQL → `object.projection` + `origin.passthrough`
|
|
408
408
|
/ `origin.aggregate` (`count`/`sum`/`avg`/`min`/`max`, +`@filter` for scoped
|
|
409
409
|
aggregates, +`any`/`all` predicate quantifiers, +`collect` array rollups) /
|
|
410
|
-
`origin.
|
|
410
|
+
`origin.computed` (a non-aggregate derived scalar) /
|
|
411
411
|
`origin.first` (an argmax-style "one related row's column" pick — the usual
|
|
412
412
|
`DISTINCT ON`/lateral-join case) — plus a soft-delete/status/type view via the
|
|
413
413
|
object-level `@filter` on `object.projection` instead of a hand `WHERE`.
|
|
@@ -19,10 +19,13 @@ language lives in a reference fragment (pointed to at the bottom).
|
|
|
19
19
|
A **template** is a typed pair: a logical reference to external text + a payload
|
|
20
20
|
value-object declaring exactly what data the text expects.
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
|
22
|
+
A template subtype's axis is **DIRECTION** — which way the text travels, not what it
|
|
23
|
+
is about (ADR-0052).
|
|
24
|
+
|
|
25
|
+
| Subtype | Direction | Use | Extra attrs |
|
|
26
|
+
|---|---|---|---|
|
|
27
|
+
| `template.prompt` | outbound, and optionally inbound | LLM-targeted | `@maxTokens`, `@requiredSlots`, `@requiredTags`, `@model`, `@responseRef`, `@responseFormat`, `@promptStyle` |
|
|
28
|
+
| `template.output` | outbound ONLY | email / docs / config / export | `@kind: document \| email` (default `document`), `@requiredTags`; `@kind: email` adds `@subjectRef` / `@htmlBodyRef` / `@textBodyRef` |
|
|
26
29
|
|
|
27
30
|
Both carry the generic attrs:
|
|
28
31
|
|
|
@@ -36,13 +39,22 @@ Both carry the generic attrs:
|
|
|
36
39
|
`template.output @kind: email` renders a structured `EmailDocument` (subject + HTML
|
|
37
40
|
body + optional plain-text body) instead of one string — the TS render helper emits
|
|
38
41
|
an `EmailDocument`-returning function for it (see the `render-example-email`
|
|
39
|
-
conformance fixture). `@
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
`
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
conformance fixture). `@requiredTags` names output tags the rendered text must contain
|
|
43
|
+
(`verify` checks it) on both subtypes.
|
|
44
|
+
|
|
45
|
+
**The INBOUND half belongs to `template.prompt` alone.** `@responseRef` names the
|
|
46
|
+
response shape (an `object.value` or sourceless `object.projection`, #210) a model's
|
|
47
|
+
reply is parsed into, and its PRESENCE is what asks for the whole inbound tier: the
|
|
48
|
+
response record, the response-format fragment, the parser-on-receipt and the tolerant
|
|
49
|
+
extractor. `@responseFormat` (`json` default / `xml`, ADR-0053) is the syntax of that
|
|
50
|
+
REPLY; `@promptStyle` (`guide` / `inline` / `exampleOnly`, FR-010) selects how the
|
|
51
|
+
fragment presents the shape.
|
|
52
|
+
|
|
53
|
+
> **`@format` and `@responseFormat` are different facts.** `@format` is the syntax of
|
|
54
|
+
> the BODY you render; `@responseFormat` is the syntax of the answer you expect. A
|
|
55
|
+
> plain-text prompt asking for a JSON object is the common case. Putting `@promptStyle`
|
|
56
|
+
> or `@responseFormat` on a `template.output` is a LOAD ERROR — an output renders a
|
|
57
|
+
> document and nothing reads a reply to it.
|
|
46
58
|
|
|
47
59
|
A third, structurally different subtype is also registered core vocabulary:
|
|
48
60
|
**`template.toolcall`** (`@toolName` + `@payloadRef`, ADR-0011) — a vendor-agnostic
|
|
@@ -63,7 +75,7 @@ declared `field.<subType>` + `isArray` + `@objectRef`, and a nested payload is a
|
|
|
63
75
|
declared `field.object @objectRef` to another `object.value` (`isArray: true` for a
|
|
64
76
|
list — nested targets stay value-only, loader-enforced). The caller supplies the
|
|
65
77
|
field values at render time. An `origin.*` child on a payload field is IGNORED for
|
|
66
|
-
typing — and the assembly origins (`aggregate` / `
|
|
78
|
+
typing — and the assembly origins (`aggregate` / `computed` /
|
|
67
79
|
`first`) are ILLEGAL on an `object.value` host (`ERR_SUBTYPE_RULE_VIOLATION`, #210):
|
|
68
80
|
an origin-derived payload lives on the sourceless projection, which `@payloadRef`
|
|
69
81
|
accepts. Projections generally are covered by the `metaobjects-authoring` skill and
|
|
@@ -192,33 +204,42 @@ For every template, the verify step resolves the text, parses each `{{...}}`
|
|
|
192
204
|
reference, and checks it exists on the payload VO. If the text references
|
|
193
205
|
`{{authorName}}` but the payload only has `displayName`, **the build fails.** This
|
|
194
206
|
is the prompt-vs-payload drift gate — run it in CI. It walks both `template.prompt`
|
|
195
|
-
and `template.output` nodes the same way
|
|
207
|
+
and `template.output` nodes the same way (both RENDER; only the direction of what comes
|
|
208
|
+
back differs).
|
|
209
|
+
|
|
210
|
+
## A RESPONDING `template.prompt` generates a parser-on-receipt
|
|
211
|
+
|
|
212
|
+
For every `template.prompt` declaring `@responseRef`, codegen emits a **typed parser**
|
|
213
|
+
that turns a model's reply back into that shape. It binds `@responseRef`, never
|
|
214
|
+
`@payloadRef` — `@payloadRef` types the request the prompt renders outbound, and the
|
|
215
|
+
question and the answer are usually different shapes. Each port emits the parser
|
|
216
|
+
idiomatically: a throw-on-invalid parse plus, where the language has the precedent, a
|
|
217
|
+
Result-style "safe" variant that doesn't throw.
|
|
196
218
|
|
|
197
|
-
|
|
219
|
+
**A `template.output` gets no parser, ever.** Nothing reads a reply to a document. (Before
|
|
220
|
+
ADR-0052 it did, with no format filter at all — so an `@format: markdown` document got a
|
|
221
|
+
generated `JSON.parse` over rendered prose.)
|
|
198
222
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
throw-on-invalid parse plus, where the language has the precedent, a Result-style
|
|
203
|
-
"safe" variant that doesn't throw. The parser file is a companion to the payload-VO
|
|
204
|
-
file; `verify` catches payload-VO ↔ parser drift at build time too.
|
|
223
|
+
The strict tier is JSON-only: an `@responseFormat: xml` reply gets the tolerant extract
|
|
224
|
+
and nothing strict, because strict all-or-nothing semantics layered over a REPAIRING XML
|
|
225
|
+
reader would raise or accept based on how much repair happened.
|
|
205
226
|
|
|
206
227
|
The three-step consumer pattern is identical everywhere: render the prompt → call
|
|
207
|
-
your LLM client → parse the
|
|
228
|
+
your LLM client → parse the reply with the generated parser.
|
|
208
229
|
|
|
209
|
-
## `template.
|
|
230
|
+
## A RESPONDING `template.prompt` generates the response-format fragment (FR-010)
|
|
210
231
|
|
|
211
|
-
For every
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
232
|
+
For every `template.prompt` whose `@responseRef` resolves, codegen additionally emits a
|
|
233
|
+
`render<Name>Format(...)`-shaped function backed by the render engine's output-format
|
|
234
|
+
renderer — the "produce your answer like this" instruction fragment you splice into the
|
|
235
|
+
prompt text so the model returns exactly the shape the parser above expects. The gate is
|
|
236
|
+
`@responseRef` PRESENCE, not a format value: the old `@format ∈ {json,xml}` gate read the
|
|
237
|
+
syntax of the OUTBOUND body to decide whether to instruct the model about the syntax of
|
|
238
|
+
its REPLY, so a text-bodied prompt asking for a JSON answer got no fragment at all.
|
|
239
|
+
`@responseFormat` selects which syntax the fragment teaches; the fragment and the
|
|
240
|
+
extractor agree on the same root name.
|
|
220
241
|
|
|
221
|
-
`@promptStyle` on the `template.
|
|
242
|
+
`@promptStyle` on the `template.prompt` controls the fragment's presentation
|
|
222
243
|
(default `guide`):
|
|
223
244
|
|
|
224
245
|
| `@promptStyle` | Presentation |
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
# C# parser-on-receipt
|
|
2
2
|
|
|
3
|
-
For every `template.
|
|
4
|
-
**typed parser** that validates
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
For every RESPONDING `template.prompt` — one declaring `@responseRef` —
|
|
4
|
+
`MetaObjects.Codegen`'s `OutputParserGenerator` emits a **typed parser** that validates
|
|
5
|
+
a model's reply against that shape. ADR-0052: the tier binds `@responseRef`, never
|
|
6
|
+
`@payloadRef` (which types the request the prompt renders outbound), and a
|
|
7
|
+
`template.output` gets no parser at all. This is the receive side only — codegen emits
|
|
8
|
+
**no** provider/LLM-call layer; you compose the call yourself. C# names records after the
|
|
9
|
+
resolved VALUE OBJECT, so the response record simply IS that VO's record — no second
|
|
10
|
+
naming convention, and the parser and the record can't silently drift.
|
|
8
11
|
|
|
9
12
|
## Contents
|
|
10
13
|
- Wire the generator
|
|
11
14
|
- What it emits
|
|
12
|
-
- The
|
|
15
|
+
- The response-format prompt fragment (FR-010)
|
|
13
16
|
- The three-step consumer pattern
|
|
14
17
|
- Recommended LLM caller (bring-your-own)
|
|
15
18
|
- Consumer dependency
|
|
@@ -27,12 +30,14 @@ dotnet meta gen ./metadata --out ./Generated --namespace Acme.Blog
|
|
|
27
30
|
|
|
28
31
|
## What it emits
|
|
29
32
|
|
|
30
|
-
Per `template.
|
|
31
|
-
static `<
|
|
33
|
+
Per responding `template.prompt`, `dotnet meta gen` writes one
|
|
34
|
+
`<PromptName>.response.cs` with a static `<PromptName>Parser` following the .NET BCL
|
|
35
|
+
`Parse`/`TryParse` dual API. The strict tier is JSON-only — an `@responseFormat: xml`
|
|
36
|
+
reply gets the tolerant extract and neither `Parse` nor `TryParse` —
|
|
32
37
|
`Parse` throws, `TryParse` returns a bool plus an out-error:
|
|
33
38
|
|
|
34
39
|
```csharp
|
|
35
|
-
// generated <
|
|
40
|
+
// generated <PromptName>.response.cs (shape)
|
|
36
41
|
public static class NpcResponseParser
|
|
37
42
|
{
|
|
38
43
|
/// <exception cref="JsonException">malformed JSON or schema mismatch.</exception>
|
|
@@ -53,25 +58,25 @@ generator also emits a tolerant `Extract(string[, ExtractOptions])` (self-contai
|
|
|
53
58
|
components) returning an `ExtractionResult` with a nullable `<Payload>Extracted` mirror
|
|
54
59
|
— a classified per-field report rather than a throw.
|
|
55
60
|
|
|
56
|
-
## The
|
|
61
|
+
## The response-format prompt fragment (FR-010)
|
|
57
62
|
|
|
58
|
-
For every
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
`RenderFormat(
|
|
62
|
-
|
|
63
|
-
the model. It runs as part of the same `dotnet meta gen` invocation as the payload
|
|
63
|
+
For every responding `template.prompt`, `MetaObjects.Codegen`'s `OutputPromptGenerator`
|
|
64
|
+
(stable name `output-prompt-generator`) emits a `<PromptName>.responseFormat.cs`
|
|
65
|
+
declaring a static `<PromptName>ResponseFormat` class with a `RenderFormat()` /
|
|
66
|
+
`RenderFormat(PromptOverrides)` pair, backed by the render engine's
|
|
67
|
+
`OutputFormatRenderer` — the "produce your answer like this" fragment for the model. It runs as part of the same `dotnet meta gen` invocation as the payload
|
|
64
68
|
and parser generators:
|
|
65
69
|
|
|
66
70
|
```bash
|
|
67
71
|
dotnet meta gen ./metadata --out ./Generated --namespace Acme.Blog
|
|
68
72
|
```
|
|
69
73
|
|
|
70
|
-
`@promptStyle` on the `template.
|
|
71
|
-
controls the fragment's presentation; guidance is never emitted as comments. Skipped
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
`@promptStyle` on the `template.prompt` (`guide` default / `inline` / `exampleOnly`)
|
|
75
|
+
controls the fragment's presentation; guidance is never emitted as comments. Skipped for
|
|
76
|
+
`template.output` nodes and an unresolved `@responseRef` — the same skip contract as the
|
|
77
|
+
parser generator. There is NO format gate: the old `@format ∈ {json,xml}` test read the
|
|
78
|
+
syntax of the outbound body to decide whether to describe the reply. The baked spec's
|
|
79
|
+
root name is the response record's, agreeing with the parser's root.
|
|
75
80
|
|
|
76
81
|
## The three-step consumer pattern
|
|
77
82
|
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# Java parser-on-receipt
|
|
2
2
|
|
|
3
|
-
For every `template.
|
|
4
|
-
a **typed parser** that validates
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
For every RESPONDING `template.prompt` — one declaring `@responseRef` —
|
|
4
|
+
`codegen-spring`'s `SpringOutputParserGenerator` emits a **typed parser** that validates
|
|
5
|
+
a model's reply against that shape. ADR-0052: the tier binds `@responseRef`, never
|
|
6
|
+
`@payloadRef` (which types the request the prompt renders outbound), and a
|
|
7
|
+
`template.output` gets no parser at all. This is the receive side only — codegen emits
|
|
8
|
+
**no** provider/LLM-call layer; you compose the call yourself.
|
|
7
9
|
|
|
8
10
|
## Contents
|
|
9
11
|
- Wire the generator
|
|
10
12
|
- What it emits
|
|
11
|
-
- The
|
|
13
|
+
- The response-format prompt fragment (FR-010)
|
|
12
14
|
- The three-step consumer pattern
|
|
13
15
|
- Recommended LLM caller (bring-your-own)
|
|
14
16
|
- Drift gate
|
|
@@ -31,17 +33,20 @@ the payload record it parses into) to the Maven plugin's `<generators>` list:
|
|
|
31
33
|
|
|
32
34
|
## What it emits
|
|
33
35
|
|
|
34
|
-
Per `template.
|
|
35
|
-
`parse` method returning the
|
|
36
|
-
|
|
36
|
+
Per responding `template.prompt`, `mvn metaobjects:generate` writes a `<Name>Parser`
|
|
37
|
+
class with a static `parse` method returning the `<Name>Response` record — this port's
|
|
38
|
+
records are TEMPLATE-named, so a responding prompt gets a SECOND record beside
|
|
39
|
+
`<Name>Payload`. The strict path throws
|
|
40
|
+
`com.fasterxml.jackson.core.JsonProcessingException` on malformed input, and is
|
|
41
|
+
JSON-only: an `@responseFormat: xml` reply gets the tolerant extract and no `parse`:
|
|
37
42
|
|
|
38
43
|
```java
|
|
39
44
|
// generated <Name>Parser.java (shape)
|
|
40
45
|
public final class NpcResponseParser {
|
|
41
46
|
private NpcResponseParser() { } // no instances
|
|
42
47
|
|
|
43
|
-
public static
|
|
44
|
-
// Jackson-backed: validates the text against the
|
|
48
|
+
public static NpcResponseResponse parse(String text) throws JsonProcessingException {
|
|
49
|
+
// Jackson-backed: validates the text against the @responseRef record
|
|
45
50
|
}
|
|
46
51
|
}
|
|
47
52
|
```
|
|
@@ -61,10 +66,10 @@ reference) return `MetaObjectAware` instances instead, and those need
|
|
|
61
66
|
`JsonObjectWriter`/`MetaObjectSerializer` — not a bare mapper — to serialize
|
|
62
67
|
correctly (see the codegen reference's "Serializing generated objects" section).
|
|
63
68
|
|
|
64
|
-
## The
|
|
69
|
+
## The response-format prompt fragment (FR-010)
|
|
65
70
|
|
|
66
|
-
For every
|
|
67
|
-
`SpringOutputPromptGenerator` emits a `<
|
|
71
|
+
For every responding `template.prompt`, `codegen-spring`'s
|
|
72
|
+
`SpringOutputPromptGenerator` emits a `<PromptShortName>ResponseFormat` class with a
|
|
68
73
|
static `renderFormat()` / `renderFormat(PromptOverrides)` pair, backed by
|
|
69
74
|
`OutputFormatRenderer` from the `metaobjects-render` module — the "produce your
|
|
70
75
|
answer like this" fragment for the model. Wire it alongside
|
|
@@ -77,11 +82,12 @@ answer like this" fragment for the model. Wire it alongside
|
|
|
77
82
|
</generator>
|
|
78
83
|
```
|
|
79
84
|
|
|
80
|
-
`@promptStyle` on the `template.
|
|
81
|
-
controls the fragment's presentation; guidance is never emitted as comments. Skipped
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
`@promptStyle` on the `template.prompt` (`guide` default / `inline` / `exampleOnly`)
|
|
86
|
+
controls the fragment's presentation; guidance is never emitted as comments. Skipped for
|
|
87
|
+
`template.output` nodes and an unresolved `@responseRef` — the same skip contract as the
|
|
88
|
+
parser generator. There is NO format gate: the old `@format ∈ {json,xml}` test read the
|
|
89
|
+
syntax of the outbound body to decide whether to describe the reply. The `SPEC`'s root
|
|
90
|
+
name is the response record's, agreeing with the parser's extract-codegen root.
|
|
85
91
|
|
|
86
92
|
## The three-step consumer pattern
|
|
87
93
|
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
# Kotlin parser-on-receipt
|
|
2
2
|
|
|
3
|
-
For every `template.
|
|
4
|
-
a **typed parser** that validates
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
For every RESPONDING `template.prompt` — one declaring `@responseRef` —
|
|
4
|
+
`codegen-kotlin`'s `KotlinOutputParserGenerator` emits a **typed parser** that validates
|
|
5
|
+
a model's reply against that shape. ADR-0052: the tier binds `@responseRef`, never
|
|
6
|
+
`@payloadRef` (which types the request the prompt renders outbound), and a
|
|
7
|
+
`template.output` gets no parser at all. This is the receive side only — codegen emits
|
|
8
|
+
**no** provider/LLM-call layer; you compose the call yourself. The data class itself comes
|
|
9
|
+
from `KotlinPayloadGenerator` (a `@Serializable data class`) — this port's classes are
|
|
10
|
+
TEMPLATE-named, so a responding prompt gets a SECOND one, `<Name>Response`, beside
|
|
11
|
+
`<Name>Payload`, and the parser and the record can't silently drift.
|
|
9
12
|
|
|
10
13
|
## Contents
|
|
11
14
|
- Wire the generators
|
|
12
15
|
- What it emits
|
|
13
|
-
- The
|
|
16
|
+
- The response-format prompt fragment (FR-010)
|
|
14
17
|
- The three-step consumer pattern
|
|
15
18
|
- Consumer dependency
|
|
16
19
|
- Recommended LLM caller (bring-your-own)
|
|
@@ -34,9 +37,10 @@ the payload it parses into) to the Maven plugin's `<generators>` list:
|
|
|
34
37
|
|
|
35
38
|
## What it emits
|
|
36
39
|
|
|
37
|
-
Per `template.
|
|
38
|
-
`object` with a dual API matching kotlinx.serialization's exception
|
|
39
|
-
Kotlin stdlib `Result<T>` convention
|
|
40
|
+
Per responding `template.prompt`, `mvn metaobjects:generate` writes a
|
|
41
|
+
`<Name>Parser.kt` `object` with a dual API matching kotlinx.serialization's exception
|
|
42
|
+
model plus the Kotlin stdlib `Result<T>` convention. The strict tier is JSON-only — an
|
|
43
|
+
`@responseFormat: xml` reply gets the tolerant extract and neither strict function:
|
|
40
44
|
|
|
41
45
|
```kotlin
|
|
42
46
|
// generated <Name>Parser.kt (shape)
|
|
@@ -44,8 +48,8 @@ object NpcResponseParser {
|
|
|
44
48
|
private val json: Json = Json { ignoreUnknownKeys = false }
|
|
45
49
|
|
|
46
50
|
/** Throws kotlinx.serialization.SerializationException on bad input. */
|
|
47
|
-
fun parseNpcResponse(text: String):
|
|
48
|
-
json.decodeFromString<
|
|
51
|
+
fun parseNpcResponse(text: String): NpcResponseResponse =
|
|
52
|
+
json.decodeFromString<NpcResponseResponse>(text)
|
|
49
53
|
|
|
50
54
|
/** Result-style — does not throw. */
|
|
51
55
|
fun safeParseNpcResponse(text: String): Result<NpcResponsePayload> =
|
|
@@ -63,10 +67,10 @@ null) and a `extractLenient(loader, text)` overload that delegates to the runtim
|
|
|
63
67
|
components. The lenient mirror type (`<Name>Extracted`) uses nullable fields per
|
|
64
68
|
the Kotlin null-safety port — a missing/malformed component is `null`, not a throw.
|
|
65
69
|
|
|
66
|
-
## The
|
|
70
|
+
## The response-format prompt fragment (FR-010)
|
|
67
71
|
|
|
68
|
-
For every
|
|
69
|
-
`KotlinOutputPromptGenerator` emits a `<
|
|
72
|
+
For every responding `template.prompt`, `codegen-kotlin`'s
|
|
73
|
+
`KotlinOutputPromptGenerator` emits a `<PromptShortName>ResponseFormat.kt` `object`
|
|
70
74
|
with `renderFormat()` / `renderFormat(overrides: PromptOverrides)`, backed by
|
|
71
75
|
`OutputFormatRenderer` from the `metaobjects-render` module — the "produce your
|
|
72
76
|
answer like this" fragment for the model. Wire it alongside
|
|
@@ -79,11 +83,12 @@ answer like this" fragment for the model. Wire it alongside
|
|
|
79
83
|
</generator>
|
|
80
84
|
```
|
|
81
85
|
|
|
82
|
-
`@promptStyle` on the `template.
|
|
83
|
-
controls the fragment's presentation; guidance is never emitted as comments. Skipped
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
`@promptStyle` on the `template.prompt` (`guide` default / `inline` / `exampleOnly`)
|
|
87
|
+
controls the fragment's presentation; guidance is never emitted as comments. Skipped for
|
|
88
|
+
`template.output` nodes and an unresolved `@responseRef` — the same skip contract as the
|
|
89
|
+
parser generator. There is NO format gate: the old `@format ∈ {json,xml}` test read the
|
|
90
|
+
syntax of the outbound body to decide whether to describe the reply. The `SPEC`'s root
|
|
91
|
+
name is the response class's, agreeing with the parser's extract-codegen root.
|
|
87
92
|
|
|
88
93
|
## The three-step consumer pattern
|
|
89
94
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# Python parser-on-receipt
|
|
2
2
|
|
|
3
|
-
For every `template.
|
|
4
|
-
emits a **typed parser** that
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
For every RESPONDING `template.prompt` — one declaring `@responseRef` — the
|
|
4
|
+
`output-parser` generator (run via `metaobjects gen`) emits a **typed parser** that
|
|
5
|
+
validates a model's reply against that shape's Pydantic model. ADR-0052: the tier binds
|
|
6
|
+
`@responseRef`, never `@payloadRef` (which types the request the prompt renders
|
|
7
|
+
outbound), and a `template.output` gets no parser at all. This is the receive side only —
|
|
8
|
+
codegen emits **no** provider/LLM-call layer; you compose the call yourself. The record
|
|
9
|
+
class comes from the sibling `payload` generator, so the parser and the record can't
|
|
10
|
+
silently drift.
|
|
9
11
|
|
|
10
12
|
## Contents
|
|
11
13
|
- Wire the generators
|
|
@@ -18,7 +20,7 @@ drift.
|
|
|
18
20
|
|
|
19
21
|
## Wire the generators
|
|
20
22
|
|
|
21
|
-
Select `output-parser` (the `payload` generator that emits the `<Name>
|
|
23
|
+
Select `output-parser` (the `payload` generator that emits the `<Name>Response` it
|
|
22
24
|
parses into runs alongside it):
|
|
23
25
|
|
|
24
26
|
```bash
|
|
@@ -30,17 +32,17 @@ metaobjects gen ./metadata --out ./generated --generators payload,output-parser
|
|
|
30
32
|
|
|
31
33
|
## What it emits
|
|
32
34
|
|
|
33
|
-
Per `template.
|
|
34
|
-
with a single throw-only entry point. Python uses one API (not TS's
|
|
35
|
+
Per responding `template.prompt`, `metaobjects gen` writes one
|
|
36
|
+
`<template_name>_response_parser.py` with a single throw-only entry point. Python uses one API (not TS's
|
|
35
37
|
`parse`/`safeParse`) because raising `pydantic.ValidationError` is the idiomatic
|
|
36
38
|
failure — the pydantic / Instructor / FastAPI norm; a Result-style wrapper would be
|
|
37
39
|
un-Pythonic.
|
|
38
40
|
|
|
39
41
|
```python
|
|
40
|
-
# generated <template_name>
|
|
41
|
-
from .
|
|
42
|
+
# generated <template_name>_response_parser.py (shape)
|
|
43
|
+
from .npc_response_response import NpcResponseResponse # the @responseRef record (Pydantic v2 BaseModel)
|
|
42
44
|
|
|
43
|
-
def parse_npc_response(text: str) ->
|
|
45
|
+
def parse_npc_response(text: str) -> NpcResponseResponse:
|
|
44
46
|
"""Validates text against the payload model.
|
|
45
47
|
|
|
46
48
|
Raises:
|
|
@@ -49,16 +51,20 @@ def parse_npc_response(text: str) -> NpcResponsePayload:
|
|
|
49
51
|
...
|
|
50
52
|
```
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
Every responding prompt ALSO gets a **tolerant** best-effort variant —
|
|
55
|
+
`extract_lenient_<name>_with_loader(root, text) -> ExtractionResult[<Name>ResponseExtracted]`
|
|
54
56
|
(from the `metaobjects` render `extract` engine) for cases where you want a classified
|
|
55
|
-
per-field report rather than a raise. The lenient mirror (`<Name>
|
|
56
|
-
|
|
57
|
+
per-field report rather than a raise. The lenient mirror (`<Name>ResponseExtracted`) uses
|
|
58
|
+
`Optional[...]` fields — a missing/malformed component is `None`, not a raise.
|
|
57
59
|
|
|
58
|
-
|
|
60
|
+
The STRICT `parse_*` is JSON-only (ADR-0053): an `@responseFormat: xml` reply gets the
|
|
61
|
+
tolerant path and no `parse_*` at all, because strict all-or-nothing semantics layered
|
|
62
|
+
over a REPAIRING XML reader would raise or accept based on how much repair happened.
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
## The response-format prompt fragment (FR-010)
|
|
65
|
+
|
|
66
|
+
For every responding `template.prompt`, the `output-prompt` generator (run via
|
|
67
|
+
`metaobjects gen`) emits one `<template_name>_response_format.py` module exposing
|
|
62
68
|
`render_<name>_format(overrides=None) -> str`, backed by the render engine's
|
|
63
69
|
`render_output_format()` — the "produce your answer like this" fragment for the
|
|
64
70
|
model:
|
|
@@ -67,11 +73,13 @@ model:
|
|
|
67
73
|
metaobjects gen ./metadata --out ./generated --generators payload,output-prompt
|
|
68
74
|
```
|
|
69
75
|
|
|
70
|
-
`@promptStyle` on the `template.
|
|
71
|
-
controls the fragment's presentation; guidance is never emitted as comments. Skipped
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
`@promptStyle` on the `template.prompt` (`guide` default / `inline` / `exampleOnly`)
|
|
77
|
+
controls the fragment's presentation; guidance is never emitted as comments. Skipped for
|
|
78
|
+
`template.output` nodes and an unresolved `@responseRef` — the same skip contract as the
|
|
79
|
+
`output-parser` generator. There is NO format gate: the old `@format ∈ {json,xml}` test
|
|
80
|
+
read the syntax of the outbound body to decide whether to describe the reply, so a
|
|
81
|
+
text-bodied prompt asking for a JSON answer got no fragment. The baked spec's root name
|
|
82
|
+
is the response class name, agreeing with the parser's `extract_<name>()` root.
|
|
75
83
|
|
|
76
84
|
## The three-step consumer pattern
|
|
77
85
|
|
|
@@ -80,7 +88,7 @@ here) → parse the response with the generated parser:
|
|
|
80
88
|
|
|
81
89
|
```python
|
|
82
90
|
from pydantic import ValidationError
|
|
83
|
-
from .
|
|
91
|
+
from .npc_response_response_parser import parse_npc_response
|
|
84
92
|
|
|
85
93
|
text = my_llm_client.complete(prompt_text) # YOUR code — no generated provider
|
|
86
94
|
try:
|
|
@@ -119,7 +127,41 @@ one boundary, not both.
|
|
|
119
127
|
> generator emits a `record_<entity>(recorder, input, redact=None)` helper (per
|
|
120
128
|
> concrete entity extending `LlmCallBase` with a `@responseRef`/`@payloadRef`-carrying
|
|
121
129
|
> `template.prompt`) that tolerantly extracts the typed response, builds the base
|
|
122
|
-
> trace row, and persists it once.
|
|
130
|
+
> trace row, and persists it once.
|
|
131
|
+
>
|
|
132
|
+
> `LlmCallBase` is shipped metadata, not something you author: opt in when you load,
|
|
133
|
+
> then inherit from it. Without the opt-in the `extends` fails with
|
|
134
|
+
> `ERR_UNRESOLVED_SUPER`.
|
|
135
|
+
>
|
|
136
|
+
> ```python
|
|
137
|
+
> load_directory("metadata/", libraries=["ai"]) # metaobjects::ai::LlmCallBase
|
|
138
|
+
> ```
|
|
139
|
+
> For `metaobjects gen` / `verify`, declare it in `metaobjects.config.yaml` instead —
|
|
140
|
+
> the CLI reads the same opt-in from there:
|
|
141
|
+
> ```yaml
|
|
142
|
+
> metadata: metadata/
|
|
143
|
+
> libraries: ["ai"]
|
|
144
|
+
> ```
|
|
145
|
+
> ```yaml
|
|
146
|
+
> - object.entity:
|
|
147
|
+
> name: AssistantCall
|
|
148
|
+
> extends: metaobjects::ai::LlmCallBase
|
|
149
|
+
> children:
|
|
150
|
+
> - source.rdb: { table: assistant_call, role: primary }
|
|
151
|
+
> - identity.primary: { name: id, fields: ["spanId"] }
|
|
152
|
+
> # Typed columns are AUTHORED, never derived (ADR-0024 amendment).
|
|
153
|
+
> # Declare BOTH: the generated record_<entity> writes voRequest and
|
|
154
|
+
> # voResponse unconditionally, and any key the entity does not declare
|
|
155
|
+
> # raises "no field '<name>' in metadata" on the first persist.
|
|
156
|
+
> - field.object: { name: voRequest, objectRef: MyRequestVO, storage: jsonb }
|
|
157
|
+
> - field.object: { name: voResponse, objectRef: MyResponseVO, storage: jsonb }
|
|
158
|
+
> ```
|
|
159
|
+
>
|
|
160
|
+
> Note the opt-in also brings in the library's own concrete `LlmCall` entity
|
|
161
|
+
> (table `llm_call`) alongside the abstract base — it will appear in codegen output
|
|
162
|
+
> and in a schema diff unless you filter it.
|
|
163
|
+
>
|
|
164
|
+
> What's still TS-only is the **`call<Entity>`
|
|
123
165
|
> render→call→record convenience loop** — Python intentionally does not emit it,
|
|
124
166
|
> because the `LlmClient` seam it wraps is BYO / vendor-neutral here (ADR-0024). So
|
|
125
167
|
> you compose render → your LLM call → the generated `record_<entity>(...)` yourself;
|