Skip to content

Commit 88e89da

Browse files
authored
new builtin: generateObject (#1143)
Added a new generateObject LLM API and builder node for generating structured JSON objects from prompts and schemas, with backend validation and example usage in recipes. New Features - Added generateObject API endpoint and handler for LLM-backed JSON object generation. - Introduced generateObject builder node and client support. - Added schema validation using Ajv on the backend. - Provided example usage in seeder/json.tsx and updated counter recipe for JSON mode.
1 parent 5203b74 commit 88e89da

File tree

18 files changed

+607
-6
lines changed

18 files changed

+607
-6
lines changed

deno.lock

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

packages/api/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,16 @@ export interface BuiltInLLMState<T> {
229229
error: unknown;
230230
}
231231

232+
export interface BuiltInGenerateObjectParams {
233+
model?: string;
234+
prompt?: string;
235+
schema?: JSONSchema;
236+
system?: string;
237+
cache?: boolean;
238+
maxTokens?: number;
239+
metadata?: Record<string, string | undefined | object>;
240+
}
241+
232242
export interface BuiltInCompileAndRunParams<T> {
233243
files: Record<string, string>;
234244
main: string;
@@ -340,6 +350,10 @@ export type LLMFunction = <T = string>(
340350
params: Opaque<BuiltInLLMParams>,
341351
) => OpaqueRef<BuiltInLLMState<T>>;
342352

353+
export type GenerateObjectFunction = <T = any>(
354+
params: Opaque<BuiltInGenerateObjectParams>,
355+
) => OpaqueRef<BuiltInLLMState<T>>;
356+
343357
export type FetchDataFunction = <T>(
344358
params: Opaque<{
345359
url: string;
@@ -404,6 +418,7 @@ export declare const render: RenderFunction;
404418
export declare const str: StrFunction;
405419
export declare const ifElse: IfElseFunction;
406420
export declare const llm: LLMFunction;
421+
export declare const generateObject: GenerateObjectFunction;
407422
export declare const fetchData: FetchDataFunction;
408423
export declare const streamData: StreamDataFunction;
409424
export declare const compileAndRun: CompileAndRunFunction;

packages/llm/src/client.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { LLMContent, LLMMessage, LLMRequest, LLMResponse } from "./types.ts";
1+
import {
2+
LLMContent,
3+
LLMMessage,
4+
LLMRequest,
5+
LLMResponse,
6+
LLMGenerateObjectRequest,
7+
LLMGenerateObjectResponse
8+
} from "./types.ts";
29

310
type PartialCallback = (text: string) => void;
411

@@ -14,6 +21,26 @@ export const setLLMUrl = (toolshedUrl: string) => {
1421
};
1522

1623
export class LLMClient {
24+
async generateObject(
25+
request: LLMGenerateObjectRequest,
26+
): Promise<LLMGenerateObjectResponse> {
27+
const response = await fetch(llmApiUrl + "/generateObject", {
28+
method: "POST",
29+
headers: { "Content-Type": "application/json" },
30+
body: JSON.stringify(request),
31+
});
32+
33+
if (!response.ok) {
34+
const errorText = await response.text();
35+
throw new Error(
36+
`HTTP error! status: ${response.status}, body: ${errorText}`,
37+
);
38+
}
39+
40+
const data = await response.json();
41+
return data;
42+
}
43+
1744
/**
1845
* Sends a request to the LLM service.
1946
*

packages/llm/src/types.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const DEFAULT_MODEL_NAME: ModelName =
66
// NOTE(ja): This should be an array of models, the first model will be tried, if it
77
// fails, the second model will be tried, etc.
88
export const DEFAULT_IFRAME_MODELS: ModelName = "openai:gpt-4.1-nano";
9+
export const DEFAULT_GENERATE_OBJECT_MODELS: ModelName = "openai:gpt-4.1-nano";
910

1011
export type LLMResponse = {
1112
content: string;
@@ -37,6 +38,21 @@ export interface LLMRequest {
3738
metadata?: LLMRequestMetadata;
3839
}
3940

41+
export interface LLMGenerateObjectRequest {
42+
schema: Record<string, unknown>;
43+
prompt: string;
44+
model?: ModelName;
45+
system?: string;
46+
cache?: boolean;
47+
maxTokens?: number;
48+
metadata?: LLMRequestMetadata;
49+
}
50+
51+
export interface LLMGenerateObjectResponse {
52+
object: Record<string, unknown>;
53+
id?: string;
54+
}
55+
4056
function isArrayOf<T>(
4157
callback: (data: any) => boolean,
4258
input: any,

packages/runner/src/builder/built-in.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,30 @@ export interface BuiltInLLMState<T> {
4848
error: unknown;
4949
}
5050

51+
export interface BuiltInGenerateObjectParams {
52+
model?: string;
53+
prompt?: string;
54+
schema?: JSONSchema;
55+
system?: string;
56+
cache?: boolean;
57+
maxTokens?: number;
58+
metadata?: Record<string, string | undefined | object>;
59+
}
60+
5161
export const llm = createNodeFactory({
5262
type: "ref",
5363
implementation: "llm",
5464
}) as <T = string>(
5565
params: Opaque<BuiltInLLMParams>,
5666
) => OpaqueRef<BuiltInLLMState<T>>;
5767

68+
export const generateObject = createNodeFactory({
69+
type: "ref",
70+
implementation: "generateObject",
71+
}) as <T = any>(
72+
params: Opaque<BuiltInGenerateObjectParams>,
73+
) => OpaqueRef<BuiltInLLMState<T>>;
74+
5875
export const fetchData = createNodeFactory({
5976
type: "ref",
6077
implementation: "fetchData",

packages/runner/src/builder/factory.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { byRef, compute, derive, handler, lift, render } from "./module.ts";
2525
import {
2626
compileAndRun,
2727
fetchData,
28+
generateObject,
2829
ifElse,
2930
llm,
3031
navigateTo,
@@ -91,6 +92,7 @@ export const createBuilder = (
9192
str,
9293
ifElse,
9394
llm,
95+
generateObject,
9496
fetchData,
9597
streamData,
9698
compileAndRun,

packages/runner/src/builder/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {
99
CreateCellFunction,
1010
DeriveFunction,
1111
FetchDataFunction,
12+
GenerateObjectFunction,
1213
GetRecipeEnvironmentFunction,
1314
HandlerFunction,
1415
IfElseFunction,
@@ -270,6 +271,7 @@ export interface BuilderFunctionsAndConstants {
270271
str: StrFunction;
271272
ifElse: IfElseFunction;
272273
llm: LLMFunction;
274+
generateObject: GenerateObjectFunction;
273275
fetchData: FetchDataFunction;
274276
streamData: StreamDataFunction;
275277
compileAndRun: CompileAndRunFunction;

packages/runner/src/builtins/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import { raw } from "../module.ts";
22
import { map } from "./map.ts";
33
import { fetchData } from "./fetch-data.ts";
44
import { streamData } from "./stream-data.ts";
5-
import { llm } from "./llm.ts";
5+
import { llm, generateObject } from "./llm.ts";
66
import { ifElse } from "./if-else.ts";
77
import type { IRuntime } from "../runtime.ts";
88
import { compileAndRun } from "./compile-and-run.ts";
9+
import type { DocImpl } from "../doc.ts";
10+
import type { BuiltInGenerateObjectParams } from "@commontools/api";
911

1012
/**
1113
* Register all built-in modules with a runtime's module registry
@@ -19,4 +21,10 @@ export function registerBuiltins(runtime: IRuntime) {
1921
moduleRegistry.addModuleByRef("llm", raw(llm));
2022
moduleRegistry.addModuleByRef("ifElse", raw(ifElse));
2123
moduleRegistry.addModuleByRef("compileAndRun", raw(compileAndRun));
24+
moduleRegistry.addModuleByRef("generateObject", raw<BuiltInGenerateObjectParams, {
25+
pending: DocImpl<boolean>;
26+
result: DocImpl<Record<string, unknown> | undefined>;
27+
partial: DocImpl<string | undefined>;
28+
requestHash: DocImpl<string | undefined>;
29+
}>(generateObject));
2230
}

0 commit comments

Comments
 (0)