Skip to content

Commit 3211eee

Browse files
authored
feat: add live-model validation for Vercel schema selection (#44)
1 parent e7367c8 commit 3211eee

9 files changed

Lines changed: 497 additions & 36 deletions

File tree

‎typescript/examples/schema_selection/README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ selects it.
2121
### `vercel_ai_sdk_generate_object`
2222

2323
Shows a host selecting a structured-output schema from compiled policy state.
24+
The provider-free tests are canonical, and the example also offers an opt-in
25+
live-model validation through Vercel AI SDK `generateObject`.
2426

2527
## Technology-specific examples
2628

‎typescript/examples/schema_selection/vercel_ai_sdk_generate_object/README.md‎

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Flow:
1616
- `generateObject` is the downstream host behavior, not the authority layer.
1717
- No model output mutates compiler state.
1818
- If compiler state does not authorize a schema, the host omits schema selection.
19+
- The provider-free tests are the canonical proof for this example.
1920

2021
## Boundary
2122

@@ -30,12 +31,12 @@ Flow:
3031
Given policy state:
3132

3233
```text
33-
use python_script
34-
prohibit shell_command
34+
use refund_intake
35+
prohibit technical_support
3536
```
3637

37-
the host offers the `python_script` schema and does not offer the
38-
`shell_command` schema.
38+
the host offers the `refund_intake` schema and does not offer the
39+
`technical_support` schema.
3940

4041
If state prohibits every known schema, the host omits schema selection and does
4142
not build a `generateObject` request.
@@ -47,9 +48,41 @@ Tests assert:
4748
- compiler state -> selected schema
4849
- selected schema -> request config
4950
- omit schema when state does not authorize one
51+
- contradiction triggers clarification while preserving the previously
52+
authorized schema in current state
5053

5154
Primary tests are deterministic and do not call a model.
5255

56+
## Opt-in live-model validation
57+
58+
The example also includes an opt-in Tier 3 path that uses the real Vercel AI
59+
SDK `generateObject` call with the same host-side schema selection logic.
60+
61+
Use the same prompt across states:
62+
63+
```text
64+
Customer customer-123 says: I need help with order A-100.
65+
```
66+
67+
What to observe:
68+
69+
- absent state: no schema is authorized, so the host does not call
70+
`generateObject`
71+
- `use refund_intake`: the host selects the `refund_intake` schema and the live
72+
output has refund-intake shape
73+
- `use technical_support`: the host selects the `technical_support` schema and
74+
the live output has technical-support shape
75+
76+
The live-model proof stays focused on absent, `refund_intake`, and
77+
`technical_support` selection.
78+
79+
If you probe contradiction separately, the current deterministic behavior is:
80+
81+
- `use refund_intake` followed by `prohibit refund_intake` produces
82+
clarification from the compiler
83+
- the previously authorized `refund_intake` schema remains selected in current
84+
state until that contradiction is resolved
85+
5386
## Install
5487

5588
```bash
@@ -65,6 +98,21 @@ npm run typecheck
6598
npm test
6699
```
67100

101+
Run the opt-in live-model validation:
102+
103+
```bash
104+
export RUN_VERCEL_AI_SDK_LIVE_MODEL=1
105+
export OPENAI_API_KEY=...
106+
export MODEL=gpt-4o-mini
107+
npm test -- --test-name-pattern="live model generateObject output changes with authoritative schema selection"
108+
```
109+
110+
Live-model contract:
111+
112+
- `OPENAI_API_KEY` is required
113+
- `MODEL` defaults to `gpt-4o-mini`
114+
- canonical provider-free tests remain the primary proof
115+
68116
## Run the example
69117

70118
```bash

‎typescript/examples/schema_selection/vercel_ai_sdk_generate_object/package-lock.json‎

Lines changed: 118 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎typescript/examples/schema_selection/vercel_ai_sdk_generate_object/package.json‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
"example": "node dist/src/index.js"
1111
},
1212
"dependencies": {
13+
"@ai-sdk/openai": "^4.0.7",
1314
"@rlippmann/context-compiler": "^0.7.5",
15+
"ai": "^7.0.15",
1416
"zod": "^3.25.76"
1517
},
1618
"devDependencies": {

‎typescript/examples/schema_selection/vercel_ai_sdk_generate_object/src/index.ts‎

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { z, type ZodTypeAny } from "zod";
99

1010
declare const process: { argv: string[]; exitCode?: number };
1111

12-
export type StructuredSchemaName = "python_script" | "shell_command";
12+
export type StructuredSchemaName = "refund_intake" | "technical_support";
1313

1414
export type StructuredSchema = {
1515
name: StructuredSchemaName;
@@ -27,30 +27,35 @@ export type GenerateObjectLike<TObject> = (
2727
request: GenerateObjectRequest
2828
) => Promise<{ object: TObject }>;
2929

30-
const PYTHON_SCRIPT_SCHEMA = z.object({
31-
code: z.string().describe("A complete Python script.")
30+
const REFUND_INTAKE_SCHEMA = z.object({
31+
kind: z.literal("refund"),
32+
customerId: z.string().describe("Customer identifier for the refund request."),
33+
orderId: z.string().describe("Order identifier for the refund request."),
34+
reason: z.string().describe("Customer reason for requesting the refund.")
3235
});
3336

34-
const SHELL_COMMAND_SCHEMA = z.object({
35-
command: z.string().describe("A single shell command.")
37+
const TECHNICAL_SUPPORT_SCHEMA = z.object({
38+
kind: z.literal("technical_support"),
39+
customerId: z.string().describe("Customer identifier for the support request."),
40+
issue: z.string().describe("Customer issue that needs technical support.")
3641
});
3742

3843
const SCHEMA_REGISTRY: Record<StructuredSchemaName, StructuredSchema> = {
39-
python_script: {
40-
name: "python_script",
41-
description: "Generate a Python script object.",
42-
schema: PYTHON_SCRIPT_SCHEMA
44+
refund_intake: {
45+
name: "refund_intake",
46+
description: "Generate a refund intake object.",
47+
schema: REFUND_INTAKE_SCHEMA
4348
},
44-
shell_command: {
45-
name: "shell_command",
46-
description: "Generate a shell command object.",
47-
schema: SHELL_COMMAND_SCHEMA
49+
technical_support: {
50+
name: "technical_support",
51+
description: "Generate a technical support intake object.",
52+
schema: TECHNICAL_SUPPORT_SCHEMA
4853
}
4954
};
5055

5156
const KNOWN_SCHEMAS: readonly StructuredSchemaName[] = [
52-
"python_script",
53-
"shell_command"
57+
"refund_intake",
58+
"technical_support"
5459
];
5560

5661
export function selectStructuredSchemasFromState(
@@ -109,19 +114,32 @@ export async function generateStructuredObject<TObject>(
109114
export async function runExample(): Promise<{
110115
availableSchemaNames: StructuredSchemaName[];
111116
requestBuilt: boolean;
112-
object: { code: string } | null;
117+
object: {
118+
kind: "refund";
119+
customerId: string;
120+
orderId: string;
121+
reason: string;
122+
} | null;
113123
}> {
114124
const engine = createEngine();
115-
engine.step("use python_script");
116-
engine.step("prohibit shell_command");
125+
engine.step("use refund_intake");
126+
engine.step("prohibit technical_support");
117127

118128
const availableSchemas = selectStructuredSchemasFromState(engine.state);
119-
const generated = await generateStructuredObject<{ code: string }>(
129+
const generated = await generateStructuredObject<{
130+
kind: "refund";
131+
customerId: string;
132+
orderId: string;
133+
reason: string;
134+
}>(
120135
engine.state,
121-
"Write a short Python script that prints hello.",
136+
"Customer customer-123 says: I need a refund for order A-100.",
122137
async (request) => ({
123138
object: {
124-
code: `# schema=${request.schemaName}\nprint("hello")`
139+
kind: "refund",
140+
customerId: "customer-123",
141+
orderId: "A-100",
142+
reason: `Selected schema: ${request.schemaName}`
125143
}
126144
})
127145
);

0 commit comments

Comments
 (0)