@nodetool-ai/llm-nodes 0.8.1
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/README.md +52 -0
- package/dist/.nodetool-build-stamp +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/nodes/agent-control-tools.d.ts +22 -0
- package/dist/nodes/agent-control-tools.d.ts.map +1 -0
- package/dist/nodes/agent-control-tools.js +123 -0
- package/dist/nodes/agent-control-tools.js.map +1 -0
- package/dist/nodes/agent-loop.d.ts +73 -0
- package/dist/nodes/agent-loop.d.ts.map +1 -0
- package/dist/nodes/agent-loop.js +168 -0
- package/dist/nodes/agent-loop.js.map +1 -0
- package/dist/nodes/agent-recommended-models.d.ts +152 -0
- package/dist/nodes/agent-recommended-models.d.ts.map +1 -0
- package/dist/nodes/agent-recommended-models.js +656 -0
- package/dist/nodes/agent-recommended-models.js.map +1 -0
- package/dist/nodes/agent-thinking.d.ts +29 -0
- package/dist/nodes/agent-thinking.d.ts.map +1 -0
- package/dist/nodes/agent-thinking.js +139 -0
- package/dist/nodes/agent-thinking.js.map +1 -0
- package/dist/nodes/agent-threads.d.ts +17 -0
- package/dist/nodes/agent-threads.d.ts.map +1 -0
- package/dist/nodes/agent-threads.js +93 -0
- package/dist/nodes/agent-threads.js.map +1 -0
- package/dist/nodes/agent-tool-hydration.d.ts +50 -0
- package/dist/nodes/agent-tool-hydration.d.ts.map +1 -0
- package/dist/nodes/agent-tool-hydration.js +106 -0
- package/dist/nodes/agent-tool-hydration.js.map +1 -0
- package/dist/nodes/agent-utils.d.ts +174 -0
- package/dist/nodes/agent-utils.d.ts.map +1 -0
- package/dist/nodes/agent-utils.js +553 -0
- package/dist/nodes/agent-utils.js.map +1 -0
- package/dist/nodes/agents.d.ts +402 -0
- package/dist/nodes/agents.d.ts.map +1 -0
- package/dist/nodes/agents.js +1491 -0
- package/dist/nodes/agents.js.map +1 -0
- package/dist/nodes/director.d.ts +126 -0
- package/dist/nodes/director.d.ts.map +1 -0
- package/dist/nodes/director.js +337 -0
- package/dist/nodes/director.js.map +1 -0
- package/dist/nodes/gemini.d.ts +171 -0
- package/dist/nodes/gemini.d.ts.map +1 -0
- package/dist/nodes/gemini.js +985 -0
- package/dist/nodes/gemini.js.map +1 -0
- package/dist/nodes/generators.d.ts +162 -0
- package/dist/nodes/generators.d.ts.map +1 -0
- package/dist/nodes/generators.js +966 -0
- package/dist/nodes/generators.js.map +1 -0
- package/dist/nodes/mistral.d.ts +109 -0
- package/dist/nodes/mistral.d.ts.map +1 -0
- package/dist/nodes/mistral.js +447 -0
- package/dist/nodes/mistral.js.map +1 -0
- package/dist/nodes/openai.d.ts +292 -0
- package/dist/nodes/openai.d.ts.map +1 -0
- package/dist/nodes/openai.js +1806 -0
- package/dist/nodes/openai.js.map +1 -0
- package/dist/nodes/provider-spend.d.ts +45 -0
- package/dist/nodes/provider-spend.d.ts.map +1 -0
- package/dist/nodes/provider-spend.js +69 -0
- package/dist/nodes/provider-spend.js.map +1 -0
- package/dist/nodes/shots.d.ts +108 -0
- package/dist/nodes/shots.d.ts.map +1 -0
- package/dist/nodes/shots.js +378 -0
- package/dist/nodes/shots.js.map +1 -0
- package/dist/nodes/xai.d.ts +99 -0
- package/dist/nodes/xai.d.ts.map +1 -0
- package/dist/nodes/xai.js +360 -0
- package/dist/nodes/xai.js.map +1 -0
- package/package.json +111 -0
|
@@ -0,0 +1,966 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { BaseNode, prop } from "@nodetool-ai/node-sdk";
|
|
8
|
+
import { isChunk } from "@nodetool-ai/protocol";
|
|
9
|
+
import { isToolCall } from "@nodetool-ai/runtime";
|
|
10
|
+
import { tagAsServer } from "@nodetool-ai/nodes-utils";
|
|
11
|
+
import { serializeToolResult, streamProviderMessages, toProviderTools } from "./agents.js";
|
|
12
|
+
import { asText, buildUserMessage, getModelConfig, hasProviderAccess, hasProviderSupport } from "./agent-utils.js";
|
|
13
|
+
import { meterProviderSpend } from "./provider-spend.js";
|
|
14
|
+
import { isCallable, isObjectLike, isRecord, isString } from "@nodetool-ai/node-sdk";
|
|
15
|
+
function parseRequestedCount(prompt, fallback) {
|
|
16
|
+
// Match any integer; large requests are clamped below rather than ignored.
|
|
17
|
+
const m = prompt.match(/\b(\d{1,9})\b/);
|
|
18
|
+
if (!m)
|
|
19
|
+
return fallback;
|
|
20
|
+
const n = Number(m[1]);
|
|
21
|
+
if (!Number.isFinite(n))
|
|
22
|
+
return fallback;
|
|
23
|
+
return Math.max(1, Math.min(200, Math.floor(n)));
|
|
24
|
+
}
|
|
25
|
+
function parseColumns(input) {
|
|
26
|
+
return parseColumnSpecs(input).map((column) => column.name);
|
|
27
|
+
}
|
|
28
|
+
function parseColumnSpecs(input) {
|
|
29
|
+
if (Array.isArray(input)) {
|
|
30
|
+
return input
|
|
31
|
+
.map((c) => {
|
|
32
|
+
if (isObjectLike(c)) {
|
|
33
|
+
const record = c;
|
|
34
|
+
const name = isString(record.name)
|
|
35
|
+
? record.name
|
|
36
|
+
: String(record.name ?? "");
|
|
37
|
+
const dataType = isString(record.data_type)
|
|
38
|
+
? record.data_type
|
|
39
|
+
: isString(record.type)
|
|
40
|
+
? record.type
|
|
41
|
+
: undefined;
|
|
42
|
+
return { name, data_type: dataType };
|
|
43
|
+
}
|
|
44
|
+
return { name: String(c) };
|
|
45
|
+
})
|
|
46
|
+
.filter((c) => c.name.length > 0);
|
|
47
|
+
}
|
|
48
|
+
if (isObjectLike(input)) {
|
|
49
|
+
const columns = input.columns;
|
|
50
|
+
if (Array.isArray(columns))
|
|
51
|
+
return parseColumnSpecs(columns);
|
|
52
|
+
}
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
function makeRows(columns, count, seedText) {
|
|
56
|
+
const names = columns.length > 0 ? columns : ["value"];
|
|
57
|
+
const rows = [];
|
|
58
|
+
for (let i = 0; i < count; i += 1) {
|
|
59
|
+
const row = {};
|
|
60
|
+
for (const col of names) {
|
|
61
|
+
const lower = col.toLowerCase();
|
|
62
|
+
if (lower.includes("id"))
|
|
63
|
+
row[col] = i + 1;
|
|
64
|
+
else if (lower.includes("name"))
|
|
65
|
+
row[col] = `${seedText || "item"}_${i + 1}`;
|
|
66
|
+
else if (lower.includes("date"))
|
|
67
|
+
row[col] = new Date(Date.now() + i * 86_400_000).toISOString();
|
|
68
|
+
else if (lower.includes("price") ||
|
|
69
|
+
lower.includes("amount") ||
|
|
70
|
+
lower.includes("score")) {
|
|
71
|
+
row[col] = Number((10 + i * 1.5).toFixed(2));
|
|
72
|
+
}
|
|
73
|
+
else if (lower.includes("active") || lower.startsWith("is_")) {
|
|
74
|
+
row[col] = i % 2 === 0;
|
|
75
|
+
}
|
|
76
|
+
else
|
|
77
|
+
row[col] = `${seedText || "value"}_${i + 1}`;
|
|
78
|
+
}
|
|
79
|
+
rows.push(row);
|
|
80
|
+
}
|
|
81
|
+
return rows;
|
|
82
|
+
}
|
|
83
|
+
function buildSchemaFromDynamicOutputs(outputs) {
|
|
84
|
+
if (!isRecord(outputs))
|
|
85
|
+
return null;
|
|
86
|
+
const properties = {};
|
|
87
|
+
const required = [];
|
|
88
|
+
for (const [name, spec] of Object.entries(outputs)) {
|
|
89
|
+
required.push(name);
|
|
90
|
+
const value = isRecord(spec) ? spec : {};
|
|
91
|
+
const declared = isString(value.type) ? value.type.toLowerCase() : "str";
|
|
92
|
+
let type = "string";
|
|
93
|
+
if (["int", "integer"].includes(declared))
|
|
94
|
+
type = "integer";
|
|
95
|
+
else if (["float", "number"].includes(declared))
|
|
96
|
+
type = "number";
|
|
97
|
+
else if (["bool", "boolean"].includes(declared))
|
|
98
|
+
type = "boolean";
|
|
99
|
+
else if (["list", "array"].includes(declared))
|
|
100
|
+
type = "array";
|
|
101
|
+
else if (["dict", "object"].includes(declared))
|
|
102
|
+
type = "object";
|
|
103
|
+
properties[name] = { type };
|
|
104
|
+
}
|
|
105
|
+
if (required.length === 0)
|
|
106
|
+
return null;
|
|
107
|
+
return { type: "object", additionalProperties: false, required, properties };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Run one chat completion for a generator node and book what it cost.
|
|
111
|
+
*
|
|
112
|
+
* `runProviderPrediction` emits `prediction` messages, but both consumers drop
|
|
113
|
+
* a token-billed capability, so a workflow made only of generator nodes left no
|
|
114
|
+
* ledger row. The provider instance `runGenerationWith` calls is the one
|
|
115
|
+
* `context.getProvider` memoizes, so the same before/after delta the other LLM
|
|
116
|
+
* nodes report covers this call too. See `meterProviderSpend`.
|
|
117
|
+
*
|
|
118
|
+
* A context without `getProvider` (a partial harness or test double) has no
|
|
119
|
+
* provider to read a running total from, so the call runs unmetered.
|
|
120
|
+
*/
|
|
121
|
+
async function runMeteredMessage(context, providerId, modelId, params) {
|
|
122
|
+
const run = () => context.runProviderPrediction({
|
|
123
|
+
provider: providerId,
|
|
124
|
+
capability: "generate_message",
|
|
125
|
+
model: modelId,
|
|
126
|
+
params
|
|
127
|
+
});
|
|
128
|
+
if (!isCallable(context.getProvider))
|
|
129
|
+
return run();
|
|
130
|
+
const provider = await context.getProvider(providerId);
|
|
131
|
+
const spend = meterProviderSpend(context, provider, modelId);
|
|
132
|
+
try {
|
|
133
|
+
return await run();
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
spend.report();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const LIST_GENERATOR_SYSTEM_PROMPT = "You generate a list by calling the `add_item` tool once per item. " +
|
|
140
|
+
"Each call submits one item, which may be a single line or span multiple " +
|
|
141
|
+
"lines or paragraphs, as the request requires. " +
|
|
142
|
+
"Continue calling `add_item` until all requested items have been emitted, " +
|
|
143
|
+
"then stop. " +
|
|
144
|
+
"If the user does not specify a count, emit 5 items. " +
|
|
145
|
+
"Use tool calls only — do not write the list as prose.";
|
|
146
|
+
// Appended to the system prompt for a single retry when the model answers in
|
|
147
|
+
// prose and emits no `add_item` calls, to push it into using the tool.
|
|
148
|
+
const LIST_GENERATOR_ENFORCEMENT_PROMPT = "Your previous response produced no items because you did not call the " +
|
|
149
|
+
"`add_item` tool. Build the list now by calling `add_item` once per item. " +
|
|
150
|
+
"Do not reply with prose or explanations — only emit `add_item` tool calls.";
|
|
151
|
+
function makeAddItemTool() {
|
|
152
|
+
return {
|
|
153
|
+
name: "add_item",
|
|
154
|
+
description: "Add one item to the output list. Call this once per item in the list.",
|
|
155
|
+
inputSchema: {
|
|
156
|
+
type: "object",
|
|
157
|
+
additionalProperties: false,
|
|
158
|
+
required: ["item"],
|
|
159
|
+
properties: {
|
|
160
|
+
item: {
|
|
161
|
+
type: "string",
|
|
162
|
+
description: "The content of one list item. It may be a single line or span " +
|
|
163
|
+
"multiple lines or paragraphs, as the request requires."
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
process: async (_ctx, params) => {
|
|
168
|
+
const item = asText(params?.item ?? "");
|
|
169
|
+
return { ok: true, item };
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
async function* streamListItemsViaToolCalls(context, providerId, modelId, prompt, maxTokens, systemPrompt = LIST_GENERATOR_SYSTEM_PROMPT) {
|
|
174
|
+
const provider = await context.getProvider(providerId);
|
|
175
|
+
const tool = makeAddItemTool();
|
|
176
|
+
const providerTools = toProviderTools([tool]);
|
|
177
|
+
const messages = [
|
|
178
|
+
{ role: "system", content: systemPrompt },
|
|
179
|
+
{ role: "user", content: prompt }
|
|
180
|
+
];
|
|
181
|
+
const maxIterations = parseRequestedCount(prompt, 5);
|
|
182
|
+
for (let iteration = 0; iteration < maxIterations; iteration += 1) {
|
|
183
|
+
const assistantToolCalls = [];
|
|
184
|
+
let assistantText = "";
|
|
185
|
+
for await (const item of streamProviderMessages(provider, {
|
|
186
|
+
messages,
|
|
187
|
+
model: modelId,
|
|
188
|
+
tools: providerTools,
|
|
189
|
+
maxTokens
|
|
190
|
+
})) {
|
|
191
|
+
if (isToolCall(item)) {
|
|
192
|
+
assistantToolCalls.push(item);
|
|
193
|
+
if (item.name === "add_item") {
|
|
194
|
+
const text = asText(item.args?.item ?? "").trim();
|
|
195
|
+
if (text) {
|
|
196
|
+
yield text;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else if (isChunk(item) && !item.thinking) {
|
|
201
|
+
// Narrative text is ignored for output, but kept in the conversation
|
|
202
|
+
// history so providers can continue after tool calls correctly.
|
|
203
|
+
assistantText += item.content ?? "";
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (assistantToolCalls.length === 0) {
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
messages.push({
|
|
210
|
+
role: "assistant",
|
|
211
|
+
content: assistantText || null,
|
|
212
|
+
toolCalls: assistantToolCalls
|
|
213
|
+
});
|
|
214
|
+
for (const toolCall of assistantToolCalls) {
|
|
215
|
+
const result = toolCall.name === "add_item" && isCallable(tool.process)
|
|
216
|
+
? await tool.process(context, toolCall.args)
|
|
217
|
+
: { error: `Unknown tool: ${toolCall.name}` };
|
|
218
|
+
messages.push({
|
|
219
|
+
role: "tool",
|
|
220
|
+
toolCallId: toolCall.id,
|
|
221
|
+
content: JSON.stringify(serializeToolResult(result))
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function dataframeFromRows(rows, columnsInput) {
|
|
227
|
+
const names = rows.length > 0
|
|
228
|
+
? Object.keys(rows[0])
|
|
229
|
+
: parseColumnSpecs(columnsInput).map((s) => s.name);
|
|
230
|
+
return {
|
|
231
|
+
rows,
|
|
232
|
+
columns: names.map((name) => ({ name })),
|
|
233
|
+
data: rows.map((row) => names.map((name) => row[name] ?? null))
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function parseCsv(text, specs) {
|
|
237
|
+
const lines = text
|
|
238
|
+
.split(/\r?\n/)
|
|
239
|
+
.map((l) => l.trim())
|
|
240
|
+
.filter((l) => l.length > 0 && !l.startsWith("```"));
|
|
241
|
+
if (lines.length < 2)
|
|
242
|
+
return [];
|
|
243
|
+
const header = lines[0].split(",").map((h) => h.trim().replace(/^"|"$/g, ""));
|
|
244
|
+
return lines.slice(1).map((line) => {
|
|
245
|
+
const cells = line.match(/(".*?"|[^,]+|(?<=,)(?=,)|(?<=,)$|^(?=,))/g) ??
|
|
246
|
+
line.split(",");
|
|
247
|
+
const row = {};
|
|
248
|
+
header.forEach((name, i) => {
|
|
249
|
+
const raw = (cells[i] ?? "").trim().replace(/^"|"$/g, "");
|
|
250
|
+
const spec = specs.find((s) => s.name === name);
|
|
251
|
+
const kind = (spec?.data_type ?? "").toLowerCase();
|
|
252
|
+
if (kind === "int" || kind === "integer") {
|
|
253
|
+
const n = Number.parseInt(raw, 10);
|
|
254
|
+
row[name] = Number.isFinite(n) ? n : raw;
|
|
255
|
+
}
|
|
256
|
+
else if (kind === "float" || kind === "number") {
|
|
257
|
+
const n = Number.parseFloat(raw);
|
|
258
|
+
row[name] = Number.isFinite(n) ? n : raw;
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
row[name] = raw;
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
return row;
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
async function generateDataframeFromCsv(context, providerId, modelId, prompt, columnsInput, count, maxTokens) {
|
|
268
|
+
const specs = parseColumnSpecs(columnsInput);
|
|
269
|
+
const columnHint = specs.length > 0
|
|
270
|
+
? `Use exactly these columns: ${specs.map((s) => s.name).join(", ")}.`
|
|
271
|
+
: "Choose appropriate columns for the data.";
|
|
272
|
+
const systemPrompt = `You are a data generator. Return ONLY a CSV with a header row and exactly ${count} data rows. No explanation, no markdown fences, no extra text. ${columnHint}`;
|
|
273
|
+
const result = await runMeteredMessage(context, providerId, modelId, {
|
|
274
|
+
model: modelId,
|
|
275
|
+
messages: [
|
|
276
|
+
{ role: "system", content: systemPrompt },
|
|
277
|
+
{ role: "user", content: prompt }
|
|
278
|
+
],
|
|
279
|
+
max_tokens: maxTokens
|
|
280
|
+
});
|
|
281
|
+
const content = asText(result.content ?? result);
|
|
282
|
+
return parseCsv(content, specs);
|
|
283
|
+
}
|
|
284
|
+
export class StructuredOutputGeneratorNode extends BaseNode {
|
|
285
|
+
static nodeType = "nodetool.generators.StructuredOutputGenerator";
|
|
286
|
+
static title = "Structured Output Generator";
|
|
287
|
+
static description = "Generate structured JSON objects from instructions using LLM providers.\n data-generation, structured-data, json, synthesis\n\n Specialized for creating structured information:\n - Generating JSON that follows dynamic schemas\n - Fabricating records from requirements and guidance\n - Simulating sample data for downstream workflows\n - Producing consistent structured outputs for testing";
|
|
288
|
+
static inlineFields = ["instructions", "context"];
|
|
289
|
+
static inputFields = ["system_prompt", "image", "audio"];
|
|
290
|
+
static supportsDynamicOutputs = true;
|
|
291
|
+
// Persist the generated JSON object as a generation so it reloads on reopen.
|
|
292
|
+
static autoSaveAsset = true;
|
|
293
|
+
async process(context) {
|
|
294
|
+
const { providerId, modelId } = getModelConfig(this.serialize());
|
|
295
|
+
const schema = buildSchemaFromDynamicOutputs(this._dynamic_outputs);
|
|
296
|
+
if (schema && hasProviderSupport(context, providerId, modelId)) {
|
|
297
|
+
const instructions = asText(this.instructions);
|
|
298
|
+
const extraContext = asText(this.context);
|
|
299
|
+
const systemPrompt = asText(this.system_prompt);
|
|
300
|
+
const userText = [instructions, extraContext]
|
|
301
|
+
.filter(Boolean)
|
|
302
|
+
.join("\n\n");
|
|
303
|
+
const messages = [];
|
|
304
|
+
if (systemPrompt) {
|
|
305
|
+
messages.push({ role: "system", content: systemPrompt });
|
|
306
|
+
}
|
|
307
|
+
messages.push(buildUserMessage(userText, this.image, this.audio));
|
|
308
|
+
const result = await runMeteredMessage(context, providerId, modelId, {
|
|
309
|
+
model: modelId,
|
|
310
|
+
messages,
|
|
311
|
+
max_tokens: this.max_tokens,
|
|
312
|
+
response_format: {
|
|
313
|
+
type: "json_schema",
|
|
314
|
+
json_schema: {
|
|
315
|
+
name: "structured_output",
|
|
316
|
+
schema
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
if (isObjectLike(result) && "content" in result) {
|
|
321
|
+
const content = asText(result.content ?? "");
|
|
322
|
+
try {
|
|
323
|
+
return JSON.parse(content);
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
return { output: content };
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (isRecord(schema)) {
|
|
331
|
+
const props = schema.properties ?? {};
|
|
332
|
+
const out = {};
|
|
333
|
+
for (const key of Object.keys(props)) {
|
|
334
|
+
const spec = props[key];
|
|
335
|
+
if (spec?.type === "number" || spec?.type === "integer")
|
|
336
|
+
out[key] = 0;
|
|
337
|
+
else if (spec?.type === "boolean")
|
|
338
|
+
out[key] = false;
|
|
339
|
+
else if (spec?.type === "array")
|
|
340
|
+
out[key] = [];
|
|
341
|
+
else if (spec?.type === "object")
|
|
342
|
+
out[key] = {};
|
|
343
|
+
else
|
|
344
|
+
out[key] = "";
|
|
345
|
+
}
|
|
346
|
+
return out;
|
|
347
|
+
}
|
|
348
|
+
const instructions = asText(this.instructions);
|
|
349
|
+
const contextText = asText(this.context);
|
|
350
|
+
return {
|
|
351
|
+
output: {
|
|
352
|
+
instructions,
|
|
353
|
+
context: contextText
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
__decorate([
|
|
359
|
+
prop({
|
|
360
|
+
type: "str",
|
|
361
|
+
default: "\nYou are a structured data generator focused on JSON outputs.\n\nGoal\n- Produce a high-quality JSON object that matches <JSON_SCHEMA> using the guidance in <INSTRUCTIONS> and any supplemental <CONTEXT>.\n\nOutput format (MANDATORY)\n- Output exactly ONE fenced code block labeled json containing ONLY the JSON object:\n\n ```json\n { ...single JSON object matching <JSON_SCHEMA>... }\n ```\n\n- No additional prose before or after the block.\n\nGeneration rules\n- Invent plausible, internally consistent values when not explicitly provided.\n- Honor all constraints from <JSON_SCHEMA> (types, enums, ranges, formats).\n- Prefer ISO 8601 for dates/times when applicable.\n- Ensure numbers respect reasonable magnitudes and relationships described in <INSTRUCTIONS>.\n- Avoid referencing external sources; rely solely on the provided guidance.\n\nValidation\n- Ensure the final JSON validates against <JSON_SCHEMA> exactly.\n",
|
|
362
|
+
title: "System Prompt",
|
|
363
|
+
description: "The system prompt guiding JSON generation."
|
|
364
|
+
})
|
|
365
|
+
], StructuredOutputGeneratorNode.prototype, "system_prompt", void 0);
|
|
366
|
+
__decorate([
|
|
367
|
+
prop({
|
|
368
|
+
type: "language_model",
|
|
369
|
+
default: {
|
|
370
|
+
type: "language_model",
|
|
371
|
+
provider: "empty",
|
|
372
|
+
id: "",
|
|
373
|
+
name: "",
|
|
374
|
+
path: null,
|
|
375
|
+
supported_tasks: []
|
|
376
|
+
},
|
|
377
|
+
title: "Model",
|
|
378
|
+
description: "Model to use for structured generation."
|
|
379
|
+
})
|
|
380
|
+
], StructuredOutputGeneratorNode.prototype, "model", void 0);
|
|
381
|
+
__decorate([
|
|
382
|
+
prop({
|
|
383
|
+
type: "str",
|
|
384
|
+
default: "",
|
|
385
|
+
title: "Instructions",
|
|
386
|
+
description: "Detailed instructions for the structured output."
|
|
387
|
+
})
|
|
388
|
+
], StructuredOutputGeneratorNode.prototype, "instructions", void 0);
|
|
389
|
+
__decorate([
|
|
390
|
+
prop({
|
|
391
|
+
type: "str",
|
|
392
|
+
default: "",
|
|
393
|
+
title: "Context",
|
|
394
|
+
description: "Optional context to ground the generation."
|
|
395
|
+
})
|
|
396
|
+
], StructuredOutputGeneratorNode.prototype, "context", void 0);
|
|
397
|
+
__decorate([
|
|
398
|
+
prop({
|
|
399
|
+
type: "int",
|
|
400
|
+
default: 4096,
|
|
401
|
+
title: "Max Tokens",
|
|
402
|
+
description: "The maximum number of tokens to generate.",
|
|
403
|
+
min: 1,
|
|
404
|
+
max: 16384
|
|
405
|
+
})
|
|
406
|
+
], StructuredOutputGeneratorNode.prototype, "max_tokens", void 0);
|
|
407
|
+
__decorate([
|
|
408
|
+
prop({
|
|
409
|
+
type: "list[image]",
|
|
410
|
+
default: [],
|
|
411
|
+
title: "Images",
|
|
412
|
+
description: "Optional images to include in the generation request. Accepts a list, or a single Image (auto-wrapped). Each becomes a separate block in the message sent to the provider."
|
|
413
|
+
})
|
|
414
|
+
], StructuredOutputGeneratorNode.prototype, "image", void 0);
|
|
415
|
+
__decorate([
|
|
416
|
+
prop({
|
|
417
|
+
type: "list[audio]",
|
|
418
|
+
default: [],
|
|
419
|
+
title: "Audio",
|
|
420
|
+
description: "Optional audio to include in the generation request. Accepts a list, or a single Audio (auto-wrapped). Each becomes a separate block in the message sent to the provider."
|
|
421
|
+
})
|
|
422
|
+
], StructuredOutputGeneratorNode.prototype, "audio", void 0);
|
|
423
|
+
export class DataGeneratorNode extends BaseNode {
|
|
424
|
+
static nodeType = "nodetool.generators.DataGenerator";
|
|
425
|
+
static title = "Data Generator";
|
|
426
|
+
static description = "LLM Agent to create a dataframe based on a user prompt.\n llm, dataframe creation, data structuring\n\n Use cases:\n - Generating structured data from natural language descriptions\n - Creating sample datasets for testing or demonstration\n - Converting unstructured text into tabular format";
|
|
427
|
+
static metadataOutputTypes = {
|
|
428
|
+
record: "dict",
|
|
429
|
+
dataframe: "dataframe",
|
|
430
|
+
index: "int"
|
|
431
|
+
};
|
|
432
|
+
static inlineFields = ["columns"];
|
|
433
|
+
static inputFields = ["prompt"];
|
|
434
|
+
// Persist the generated dataframe as a generation so it reloads on reopen.
|
|
435
|
+
static autoSaveAsset = true;
|
|
436
|
+
static primaryOutput = "dataframe";
|
|
437
|
+
static inputMode = "buffered";
|
|
438
|
+
static outputCorrelation = {
|
|
439
|
+
record: { kind: "iteration", source: "__execution__", group: "items" },
|
|
440
|
+
index: { kind: "iteration", source: "__execution__", group: "items" },
|
|
441
|
+
dataframe: { kind: "single", source: "__execution__" }
|
|
442
|
+
};
|
|
443
|
+
async process(context) {
|
|
444
|
+
const prompt = asText(this.prompt);
|
|
445
|
+
const inputText = asText(this.input_text);
|
|
446
|
+
const columnsInput = this.columns;
|
|
447
|
+
const columns = parseColumns(columnsInput);
|
|
448
|
+
const count = parseRequestedCount(`${prompt} ${inputText}`, 5);
|
|
449
|
+
const { providerId, modelId } = getModelConfig(this.serialize());
|
|
450
|
+
if (hasProviderSupport(context, providerId, modelId)) {
|
|
451
|
+
const rows = await generateDataframeFromCsv(context, providerId, modelId, [prompt, inputText].filter(Boolean).join("\n\n"), columnsInput, count, this.max_tokens);
|
|
452
|
+
if (rows.length > 0) {
|
|
453
|
+
return { output: dataframeFromRows(rows, columnsInput) };
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
const rows = makeRows(columns, count, inputText || prompt || "item");
|
|
457
|
+
return { output: dataframeFromRows(rows, columnsInput) };
|
|
458
|
+
}
|
|
459
|
+
async *genProcess(context) {
|
|
460
|
+
const full = await this.process(context);
|
|
461
|
+
const rows = (full.output.rows ?? []);
|
|
462
|
+
for (let i = 0; i < rows.length; i += 1) {
|
|
463
|
+
yield { record: rows[i], index: i, dataframe: null };
|
|
464
|
+
}
|
|
465
|
+
yield { record: null, index: null, dataframe: full.output };
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
__decorate([
|
|
469
|
+
prop({
|
|
470
|
+
type: "language_model",
|
|
471
|
+
default: {
|
|
472
|
+
type: "language_model",
|
|
473
|
+
provider: "empty",
|
|
474
|
+
id: "",
|
|
475
|
+
name: "",
|
|
476
|
+
path: null,
|
|
477
|
+
supported_tasks: []
|
|
478
|
+
},
|
|
479
|
+
title: "Model",
|
|
480
|
+
description: "The model to use for data generation."
|
|
481
|
+
})
|
|
482
|
+
], DataGeneratorNode.prototype, "model", void 0);
|
|
483
|
+
__decorate([
|
|
484
|
+
prop({
|
|
485
|
+
type: "str",
|
|
486
|
+
default: "",
|
|
487
|
+
title: "Prompt",
|
|
488
|
+
description: "The user prompt"
|
|
489
|
+
})
|
|
490
|
+
], DataGeneratorNode.prototype, "prompt", void 0);
|
|
491
|
+
__decorate([
|
|
492
|
+
prop({
|
|
493
|
+
type: "str",
|
|
494
|
+
default: "",
|
|
495
|
+
title: "Input Text",
|
|
496
|
+
description: "The input text to be analyzed by the agent."
|
|
497
|
+
})
|
|
498
|
+
], DataGeneratorNode.prototype, "input_text", void 0);
|
|
499
|
+
__decorate([
|
|
500
|
+
prop({
|
|
501
|
+
type: "int",
|
|
502
|
+
default: 4096,
|
|
503
|
+
title: "Max Tokens",
|
|
504
|
+
description: "The maximum number of tokens to generate.",
|
|
505
|
+
min: 1,
|
|
506
|
+
max: 100000
|
|
507
|
+
})
|
|
508
|
+
], DataGeneratorNode.prototype, "max_tokens", void 0);
|
|
509
|
+
__decorate([
|
|
510
|
+
prop({
|
|
511
|
+
type: "record_type",
|
|
512
|
+
default: {
|
|
513
|
+
type: "record_type",
|
|
514
|
+
columns: []
|
|
515
|
+
},
|
|
516
|
+
title: "Columns",
|
|
517
|
+
description: "The columns to use in the dataframe."
|
|
518
|
+
})
|
|
519
|
+
], DataGeneratorNode.prototype, "columns", void 0);
|
|
520
|
+
export class ListGeneratorNode extends BaseNode {
|
|
521
|
+
static nodeType = "nodetool.generators.ListGenerator";
|
|
522
|
+
static title = "List Generator";
|
|
523
|
+
static description = "LLM Agent to create a stream of strings based on a user prompt.\n llm, text streaming\n\n Use cases:\n - Generating text from natural language descriptions\n - Streaming responses from an LLM";
|
|
524
|
+
static metadataOutputTypes = {
|
|
525
|
+
item: "str",
|
|
526
|
+
index: "int",
|
|
527
|
+
output: "list[str]"
|
|
528
|
+
};
|
|
529
|
+
static inlineFields = [];
|
|
530
|
+
static inputFields = ["prompt"];
|
|
531
|
+
// Emit `item`/`index` output_updates even when the handles are wired onward,
|
|
532
|
+
// so the node's bespoke list body can show items as they stream regardless
|
|
533
|
+
// of downstream connections.
|
|
534
|
+
static alwaysEmitOutputUpdates = true;
|
|
535
|
+
// Persist the full generated list as a generation so the node reloads its
|
|
536
|
+
// items when the workflow is reopened. `output` carries the whole list (see
|
|
537
|
+
// the consolidating final yield in genProcess); `item`/`index` stay per-item
|
|
538
|
+
// for live streaming and downstream iteration.
|
|
539
|
+
static autoSaveAsset = true;
|
|
540
|
+
static primaryOutput = "output";
|
|
541
|
+
static inputMode = "buffered";
|
|
542
|
+
static outputCorrelation = {
|
|
543
|
+
item: { kind: "iteration", source: "__execution__", group: "items" },
|
|
544
|
+
index: { kind: "iteration", source: "__execution__", group: "items" },
|
|
545
|
+
output: { kind: "single", source: "__execution__" }
|
|
546
|
+
};
|
|
547
|
+
async process(context) {
|
|
548
|
+
const items = [];
|
|
549
|
+
for await (const chunk of this.genProcess(context)) {
|
|
550
|
+
const item = chunk.item;
|
|
551
|
+
if (isString(item))
|
|
552
|
+
items.push(item);
|
|
553
|
+
}
|
|
554
|
+
return { output: items };
|
|
555
|
+
}
|
|
556
|
+
async *genProcess(context) {
|
|
557
|
+
const prompt = asText(this.prompt);
|
|
558
|
+
const inputText = asText(this.input_text);
|
|
559
|
+
const userMessage = [prompt, inputText].filter(Boolean).join("\n\n");
|
|
560
|
+
const { providerId, modelId } = getModelConfig(this.serialize());
|
|
561
|
+
// Accumulate items so the stream-end frame can carry the whole list on the
|
|
562
|
+
// single `output` handle (drives the saved generation + reload).
|
|
563
|
+
const items = [];
|
|
564
|
+
if (hasProviderAccess(context) && providerId && modelId) {
|
|
565
|
+
let index = 0;
|
|
566
|
+
const maxTokens = this.max_tokens;
|
|
567
|
+
// Try the neutral prompt first. If the model answers in prose and emits no
|
|
568
|
+
// `add_item` calls (zero items), re-run once with an enforcement prompt
|
|
569
|
+
// that pushes it to use the tool before giving up.
|
|
570
|
+
const systemPrompts = [
|
|
571
|
+
LIST_GENERATOR_SYSTEM_PROMPT,
|
|
572
|
+
`${LIST_GENERATOR_SYSTEM_PROMPT}\n\n${LIST_GENERATOR_ENFORCEMENT_PROMPT}`
|
|
573
|
+
];
|
|
574
|
+
for (const systemPrompt of systemPrompts) {
|
|
575
|
+
for await (const item of streamListItemsViaToolCalls(context, providerId, modelId, userMessage, maxTokens, systemPrompt)) {
|
|
576
|
+
items.push(asText(item));
|
|
577
|
+
yield { item, index };
|
|
578
|
+
index += 1;
|
|
579
|
+
}
|
|
580
|
+
if (index > 0) {
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (index === 0) {
|
|
585
|
+
throw new Error("Model returned no add_item tool calls — list is empty.");
|
|
586
|
+
}
|
|
587
|
+
yield { output: items };
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const seed = inputText || prompt || "item";
|
|
591
|
+
const count = parseRequestedCount(userMessage, 5);
|
|
592
|
+
for (let i = 0; i < count; i += 1) {
|
|
593
|
+
const item = `${seed}_${i + 1}`;
|
|
594
|
+
items.push(item);
|
|
595
|
+
yield { item, index: i };
|
|
596
|
+
}
|
|
597
|
+
yield { output: items };
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
__decorate([
|
|
601
|
+
prop({
|
|
602
|
+
type: "language_model",
|
|
603
|
+
default: {
|
|
604
|
+
type: "language_model",
|
|
605
|
+
provider: "empty",
|
|
606
|
+
id: "",
|
|
607
|
+
name: "",
|
|
608
|
+
path: null,
|
|
609
|
+
supported_tasks: []
|
|
610
|
+
},
|
|
611
|
+
title: "Model",
|
|
612
|
+
description: "The model to use for string generation."
|
|
613
|
+
})
|
|
614
|
+
], ListGeneratorNode.prototype, "model", void 0);
|
|
615
|
+
__decorate([
|
|
616
|
+
prop({
|
|
617
|
+
type: "str",
|
|
618
|
+
default: "",
|
|
619
|
+
title: "Prompt",
|
|
620
|
+
description: "The user prompt"
|
|
621
|
+
})
|
|
622
|
+
], ListGeneratorNode.prototype, "prompt", void 0);
|
|
623
|
+
__decorate([
|
|
624
|
+
prop({
|
|
625
|
+
type: "str",
|
|
626
|
+
default: "",
|
|
627
|
+
title: "Input Text",
|
|
628
|
+
description: "The input text to be analyzed by the agent."
|
|
629
|
+
})
|
|
630
|
+
], ListGeneratorNode.prototype, "input_text", void 0);
|
|
631
|
+
__decorate([
|
|
632
|
+
prop({
|
|
633
|
+
type: "int",
|
|
634
|
+
default: 4096,
|
|
635
|
+
title: "Max Tokens",
|
|
636
|
+
description: "The maximum number of tokens to generate.",
|
|
637
|
+
min: 1,
|
|
638
|
+
max: 100000
|
|
639
|
+
})
|
|
640
|
+
], ListGeneratorNode.prototype, "max_tokens", void 0);
|
|
641
|
+
export class ChartGeneratorNode extends BaseNode {
|
|
642
|
+
static nodeType = "nodetool.generators.ChartGenerator";
|
|
643
|
+
static title = "Chart Generator";
|
|
644
|
+
static description = "LLM Agent to create Plotly Express charts based on natural language descriptions.\n llm, data visualization, charts\n\n Use cases:\n - Generating interactive charts from natural language descriptions\n - Creating data visualizations with minimal configuration\n - Converting data analysis requirements into visual representations";
|
|
645
|
+
static metadataOutputTypes = {
|
|
646
|
+
output: "chart_config"
|
|
647
|
+
};
|
|
648
|
+
static inlineFields = [];
|
|
649
|
+
static inputFields = ["prompt", "data"];
|
|
650
|
+
// Persist the generated chart config as a generation so it reloads on reopen.
|
|
651
|
+
static autoSaveAsset = true;
|
|
652
|
+
static primaryOutput = "output";
|
|
653
|
+
async process(context) {
|
|
654
|
+
const prompt = asText(this.prompt);
|
|
655
|
+
const rows = Array.isArray(this.data.rows) ? this.data.rows : [];
|
|
656
|
+
const keys = rows.length > 0 ? Object.keys(rows[0]) : [];
|
|
657
|
+
const xKey = keys[0] ?? "x";
|
|
658
|
+
const yKey = keys[1] ?? xKey;
|
|
659
|
+
const { providerId, modelId } = getModelConfig(this.serialize());
|
|
660
|
+
if (hasProviderSupport(context, providerId, modelId)) {
|
|
661
|
+
const dataSample = rows.length > 0
|
|
662
|
+
? `Data columns: ${keys.join(", ")}\nSample rows:\n${JSON.stringify(rows.slice(0, 5), null, 2)}`
|
|
663
|
+
: "No data provided.";
|
|
664
|
+
const systemPrompt = `You are a Plotly visualization expert. Given a user's description and optional data, generate a Plotly chart configuration as JSON.
|
|
665
|
+
The JSON must conform to the provided schema. Choose appropriate chart types (bar, line, scatter, pie, histogram, box, heatmap, etc.) based on the user's description.
|
|
666
|
+
Use the data columns provided to set x_column, y_column, and label fields in the series array.
|
|
667
|
+
Set a descriptive title, axis labels, and legend settings.`;
|
|
668
|
+
const userMessage = `${prompt}\n\n${dataSample}`;
|
|
669
|
+
const chartSchema = {
|
|
670
|
+
type: "object",
|
|
671
|
+
additionalProperties: false,
|
|
672
|
+
required: ["title", "x_label", "y_label", "legend", "data"],
|
|
673
|
+
properties: {
|
|
674
|
+
title: { type: "string" },
|
|
675
|
+
x_label: { type: "string" },
|
|
676
|
+
y_label: { type: "string" },
|
|
677
|
+
legend: { type: "boolean" },
|
|
678
|
+
legend_position: { type: "string" },
|
|
679
|
+
data: {
|
|
680
|
+
type: "object",
|
|
681
|
+
additionalProperties: false,
|
|
682
|
+
required: ["series"],
|
|
683
|
+
properties: {
|
|
684
|
+
series: {
|
|
685
|
+
type: "array",
|
|
686
|
+
items: {
|
|
687
|
+
type: "object",
|
|
688
|
+
additionalProperties: false,
|
|
689
|
+
required: ["type", "x_column", "y_column", "label"],
|
|
690
|
+
properties: {
|
|
691
|
+
type: { type: "string" },
|
|
692
|
+
x_column: { type: "string" },
|
|
693
|
+
y_column: { type: "string" },
|
|
694
|
+
label: { type: "string" }
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
const result = await runMeteredMessage(context, providerId, modelId, {
|
|
703
|
+
model: modelId,
|
|
704
|
+
messages: [
|
|
705
|
+
{ role: "system", content: systemPrompt },
|
|
706
|
+
{ role: "user", content: userMessage }
|
|
707
|
+
],
|
|
708
|
+
max_tokens: this.max_tokens,
|
|
709
|
+
response_format: {
|
|
710
|
+
type: "json_schema",
|
|
711
|
+
json_schema: {
|
|
712
|
+
name: "chart_config",
|
|
713
|
+
schema: chartSchema
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
const content = asText(result.content ?? result);
|
|
718
|
+
try {
|
|
719
|
+
const parsed = JSON.parse(content);
|
|
720
|
+
return {
|
|
721
|
+
output: {
|
|
722
|
+
type: "chart_config",
|
|
723
|
+
...parsed,
|
|
724
|
+
data: {
|
|
725
|
+
type: "chart_data",
|
|
726
|
+
...(parsed.data ?? {}),
|
|
727
|
+
row: null,
|
|
728
|
+
col: null,
|
|
729
|
+
col_wrap: null
|
|
730
|
+
},
|
|
731
|
+
height: null,
|
|
732
|
+
aspect: null,
|
|
733
|
+
x_lim: null,
|
|
734
|
+
y_lim: null,
|
|
735
|
+
x_scale: null,
|
|
736
|
+
y_scale: null,
|
|
737
|
+
palette: null,
|
|
738
|
+
hue_order: null,
|
|
739
|
+
hue_norm: null,
|
|
740
|
+
sizes: null,
|
|
741
|
+
size_order: null,
|
|
742
|
+
size_norm: null,
|
|
743
|
+
marginal_kws: null,
|
|
744
|
+
joint_kws: null,
|
|
745
|
+
diag_kind: null,
|
|
746
|
+
corner: false,
|
|
747
|
+
center: null,
|
|
748
|
+
vmin: null,
|
|
749
|
+
vmax: null,
|
|
750
|
+
cmap: null,
|
|
751
|
+
annot: false,
|
|
752
|
+
fmt: ".2g",
|
|
753
|
+
square: false
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
catch {
|
|
758
|
+
// Fall through to hardcoded fallback
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return {
|
|
762
|
+
output: {
|
|
763
|
+
type: "chart_config",
|
|
764
|
+
title: prompt || "Generated Chart",
|
|
765
|
+
x_label: xKey,
|
|
766
|
+
y_label: yKey,
|
|
767
|
+
legend: true,
|
|
768
|
+
data: {
|
|
769
|
+
type: "chart_data",
|
|
770
|
+
series: [
|
|
771
|
+
{
|
|
772
|
+
type: "bar",
|
|
773
|
+
x_column: xKey,
|
|
774
|
+
y_column: yKey,
|
|
775
|
+
label: prompt || "series"
|
|
776
|
+
}
|
|
777
|
+
],
|
|
778
|
+
row: null,
|
|
779
|
+
col: null,
|
|
780
|
+
col_wrap: null
|
|
781
|
+
},
|
|
782
|
+
height: null,
|
|
783
|
+
aspect: null,
|
|
784
|
+
x_lim: null,
|
|
785
|
+
y_lim: null,
|
|
786
|
+
x_scale: null,
|
|
787
|
+
y_scale: null,
|
|
788
|
+
legend_position: "auto",
|
|
789
|
+
palette: null,
|
|
790
|
+
hue_order: null,
|
|
791
|
+
hue_norm: null,
|
|
792
|
+
sizes: null,
|
|
793
|
+
size_order: null,
|
|
794
|
+
size_norm: null,
|
|
795
|
+
marginal_kws: null,
|
|
796
|
+
joint_kws: null,
|
|
797
|
+
diag_kind: null,
|
|
798
|
+
corner: false,
|
|
799
|
+
center: null,
|
|
800
|
+
vmin: null,
|
|
801
|
+
vmax: null,
|
|
802
|
+
cmap: null,
|
|
803
|
+
annot: false,
|
|
804
|
+
fmt: ".2g",
|
|
805
|
+
square: false
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
__decorate([
|
|
811
|
+
prop({
|
|
812
|
+
type: "language_model",
|
|
813
|
+
default: {
|
|
814
|
+
type: "language_model",
|
|
815
|
+
provider: "empty",
|
|
816
|
+
id: "",
|
|
817
|
+
name: "",
|
|
818
|
+
path: null,
|
|
819
|
+
supported_tasks: []
|
|
820
|
+
},
|
|
821
|
+
title: "Model",
|
|
822
|
+
description: "The model to use for chart generation."
|
|
823
|
+
})
|
|
824
|
+
], ChartGeneratorNode.prototype, "model", void 0);
|
|
825
|
+
__decorate([
|
|
826
|
+
prop({
|
|
827
|
+
type: "str",
|
|
828
|
+
default: "",
|
|
829
|
+
title: "Prompt",
|
|
830
|
+
description: "Natural language description of the desired chart"
|
|
831
|
+
})
|
|
832
|
+
], ChartGeneratorNode.prototype, "prompt", void 0);
|
|
833
|
+
__decorate([
|
|
834
|
+
prop({
|
|
835
|
+
type: "dataframe",
|
|
836
|
+
default: {
|
|
837
|
+
type: "dataframe",
|
|
838
|
+
uri: "",
|
|
839
|
+
asset_id: null,
|
|
840
|
+
data: null,
|
|
841
|
+
metadata: null,
|
|
842
|
+
columns: null
|
|
843
|
+
},
|
|
844
|
+
title: "Data",
|
|
845
|
+
description: "The data to visualize"
|
|
846
|
+
})
|
|
847
|
+
// `DataframeRef` in the protocol has no `rows`, but every dataframe this repo
|
|
848
|
+
// actually passes carries one — `dataframeFromRows` above emits it, and
|
|
849
|
+
// `data-nodes` builds the same `RowsDataframe` shape.
|
|
850
|
+
], ChartGeneratorNode.prototype, "data", void 0);
|
|
851
|
+
__decorate([
|
|
852
|
+
prop({
|
|
853
|
+
type: "int",
|
|
854
|
+
default: 4096,
|
|
855
|
+
title: "Max Tokens",
|
|
856
|
+
description: "The maximum number of tokens to generate.",
|
|
857
|
+
min: 1,
|
|
858
|
+
max: 100000
|
|
859
|
+
})
|
|
860
|
+
], ChartGeneratorNode.prototype, "max_tokens", void 0);
|
|
861
|
+
export class SVGGeneratorNode extends BaseNode {
|
|
862
|
+
static nodeType = "nodetool.generators.SVGGenerator";
|
|
863
|
+
static title = "SVGGenerator";
|
|
864
|
+
static description = "LLM Agent to create SVG elements based on user prompts.\n svg, generator, vector, graphics\n\n Use cases:\n - Creating vector graphics from text descriptions\n - Generating scalable illustrations\n - Creating custom icons and diagrams";
|
|
865
|
+
static metadataOutputTypes = {
|
|
866
|
+
output: "list[svg_element]"
|
|
867
|
+
};
|
|
868
|
+
static inlineFields = [];
|
|
869
|
+
static inputFields = ["prompt", "image", "audio"];
|
|
870
|
+
// Persist the generated SVG elements as a generation so they reload on reopen.
|
|
871
|
+
static autoSaveAsset = true;
|
|
872
|
+
static primaryOutput = "output";
|
|
873
|
+
async process(context) {
|
|
874
|
+
const prompt = asText(this.prompt);
|
|
875
|
+
const width = Number(this.width ?? 512) || 512;
|
|
876
|
+
const height = Number(this.height ?? 512) || 512;
|
|
877
|
+
const { providerId, modelId } = getModelConfig(this.serialize());
|
|
878
|
+
if (hasProviderSupport(context, providerId, modelId)) {
|
|
879
|
+
const systemPrompt = `You are an SVG graphics expert. Generate SVG markup based on the user's description.
|
|
880
|
+
Return one or more complete <svg>...</svg> elements. Each SVG should use the xmlns="http://www.w3.org/2000/svg" attribute.
|
|
881
|
+
Default canvas size is ${width}x${height} unless the user specifies otherwise.
|
|
882
|
+
Output only SVG markup, no explanations or markdown fences.`;
|
|
883
|
+
const messages = [
|
|
884
|
+
{ role: "system", content: systemPrompt },
|
|
885
|
+
buildUserMessage(prompt, this.image, this.audio)
|
|
886
|
+
];
|
|
887
|
+
const result = await runMeteredMessage(context, providerId, modelId, {
|
|
888
|
+
model: modelId,
|
|
889
|
+
messages,
|
|
890
|
+
max_tokens: this.max_tokens
|
|
891
|
+
});
|
|
892
|
+
const content = asText(result.content ?? result);
|
|
893
|
+
const svgMatches = Array.from(content.matchAll(/<svg[\s\S]*?<\/svg>/gi));
|
|
894
|
+
if (svgMatches.length > 0) {
|
|
895
|
+
return {
|
|
896
|
+
output: svgMatches.map((m) => ({ content: m[0] }))
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
// Fallback: generate a simple placeholder SVG
|
|
901
|
+
const text = prompt || "SVG";
|
|
902
|
+
const safeText = text
|
|
903
|
+
.replace(/&/g, "&")
|
|
904
|
+
.replace(/</g, "<")
|
|
905
|
+
.replace(/>/g, ">");
|
|
906
|
+
const svg = `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="#f2f2f2"/><text x="16" y="32" font-size="20" fill="#111">${safeText}</text></svg>`;
|
|
907
|
+
return { output: [{ content: svg }] };
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
__decorate([
|
|
911
|
+
prop({
|
|
912
|
+
type: "language_model",
|
|
913
|
+
default: {
|
|
914
|
+
type: "language_model",
|
|
915
|
+
provider: "empty",
|
|
916
|
+
id: "",
|
|
917
|
+
name: "",
|
|
918
|
+
path: null,
|
|
919
|
+
supported_tasks: []
|
|
920
|
+
},
|
|
921
|
+
title: "Model",
|
|
922
|
+
description: "The language model to use for SVG generation."
|
|
923
|
+
})
|
|
924
|
+
], SVGGeneratorNode.prototype, "model", void 0);
|
|
925
|
+
__decorate([
|
|
926
|
+
prop({
|
|
927
|
+
type: "str",
|
|
928
|
+
default: "",
|
|
929
|
+
title: "Prompt",
|
|
930
|
+
description: "The user prompt for SVG generation"
|
|
931
|
+
})
|
|
932
|
+
], SVGGeneratorNode.prototype, "prompt", void 0);
|
|
933
|
+
__decorate([
|
|
934
|
+
prop({
|
|
935
|
+
type: "list[image]",
|
|
936
|
+
default: [],
|
|
937
|
+
title: "Images",
|
|
938
|
+
description: "Images to use for generation. Accepts a list, or a single Image (auto-wrapped). Each becomes a separate block in the message sent to the provider."
|
|
939
|
+
})
|
|
940
|
+
], SVGGeneratorNode.prototype, "image", void 0);
|
|
941
|
+
__decorate([
|
|
942
|
+
prop({
|
|
943
|
+
type: "list[audio]",
|
|
944
|
+
default: [],
|
|
945
|
+
title: "Audio",
|
|
946
|
+
description: "Audio to use for generation. Accepts a list, or a single Audio (auto-wrapped). Each becomes a separate block in the message sent to the provider."
|
|
947
|
+
})
|
|
948
|
+
], SVGGeneratorNode.prototype, "audio", void 0);
|
|
949
|
+
__decorate([
|
|
950
|
+
prop({
|
|
951
|
+
type: "int",
|
|
952
|
+
default: 8192,
|
|
953
|
+
title: "Max Tokens",
|
|
954
|
+
description: "The maximum number of tokens to generate.",
|
|
955
|
+
min: 1,
|
|
956
|
+
max: 100000
|
|
957
|
+
})
|
|
958
|
+
], SVGGeneratorNode.prototype, "max_tokens", void 0);
|
|
959
|
+
export const GENERATOR_NODES = tagAsServer([
|
|
960
|
+
StructuredOutputGeneratorNode,
|
|
961
|
+
DataGeneratorNode,
|
|
962
|
+
ListGeneratorNode,
|
|
963
|
+
ChartGeneratorNode,
|
|
964
|
+
SVGGeneratorNode
|
|
965
|
+
]);
|
|
966
|
+
//# sourceMappingURL=generators.js.map
|