Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add type information
  • Loading branch information
anotherjesse committed Jun 18, 2025
commit 6a1367899a25dc9be5995beeceba2644fb761c67
31 changes: 20 additions & 11 deletions packages/runner/src/builtins/llm.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { type DocImpl } from "../doc.ts";
import {
DEFAULT_MODEL_NAME,
import {
DEFAULT_GENERATE_OBJECT_MODELS,
LLMClient,
LLMRequest,
DEFAULT_MODEL_NAME,
LLMClient,
LLMGenerateObjectRequest,
LLMRequest,
} from "@commontools/llm";
import { type Action } from "../scheduler.ts";
import type { IRuntime } from "../runtime.ts";
import { refer } from "merkle-reference";
import { type ReactivityLog } from "../scheduler.ts";
import {
BuiltInGenerateObjectParams,
BuiltInLLMParams,
BuiltInLLMState,
BuiltInGenerateObjectParams,
} from "@commontools/api";

const client = new LLMClient();
Expand Down Expand Up @@ -177,9 +177,14 @@ export function llm(
* docs, representing `pending` state, final `result` and incrementally
* updating `partial` result.
*/
export function generateObject(
export function generateObject<T extends Record<string, unknown>>(
inputsCell: DocImpl<BuiltInGenerateObjectParams>,
sendResult: (result: any) => void,
sendResult: (docs: {
pending: DocImpl<boolean>;
result: DocImpl<T | undefined>;
partial: DocImpl<string | undefined>;
requestHash: DocImpl<string | undefined>;
}) => void,
_addCancel: (cancel: () => void) => void,
cause: any,
parentDoc: DocImpl<any>,
Expand All @@ -190,7 +195,7 @@ export function generateObject(
{ generateObject: { pending: cause } },
parentDoc.space,
);
const result = runtime.documentMap.getDoc<Record<string, unknown> | undefined>(
const result = runtime.documentMap.getDoc<T | undefined>(
undefined,
{
generateObject: { result: cause },
Expand Down Expand Up @@ -254,11 +259,15 @@ export function generateObject(
if (hash === previousCallHash || hash === requestHash.get()) return;
previousCallHash = hash;

result.setAtPath([], undefined, log);
result.setAtPath([], {}, log); // FIXME(ja): setting result to undefined causes a storage conflict
partial.setAtPath([], undefined, log);
pending.setAtPath([], true, log);

const resultPromise = client.generateObject(generateObjectParams);
const resultPromise = client.generateObject(
generateObjectParams,
) as Promise<{
object: T;
}>;

resultPromise
.then(async (response) => {
Expand All @@ -278,7 +287,7 @@ export function generateObject(
await runtime.idle();

pending.setAtPath([], false, log);
result.setAtPath([], undefined, log);
result.setAtPath([], {}, log); // FIXME(ja): setting result to undefined causes a storage conflict
partial.setAtPath([], undefined, log);

// TODO(seefeld): Not writing now, so we retry the request after failure.
Expand Down
65 changes: 28 additions & 37 deletions recipes/test-generate-object.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
lift,
NAME,
recipe,
Schema,
schema,
str,
UI,
Expand Down Expand Up @@ -35,6 +36,8 @@ const outputSchema = {
},
} as const satisfies JSONSchema;

type OutputSchema = Schema<typeof outputSchema>;

// Handler to increment the number
const adder = handler({}, inputSchema, (_, state) => {
state.number.set(state.number.get() + 1);
Expand Down Expand Up @@ -69,7 +72,7 @@ const generateImageUrl = lift(({ imagePrompt }) => {

export default recipe(inputSchema, outputSchema, (cell) => {
// Use generateObject to get structured data from the LLM
const { result: object, pending } = generateObject(
const { result: object, pending } = generateObject<OutputSchema>(
generatePrompt({ number: cell.number }),
);

Expand All @@ -84,42 +87,30 @@ export default recipe(inputSchema, outputSchema, (cell) => {
pending,
<p>Generating story...</p>,
<div>
{ifElse(object?.title, <h1>{object.title}</h1>, <p>No title</p>)}
{ifElse(
object?.imagePrompt,
<p>
<img
src={generateImageUrl({ imagePrompt: object.imagePrompt })}
/>
</p>,
<p>No image prompt</p>,
)}
{ifElse(object?.story, <p>{object.story}</p>, <p>No story yet</p>)}
{ifElse(
object?.storyOrigin,
<p>
<em>{object.storyOrigin}</em>
</p>,
<p>No story origin</p>,
)}
{ifElse(
object?.seeAlso,
<div>
<p>See also these interesting numbers:</p>
<ul>
{object.seeAlso.map((n: number) => (
<li>
<ct-button
onClick={setNumber({ number: cell.number, n })}
>
{n}
</ct-button>
</li>
))}
</ul>
</div>,
<p>No related numbers</p>,
)}
<h1>{object?.title}</h1>
<p>
<img
src={generateImageUrl({ imagePrompt: object?.imagePrompt })}
/>
</p>
<p>{object?.story}</p>
<p>
<em>{object?.storyOrigin}</em>
</p>
<div>
<p>See also these interesting numbers:</p>
<ul>
{object?.seeAlso?.map((n: number) => (
<li>
<ct-button
onClick={setNumber({ number: cell.number, n })}
>
{n}
</ct-button>
</li>
))}
</ul>
</div>
</div>,
)}
</div>
Expand Down
Loading