Add Node.js agent templates and unlock JavaScript in agent builder#3374
Add Node.js agent templates and unlock JavaScript in agent builder#3374mich-elle-luna wants to merge 7 commits into
Conversation
Add conversational and recommendation agent templates using node-redis v4 with Redis Search for vector-based message history and movie indexing. Conversational agent uses hybrid recent+semantic context retrieval, runtime embedding dimension validation, and a clear note on Redis version requirements. Recommendation agent parses genres from MovieLens CSV format into Redis TAGs, validates LLM query params against an explicit allowlist, skips dataset reload if the index is already warm, and filters genres in Redis rather than JS. Fix template URL to use root-relative path so local templates load in dev. Update agent builder to support JavaScript alongside Python. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🛡️ Jit Security Scan Results✅ No security findings were detected in this PR
Security scan by Jit
|
dwdougherty
left a comment
There was a problem hiding this comment.
Looks good to me, but I think somebody with more Node.js experience should take a look. I'll add Docs back into the review fray.
|
I got Claude to have a look and he found the following: Agent builder — JavaScript generation reviewFindings from auditing the JavaScript code generated by the agent builder on the Agent builder page. Templates live in static/code/agent-templates/javascript/; the substitution logic is agent-builder.js:529-560. What was checked: syntax of all 6 generated outputs (2 agent types × 3 models), template substitution correctness, Python ↔ JS parity, and runtime behaviour against Redis 8.2.1 (RediSearch + ReJSON loaded) with the OpenAI SDK stubbed. What was not checked: real LLM calls, the Anthropic and Llama3 variants end-to-end, the full MovieLens dataset (used a 5-movie synthetic seed), or non-OpenAI embedding behaviour. Critical — blocks fresh installs1.
|
- Fix deduplication bug: _getRecentMessages now zips Redis keys with json.mGet results so m._key is the actual key, not undefined - Fix recent-window arithmetic: lTrim and lRange now use RECENT_WINDOW * 2 (user + assistant per turn) instead of * 4 - Fix ft.dropIndex error-string match: replaced brittle catch with ft.info existence check to handle Redis 8 error wording - Fix num_docs field name: ft.info returns num_docs not numDocs, so _indexExists was always returning false and reloading data on every start - Fix Llama3 requiring LLM_API_KEY: default to 'no-key-needed' instead of throwing so local Ollama users don't need a dummy value - Hide Anthropic from JS model selector: JS templates use the OpenAI SDK which is not compatible with api.anthropic.com - Fix Jupyter button: always disabled (feature not yet available) - Fix generic JS fallback: use LLM_API_KEY and node-redis v4 socket shape Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix multi-word genre TAG queries: escape spaces as \\ so 'Science Fiction' matches as a single token rather than two separate terms in RediSearch - Fix reindex leaving stale data: use DD flag on ft.dropIndex to delete movie documents along with the index on reload - Fix Anthropic bypass: processModelSelection now checks allowedModels so typing 'anthropic' or 'claude' while on JavaScript is rejected with a clear message rather than generating broken code - Fix context messages out of order: sort combined recent + semantic results by key (which encodes timestamp) before passing to the LLM - Fix trimmed messages never deleted: evict and delete JSON documents for keys that will fall off the recent window before each lTrim call Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix hyphenated genres stripped from TAG query: allow hyphens through the sanitizer and backslash-escape them alongside spaces so Film-Noir matches the stored value instead of becoming FilmNoir - Fix zero minRating silently dropped: use != null instead of truthy check so a minimum rating of 0 is included in the filter query - Fix Llama/Ollama breaking semantic history: add separate embedder client (EMBEDDING_API_KEY / EMBEDDING_API_BASE_URL) that defaults to the LLM values so Ollama users just set EMBEDDING_MODEL=nomic-embed-text with no extra config, matching the pattern used in the RAG templates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
I tested both of these in a local environment to make sure they work. |
LLM prompt, validation range, and output label all said 0-10 but MovieLens avgRating is a 0-5 star scale. minRating values above 5 produced filters that matched nothing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 3575979. Configure here.
Hardcoded /code/agent-templates/ breaks deployments under a subdirectory (e.g. staging). Inject window.AGENT_TEMPLATE_BASE via Hugo relURL in the shortcode and use it in agent-builder.js, with /code/agent-templates as the fallback for local/root deploys. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
node-redis field name varies across versions; fall back through num_docs -> numDocs -> '0' to avoid NaN causing every startup to reload the full dataset. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
This is Claude's work, btw! I wasn't aware of this until now, but you can ask Claude to post its review findings (or indeed anything else) directly as a comment on the PR. Review of the new JavaScript agent templatesReviewed both new templates ( Main finding — the semantic memory feature is effectively dead code
const evictCount = listLen - (RECENT_WINDOW * 2 - 1);
if (evictCount > 0) {
const toEvict = await this.redisClient.lRange(RECENT_KEY(this.sessionName), 0, evictCount - 1);
if (toEvict.length) await this.redisClient.del(toEvict); // <-- deletes the JSON docs + their index entries
}Because a message's JSON document and its entry in the const seen = new Set(recent.map((m) => m._key));
const extra = semantic.filter((m) => !seen.has(m._key)); // <-- always empty
The eviction was a deliberate choice to bound memory, but it's mutually exclusive with semantic recall. Two coherent options:
I'd lean toward the first, since "semantic message history" is what differentiates this template from a plain recent-window chat. Minor
Verified correct
|
|
Bonus: another more serious issue found by Codex. Follow-up: a second-opinion review surfaced a HIGH-severity data bugAfter the review above, I had a second reviewer (Codex) go over the same two templates independently and verified its most important finding against the real datasets. One new HIGH issue, plus two smaller confirmed ones. 🔴 HIGH —
|
| MovieLens movieId | #ratings | What the join labels it | What it actually is |
|---|---|---|---|
| 356 | 341 | dropped | Forrest Gump |
| 296 | 324 | Terminator 3 | Pulp Fiction |
| 318 | 311 | The Million Dollar Hotel | Shawshank Redemption |
| 593 | 304 | Solaris | Silence of the Lambs |
| 260 | 291 | The 39 Steps | Star Wars |
| 1 | 247 | dropped | Toy Story |
Since popularityScore = ratingCount × avgRating is computed on these misattributed ratings, every recommendation the template produces is wrong — and the most popular films (Toy Story, Forrest Gump, The Matrix, Terminator 2) never appear at all because their TMDB ids aren't present as MovieLens movieIds.
Fix: the join needs the dataset's links.csv (movieId → tmdbId) to bridge the two id spaces before merging metadata with aggregated ratings. This is a code-and-data change, not a one-liner.
Other confirmed items
- MEDIUM —
conversational_agent.js: unescapedsessionNamein the TAG query._getSemanticMessagesbuilds@session:{${this.sessionName}}directly. The constructor accepts arbitrary session names, and TAG metacharacters (spaces,{},|,-) would break the query or match unintended sessions. Escape it the way genres are escaped in the recommendation template. - LOW —
recommendation_agent.js:revenuesort isn't surfaced.validateQueryParamsallowssortBy: "revenue"and the search sorts on it, butrevenueis absent from both theRETURNlist and the formatted output, so "highest revenue" / "blockbuster" requests can't be verified from the result. AddrevenuetoRETURNand the printed lines.
(The eviction-nullifies-semantic-search issue from my first comment still stands as the headline item for conversational_agent.js.)
dwdougherty
left a comment
There was a problem hiding this comment.
Everything looks good in the staged preview. Approved.

Add conversational and recommendation agent templates using node-redis v4 with Redis Search for vector-based message history and movie indexing. Conversational agent uses hybrid recent+semantic context retrieval, runtime embedding dimension validation, and a clear note on Redis version requirements. Recommendation agent parses genres from MovieLens CSV format into Redis TAGs, validates LLM query params against an explicit allowlist, skips dataset reload if the index is already warm, and filters genres in Redis rather than JS. Fix template URL to use root-relative path so local templates load in dev. Update agent builder to support JavaScript alongside Python.
Note
Low Risk
Changes are limited to documentation, static templates, and client-side wizard logic; no production API or auth paths are modified.
Overview
JavaScript (Node.js) is now a first-class option in the agent builder alongside Python, with docs updated to match.
The builder loads static templates via a Hugo-injected
AGENT_TEMPLATE_BASE(relURL) so template fetches work under subdirectory deployments, and treats Python and JavaScript as fully supported (coming-soon flows offer both). For JavaScript it hides Anthropic from model chips and validation because the Node templates use the OpenAI-compatible SDK.New
conversational_agent.jsandrecommendation_agent.jstemplates use node-redis with Redis Search: conversational agents combine recent turns with vector KNN over JSON-stored embeddings (with dimension checks and eviction of trimmed history keys); recommendation agents ingest MovieLens CSVs into a JSON index, validate LLM-parsed filters, skip reload when the index is warm, and apply genre TAG filters in Redis.Minor UX tweaks: generic JS stub uses modern Redis client options and
LLM_API_KEY; the Jupyter try button stays disabled for all models.Reviewed by Cursor Bugbot for commit c0a3f07. Bugbot is set up for automated code reviews on this repo. Configure here.