diff --git a/docs/kits/core.md b/docs/kits/core.md new file mode 100644 index 00000000000..ef1350ef15e --- /dev/null +++ b/docs/kits/core.md @@ -0,0 +1,199 @@ +# Core Kit + +The Core Kit provides essential nodes for Breadboard graphs, including input/output handling, flow control, and data transformation. + +## Installation + +```bash +npm install @google-labs/core-kit +``` + +## Usage + +Import the core kit and use its nodes within your boards: + +```typescript +import { board } from "@breadboard-ai/build"; +import { coreKit } from "@google-labs/core-kit"; +``` + +## Nodes + +### input / output + +Define board inputs and outputs with JSON schemas: + +```typescript +import { board, string } from "@breadboard-ai/build"; + +const myBoard = board({ + inputs: { + message: string({ description: "Input message" }), + }, + outputs: { + result: string({ description: "Output result" }), + }, +}, ({ message }) => { + return { result: `Processed: ${message}` }; +}); +``` + +### invoke + +Invoke another board or function: + +```typescript +import { board, string } from "@breadboard-ai/build"; +import { coreKit } from "@google-labs/core-kit"; + +const helper = board({ + inputs: { text: string() }, + outputs: { reversed: string() }, +}, ({ text }) => ({ + reversed: text.split("").reverse().join(""), +})); + +const main = board({ + inputs: { input: string() }, + outputs: { output: string() }, +}, ({ input }) => { + const result = coreKit.invoke({ + board: helper, + text: input, + }); + return { output: result.reversed }; +}); +``` + +### map + +Map over arrays: + +```typescript +import { board, array, string } from "@breadboard-ai/build"; +import { coreKit } from "@google-labs/core-kit"; + +const processItem = board({ + inputs: { item: string() }, + outputs: { upper: string() }, +}, ({ item }) => ({ upper: item.toUpperCase() })); + +const main = board({ + inputs: { items: array(string()) }, + outputs: { results: array(string()) }, +}, ({ items }) => { + const mapped = coreKit.map({ + list: items, + board: processItem, + item: "item", + }); + return { results: mapped.list }; +}); +``` + +### reduce + +Reduce arrays to single values: + +```typescript +import { board, array, number } from "@breadboard-ai/build"; +import { coreKit } from "@google-labs/core-kit"; + +const summer = board({ + inputs: { accumulator: number(), item: number() }, + outputs: { accumulator: number() }, +}, ({ accumulator, item }) => ({ + accumulator: accumulator + item, +})); + +const main = board({ + inputs: { numbers: array(number()) }, + outputs: { sum: number() }, +}, ({ numbers }) => { + const result = coreKit.reduce({ + list: numbers, + board: summer, + accumulator: 0, + }); + return { sum: result.accumulator }; +}); +``` + +### secrets + +Access environment secrets: + +```typescript +import { board, string } from "@breadboard-ai/build"; +import { coreKit } from "@google-labs/core-kit"; + +const secureBoard = board({ + inputs: {}, + outputs: { key: string() }, +}, () => { + const secrets = coreKit.secrets({ keys: ["API_KEY"] }); + return { key: secrets.API_KEY }; +}); +``` + +## Error Handling + +Handle errors within node implementations: + +```typescript +import { board, string } from "@breadboard-ai/build"; + +const safeParse = board({ + inputs: { json: string() }, + outputs: { data: string(), error: string() }, +}, ({ json }) => { + try { + const parsed = JSON.parse(json); + return { data: JSON.stringify(parsed), error: "" }; + } catch (e) { + return { data: "", error: (e as Error).message }; + } +}); +``` + +## Declarative Syntax + +Core kit nodes can also be used in declarative graph definitions: + +```yaml +title: Core Kit Example +nodes: + - id: input + type: input + configuration: + schema: + type: object + properties: + text: + type: string + + - id: process + type: invoke + configuration: + path: ./helper.bgl.json + + - id: output + type: output + configuration: + schema: + type: object + properties: + result: + type: string + +edges: + - from: input + to: process + out: text + in: input + + - from: process + to: output + out: result + in: result +``` diff --git a/docs/kits/gemini.md b/docs/kits/gemini.md new file mode 100644 index 00000000000..fd3994177ae --- /dev/null +++ b/docs/kits/gemini.md @@ -0,0 +1,359 @@ +--- +title: Gemini Kit +description: Integration with Google's Gemini AI models for text generation, chat, and embeddings. +layout: docs +--- + +# Gemini Kit + +The Gemini Kit provides nodes for integrating with Google's Gemini AI models, enabling text generation, multi-turn conversations, and text embeddings within your Breadboard graphs. + +## Prerequisites + +To use the Gemini Kit, you need a Google AI Studio API key: + +1. Visit [Google AI Studio](https://makersuite.google.com/app/apikey) and sign in with your Google account +2. Create a new API key +3. Store the key securely using the Secrets node (see [Security Best Practices](#security-best-practices)) + +## Available Nodes + +### `gemini.generate` + +Generates text completions using Gemini models. + +#### Inputs + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `text` | `string` | Yes | The prompt text to send to the model | +| `model` | `string` | No | Model name (default: `gemini-pro`) | +| `temperature` | `number` | No | Sampling temperature (0.0 - 1.0, default: 0.7) | +| `maxTokens` | `number` | No | Maximum tokens to generate | +| `topK` | `number` | No | Top-k sampling parameter | +| `topP` | `number` | No | Top-p sampling parameter | +| `stopSequences` | `string[]` | No | Sequences that stop generation | +| `safetySettings` | `object` | No | Content safety configuration | +| `apiKey` | `string` | Yes | API key (use Secrets node) | + +#### Outputs + +| Property | Type | Description | +|----------|------|-------------| +| `text` | `string` | Generated text response | +| `candidates` | `object[]` | Alternative responses | +| `usage` | `object` | Token usage statistics | + +### `gemini.chat` + +Handles multi-turn conversations with context preservation. + +#### Inputs + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `text` | `string` | Yes | The current message to send | +| `context` | `object[]` | No | Previous conversation history | +| `model` | `string` | No | Model name (default: `gemini-pro`) | +| `temperature` | `number` | No | Sampling temperature (0.0 - 1.0) | +| `maxTokens` | `number` | No | Maximum tokens to generate | +| `apiKey` | `string` | Yes | API key (use Secrets node) | + +#### Outputs + +| Property | Type | Description | +|----------|------|-------------| +| `text` | `string` | Model's response text | +| `context` | `object[]` | Updated conversation history | +| `usage` | `object` | Token usage statistics | + +### `gemini.embed` + +Generates embeddings for text input. + +#### Inputs + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `text` | `string` | Yes | Text to embed | +| `model` | `string` | No | Embedding model (default: `embedding-001`) | +| `apiKey` | `string` | Yes | API key (use Secrets node) | + +#### Outputs + +| Property | Type | Description | +|----------|------|-------------| +| `embedding` | `number[]` | Vector representation of input text | +| `usage` | `object` | Token usage statistics | + +## Configuration Options + +### Model Selection + +Available models: +- `gemini-pro`: General purpose text generation and chat +- `gemini-pro-vision`: Multimodal (text and image) understanding +- `embedding-001`: Text embeddings for similarity search + +### Generation Parameters + +- **temperature**: Controls randomness (0.0 = deterministic, 1.0 = creative) +- **maxTokens**: Limits response length +- **topK**: Limits vocabulary to top K tokens +- **topP**: Nucleus sampling threshold + +### Safety Settings + +Configure content filtering: + +```json +{ + "harassment": "BLOCK_MEDIUM_AND_ABOVE", + "hateSpeech": "BLOCK_MEDIUM_AND_ABOVE", + "sexuallyExplicit": "BLOCK_MEDIUM_AND_ABOVE", + "dangerousContent": "BLOCK_MEDIUM_AND_ABOVE" +} +``` + +## Examples + +### Basic Text Generation + +```json +{ + "nodes": [ + { + "id": "secrets", + "type": "secrets", + "configuration": { + "keys": ["GEMINI_API_KEY"] + } + }, + { + "id": "generate", + "type": "gemini.generate", + "configuration": { + "text": "Explain quantum computing in simple terms", + "temperature": 0.7 + } + } + ], + "edges": [ + { + "from": "secrets", + "to": "generate", + "out": "GEMINI_API_KEY", + "in": "apiKey" + } + ] +} +``` + +### Multi-Turn Conversation + +```json +{ + "nodes": [ + { + "id": "secrets", + "type": "secrets", + "configuration": { + "keys": ["GEMINI_API_KEY"] + } + }, + { + "id": "chat", + "type": "gemini.chat", + "configuration": { + "text": "What are the main ingredients?", + "context": [ + {"role": "user", "content": "I want to bake chocolate chip cookies."}, + {"role": "model", "content": "Great choice! You'll need flour, sugar, butter, eggs, and chocolate chips."} + ] + } + } + ], + "edges": [ + { + "from": "secrets", + "to": "chat", + "out": "GEMINI_API_KEY", + "in": "apiKey" + } + ] +} +``` + +### Streaming Responses + +To handle streaming responses, wire the output to a node that processes chunks: + +```json +{ + "nodes": [ + { + "id": "generate", + "type": "gemini.generate", + "configuration": { + "text": "Write a short story", + "stream": true + } + }, + { + "id": "output", + "type": "output" + } + ], + "edges": [ + { + "from": "generate", + "to": "output", + "out": "stream", + "in": "text" + } + ] +} +``` + +### Error Handling + +Handle API errors gracefully: + +```json +{ + "nodes": [ + { + "id": "generate", + "type": "gemini.generate" + }, + { + "id": "errorHandler", + "type": "runJavascript", + "configuration": { + "code": "if (input.error) { return { fallback: 'Service temporarily unavailable' }; } return input;" + } + } + ], + "edges": [ + { + "from": "generate", + "to": "errorHandler", + "out": "*" + } + ] +} +``` + +## Integration Examples + +### With Template Kit + +Use templates to construct dynamic prompts: + +```json +{ + "nodes": [ + { + "id": "template", + "type": "template", + "configuration": { + "template": "Translate the following to {{language}}: {{text}}" + } + }, + { + "id": "generate", + "type": "gemini.generate" + } + ], + "edges": [ + { + "from": "template", + "to": "generate", + "out": "string", + "in": "text" + } + ] +} +``` + +### With Core Kit + +Wire outputs to conditional logic: + +```json +{ + "nodes": [ + { + "id": "generate", + "type": "gemini.generate" + }, + { + "id": "checkLength", + "type": "jsonata", + "configuration": { + "expression": "$length(text) > 100 ? 'long' : 'short'" + } + } + ], + "edges": [ + { + "from": "generate", + "to": "checkLength", + "out": "text", + "in": "json" + } + ] +} +``` + +## Security Best Practices + +### Using Secrets Node + +Never hardcode API keys in board definitions. Use the Secrets node: + +```json +{ + "nodes": [ + { + "id": "secrets", + "type": "secrets", + "configuration": { + "keys": ["GEMINI_API_KEY"] + } + }, + { + "id": "generate", + "type": "gemini.generate" + } + ], + "edges": [ + { + "from": "secrets", + "to": "generate", + "out": "GEMINI_API_KEY", + "in": "apiKey" + } + ] +} +``` + +Store the actual key in your environment: + +```bash +export GEMINI_API_KEY="your-api-key-here" +``` + +### Key Rotation + +- Rotate API keys regularly +- Use separate keys for development and production +- Monitor usage in Google AI Studio dashboard + +### Rate Limiting + +Be aware of Gemini API rate limits: +- Free tier: 60 requests per minute +- Pay-as-you-go: Higher limits available + +Implement retry logic with exponential backoff for production applications. diff --git a/docs/kits/json.md b/docs/kits/json.md new file mode 100644 index 00000000000..d22206c22e9 --- /dev/null +++ b/docs/kits/json.md @@ -0,0 +1,235 @@ +# JSON Kit + +The JSON Kit provides nodes for parsing, stringifying, and validating JSON data within Breadboard boards. + +## Installation + +```bash +npm install @google-labs/json-kit +``` + +## Nodes + +### json.parse + +Parses a JSON string into a JavaScript value. + +**Inputs:** +- `json` (string): The JSON string to parse + +**Outputs:** +- `json` (unknown): The parsed value +- `error` (string): Error message if parsing fails + +**Example:** + +```typescript +import { board, input } from "@google-labs/breadboard"; +import { json } from "@google-labs/json-kit"; + +const rawJson = input({ description: "JSON string to parse" }); + +const parsed = json.parse({ + $id: "parse", + json: rawJson, +}); + +export default board({ + inputs: { rawJson }, + outputs: { + result: parsed.json, + error: parsed.error + }, +}); +``` + +### json.stringify + +Converts a JavaScript value to a JSON string. + +**Inputs:** +- `json` (unknown): The value to convert +- `indent` (number, optional): Number of spaces for indentation (default: 2) + +**Outputs:** +- `json` (string): The JSON string + +**Example:** + +```typescript +import { board, input } from "@google-labs/breadboard"; +import { json } from "@google-labs/json-kit"; + +const data = input({ description: "Data to stringify" }); + +const stringified = json.stringify({ + $id: "stringify", + json: data, + indent: 2, +}); + +export default board({ + inputs: { data }, + outputs: { jsonString: stringified.json }, +}); +``` + +### json.validate + +Validates JSON data against a JSON Schema. + +**Inputs:** +- `json` (unknown): The value to validate +- `schema` (object): JSON Schema object + +**Outputs:** +- `json` (unknown): The validated value (if valid) +- `valid` (boolean): Whether validation passed +- `error` (string): Validation error message (if invalid) + +**Example:** + +```typescript +import { board, input } from "@google-labs/breadboard"; +import { json } from "@google-labs/json-kit"; + +const data = input({ description: "Data to validate" }); +const schema = input({ description: "JSON Schema" }); + +const validated = json.validate({ + $id: "validate", + json: data, + schema: schema, +}); + +export default board({ + inputs: { data, schema }, + outputs: { + isValid: validated.valid, + result: validated.json, + error: validated.error, + }, +}); +``` + +## Error Handling + +Handle malformed JSON using the error output: + +```typescript +import { board, input } from "@google-labs/breadboard"; +import { json } from "@google-labs/json-kit"; + +const rawJson = input({ description: "Raw JSON string" }); + +const parsed = json.parse({ + $id: "safeParse", + json: rawJson, +}); + +export default board({ + inputs: { rawJson }, + outputs: { + data: parsed.json, + parseError: parsed.error, + }, +}); +``` + +## Integration Patterns + +### Chaining with Core Kit + +```typescript +import { board, input } from "@google-labs/breadboard"; +import { json } from "@google-labs/json-kit"; +import { core } from "@google-labs/core-kit"; + +const jsonString = input({ description: "Input JSON" }); + +const parsed = json.parse({ + $id: "parse", + json: jsonString, +}); + +const processed = core.invoke({ + $id: "process", + path: "./process-board.json", + data: parsed.json, +}); + +const result = json.stringify({ + $id: "stringify", + json: processed.result, +}); + +export default board({ + inputs: { jsonString }, + outputs: { result: result.json }, +}); +``` + +### Validation Pipeline + +```typescript +import { board, input } from "@google-labs/breadboard"; +import { json } from "@google-labs/json-kit"; + +const rawData = input({ description: "Raw JSON string" }); +const schema = input({ description: "Validation schema" }); + +const parsed = json.parse({ + $id: "parse", + json: rawData, +}); + +const validated = json.validate({ + $id: "validate", + json: parsed.json, + schema: schema, +}); + +export default board({ + inputs: { rawData, schema }, + outputs: { + data: validated.json, + isValid: validated.valid, + validationError: validated.error, + }, +}); +``` + +## TypeScript Types + +```typescript +import type { InputValues, OutputValues } from "@google-labs/breadboard"; + +export interface ParseInputs extends InputValues { + json: string; +} + +export interface ParseOutputs extends OutputValues { + json?: unknown; + error?: string; +} + +export interface StringifyInputs extends InputValues { + json: unknown; + indent?: number; +} + +export interface StringifyOutputs extends OutputValues { + json: string; +} + +export interface ValidateInputs extends InputValues { + json: unknown; + schema: object; +} + +export interface ValidateOutputs extends OutputValues { + json?: unknown; + valid?: boolean; + error?: string; +} +``` diff --git a/docs/kits/palm.md b/docs/kits/palm.md new file mode 100644 index 00000000000..f06c31bcb9b --- /dev/null +++ b/docs/kits/palm.md @@ -0,0 +1,234 @@ +--- +title: PaLM Kit +description: Integration with Google's PaLM API for text generation and embeddings. +--- + +# PaLM Kit + +The PaLM Kit provides nodes for integrating with Google's PaLM (Pathways Language Model) API, enabling text generation and embedding capabilities in your Breadboard graphs. + +## Overview + +The PaLM Kit wraps the Google PaLM API, providing easy-to-use nodes for: + +- **Text Generation**: Generate text completions using PaLM models +- **Text Embeddings**: Generate embeddings for text inputs + +## Authentication + +The PaLM Kit requires a Google API key with access to the PaLM API. Use the Secrets Kit to securely provide your API key. + +```typescript +import { board } from "@google-labs/breadboard"; +import { secrets } from "@google-labs/core-kit"; + +const palmBoard = board(() => { + const apiKey = secrets({ keys: ["PALM_API_KEY"] }); + // Use apiKey in PaLM nodes + return { apiKey }; +}); +``` + +Store your API key in an environment variable or `.env` file: + +```bash +PALM_API_KEY=your_api_key_here +``` + +## Nodes + +### `generateText` + +Generates text completions using the PaLM text generation model. + +**Inputs:** + +| Property | Type | Description | Default | +|----------|------|-------------|---------| +| `text` | string | The prompt text to complete | (required) | +| `temperature` | number | Controls randomness (0-1) | 0.7 | +| `candidateCount` | number | Number of completions to generate | 1 | +| `topP` | number | Nucleus sampling parameter | 0.95 | +| `topK` | number | Top-k sampling parameter | 40 | +| `maxOutputTokens` | number | Maximum tokens to generate | 256 | + +**Outputs:** + +| Property | Type | Description | +|----------|------|-------------| +| `candidates` | array | Array of generated text completions | +| `candidates[].output` | string | The generated text | + +**Example:** + +```typescript +import { board } from "@google-labs/breadboard"; +import { palm } from "@google-labs/palm-kit"; +import { secrets } from "@google-labs/core-kit"; + +const storyGenerator = board(({ prompt }) => { + const apiKey = secrets({ keys: ["PALM_API_KEY"] }); + + const completion = palm.generateText({ + text: prompt, + temperature: 0.8, + maxOutputTokens: 512, + }).in({ apiKey }); + + return { story: completion.candidates[0].output }; +}); +``` + +### `embedText` + +Generates embeddings for text inputs using the PaLM embedding model. + +**Inputs:** + +| Property | Type | Description | +|----------|------|-------------| +| `text` | string | The text to embed | (required) | + +**Outputs:** + +| Property | Type | Description | +|----------|------|-------------| +| `embedding` | array | Array of floating point numbers representing the text embedding | + +**Example:** + +```typescript +import { board } from "@google-labs/breadboard"; +import { palm } from "@google-labs/palm-kit"; +import { secrets } from "@google-labs/core-kit"; + +const textEmbedder = board(({ text }) => { + const apiKey = secrets({ keys: ["PALM_API_KEY"] }); + + const embedding = palm.embedText({ + text, + }).in({ apiKey }); + + return { embedding: embedding.embedding }; +}); +``` + +## Integration with Template Kit + +Combine the PaLM Kit with the Template Kit for dynamic prompt engineering. + +```typescript +import { board } from "@google-labs/breadboard"; +import { palm } from "@google-labs/palm-kit"; +import { core } from "@google-labs/core-kit"; +import { templates } from "@google-labs/template-kit"; +import { secrets } from "@google-labs/core-kit"; + +const promptEngineering = board(({ topic, style }) => { + const apiKey = secrets({ keys: ["PALM_API_KEY"] }); + + // Create a dynamic prompt using templates + const prompt = templates.prompt({ + template: `Write a {{style}} story about {{topic}}.`, + topic, + style, + }); + + // Generate text using the templated prompt + const story = palm.generateText({ + text: prompt.prompt, + temperature: 0.9, + maxOutputTokens: 1024, + }).in({ apiKey }); + + return { + story: story.candidates[0].output, + prompt: prompt.prompt + }; +}); +``` + +## Complete Example + +Here's a complete example showing a board that generates creative content with error handling: + +```typescript +import { board } from "@google-labs/breadboard"; +import { palm } from "@google-labs/palm-kit"; +import { templates } from "@google-labs/template-kit"; +import { secrets } from "@google-labs/core-kit"; + +const creativeWriter = board(({ subject, genre }) => { + const apiKey = secrets({ keys: ["PALM_API_KEY"] }); + + // Build a structured prompt + const prompt = templates.prompt({ + template: `Create a {{genre}} story about {{subject}}. +Make it engaging and approximately 200 words long.`, + subject, + genre, + }); + + // Generate the story + const result = palm.generateText({ + text: prompt.prompt, + temperature: 0.8, + candidateCount: 1, + maxOutputTokens: 800, + }).in({ apiKey }); + + return { + story: result.candidates[0].output, + metadata: { + prompt: prompt.prompt, + tokensUsed: result.candidates[0].tokenCount, + } + }; +}); + +// Run the board +const result = await creativeWriter({ + subject: "a robot learning to paint", + genre: "heartwarming" +}); + +console.log(result.story); +``` + +## Error Handling + +The PaLM Kit nodes will throw errors for invalid API keys, rate limits, or invalid parameters. Wrap calls in try-catch blocks or use the Core Kit's `run` node for error handling: + +```typescript +import { board } from "@google-labs/breadboard"; +import { palm } from "@google-labs/palm-kit"; +import { core } from "@google-labs/core-kit"; + +const safeGenerate = board(({ text, apiKey }) => { + const result = core.run({ + board: palm.generateText({ text }), + inputs: { apiKey }, + }); + + return { + output: result.candidates?.[0]?.output || "Error generating text", + success: !!result.candidates + }; +}); +``` + +## API Reference + +For the most up-to-date information on the PaLM API, see the [Google AI documentation](https://developers.generativeai.google/). + +## Installation + +```bash +npm install @google-labs/palm-kit +``` + +Import in your project: + +```typescript +import { palm } from "@google-labs/palm-kit"; +``` diff --git a/docs/kits/template.md b/docs/kits/template.md new file mode 100644 index 00000000000..79e81b1b7da --- /dev/null +++ b/docs/kits/template.md @@ -0,0 +1,191 @@ +# Template Kit + +The Template Kit provides text templating capabilities with variable substitution for Breadboard boards. + +## Overview + +The `template` node enables dynamic text generation by substituting variables within template strings using the `{{variableName}}` syntax. + +## Basic Usage + +```typescript +import { board } from "@breadboard-ai/build"; +import { template } from "@google-labs/template-kit"; + +const myBoard = board({ + inputs: { + userName: { type: "string", description: "Name of the user" }, + topic: { type: "string", description: "Topic to discuss" } + }, + outputs: { + message: { type: "string" } + } +}, async ({ userName, topic }) => { + const greeting = template({ + template: "Hello {{userName}}, let's talk about {{topic}}." + }); + + return { + message: greeting({ userName, topic }) + }; +}); +``` + +## Template Syntax + +Templates use double curly braces for variable interpolation: + +- `{{variableName}}` - Replaced with the value of the input port named `variableName` +- Variables are replaced as strings +- Undefined variables result in empty strings or errors depending on configuration + +## Input/Output Wiring + +Template variables are wired as inputs to the template node: + +```typescript +const promptTemplate = template({ + template: `Context: {{context}} +Question: {{question}} +Answer:` +}); + +// Wire inputs +promptTemplate.wire("context", contextInput); +promptTemplate.wire("question", questionInput); + +// Or inline +const result = promptTemplate({ + context: "Some context", + question: "The question?" +}); +``` + +## Schema Definitions + +Define input schemas for type safety and documentation: + +```typescript +import { input } from "@breadboard-ai/build"; + +const contextInput = input({ + type: "string", + description: "Background context for the prompt", + title: "Context" +}); + +const questionInput = input({ + type: "string", + description: "The question to answer", + title: "Question" +}); + +const board = { + inputs: { + context: contextInput, + question: questionInput + }, + // ... rest of board definition +}; +``` + +## Integration with LLM Nodes + +Templates are commonly used to construct prompts for LLM nodes: + +```typescript +import { gemini } from "@google-labs/gemini-kit"; + +const board = { + inputs: { + userQuery: { type: "string" }, + systemContext: { type: "string" } + }, + outputs: { + response: { type: "string" } + } +}, async (inputs) => { + // Create prompt template + const prompt = template({ + template: `System: {{systemContext}} + +User: {{userQuery}} + +Assistant:` + }); + + // Wire template inputs + const formattedPrompt = prompt({ + systemContext: inputs.systemContext, + userQuery: inputs.userQuery + }); + + // Pass to Gemini + const response = gemini({ + text: formattedPrompt, + model: "gemini-1.5-flash" + }); + + return { response }; +}); +``` + +## Nested Templates + +Compose complex templates by nesting: + +```typescript +const header = template({ + template: "# {{title}}\n\n" +}); + +const body = template({ + template: "{{header}}Content: {{content}}\nFooter: {{footer}}" +}); + +// Chain templates +const headerResult = header({ title: "My Doc" }); +const final = body({ + header: headerResult, + content: "Main content here", + footer: "End of document" +}); +``` + +## Conditional Logic + +Use conditional expressions within templates: + +```typescript +const conditionalTemplate = template({ + template: `Hello {{name}}{{^name}}there{{/name}}!` +}); +``` + +Or handle conditionals in the board logic: + +```typescript +const board = async (inputs) => { + const template = inputs.includeDetails + ? "Details: {{details}}" + : "Summary: {{summary}}"; + + return template({ ... }); +}; +``` + +## Best Practices + +1. **Use descriptive variable names**: Prefer `{{userName}}` over `{{n}}` +2. **Define schemas**: Always specify input types and descriptions +3. **Sanitize inputs**: Validate user input before templating +4. **Handle optional values**: Use conditional logic for optional template variables +5. **Separate concerns**: Use multiple template nodes rather than one complex template + +## Error Handling + +The template node throws errors when: +- Required template variables are not provided +- The template string is null or undefined + +Always ensure all referenced variables in `{{}}` are provided as inputs. \ No newline at end of file