Skip to content

Commit 43ef2fc

Browse files
committed
another pass through all recipes
generateText -> llm
1 parent a0a3305 commit 43ef2fc

File tree

13 files changed

+271
-209
lines changed

13 files changed

+271
-209
lines changed

typescript/packages/common-builder/src/built-in.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
import { lift, createNodeFactory } from "./module.js";
22
import { Value, NodeFactory, CellProxy } from "./types.js";
33

4-
export function generateText(
4+
export function llm(
55
params: Value<{
6-
messages: string[];
6+
messages?: string[];
7+
prompt?: string;
78
system?: string;
89
stop?: string;
910
max_tokens?: number;
1011
}>
11-
): CellProxy<{ pending: boolean; result: string; partial: string; error: any }> {
12-
generateTextFactory ||= createNodeFactory({
12+
): CellProxy<{ pending: boolean; result?: string; partial?: string; error: any }> {
13+
llmFactory ||= createNodeFactory({
1314
type: "builtin",
14-
implementation: "generateText",
15+
implementation: "llm",
1516
});
16-
return generateTextFactory(params);
17+
return llmFactory(params);
1718
}
1819

1920
export function fetchData<T>(
@@ -52,10 +53,10 @@ export function ifElse<T, U, V>(
5253

5354
let ifElseFactory: NodeFactory<[any, any, any], any> | undefined = undefined;
5455

55-
let generateTextFactory:
56+
let llmFactory:
5657
| NodeFactory<
57-
{ messages: string[]; system?: string; stop?: string; max_tokens?: number },
58-
{ pending: boolean; result: any; partial: any; error: any }
58+
{ messages?: string[]; prompt?: string; system?: string; stop?: string; max_tokens?: number },
59+
{ pending: boolean; result?: string; partial?: string; error: any }
5960
>
6061
| undefined = undefined;
6162

typescript/packages/common-builder/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export {
66
isolated,
77
} from "./module.js";
88
export { recipe } from "./recipe.js";
9-
export { streamData, fetchData, generateText, ifElse, str } from "./built-in.js";
9+
export { streamData, fetchData, llm, ifElse, str } from "./built-in.js";
1010
export {
1111
ID,
1212
TYPE,

typescript/packages/common-runner/src/builtins/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import { type CellImpl } from "../cell.js";
33
import { map } from "./map.js";
44
import { fetchData } from "./fetch-data.js";
55
import { streamData } from "./stream-data.js";
6-
import { generateText } from "./generate-text.js";
6+
import { llm } from "./llm.js";
77
import { ifElse } from "./if-else.js";
88
export const builtins: {
99
[key: string]: (recipeCell: CellImpl<any>, node: Node) => void;
1010
} = {
1111
map,
1212
fetchData,
1313
streamData,
14-
generateText,
14+
llm,
1515
ifElse,
1616
};

typescript/packages/common-runner/src/builtins/generate-text.ts renamed to typescript/packages/common-runner/src/builtins/llm.ts

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ import { makeClient, SimpleMessage, SimpleContent } from "../llm-client.js";
2323
* @param stop - A cell to store (optional) stop sequence.
2424
* @param max_tokens - A cell to store the maximum number of tokens to generate.
2525
*
26-
* @returns { pending: boolean, result: any, partial: any } - As individual
26+
* @returns { pending: boolean, result?: string, partial?: string } - As individual
2727
* cells, representing `pending` state, final `result` and incrementally
2828
* updating `partial` result.
2929
*/
30-
export function generateText(
30+
export function llm(
3131
recipeCell: CellImpl<any>,
3232
{ inputs, outputs }: Node
3333
) {
@@ -41,37 +41,33 @@ export function generateText(
4141
const inputsCell = cell(inputBindings);
4242

4343
const pending = cell(false);
44-
const fullResult = cell<string | undefined>(undefined);
45-
const partialResult = cell<string | undefined>(undefined);
44+
const result = cell<string | undefined>(undefined);
45+
const partial = cell<string | undefined>(undefined);
4646

47-
const resultCell = cell({
48-
pending,
49-
result: fullResult,
50-
partial: partialResult,
51-
});
47+
const outputCell = cell({ pending, result, partial });
5248

5349
const outputBindings = mapBindingsToCell(outputs, recipeCell) as any[];
54-
sendValueToBinding(recipeCell, outputBindings, resultCell);
50+
sendValueToBinding(recipeCell, outputBindings, outputCell);
5551

5652
let currentRun = 0;
5753

5854
const startGeneration: Action = (log: ReactivityLog) => {
5955
const thisRun = ++currentRun;
6056

61-
const { system, messages, prompt, stop, max_tokens } = inputsCell.getAsProxy([], log);
57+
const { system, messages, prompt, stop, max_tokens } = inputsCell.getAsProxy([], log) ?? {};
6258

63-
fullResult.setAtPath([], undefined, log);
64-
partialResult.setAtPath([], undefined, log);
59+
result.setAtPath([], undefined, log);
60+
partial.setAtPath([], undefined, log);
6561

6662
if (((prompt === undefined || prompt.length === 0) && (messages === undefined || messages.length === 0)) || system === undefined) {
6763
pending.setAtPath([], false, log);
6864
return;
6965
}
7066
pending.setAtPath([], true, log);
7167

72-
const updatePartial = (t: string) => {
68+
const updatePartial = (text: string) => {
7369
if (thisRun != currentRun) return;
74-
partialResult.setAtPath([], t, log);
70+
partial.setAtPath([], text, log);
7571
}
7672

7773
let resultPromise = makeClient().sendRequest({
@@ -83,20 +79,20 @@ export function generateText(
8379
}, updatePartial)
8480

8581
resultPromise
86-
.then((result) => {
82+
.then((text) => {
8783
if (thisRun !== currentRun) return;
8884
// normalizeToCells(result, undefined, log);
8985

9086
pending.setAtPath([], false, log);
91-
fullResult.setAtPath([], result, log);
92-
partialResult.setAtPath([], result, log);
87+
result.setAtPath([], text, log);
88+
partial.setAtPath([], text, log);
9389
}).catch((error) => {
9490
if (thisRun !== currentRun) return;
9591

9692
console.error("Error generating data", error);
9793
pending.setAtPath([], false, log);
98-
fullResult.setAtPath([], undefined, log);
99-
partialResult.setAtPath([], undefined, log);
94+
result.setAtPath([], undefined, log);
95+
partial.setAtPath([], undefined, log);
10096
});
10197
};
10298

typescript/packages/common-ui/src/components/common-suggestions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ export class CommonSuggestionsElement extends LitElement {
5757
}
5858
};
5959

60+
// FIXME(ja): Cannot read properties of undefined (reading 'slice')
61+
// broken on main - doesn't seem to be regression of the llm changes
6062
const suggestions = this.suggestions.slice(0, this.limit);
6163

6264
return html`

typescript/packages/lookslike-high-level/src/recipes/annotation.ts

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
NAME,
1010
TYPE,
1111
ifElse,
12-
generateData,
12+
llm,
1313
} from "@commontools/common-builder";
1414
import { type Charm, openCharm } from "../data.js";
1515
import {
@@ -18,9 +18,17 @@ import {
1818
getCellReferenceOrValue,
1919
} from "@commontools/common-runner";
2020
import { suggestions } from "../suggestions.js";
21+
import { z } from "zod";
2122

2223
const MINIMUM_CONFIDENCE = -1.0;
2324

25+
const Suggestion = z.object({
26+
index: z.number(),
27+
confidence: z.number(),
28+
reason: z.string(),
29+
});
30+
type Suggestion = z.infer<typeof Suggestion>;
31+
2432
type CharmInfo = { id: number; name: string; type: string };
2533

2634
// Lifted functions at module scope
@@ -36,11 +44,13 @@ const getCharmInfo = lift(({ charms }) => {
3644
return charmInfo;
3745
});
3846

39-
const buildMessages = lift(
47+
const buildQuery = lift(
4048
({ query, charmInfo }) => {
4149

42-
return [
43-
`Given the following user query and list of data charms, return the indices of the charms that are most relevant to the query.
50+
return {
51+
system: "You are an assistant that helps match user queries to relevant data charms based on their names and types.",
52+
messages: [
53+
`Given the following user query and list of data charms, return the indices of the charms that are most relevant to the query.
4454
Consider both the names and types of the charms when making your selection.
4555
Think broadly, e.g. a stay in a hotel could match a charm called "morning routine", as the user would want to pick a hotel that supports their morning routine.
4656
@@ -61,11 +71,32 @@ Respond with only JSON array of suggestions, e.g.
6171
{ index: 5, chosen: "suzy collab", reason: "could this be related to Susan? she appears in several project related lists", confidence: 0.33 }
6272
]
6373
\`\`\`
64-
`, '```json\n['];
74+
`, '```json\n['
75+
],
76+
stop: '```',
77+
};
6578
});
6679

80+
const grabJson = lift<{ result?: string }, Suggestion[]>(({ result }) => {
81+
if (!result) {
82+
return [];
83+
}
84+
const jsonMatch = result.match(/```json\n([\s\S]+?)```/);
85+
if (!jsonMatch) {
86+
console.error("No JSON found in text:", result);
87+
return [];
88+
}
89+
let rawData = JSON.parse(jsonMatch[1]);
90+
let parsedData = z.array(Suggestion).safeParse(rawData);
91+
if (!parsedData.success) {
92+
console.error("Invalid JSON:", parsedData.error);
93+
return [];
94+
}
95+
return parsedData.data;
96+
});
97+
6798
const filterMatchingCharms = lift<{
68-
matchedIndices: { index: number; confidence: number }[];
99+
matchedIndices: Suggestion[];
69100
charmInfo: CharmInfo[];
70101
}>(
71102
({ matchedIndices, charmInfo }) =>
@@ -168,12 +199,7 @@ export const annotation = recipe<{
168199
charms: Charm[];
169200
}>("annotation", ({ query, target, data, charms }) => {
170201
const charmInfo = getCharmInfo({ charms });
171-
const { result: matchedIndices } = generateData<
172-
{ index: number; confidence: number }[]
173-
>({
174-
messages: buildMessages({ query, charmInfo }),
175-
system: "You are an assistant that helps match user queries to relevant data charms based on their names and types.",
176-
});
202+
const matchedIndices = grabJson(llm(buildQuery({ query, charmInfo })));
177203
const matchingCharms = filterMatchingCharms({ matchedIndices, charmInfo });
178204
const suggestion = findSuggestion({ matchingCharms, data });
179205

typescript/packages/lookslike-high-level/src/recipes/generator.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { html } from "@commontools/common-html";
2-
import { recipe, NAME, UI, handler, lift, generateText } from "@commontools/common-builder";
2+
import { recipe, NAME, UI, handler, lift, llm } from "@commontools/common-builder";
33
import { z } from 'zod';
44
import zodToJsonSchema from 'zod-to-json-schema';
55

@@ -17,7 +17,7 @@ const prepText = lift(({ prompt }) => {
1717
}
1818
return {};
1919
});
20-
const grabText = lift(({ partial }) => { return partial || '' })
20+
const grabText = lift<{ partial?: string }, string>(({ partial }) => { return partial || '' })
2121

2222

2323
const prepHTML = lift(({ prompt }) => {
@@ -30,13 +30,13 @@ const prepHTML = lift(({ prompt }) => {
3030
}
3131
return {};
3232
});
33-
const grabHtml = lift(({ partial, pending }) => {
33+
const grabHtml = lift<{ partial?: string; pending?: boolean }, string>(({ partial, pending }) => {
3434
if (!partial) {
3535
return ""
3636
}
3737

3838
if (pending) {
39-
return `<code>${partial.slice(-1000).replace(/</g, "&lt;").replace(/>/g, "&gt;").slice(-1000)}</code>`;
39+
return `<code>${partial.split('\n').slice(-5).join('\n').replace(/</g, "&lt;").replace(/>/g, "&gt;")}</code>`;
4040
}
4141

4242
const html = partial.match(/```html\n([\s\S]+?)```/)?.[1];
@@ -69,7 +69,7 @@ const prepJSON = lift(({ prompt }) => {
6969
}
7070
return {};
7171
});
72-
const grabJson = lift<{ result: string }, Character | undefined>(({ result }) => {
72+
const grabJson = lift<{ result?: string }, Character | undefined>(({ result }) => {
7373
if (!result) {
7474
return;
7575
}
@@ -93,14 +93,13 @@ export const generator = recipe<{ jsonprompt: string; htmlprompt: string; textpr
9393
({ jsonprompt, htmlprompt, textprompt, data }) => {
9494

9595
textprompt.setDefault("2 sentence story");
96-
const maybeText = grabText(generateText(prepText({ prompt: textprompt })))
96+
const maybeText = grabText(llm(prepText({ prompt: textprompt })))
9797

9898
jsonprompt.setDefault("pet");
99-
data = grabJson(generateText(prepJSON({ prompt: jsonprompt })));
100-
const maybeJSON = jsonify({ data });
99+
data = grabJson(llm(prepJSON({ prompt: jsonprompt })));
101100

102101
htmlprompt.setDefault("simple html about recipes");
103-
const maybeHTML = grabHtml(generateText(prepHTML({ prompt: htmlprompt })));
102+
const maybeHTML = grabHtml(llm(prepHTML({ prompt: htmlprompt })));
104103

105104
return {
106105
[NAME]: 'data generator',
@@ -119,7 +118,7 @@ export const generator = recipe<{ jsonprompt: string; htmlprompt: string; textpr
119118
placeholder="Request to LLM"
120119
oncommon-input=${updateValue({ value: jsonprompt })}
121120
></common-input>
122-
<pre>${maybeJSON}</pre>
121+
<pre>${jsonify({ data })}</pre>
123122
124123
<p>HTML</p>
125124
<common-input

0 commit comments

Comments
 (0)