Узлы
Paragraph
Проза, отрисованная из ваших данных или разрешённая из промптов во время запроса.
The workhorse. A static paragraph returns a string from your typed data; a dynamic one carries prompts and a placeholder, and is filled at request time. Both land as the same node kind, differing only by `mode`.
- Хелперы
- paragraph
- Вид узла
- paragraph
- Категория
- Текст
- Разрешается
- Both
- Дочерние узлы
- None. Paragraphs are leaves.
| Опция | Тип | Что делает |
|---|---|---|
id обязательно | string | Stable address for the node. Generation endpoints target it and build artifacts diff on it, so treat a rename as a breaking change. |
render обязательно | (data, availableTokens) => string | Static only. Receives your typed data and the token budget, returns the text. May be async. |
placeholder | (data, availableTokens) => string | Dynamic only. What previews show in place of generated content. Optional, but a letter that reads badly without one cannot be reviewed. |
generalPrompt обязательно | (data, availableTokens) => string | Dynamic only. What this node should say. |
infoPrompt | (data, availableTokens) => string | Dynamic only. Context the model should have but should not restate. |
negativePrompt | (data, availableTokens) => string | Dynamic only. What to avoid — claims, tones, or facts it must not invent. |
systemPrompt | (data, availableTokens) => string | Dynamic only. Role and voice, applied ahead of the other prompts. |
derivers | DeriverInvocation[] | Values computed before the node resolves, written to derived.* and readable from a template token. Built with derive(). |
Как его написать
render получает ваши типизированные данные и бюджет токенов, а возвращает
строку. Никакого языка шаблонов между ними нет, поэтому форматирование,
ветвление и множественные формы — это обычный TypeScript:
import { paragraph } from "docxcelerate";
import type { MemberData } from "../types.ts";
export const Greeting = paragraph<MemberData>({
id: "greeting",
render: (data) => `Dear ${data.memberName},`,
});
Он может быть async — но узел, который ходит за данными, это узел, способный
упасть посреди сборки, так что лучше сначала положить значение в свои данные.
Бюджет токенов
Второй аргумент render, placeholder и каждого промпта — availableTokens:
бюджет, выделенный этому узлу, равный 2000, если вы не задали
availableTokens в опциях сборки. Статические узлы могут его игнорировать.
Динамическим стоит его расходовать:
generalPrompt: (data, availableTokens) =>
`Explain the change. At most ${Math.floor(availableTokens / 4)} words.`,
Ветвление — дело функции render
Документ, который говорит одно из трёх, — это один узел с тремя исходами, а не три узла под условиями. Идентификатор остаётся на месте, дерево сохраняет форму, кем бы ни был получатель, а логика — это код, который можно протестировать.
Динамические абзацы
Динамический абзац меняет render на промпты и заполнитель. Сборки
предпросмотра разрешают его в заполнитель и помечают; сборки во время запроса
отправляют промпты. Обязателен только generalPrompt — четвёртый вариант ниже
показывает, зачем нужны остальные три.
Варианты
Static
src/nodes/paragraph/static.node.ts Data in, a line of text out.
import { paragraph } from "docxcelerate";
import type { SampleData } from "../sample-data.ts";
/** The smallest useful node: an id, and a render that turns data into a line. */
export const Greeting = paragraph<SampleData>({
id: "greeting",
render: (data) => `Dear ${data.memberName},`,
}); Во что это разрешается
Узел в том виде, в каком он попадает в DocumentModel: JSON, который получает рендерер. Без стилей и без вёрстки.
{
"id": "greeting",
"kind": "paragraph",
"mode": "static",
"text": "Dear Adaeze Nkemelu,"
} Static, with branching
src/nodes/paragraph/conditional.node.ts One node, several outcomes — and the id stays put.
import { paragraph } from "docxcelerate";
import { money, type SampleData } from "../sample-data.ts";
/**
* Branching lives in the render, not the template: one node and one id,
* whichever branch a member falls down.
*/
export const PriceChange = paragraph<SampleData>({
id: "price-change",
render: (data) => {
const delta = data.newPrice - data.lastPrice;
if (delta === 0) {
return `Your ${data.plan} membership renews at ${money(data.newPrice)} ` +
`a year — the same price you paid last year.`;
}
const direction = delta > 0 ? "rising" : "falling";
const percent = Math.abs((delta / data.lastPrice) * 100).toFixed(1);
return `Your ${data.plan} membership is ${direction} by ${percent}%, from ` +
`${money(data.lastPrice)} to ${money(data.newPrice)} a year. That is ` +
`${money(data.newPrice / 12)} a month from ${data.renewsOn}.`;
},
}); Во что это разрешается
Узел в том виде, в каком он попадает в DocumentModel: JSON, который получает рендерер. Без стилей и без вёрстки.
{
"id": "price-change",
"kind": "paragraph",
"mode": "static",
"text": "Your Peak Anytime membership is rising by 5.1%, from £468 to £492 a year. That is £41 a month from 1 October 2026."
} Dynamic
src/nodes/paragraph/dynamic.node.ts A prompt and a placeholder. Previews show the placeholder, labelled.
import { paragraph } from "docxcelerate";
import type { SampleData } from "../sample-data.ts";
/**
* The minimum a dynamic paragraph needs: one prompt, and a placeholder so the
* preview still reads as a letter.
*/
export const NextSteps = paragraph<SampleData>({
id: "next-steps",
placeholder: (data) =>
`Your membership renews automatically on ${data.renewsOn}. ` +
`Nothing is needed from you unless you want to change plan.`,
generalPrompt: (data) =>
`In two sentences, tell ${data.memberName} that their membership renews ` +
`automatically on ${data.renewsOn} and how to change plan before then.`,
}); Во что это разрешается
Узел в том виде, в каком он попадает в DocumentModel: JSON, который получает рендерер. Без стилей и без вёрстки.
{
"id": "next-steps",
"kind": "paragraph",
"mode": "dynamic",
"text": "Your membership renews automatically on 1 October 2026. Nothing is needed from you unless you want to change plan."
} О чём просят эндпоинт
Разрешено на тех же тестовых данных. Сборка предпросмотра останавливается на заполнителе; сборка во время запроса отправляет их.
- general
- In two sentences, tell Adaeze Nkemelu that their membership renews automatically on 1 October 2026 and how to change plan before then.
Dynamic, all four prompts
src/nodes/paragraph/prompted.node.ts System, general, info and negative, each doing one job.
import { paragraph } from "docxcelerate";
import { money, type SampleData } from "../sample-data.ts";
/**
* All four slots: general says what to write, info supplies facts without
* asking for them back, negative fences off the failure modes, system fixes
* the voice. `availableTokens` is the budget the build allotted this node.
*/
export const Apology = paragraph<SampleData>({
id: "pool-closure",
placeholder: () =>
`The main pool is closed for resurfacing until 12 October. ` +
`The teaching pool and all land-based classes are running as normal.`,
systemPrompt: () =>
`You write for a public leisure centre. Plain British English, second ` +
`person, no marketing language.`,
generalPrompt: (data, availableTokens) =>
`Apologise to ${data.memberName} for the main pool closure and say what ` +
`is still open. At most ${Math.floor(availableTokens / 4)} words.`,
infoPrompt: (data) =>
`The main pool at ${data.centreName} is resurfacing until 12 October. ` +
`The teaching pool, gym and classes are unaffected. Members on ` +
`${data.plan} paying ${money(data.newPrice)} a year get two guest passes ` +
`as compensation.`,
negativePrompt: () =>
`Do not promise a refund, do not give a reopening date beyond 12 October, ` +
`and do not restate the price.`,
}); Во что это разрешается
Узел в том виде, в каком он попадает в DocumentModel: JSON, который получает рендерер. Без стилей и без вёрстки.
{
"id": "pool-closure",
"kind": "paragraph",
"mode": "dynamic",
"text": "The main pool is closed for resurfacing until 12 October. The teaching pool and all land-based classes are running as normal."
} О чём просят эндпоинт
Разрешено на тех же тестовых данных. Сборка предпросмотра останавливается на заполнителе; сборка во время запроса отправляет их.
- system
- You write for a public leisure centre. Plain British English, second person, no marketing language.
- general
- Apologise to Adaeze Nkemelu for the main pool closure and say what is still open. At most 500 words.
- info
- The main pool at Riverside Leisure Centre is resurfacing until 12 October. The teaching pool, gym and classes are unaffected. Members on Peak Anytime paying £492 a year get two guest passes as compensation.
- negative
- Do not promise a refund, do not give a reopening date beyond 12 October, and do not restate the price.
Примечания
- Оба режима разрешаются в
kind: "paragraph". Рендерер читаетmode, если это вообще его волнует; он никогда не ветвится по тому, какой хелпер вы вызвали. - Пустая строка даёт пустой абзац, а не отсутствие узла. Чтобы узла не было, не включайте его в дерево.
- Рендереры экранируют текст. Абзац не может протащить разметку на страницу и не является местом для оформления.