Skip to content

feat(nodes): add prompt template node with variable substitution#573

Open
charliegillet wants to merge 8 commits intorocketride-org:developfrom
charliegillet:feature/prompt-template-node
Open

feat(nodes): add prompt template node with variable substitution#573
charliegillet wants to merge 8 commits intorocketride-org:developfrom
charliegillet:feature/prompt-template-node

Conversation

@charliegillet
Copy link
Copy Markdown
Contributor

@charliegillet charliegillet commented Mar 31, 2026

Summary

  • Add a new prompt_template node that renders prompt templates with dynamic variable substitution from pipeline context
  • Supports {{variable}} placeholders, built-in helpers ({{now}}, {{uuid}}, {{random}}), conditional sections ({% if %}/{% elif %}/{% else %}/{% endif %}), and loop support ({% for item in list %})
  • Lightweight template engine implemented with stdlib only — zero external dependencies

Implementation

File Purpose
services.json Node config — lanes (text, questions), fields (template, variables), profiles
IGlobal.py Global init — loads config via Config.getNodeConfig
IInstance.py Instance logic — collects text/questions, renders template on closing
template_engine.py Standalone template engine with tokenizer, block eval (if/for), variable resolution
requirements.txt Empty — stdlib only

Test plan

  • Verify {{variable}} substitution renders correctly from configured variables
  • Verify built-in helpers ({{now}}, {{uuid}}, {{random}}) produce valid output
  • Verify {% if %} / {% else %} / {% endif %} conditional rendering
  • Verify {% for item in list %} loop rendering
  • Verify escaped braces \{\{ render as literal {{
  • Verify node registers as filter with text and questions lanes
  • Run ruff format and ruff check — all passing

#Hack-with-bay-2

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a Prompt Template node to render dynamic content with variable interpolation, conditionals (if/elif/else) and loops.
    • Built-in helpers for timestamps, IDs and random values.
    • Handles both plain text and question streams, using configurable templates and variables; default template is {{input}}.
  • Tests

    • Comprehensive test suite validating rendering behavior, edge cases and safety/resilience.

@github-actions github-actions Bot added module:nodes Python pipeline nodes module:vscode VS Code extension labels Mar 31, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 31, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new Prompt Template node: lifecycle handlers (IGlobal/IInstance), a pure-Python Jinja2-like template engine, service registration and tests to render templates for text and question lanes using configurable variables.

Changes

Cohort / File(s) Summary
Template Engine
nodes/src/nodes/prompt_template/template_engine.py
New pure-Python render(template, context) implementing {{ ... }} variable interpolation, {% if/elif/else/endif %} conditionals, {% for ... %}{% endfor %} loops, escape handling, and built-in helpers (now, uuid, random).
Node Lifecycle & Instance
nodes/src/nodes/prompt_template/IGlobal.py, nodes/src/nodes/prompt_template/IInstance.py
New IGlobal loads node config on beginGlobal() (no-op for OPEN_MODE.CONFIG) and clears it on endGlobal(). New IInstance accumulates raw text/questions, builds template context from IGlobal.config, renders templates on closing(), and emits text/questions outputs.
Service Registration & Package Exports
nodes/src/nodes/prompt_template/services.json, nodes/src/nodes/prompt_template/__init__.py, nodes/src/nodes/prompt_template/requirements.txt
Adds service definition prompt-template:// (experimental filter), default template/variables preconfig, lane shapes and field schemas; exports IGlobal and IInstance; notes no external deps in requirements file.
Tests
nodes/test/test_prompt_template_engine.py
Comprehensive pytest suite validating template features: variable resolution (dotted paths), built-ins, conditionals, loops, escaping, error/malformed-tag handling, and recursion/depth resilience.

Sequence Diagram

sequenceDiagram
    participant Client as External System
    participant IGlobal as IGlobal (Lifecycle)
    participant IInstance as IInstance (Processor)
    participant TemplateEngine as Template Engine

    Note over IGlobal: beginGlobal()
    IGlobal->>IGlobal: load dependencies & set config

    Client->>IInstance: writeText()/writeQuestions()
    IInstance->>IInstance: accumulate inputs

    Note over IInstance: closing()
    IInstance->>IGlobal: read config
    IInstance->>IInstance: build context
    IInstance->>TemplateEngine: render(template, context)
    TemplateEngine->>TemplateEngine: tokenize & evaluate
    TemplateEngine-->>IInstance: rendered output

    alt Accumulated Questions
        IInstance->>IInstance: for each question set context['question']
        IInstance->>TemplateEngine: render per question
        TemplateEngine-->>IInstance: rendered questions
        IInstance->>Client: writeQuestions()
    else Only Text
        IInstance->>Client: writeText()
    end

    Note over IGlobal: endGlobal()
    IGlobal->>IGlobal: clear config
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • jmaionchi
  • stepmikhaylov
  • Rod-Christensen

Poem

🐰 I hopped through templates, stitched each line,
Variables nested, loops spun fine,
Questions and text in tidy stride,
Rendered answers tucked inside,
*nibble* — the prompt node hops with pride!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.28% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(nodes): add prompt template node with variable substitution' accurately reflects the primary change—introduction of a new prompt template node with variable substitution capabilities.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nodes/src/nodes/prompt_template/IInstance.py`:
- Around line 69-84: The closing method currently catches all exceptions and
only calls debug(), which hides failures; update IInstance.closing to log the
exception (keeping debug or use processLogger if available) and then re-raise
the exception so callers see the failure; specifically, after the try block
around config = self.IGlobal.config / template/render(context) /
self.instance.writeQuestions / self.instance.writeText, ensure you re-raise the
caught exception (or raise a new descriptive exception) instead of just
returning, so errors from render(self._get_template_context(), template) or
missing config propagate to the caller.

In `@nodes/src/nodes/prompt_template/services.json`:
- Around line 120-134: The test case under the "test" service only asserts
non-empty output for "questions"; add a second case that validates actual
template substitution by providing a specific template input (e.g., a question
template with a placeholder) and corresponding variables and asserting the
expected rendered string; update the "cases" array to include a case where
"questions": {"questions": [{"text": "Hello, {{name}}"}], "vars":
{"name":"Alice"}} (or equivalent keys used by the prompt template processor) and
an "expect" that checks the output equals "Hello, Alice" so the prompt_template
logic (the "test" service, its "cases" and "questions" entries) verifies real
substitution rather than only notEmpty.
- Around line 24-26: Remove the unnecessary "gpu" capability from the
prompt_template node configuration: open the services.json entry for the
prompt_template node and delete "gpu" from the "capabilities" array so it
matches other non-ML nodes (e.g., dictionary) and only ML/transformer nodes
(ner, embedding_transformer, ocr, anonymize) declare GPU capability.

In `@nodes/src/nodes/prompt_template/template_engine.py`:
- Around line 67-82: The resolution currently prefers built-ins by checking
_BUILTINS before the provided context in _resolve, preventing users from
overriding names like "now", "uuid", or "random"; change the lookup order in
_resolve so it first tries to resolve the dotted name against the provided
context (using dict.get or getattr with _MISSING), and only if that yields
_MISSING should it fall back to calling the corresponding factory in _BUILTINS;
ensure you still return '' when resolution results in _MISSING and keep the
dotted traversal logic intact for nested context lookups.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 59a4dd88-832d-484d-878a-c2d9ade495de

📥 Commits

Reviewing files that changed from the base of the PR and between b6b2dc1 and 192e1b6.

📒 Files selected for processing (8)
  • apps/vscode/src/providers/views/PageStatus/PageStatus.tsx
  • apps/vscode/src/providers/views/PageStatus/styles.css
  • nodes/src/nodes/prompt_template/IGlobal.py
  • nodes/src/nodes/prompt_template/IInstance.py
  • nodes/src/nodes/prompt_template/__init__.py
  • nodes/src/nodes/prompt_template/requirements.txt
  • nodes/src/nodes/prompt_template/services.json
  • nodes/src/nodes/prompt_template/template_engine.py

Comment thread nodes/src/nodes/prompt_template/IInstance.py
Comment thread nodes/src/nodes/prompt_template/services.json Outdated
Comment thread nodes/src/nodes/prompt_template/services.json
Comment thread nodes/src/nodes/prompt_template/template_engine.py Outdated
Copy link
Copy Markdown
Collaborator

@asclearuc asclearuc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this contribution — the template engine is well thought out. The tokenizer, recursive block evaluator, and nested depth tracking are all solid, and avoiding a Jinja2 dependency is the right call for this scope.

A few things to address before merging:

Remove "gpu" from capabilities

This node does pure string rendering — no ML, no tensors, no GPU operations. Declaring "gpu" will cause the pipeline scheduler to require a GPU-equipped machine for this node, wasting resources. Remove it.

_get_template_context mutates the shared config dict

variables is a static dict set once in node configuration and stored in self.IGlobal.config, shared across all instances for the entire pipeline run. _get_template_context returns a direct reference to it rather than a copy:

variables = config.get('variables', {})   # live reference, not a copy
variables.setdefault('input', '...')      # mutates shared config
return variables                          # caller mutates it further

Please pay attention also to CodeRabbit's comments

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nodes/src/nodes/prompt_template/IInstance.py`:
- Around line 43-54: The _get_template_context method can raise AttributeError
when self.IGlobal.config is None; update _get_template_context to first check if
self.IGlobal is truthy and self.IGlobal.config is a dict-like (not None) and if
not return an empty variables dict (still adding collected_text as 'input' if
present), or safely obtain config via something like config =
getattr(self.IGlobal, "config", None) and fall back to {} before calling get;
ensure any callers like writeQuestions that rely on this method will receive a
safe empty context instead of raising.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3b019647-9ee4-4155-98ed-058f8313d26c

📥 Commits

Reviewing files that changed from the base of the PR and between 192e1b6 and 6c5f7f5.

📒 Files selected for processing (3)
  • nodes/src/nodes/prompt_template/IInstance.py
  • nodes/src/nodes/prompt_template/services.json
  • nodes/src/nodes/prompt_template/template_engine.py

Comment thread nodes/src/nodes/prompt_template/IInstance.py
@charliegillet
Copy link
Copy Markdown
Contributor Author

Addressed all review feedback:

  • Removed "gpu" from capabilities (addressed in prior commit)
  • Fixed _get_template_context config dict mutation by copying with dict() (addressed in prior commit)
  • Added guard against None config in _get_template_context, writeQuestions, and closing to prevent AttributeError during CONFIG mode (CodeRabbit finding)

Thanks for the thorough review!

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nodes/src/nodes/prompt_template/IInstance.py`:
- Around line 48-50: The code calls dict(config.get('variables', {})) which can
raise if config['variables'] is None or non-mapping; change to first retrieve
raw = config.get('variables') (or use config.get('variables', {})), validate
that raw is a dict via isinstance(raw, dict), and only then set variables =
dict(raw); otherwise set variables = {} so the fallback runs safely—update the
logic in IInstance.py where variables is initialized to use this guarded
pattern.
- Around line 58-66: writeQuestions currently renders each question immediately
using _get_template_context(), which makes output order-dependent because later
writeText() calls can modify the context (e.g., {{input}}); instead, change
writeQuestions to store incoming question texts (e.g., push q.text into a
questions buffer on the instance or into self.question as raw entries) without
calling render or question.addQuestion, and move the rendering logic into
closing() where you call _get_template_context() once with the final context and
then render each buffered question with render(template, final_context) before
calling question.addQuestion; update symbols affected: writeQuestions, closing,
_get_template_context, question.addQuestion, render, and IGlobal.config/template
handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 318de843-acad-4c44-aaab-7fa5ec7e8db5

📥 Commits

Reviewing files that changed from the base of the PR and between 6c5f7f5 and b7a26fb.

📒 Files selected for processing (1)
  • nodes/src/nodes/prompt_template/IInstance.py

Comment thread nodes/src/nodes/prompt_template/IInstance.py Outdated
Comment thread nodes/src/nodes/prompt_template/IInstance.py Outdated
@github-actions github-actions Bot removed the module:vscode VS Code extension label Apr 6, 2026
@charliegillet
Copy link
Copy Markdown
Contributor Author

@asclearuc Addressed all review feedback:

Fixed in this push:

  • Removed unrelated PageStatus.tsx and styles.css changes
  • Fixed _get_template_context to validate variables type before copying, preventing TypeError when value is None
  • Deferred question rendering to closing() so templates have the full text context (all writeText() calls) before rendering

Previously addressed:

  • Removed "gpu" from capabilities (already done in earlier commit)
  • Config dict mutation: dict() copy already ensures shared config is not mutated
  • Exception re-raise after logging already in place

CodeRabbit comments reviewed:

  • Builtins precedence: no change needed — context is checked first, builtins are fallback only
  • All other suggestions addressed or already handled

@nihalnihalani
Copy link
Copy Markdown
Contributor

Senior Review — PR #573: feat(nodes): add prompt template node

Nice work here, @charliegillet. You've been thorough in addressing asclearuc's feedback across both rounds, and the CodeRabbit items have been handled as well. The implementation looks solid.

Status: All previously requested changes appear to be addressed. This looks ready for a re-review to verify and move toward approval.

One item to double-check: In the closing() method, please confirm that exceptions are re-raised after logging. If the current pattern is:

except Exception as e:
    logger.error(...)

it should be:

except Exception as e:
    logger.error(...)
    raise

Swallowing exceptions during cleanup can mask resource leaks or downstream failures. If this is already handled, disregard.

Overall, this is in great shape. Requesting a re-review from the maintainers to get this across the finish line.

@nihalnihalani
Copy link
Copy Markdown
Contributor

🚀 Merge Request

All feedback from asclearuc has been addressed across 2 rounds. Author has been thorough and responsive.

Status: All review items resolved. Ready for re-review and merge. @asclearuc

Comment thread nodes/src/nodes/prompt_template/services.json
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 8, 2026

No description provided.

@charliegillet
Copy link
Copy Markdown
Contributor Author

@asclearuc Thanks for the review! All feedback has been addressed:

  1. "gpu" removed from capabilities — Already fixed in 6c5f7f5. The array is now empty since this node does pure string rendering.
  2. _get_template_context no longer mutates shared config — Fixed in a0c39bb. raw_variables is shallow-copied via dict() before any modification, and an isinstance guard protects against non-dict values.
  3. Builtins precedence fixed — Fixed in c5b406c. _resolve() now clearly tries context first, then falls back to builtins only when the context lookup fails. The two phases are separated with comments.
  4. More comprehensive test case addedc5b406c adds a test that configures a real template (Summarize the following: {{input}}), provides concrete variable values, and asserts the rendered output contains the expected text.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nodes/src/nodes/prompt_template/services.json`:
- Around line 120-145: Add a new test case to services.json that exercises
per-question variable substitution for IInstance.closing(): create a case with
"config" specifying a template that uses {{question}} (e.g., "Answer:
{{question}}"), provide no global "input" but set "questions":
{"questions":[{"text":"What is 2+2?"}]} and add an "expect" asserting the output
"questions" contains the substituted string (e.g., contains "Answer: What is
2+2?"); reference the existing "config"/"template", "questions" array, and
IInstance.closing() behavior so the test explicitly verifies question-level
interpolation.

In `@nodes/src/nodes/prompt_template/template_engine.py`:
- Line 67: Add explicit return type annotations: change the signature of
_resolve to return Any and the three block handlers _at_top_level, _handle_if,
and _handle_for to return int. Update their function signatures accordingly
(e.g., def _resolve(...) -> Any: and def _at_top_level(...) -> int: etc.), and
import Any from typing if it isn’t already imported so the annotations satisfy
Ruff ANN202.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5a79d197-46da-4227-ae16-632c82383754

📥 Commits

Reviewing files that changed from the base of the PR and between b7a26fb and c5b406c.

📒 Files selected for processing (3)
  • nodes/src/nodes/prompt_template/IInstance.py
  • nodes/src/nodes/prompt_template/services.json
  • nodes/src/nodes/prompt_template/template_engine.py

Comment thread nodes/src/nodes/prompt_template/services.json
Comment thread nodes/src/nodes/prompt_template/template_engine.py Outdated
@asclearuc
Copy link
Copy Markdown
Collaborator

Thanks for the work here.

One blocker before this can merge: there are no tests for template_engine.py (sorry, noticed it just today).
Given this is a custom template engine (stdlib only, no Jinja2), test coverage is
essential — bugs here will silently corrupt prompts in production pipelines.

At minimum, the tests should cover all the functions from nodes/src/nodes/prompt_template/template_engine.py .

// Capabilities are flags that change the behavior of the underlying
// engine
//
"capabilities": [],
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let mark this node as an experimental one.

Suggested change
"capabilities": [],
"capabilities": ["experimental"],

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — marked the node as "experimental" in services.json. Commit 6538ad4.

charliegillet added a commit to charliegillet/rocketride-server that referenced this pull request Apr 9, 2026
Adds 57 unit tests covering the custom template engine (stdlib-only, no
Jinja2) in nodes/test/test_prompt_template_engine.py — variable
substitution, dotted paths, missing variables, builtins, context-over-
builtin overrides, if/elif/else, nested blocks, for loops, escape
sequences, malformed/unclosed tags, HTML pass-through, and ReDoS /
recursion safety. The test module loads template_engine.py directly via
importlib so it does not trigger the depends-based package init.

Also addresses outstanding review feedback on PR rocketride-org#573:
- Mark the node as experimental in services.json (per asclearuc)
- Add an explicit {{question}} substitution test case in services.json
  (per CodeRabbit)
- Add missing return type annotations on _resolve (Any), _at_top_level
  (bool), _handle_if (int) and _handle_for (int) to satisfy Ruff ANN202

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@charliegillet
Copy link
Copy Markdown
Contributor Author

@asclearuc Added comprehensive unit tests for template_engine.py in nodes/test/test_prompt_template_engine.py — 57 tests covering variable substitution (flat + dotted paths), missing variables, built-in helpers (now/uuid/random), context overriding builtins (regression guard), conditionals (if/elif/else, nested, empty/missing conditions), for loops (flat + nested, dict iteration, missing iterables), escape sequences, unclosed / malformed tags, HTML pass-through, and ReDoS / recursion safety. All 57 tests pass in ~0.1s. The tests load template_engine.py directly via importlib so they avoid the depends-based package init and can run anywhere Python is available.

Also addressed the remaining feedback in the same commit (6538ad4):

  • Marked the node as "experimental" in services.json per your inline suggestion
  • Added an explicit {{question}} substitution test case in services.json (CodeRabbit)
  • Added missing return type annotations on _resolve, _at_top_level, _handle_if, _handle_for to satisfy Ruff ANN202 (CodeRabbit)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nodes/src/nodes/prompt_template/services.json`:
- Around line 118-161: Add a new test case in
nodes/src/nodes/prompt_template/services.json that exercises the text-only
render path so IInstance.closing()'s elif self.collected_text branch is covered;
create a case with no "questions" field and instead include a "text" lane (e.g.,
"text": {"text": "some rendered string"} or the test harness equivalent used by
other tests) and set expect to assert that the returned "text" is not empty
and/or contains the expected rendered content from the template/variables;
ensure the case uses the same "config" style as other tests if needed so the
prompt/template rendering produces the text-only output to trigger closing()'s
collected_text path.

In `@nodes/test/test_prompt_template_engine.py`:
- Around line 339-350: The test test_large_literal_input_runs_quickly currently
enforces a hard wall-clock threshold (assert elapsed < 2.0) which flakes on CI;
remove that strict timing assertion and either (a) drop the elapsed check
entirely and only assert correctness of render(template, {'v':'MID'}) contains
'MID', or (b) convert the test to use a test-timeout mechanism (e.g., add a
pytest.mark.timeout decorator with a generous timeout) so the test only fails on
extreme hangs but won't flake due to transient slowness; update the test
function test_large_literal_input_runs_quickly accordingly and keep references
to the template construction and render(...) call.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6a8e7f98-7c88-472f-81d2-ee95a91235e8

📥 Commits

Reviewing files that changed from the base of the PR and between c5b406c and 6538ad4.

📒 Files selected for processing (3)
  • nodes/src/nodes/prompt_template/services.json
  • nodes/src/nodes/prompt_template/template_engine.py
  • nodes/test/test_prompt_template_engine.py

Comment on lines +118 to +161
"test": {
"profiles": ["default"],
"cases": [
{
"questions": {
"questions": [{"text": "Hello world"}]
},
"expect": {
"questions": {
"notEmpty": true
}
}
},
{
"config": {
"template": "Summarize the following: {{input}}",
"variables": {"input": "The quick brown fox jumps over the lazy dog."}
},
"questions": {
"questions": [{"text": "test prompt"}]
},
"expect": {
"questions": {
"notEmpty": true,
"contains": "Summarize the following:"
}
}
},
{
"config": {
"template": "Q={{question}} | I={{input}}",
"variables": {"input": "ctx"}
},
"questions": {
"questions": [{"text": "what is this?"}]
},
"expect": {
"questions": {
"contains": "Q=what is this?"
}
}
}
]
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Add one text-lane test case to cover the text-only render path.

Current cases validate only questions output. The nodes/src/nodes/prompt_template/IInstance.py closing() logic has a separate text branch (elif self.collected_text), so this branch should also be exercised in services.json test cases to prevent lane-specific regressions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nodes/src/nodes/prompt_template/services.json` around lines 118 - 161, Add a
new test case in nodes/src/nodes/prompt_template/services.json that exercises
the text-only render path so IInstance.closing()'s elif self.collected_text
branch is covered; create a case with no "questions" field and instead include a
"text" lane (e.g., "text": {"text": "some rendered string"} or the test harness
equivalent used by other tests) and set expect to assert that the returned
"text" is not empty and/or contains the expected rendered content from the
template/variables; ensure the case uses the same "config" style as other tests
if needed so the prompt/template rendering produces the text-only output to
trigger closing()'s collected_text path.

Comment thread nodes/test/prompt_template/test_engine.py
@charliegillet
Copy link
Copy Markdown
Contributor Author

Latest fixes

Commit 28a9e9e:

  • Removed the wall-clock elapsed < 2.0 assertion from test_large_literal_input_runs_quickly (per CodeRabbit). The test now asserts the exact expected output of the 100KB render, which still catches a ReDoS regression (catastrophic backtracking would hang for minutes and be killed by pytest) while eliminating CI flakiness on slow/busy runners. No other wall-clock thresholds exist in the test file.

Commit 6538ad4:

  • Added comprehensive unit tests for template_engine.py at nodes/test/test_prompt_template_engine.py57 tests, all passing in ~0.1s. Covers:
    • Variable substitution (flat + dotted paths + attribute access)
    • Missing variables (graceful empty-string fallback)
    • Built-in helpers: {{now}}, {{uuid}}, {{random}}
    • Context overriding builtins (regression guard for the recent fix)
    • Conditionals: if / elif / else, nested, empty/missing conditions, dotted conditions
    • Loops: for, nested for, iteration over dicts, missing iterables, string-iterable guard, loop-var shadowing
    • Escape sequences (\\{{ / \\}})
    • Unclosed / malformed tags (graceful degradation)
    • HTML pass-through (no implicit escaping)
    • Safety: ~100KB input (ReDoS guard), 500-placeholder template, 50-deep if nesting, 10-deep for nesting
  • Marked the node as "experimental" in services.json (per asclearuc)
  • Added a third services.json test case that asserts {{question}} substitution explicitly (per CodeRabbit)
  • Added missing return type annotations on _resolve (Any), _at_top_level (bool), _handle_if (int), _handle_for (int) to satisfy Ruff ANN202 (per CodeRabbit)
  • ruff check and ruff format --check both pass cleanly on the prompt_template package and the new test file

@charliegillet
Copy link
Copy Markdown
Contributor Author

@asclearuc Thanks for the thorough reviews. Everything you raised is addressed on the current HEAD (28a9e9e):

  1. "gpu" capability — removed in 6c5f7f5, then replaced with ["experimental"] per your inline suggestion in 6538ad4.
  2. _get_template_context no longer mutates the shared config dict — in a0c39bb, raw_variables is copied via dict(raw_variables) before any setdefault/mutation. An isinstance(raw_variables, dict) guard also protects against None / non-mapping values.
  3. Custom template engine test coverage — added in 6538ad4 at nodes/test/test_prompt_template_engine.py. 57 tests, all passing in ~0.04s on Python 3.12. Coverage:
    • Variable substitution (flat + dotted paths + attribute access)
    • Missing variables (graceful empty-string fallback)
    • Built-in helpers ({{now}}, {{uuid}}, {{random}}) and regression guard that context overrides them
    • Conditionals (if / elif / else, nested, empty/missing conditions, dotted conditions)
    • Loops (for, nested for, iteration over dicts, missing iterables, string-iterable guard, loop-var shadowing)
    • Escape sequences (\\{{ / \\}})
    • Unclosed / malformed tags (graceful degradation)
    • HTML pass-through (no implicit escaping)
    • Safety: ~100KB input (ReDoS guard), 500-placeholder template, 50-deep if nesting, 10-deep for nesting

The tests load template_engine.py directly via importlib so they avoid the depends-based package init and can run anywhere Python is available — no server build required.

Ready for re-review whenever you have a moment.

@charliegillet
Copy link
Copy Markdown
Contributor Author

@nihalnihalani Thanks for the senior review. To confirm your one double-check: yes, closing() re-raises after logging. The current pattern at nodes/src/nodes/prompt_template/IInstance.py:83-85 is:

except Exception as e:
    debug(f'Error in prompt_template node: {e}')
    raise

Fixed in 6c5f7f5 per CodeRabbit's earlier finding. Exceptions propagate to the pipeline engine as expected.

asclearuc
asclearuc previously approved these changes Apr 15, 2026
charliegillet and others added 2 commits April 21, 2026 13:33
Add a new prompt_template node that renders prompt templates with dynamic
variable substitution. Features include:

- {{variable}} placeholder replacement from pipeline context
- Built-in helpers: {{now}}, {{uuid}}, {{random}}
- Conditional sections: {% if var %}...{% endif %}
- Loop support: {% for item in list %}...{% endfor %}
- Escape sequences for literal braces
- Zero external dependencies (stdlib only)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove unnecessary "gpu" capability from services.json
- Fix _get_template_context to copy variables dict instead of mutating shared config
- Re-raise exceptions in closing() instead of silently swallowing them
- Fix _resolve to check context before falling back to built-in helpers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
charliegillet and others added 5 commits April 21, 2026 13:33
Prevents AttributeError when config is None during CONFIG mode or after
endGlobal().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix variables dict copy: validate type before calling dict() to
  avoid TypeError when config['variables'] is None
- Defer question rendering to closing() so templates have access to
  all text collected via writeText(), fixing order-dependent rendering
- Remove unrelated PageStatus.tsx and styles.css changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Restructure _resolve() so context is checked before builtins with
  clear separation and comments documenting the priority order
- Add comprehensive test case validating actual template substitution

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds 57 unit tests covering the custom template engine (stdlib-only, no
Jinja2) in nodes/test/test_prompt_template_engine.py — variable
substitution, dotted paths, missing variables, builtins, context-over-
builtin overrides, if/elif/else, nested blocks, for loops, escape
sequences, malformed/unclosed tags, HTML pass-through, and ReDoS /
recursion safety. The test module loads template_engine.py directly via
importlib so it does not trigger the depends-based package init.

Also addresses outstanding review feedback on PR rocketride-org#573:
- Mark the node as experimental in services.json (per asclearuc)
- Add an explicit {{question}} substitution test case in services.json
  (per CodeRabbit)
- Add missing return type annotations on _resolve (Any), _at_top_level
  (bool), _handle_if (int) and _handle_for (int) to satisfy Ruff ANN202

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@stepmikhaylov stepmikhaylov force-pushed the feature/prompt-template-node branch from 28a9e9e to 8d0c8dd Compare April 21, 2026 11:35
@stepmikhaylov
Copy link
Copy Markdown
Collaborator

The branch rebased to force ci/cd workflow to run all nodes unit tests.

@stepmikhaylov
Copy link
Copy Markdown
Collaborator

CI/CD checks failed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

module:nodes Python pipeline nodes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants