opencode-skills-collection 4.0.7 → 4.0.9
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/bundled-skills/.antigravity-install-manifest.json +19 -1
- package/bundled-skills/antigravity-maintainer-batch-release/SKILL.md +22 -8
- package/bundled-skills/antigravity-maintainer-batch-release/agents/openai.yaml +4 -0
- package/bundled-skills/anywrite/SKILL.md +18 -8
- package/bundled-skills/cloudflare-security-audit/SKILL.md +5 -3
- package/bundled-skills/cloudflare-security-audit/references/RECONNAISSANCE.md +2 -0
- package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
- package/bundled-skills/docs/maintainers/aas-agent-first-control-plane-preview-profile.md +2 -2
- package/bundled-skills/docs/maintainers/aas-agent-first-control-plane-v1-worklog.md +1 -1
- package/bundled-skills/docs/maintainers/merge-batch.md +3 -3
- package/bundled-skills/docs/maintainers/merging-prs.md +13 -17
- package/bundled-skills/docs/maintainers/pr-autonomy.md +2 -2
- package/bundled-skills/docs/maintainers/release-process.md +17 -2
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
- package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
- package/bundled-skills/docs/users/aas-core.md +38 -3
- package/bundled-skills/docs/users/bundles.md +1 -1
- package/bundled-skills/docs/users/claude-code-skills.md +1 -1
- package/bundled-skills/docs/users/faq.md +2 -0
- package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
- package/bundled-skills/docs/users/kiro-integration.md +1 -1
- package/bundled-skills/docs/users/usage.md +4 -4
- package/bundled-skills/docs/users/visual-guide.md +4 -4
- package/bundled-skills/docs/vietnamese/README.vi.md +4 -4
- package/bundled-skills/hf-cloud-aws-context-discovery/SKILL.md +96 -0
- package/bundled-skills/loki-mode/examples/todo-app-generated/backend/package-lock.json +3 -3
- package/bundled-skills/markstream-angular/SKILL.md +76 -0
- package/bundled-skills/markstream-custom-components/SKILL.md +74 -0
- package/bundled-skills/markstream-custom-components/references/patterns.md +14 -0
- package/bundled-skills/markstream-install/SKILL.md +3 -1
- package/bundled-skills/markstream-migration/SKILL.md +78 -0
- package/bundled-skills/markstream-migration/references/adoption-checklist.md +10 -0
- package/bundled-skills/markstream-nuxt/SKILL.md +71 -0
- package/bundled-skills/markstream-react/SKILL.md +74 -0
- package/bundled-skills/markstream-svelte/SKILL.md +68 -0
- package/bundled-skills/markstream-vue/SKILL.md +71 -0
- package/bundled-skills/markstream-vue2/SKILL.md +70 -0
- package/bundled-skills/markstream-vue2-cli/SKILL.md +70 -0
- package/bundled-skills/markstream-vue2-vite/SKILL.md +69 -0
- package/bundled-skills/n8n-agents/SKILL.md +301 -0
- package/bundled-skills/n8n-agents/references/CHAT_AGENT_PATTERNS.md +228 -0
- package/bundled-skills/n8n-agents/references/EXAMPLES.md +432 -0
- package/bundled-skills/n8n-agents/references/HUMAN_REVIEW.md +180 -0
- package/bundled-skills/n8n-agents/references/MEMORY.md +139 -0
- package/bundled-skills/n8n-agents/references/RAG.md +102 -0
- package/bundled-skills/n8n-agents/references/STRUCTURED_OUTPUT.md +163 -0
- package/bundled-skills/n8n-agents/references/SUBWORKFLOW_AS_TOOL.md +199 -0
- package/bundled-skills/n8n-agents/references/SYSTEM_PROMPT.md +151 -0
- package/bundled-skills/n8n-agents/references/TOOLS.md +199 -0
- package/bundled-skills/n8n-binary-and-data/SKILL.md +269 -0
- package/bundled-skills/n8n-binary-and-data/references/AGENT_TOOL_BINARY.md +227 -0
- package/bundled-skills/n8n-binary-and-data/references/BINARY_BASICS.md +187 -0
- package/bundled-skills/n8n-binary-and-data/references/CDN_REQUIREMENT.md +109 -0
- package/bundled-skills/n8n-binary-and-data/references/MERGE_FOR_CONTEXT.md +130 -0
- package/bundled-skills/n8n-code-tool/SKILL.md +358 -0
- package/bundled-skills/n8n-code-tool/references/ERROR_PATTERNS.md +178 -0
- package/bundled-skills/n8n-code-tool/references/INPUT_SCHEMA.md +132 -0
- package/bundled-skills/n8n-error-handling/SKILL.md +289 -0
- package/bundled-skills/n8n-error-handling/references/API_WORKFLOWS.md +256 -0
- package/bundled-skills/n8n-error-handling/references/ERROR_WORKFLOWS.md +178 -0
- package/bundled-skills/n8n-error-handling/references/NODE_ERROR_OUTPUTS.md +171 -0
- package/bundled-skills/n8n-error-handling/references/RESPONSE_SHAPES.md +220 -0
- package/bundled-skills/n8n-multi-instance/SKILL.md +208 -0
- package/bundled-skills/n8n-node-configuration/references/NODE_FAMILY_GOTCHAS.md +241 -0
- package/bundled-skills/n8n-subworkflows/SKILL.md +271 -0
- package/bundled-skills/n8n-subworkflows/references/NAMING_AND_DISCOVERY.md +130 -0
- package/bundled-skills/n8n-subworkflows/references/SUBWORKFLOW_PATTERNS.md +147 -0
- package/bundled-skills/pptx-deck-creation/SKILL.md +2 -0
- package/bundled-skills/pptx-deck-creation/references/design-profiles.md +2 -0
- package/bundled-skills/sshepherd/SKILL.md +16 -6
- package/bundled-skills/using-n8n-mcp-skills/SKILL.md +193 -0
- package/bundled-skills/weaviate-cookbooks/references/pdf_multimodal_rag.md +12 -6
- package/package.json +1 -1
- package/skills_index.json +596 -13
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: n8n-code-tool
|
|
3
|
+
description: Write and debug JavaScript or Python for the AI-callable n8n Custom Code Tool, including schemas, sandbox limits, and return formats.
|
|
4
|
+
risk: unknown
|
|
5
|
+
source: https://github.com/czlonkowski/n8n-skills/tree/main/skills/n8n-code-tool
|
|
6
|
+
source_repo: czlonkowski/n8n-skills
|
|
7
|
+
source_type: community
|
|
8
|
+
date_added: "2026-07-21"
|
|
9
|
+
author: Romuald Czlonkowski
|
|
10
|
+
license: MIT
|
|
11
|
+
license_source: https://github.com/czlonkowski/n8n-skills/blob/main/LICENSE
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# n8n Custom Code Tool
|
|
15
|
+
|
|
16
|
+
## When to Use
|
|
17
|
+
|
|
18
|
+
Use this skill specifically for code executed by the AI-agent-callable n8n Custom Code Tool. Use the separate JavaScript or Python Code-node skills for ordinary workflow Code nodes.
|
|
19
|
+
|
|
20
|
+
Do not hardcode secrets or accept arbitrary executable code from untrusted input. Constrain inputs with a schema, validate outputs, allowlist any network destinations, and ask before testing a tool whose code can write data or invoke an external service.
|
|
21
|
+
|
|
22
|
+
Expert guidance for writing code inside `@n8n/n8n-nodes-langchain.toolCode` — the tool an AI Agent can invoke, **not** the regular workflow Code node.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## ⚠️ This is NOT the Code node
|
|
27
|
+
|
|
28
|
+
The Custom Code Tool looks like a Code node in the editor — same JavaScript editor, similar layout — but it is a **completely different node** from a different package with a **different runtime contract**.
|
|
29
|
+
|
|
30
|
+
| | Code node | Custom Code Tool |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| **Node type** | `n8n-nodes-base.code` | `@n8n/n8n-nodes-langchain.toolCode` |
|
|
33
|
+
| **Package** | `n8n-nodes-base` | `@n8n/n8n-nodes-langchain` |
|
|
34
|
+
| **Invoked by** | Previous node (workflow flow) | AI Agent (LangChain) |
|
|
35
|
+
| **Input** | `$input.all()` — item stream | `query` — string or object from LLM |
|
|
36
|
+
| **Return** | `[{json: {...}}]` (items array) | **A string** |
|
|
37
|
+
| **`$fromAI()`** | N/A | **Not available** (see Errors) |
|
|
38
|
+
| **HTTP helper** | `this.helpers.httpRequest` (auth helpers blocked) | Not exposed to the tool sandbox |
|
|
39
|
+
| **State** | Per-run execution data | No `getContext`, no `$getWorkflowStaticData` |
|
|
40
|
+
|
|
41
|
+
**If you treat it like a Code node, it fails.** The rest of this skill covers the Code Tool's actual contract.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
### Minimal JavaScript Code Tool
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
// `query` is whatever the AI sent (a string by default)
|
|
51
|
+
return `You asked: ${query}`;
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Minimal Python Code Tool
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
# `_query` is whatever the AI sent (a string by default)
|
|
58
|
+
return f"You asked: {_query}"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Essential Rules
|
|
62
|
+
|
|
63
|
+
1. **Return a string.** Numbers are auto-converted. Anything else throws `"The response property should be a string, but it is an object"`.
|
|
64
|
+
2. **Input variable is fixed**: `query` (JS), `_query` (Python). You cannot rename it.
|
|
65
|
+
3. **Do NOT use `$fromAI()`** inside the Code Tool sandbox — it throws `"No execution data available"`.
|
|
66
|
+
4. **Do NOT use `[{json: {...}}]`** return format — that's for Code nodes. Throws `"Wrong output type returned"`.
|
|
67
|
+
5. **Use a descriptive tool name** (letters/numbers/underscores, v1.1+). The agent calls the tool by its name.
|
|
68
|
+
6. **Write a precise description** — the LLM decides whether to invoke the tool based on it.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## The Two Input Modes
|
|
73
|
+
|
|
74
|
+
The Code Tool has two input shapes, controlled by `specifyInputSchema`:
|
|
75
|
+
|
|
76
|
+
### Mode 1: Unstructured (default, `specifyInputSchema: false`)
|
|
77
|
+
|
|
78
|
+
The AI passes **a single string** as `query`. If you need multiple fields, the AI has to stuff them into that one string and you parse them out. In practice, LLMs will happily pass a JSON string if your description tells them to.
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
// Parse a JSON string the AI sent
|
|
82
|
+
let params;
|
|
83
|
+
try {
|
|
84
|
+
params = typeof query === 'string' ? JSON.parse(query) : query;
|
|
85
|
+
} catch (e) {
|
|
86
|
+
throw new Error('Expected a JSON object. Parser said: ' + e.message);
|
|
87
|
+
}
|
|
88
|
+
const price = Number(params.price);
|
|
89
|
+
const months = Number(params.months);
|
|
90
|
+
// ...
|
|
91
|
+
return JSON.stringify({ monthly_payment: /* ... */ });
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Pros**: simplest to set up, one field to describe.
|
|
95
|
+
**Cons**: no schema validation — if the LLM forgets a field, the tool throws at runtime.
|
|
96
|
+
|
|
97
|
+
**Best for**: quick prototypes, tools with one natural input (a question, a URL, a text blob).
|
|
98
|
+
|
|
99
|
+
### Mode 2: Structured (`specifyInputSchema: true`)
|
|
100
|
+
|
|
101
|
+
The tool becomes a LangChain `DynamicStructuredTool`. The LLM sees a typed argument schema and passes a **validated object** as `query`. You access fields directly.
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
// query is now an object matching your schema
|
|
105
|
+
const price = query.price;
|
|
106
|
+
const months = query.months;
|
|
107
|
+
const residual_percent = query.residual_percent;
|
|
108
|
+
|
|
109
|
+
const monthly = computeAnnuity(price, months, residual_percent);
|
|
110
|
+
return JSON.stringify({ monthly_payment: monthly });
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Schema is defined via either:
|
|
114
|
+
- `schemaType: "fromJson"` + `jsonSchemaExample` (n8n v≥1.3) — paste an example JSON, n8n infers the schema
|
|
115
|
+
- `schemaType: "manual"` + `inputSchema` — write a full JSON Schema yourself
|
|
116
|
+
|
|
117
|
+
**Pros**: LLM gets type hints, invalid calls rejected before your code runs, cleaner code.
|
|
118
|
+
**Cons**: a little more setup; requires n8n version with schema support.
|
|
119
|
+
|
|
120
|
+
**Best for**: production tools with multiple typed parameters (calculators, API wrappers, anything with numeric fields the LLM tends to stringify).
|
|
121
|
+
|
|
122
|
+
**See**: [references/INPUT_SCHEMA.md](references/INPUT_SCHEMA.md) for complete schema setup.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Return Format
|
|
127
|
+
|
|
128
|
+
**The return value must be a string.** The LLM reads it as the tool's observation.
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
// ✅ String
|
|
132
|
+
return "42";
|
|
133
|
+
|
|
134
|
+
// ✅ Number (auto-converted to string by n8n)
|
|
135
|
+
return 42;
|
|
136
|
+
|
|
137
|
+
// ✅ JSON-encoded structured result (recommended for rich output)
|
|
138
|
+
return JSON.stringify({ result: 42, currency: "SEK" });
|
|
139
|
+
|
|
140
|
+
// ❌ Raw object → "The response property should be a string, but it is an object"
|
|
141
|
+
return { result: 42 };
|
|
142
|
+
|
|
143
|
+
// ❌ Workflow item format → "Wrong output type returned"
|
|
144
|
+
return [{ json: { result: 42 } }];
|
|
145
|
+
|
|
146
|
+
// ❌ Array → "The response property should be a string, but it is an object"
|
|
147
|
+
return [1, 2, 3];
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Best practice: JSON-stringify structured results
|
|
151
|
+
|
|
152
|
+
When your tool has more than a trivial scalar output, return a JSON string:
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
return JSON.stringify({
|
|
156
|
+
monthly_payment_sek: 5405,
|
|
157
|
+
loan_amount: 351920,
|
|
158
|
+
total_cost_of_credit: 63295
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The LLM parses JSON reliably and can pick the fields it needs to present to the user.
|
|
163
|
+
|
|
164
|
+
### Error handling: the agent reads your failures
|
|
165
|
+
|
|
166
|
+
Errors don't just stop the workflow — they go back to the LLM, which usually corrects its call and retries. Use that:
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
// Option A: throw — n8n surfaces the message to the agent
|
|
170
|
+
if (!isFinite(price)) throw new Error('price must be a number, e.g. 439900');
|
|
171
|
+
|
|
172
|
+
// Option B: return an error string — agent reads it like any tool result
|
|
173
|
+
if (!isFinite(price)) return JSON.stringify({ error: 'price must be a number, e.g. 439900' });
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Either way, write error messages **for the LLM**: state what was wrong and what a valid call looks like. A bare `throw new Error('invalid input')` wastes the retry; an instructive message usually fixes the next call.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Tool Name and Description
|
|
181
|
+
|
|
182
|
+
These fields are NOT documentation — they are the **tool contract the LLM sees**. Treat them as prompt engineering.
|
|
183
|
+
|
|
184
|
+
### Name
|
|
185
|
+
- Must match `[A-Za-z0-9_]+` (v1.1+). No spaces, no hyphens, no emoji.
|
|
186
|
+
- Use a verb-y descriptive name: `calculate_car_loan`, `get_weather`, `search_orders`.
|
|
187
|
+
- The agent calls the tool by this name. `Code Tool` (the default) is useless — the agent won't know when to call it.
|
|
188
|
+
|
|
189
|
+
### Description
|
|
190
|
+
- Explain **when** to use it and **what** to send.
|
|
191
|
+
- If unstructured mode, **include an example of the JSON string** the LLM should send.
|
|
192
|
+
- If structured mode, the schema speaks for itself — just describe purpose.
|
|
193
|
+
|
|
194
|
+
**Unstructured example (JSON-in-string pattern):**
|
|
195
|
+
```
|
|
196
|
+
Deterministiskt beräknar månadskostnad för billån. Anropa med EN JSON-sträng:
|
|
197
|
+
{"price":439900,"down_payment":87980,"interest_rate":6.95,"months":36,"residual_percent":50}
|
|
198
|
+
Fält: price (SEK), down_payment (SEK), interest_rate (% per år), months, residual_percent (0-99).
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Structured example (schema-defined):**
|
|
202
|
+
```
|
|
203
|
+
Deterministically computes the monthly car-loan payment given price, down payment,
|
|
204
|
+
annual interest rate, term, and residual percent. Use whenever the user asks for
|
|
205
|
+
monthly cost, total credit cost, or loan breakdown.
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Top Errors and Fixes
|
|
211
|
+
|
|
212
|
+
### Error 1: `"There was an error: 'Cannot assign to read only property \"name\" of object: Error: No execution data available'"`
|
|
213
|
+
|
|
214
|
+
**Cause**: you called `$fromAI()` inside the Code Tool sandbox.
|
|
215
|
+
|
|
216
|
+
**Fix**: `$fromAI()` is a helper for **other** tool-enabled nodes (HTTP Request Tool, SendGrid Tool, `toolWorkflow`, etc.) — it's not exposed inside `toolCode`. Read the AI's input from `query` directly (or use `specifyInputSchema` for structured fields).
|
|
217
|
+
|
|
218
|
+
### Error 2: `"Wrong output type returned"`
|
|
219
|
+
|
|
220
|
+
**Cause**: you returned a workflow-style array like `[{ json: { ... } }]`. That's the Code **node** contract, not the Code **Tool** contract.
|
|
221
|
+
|
|
222
|
+
**Fix**: return a string. For structured data, `return JSON.stringify(output)`.
|
|
223
|
+
|
|
224
|
+
### Error 3: `"The response property should be a string, but it is an object"`
|
|
225
|
+
|
|
226
|
+
**Cause**: you returned a plain object or array.
|
|
227
|
+
|
|
228
|
+
**Fix**: `JSON.stringify()` the result, or coerce to a string.
|
|
229
|
+
|
|
230
|
+
### Error 4: AI never calls the tool
|
|
231
|
+
|
|
232
|
+
**Cause**: tool name is generic (`Code Tool`, `My Tool`) or description doesn't clearly state when to use it.
|
|
233
|
+
|
|
234
|
+
**Fix**: rename to a verb-y name (`calculate_car_loan`), and rewrite the description to explicitly state the trigger conditions (e.g. "Use this whenever the user asks about monthly cost").
|
|
235
|
+
|
|
236
|
+
### Error 5: AI sends garbage into `query`
|
|
237
|
+
|
|
238
|
+
**Cause**: unstructured tool with a vague description. The LLM guesses at the format.
|
|
239
|
+
|
|
240
|
+
**Fix**: either (a) include a concrete JSON example in the description, or (b) switch to `specifyInputSchema: true` so the LLM gets a typed schema.
|
|
241
|
+
|
|
242
|
+
**See**: [references/ERROR_PATTERNS.md](references/ERROR_PATTERNS.md) for full catalog with reproductions.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## What's NOT Available in the Sandbox
|
|
247
|
+
|
|
248
|
+
The Code Tool sandbox is **narrower** than the Code node sandbox. Don't assume helpers carry over:
|
|
249
|
+
|
|
250
|
+
| Helper | Code node | Code Tool |
|
|
251
|
+
|---|---|---|
|
|
252
|
+
| `$input.all()`, `$input.first()`, `$input.item` | ✅ | ❌ |
|
|
253
|
+
| `$node["NodeName"]` | ✅ | ❌ |
|
|
254
|
+
| `$json`, `$binary` | ✅ | ❌ |
|
|
255
|
+
| `$fromAI()` | ❌ | ❌ (despite sitting next to an AI agent) |
|
|
256
|
+
| `this.helpers.httpRequest()` | ✅ | ❌ |
|
|
257
|
+
| `DateTime` (Luxon) | ✅ | ✅ (standard in JS sandbox) |
|
|
258
|
+
| `$jmespath()` | ✅ | ❌ |
|
|
259
|
+
| `this.getContext(...)` | ✅ | ❌ |
|
|
260
|
+
| `$getWorkflowStaticData(...)` | ✅ | ❌ |
|
|
261
|
+
|
|
262
|
+
**Implication**: the Code Tool is for **pure computation**. If you need an HTTP call, an API lookup, or cross-invocation state, use a different tool node:
|
|
263
|
+
- HTTP Request Tool for external API calls
|
|
264
|
+
- `toolWorkflow` (Call Sub-workflow Tool) for multi-step logic with access to the full Code node sandbox
|
|
265
|
+
- MCP / database tools for persistent state
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## When to Use Code Tool vs Alternatives
|
|
270
|
+
|
|
271
|
+
Use **Code Tool** when:
|
|
272
|
+
- ✅ Pure deterministic computation (math, parsing, formatting, validation)
|
|
273
|
+
- ✅ Lightweight transformations the LLM shouldn't do itself (precision math, regex)
|
|
274
|
+
- ✅ You want the code inline in the workflow, not in a separate sub-workflow
|
|
275
|
+
|
|
276
|
+
Use **`toolWorkflow`** (Call Sub-workflow Tool) when:
|
|
277
|
+
- ✅ You need multiple parameters with clean `$fromAI()` typing
|
|
278
|
+
- ✅ You need access to `this.helpers`, credentials, or other nodes
|
|
279
|
+
- ✅ Logic is reusable across agents
|
|
280
|
+
- ✅ You want structured typed inputs WITHOUT writing a JSON Schema
|
|
281
|
+
|
|
282
|
+
Use **HTTP Request Tool** when:
|
|
283
|
+
- ✅ The tool is fundamentally a single API call
|
|
284
|
+
- ✅ You want per-parameter `$fromAI()` bindings in URL/query/body
|
|
285
|
+
|
|
286
|
+
**Rule of thumb**: if you find yourself wanting `$fromAI()`, you probably want `toolWorkflow` instead of `toolCode`.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Complete Working Example
|
|
291
|
+
|
|
292
|
+
A production calculator tool (unstructured, JSON-in-string pattern):
|
|
293
|
+
|
|
294
|
+
```json
|
|
295
|
+
{
|
|
296
|
+
"parameters": {
|
|
297
|
+
"name": "calculate_car_loan",
|
|
298
|
+
"description": "Computes monthly car-loan payment using an annuity formula with residual/balloon. Call with a single JSON string. Example: {\"price\":439900,\"down_payment\":87980,\"interest_rate\":6.95,\"months\":36,\"residual_percent\":50,\"setup_fee\":695,\"monthly_admin_fee\":59}. Required: price, down_payment, interest_rate, months, residual_percent. Optional: setup_fee, monthly_admin_fee (default 0).",
|
|
299
|
+
"language": "javaScript",
|
|
300
|
+
"jsCode": "let params;\ntry {\n params = typeof query === 'string' ? JSON.parse(query) : query;\n} catch (e) {\n throw new Error('Invalid JSON: ' + e.message);\n}\n\nconst price = Number(params.price);\nconst down_payment = Number(params.down_payment);\nconst interest_rate = Number(params.interest_rate);\nconst months = Number(params.months);\nconst residual_percent= Number(params.residual_percent);\nconst setup_fee = Number(params.setup_fee ?? 0) || 0;\nconst monthly_admin_fee = Number(params.monthly_admin_fee ?? 0) || 0;\n\nif (!isFinite(price) || price <= 0) throw new Error('price must be > 0');\nif (down_payment < 0 || down_payment >= price) throw new Error('down_payment must be in [0, price)');\n\nconst principal = price - down_payment;\nconst residual = price * (residual_percent / 100);\nconst r = interest_rate / 100 / 12;\nconst growth = Math.pow(1 + r, months);\nconst base = r === 0\n ? (principal - residual) / months\n : (principal - residual / growth) * r / (1 - 1 / growth);\nconst monthly_payment = base + monthly_admin_fee;\n\nreturn JSON.stringify({\n monthly_payment_sek: Math.round(monthly_payment),\n loan_amount: Math.round(principal),\n residual_value_sek: Math.round(residual),\n total_cost_of_credit: Math.round(monthly_payment * months + residual + setup_fee - principal)\n});"
|
|
301
|
+
},
|
|
302
|
+
"type": "@n8n/n8n-nodes-langchain.toolCode",
|
|
303
|
+
"typeVersion": 1.3,
|
|
304
|
+
"name": "calculate_car_loan"
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Wire it into an AI Agent via the `ai_tool` connection type.
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Integration with Other Skills
|
|
313
|
+
|
|
314
|
+
**n8n-code-javascript**: the Code **node** skill. Most JavaScript patterns (arrays, map/filter, DateTime) transfer — but I/O contract is different. Don't copy data-access code.
|
|
315
|
+
|
|
316
|
+
**n8n-node-configuration**: `specifyInputSchema` is a classic displayOptions-driven conditional field. Use `get_node({detail: "standard"})` on `@n8n/n8n-nodes-langchain.toolCode` to see schema-related properties.
|
|
317
|
+
|
|
318
|
+
**n8n-workflow-patterns**: Code Tool sits inside the "AI Agent with tools" pattern. An agent typically has several tools; Code Tool is the "local compute" option.
|
|
319
|
+
|
|
320
|
+
**n8n-validation-expert**: the three Code Tool errors listed above have clear signatures — if validation surfaces "Wrong output type returned", you know to switch from array-of-items to a string.
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Quick Reference Checklist
|
|
325
|
+
|
|
326
|
+
Before deploying a Code Tool:
|
|
327
|
+
|
|
328
|
+
- [ ] **Node type** is `@n8n/n8n-nodes-langchain.toolCode` (not `nodes-base.code`)
|
|
329
|
+
- [ ] **Tool name** is descriptive, verb-y, snake_case (e.g. `calculate_car_loan`)
|
|
330
|
+
- [ ] **Description** states when to use the tool and (if unstructured) shows a JSON example
|
|
331
|
+
- [ ] **Input** read from `query` (JS) or `_query` (Python)
|
|
332
|
+
- [ ] **No `$fromAI()`** in the code body
|
|
333
|
+
- [ ] **No `$input` / `$json` / `$helpers`** — those aren't in the sandbox
|
|
334
|
+
- [ ] **Return** is a string (use `JSON.stringify()` for structured output)
|
|
335
|
+
- [ ] **Wired** into an AI Agent via `ai_tool` connection
|
|
336
|
+
- [ ] **Tested** with the exact kind of input the LLM will send (JSON in a string, or schema-validated object)
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Additional Resources
|
|
341
|
+
|
|
342
|
+
- [references/INPUT_SCHEMA.md](references/INPUT_SCHEMA.md) — structured input (DynamicStructuredTool) in depth
|
|
343
|
+
- [references/ERROR_PATTERNS.md](references/ERROR_PATTERNS.md) — full error catalog with causes and fixes
|
|
344
|
+
|
|
345
|
+
### Official sources
|
|
346
|
+
- [n8n Custom Code Tool docs](https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.toolcode/)
|
|
347
|
+
- [ToolCode source](https://github.com/n8n-io/n8n/blob/master/packages/%40n8n/nodes-langchain/nodes/tools/ToolCode/ToolCode.node.ts) — the sandbox contract
|
|
348
|
+
- [LangChain tool docs](https://js.langchain.com/docs/modules/agents/tools/) — DynamicTool / DynamicStructuredTool
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
**Remember**: the Code Tool is a LangChain tool wearing a Code-node UI. Contract is: **string in, string out**. Everything else follows from that.
|
|
353
|
+
|
|
354
|
+
## Limitations
|
|
355
|
+
|
|
356
|
+
- The Custom Code Tool sandbox and available globals can change with n8n releases; verify the installed node version.
|
|
357
|
+
- Static review cannot establish runtime permissions, network reachability, or the behavior of external services.
|
|
358
|
+
- This skill does not authorize arbitrary code execution or testing against production data.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Code Tool Error Patterns
|
|
2
|
+
|
|
3
|
+
The most common failure modes for `@n8n/n8n-nodes-langchain.toolCode`, with exact error strings, root causes, and fixes.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Error 1: `"Cannot assign to read only property 'name' of object: Error: No execution data available"`
|
|
8
|
+
|
|
9
|
+
**Full message (wrapped by n8n):**
|
|
10
|
+
> There was an error: "Cannot assign to read only property 'name' of object 'Error: No execution data available'"
|
|
11
|
+
|
|
12
|
+
**Cause**: Calling `$fromAI()` inside the Code Tool sandbox. `$fromAI()` is a helper intended for *other* tool-enabled nodes (HTTP Request Tool, SendGrid Tool, `toolWorkflow`) where AI-supplied values flow through workflow execution data. The Code Tool sandbox has no execution data — it receives input directly via `query`. The helper throws, n8n tries to annotate the error's `name` property, and that assignment fails because the error object is frozen.
|
|
13
|
+
|
|
14
|
+
**Fix**: remove `$fromAI()`. Read from `query` (or define an input schema, see [INPUT_SCHEMA.md](INPUT_SCHEMA.md)).
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
// ❌ Broken
|
|
18
|
+
const price = $fromAI('price', 'Car price in SEK', 'number');
|
|
19
|
+
|
|
20
|
+
// ✅ Unstructured — parse a JSON string
|
|
21
|
+
const params = JSON.parse(query);
|
|
22
|
+
const price = Number(params.price);
|
|
23
|
+
|
|
24
|
+
// ✅ Structured — with specifyInputSchema: true
|
|
25
|
+
const { price } = query;
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Error 2: `"Wrong output type returned"`
|
|
31
|
+
|
|
32
|
+
**Cause**: You returned the workflow item format (`[{json: {...}}]`) from the Code Tool. That format is for regular Code **nodes**; tools follow the LangChain contract and must return a string.
|
|
33
|
+
|
|
34
|
+
**Fix**: return a string. For structured output, stringify:
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
// ❌ Broken
|
|
38
|
+
return [{ json: { monthly_payment: 5405 } }];
|
|
39
|
+
|
|
40
|
+
// ✅ Fixed
|
|
41
|
+
return JSON.stringify({ monthly_payment: 5405 });
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Error 3: `"The response property should be a string, but it is an <type>"`
|
|
47
|
+
|
|
48
|
+
Where `<type>` is `object`, `undefined`, `function`, etc.
|
|
49
|
+
|
|
50
|
+
**Cause**: You returned a bare object, array, or nothing at all.
|
|
51
|
+
|
|
52
|
+
| Returned value | Error says | Fix |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `{ result: 42 }` | `...is an object` | `JSON.stringify({ result: 42 })` |
|
|
55
|
+
| `[1, 2, 3]` | `...is an object` | `JSON.stringify([1, 2, 3])` |
|
|
56
|
+
| *(no `return`)* | `...is an undefined` | Add a `return` |
|
|
57
|
+
| `undefined` | `...is an undefined` | Return something |
|
|
58
|
+
|
|
59
|
+
**Numbers are fine** — n8n auto-converts them to strings:
|
|
60
|
+
```javascript
|
|
61
|
+
return 42; // ✅ becomes "42"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Booleans are NOT auto-converted** — stringify explicitly:
|
|
65
|
+
```javascript
|
|
66
|
+
return String(someBoolean); // ✅
|
|
67
|
+
return JSON.stringify(someBoolean); // ✅
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Error 4: AI never calls the tool
|
|
73
|
+
|
|
74
|
+
**Symptom**: the agent answers from its own reasoning and ignores the tool. No tool invocation shows up in the execution trace.
|
|
75
|
+
|
|
76
|
+
**Common causes and fixes**:
|
|
77
|
+
|
|
78
|
+
1. **Generic name**. Default names like `Code Tool` or `My Tool` give the LLM no signal.
|
|
79
|
+
- Fix: rename to verb-y, domain-specific snake_case: `calculate_car_loan`, `search_orders`, `lookup_customer`.
|
|
80
|
+
|
|
81
|
+
2. **Description doesn't state the trigger**. "Calculates things" is too vague.
|
|
82
|
+
- Fix: explicitly list the user intents that should invoke the tool. `"Use this whenever the user asks about monthly cost, loan breakdown, or total interest."`
|
|
83
|
+
|
|
84
|
+
3. **Tool isn't wired**. The node sits in the canvas but isn't connected to the AI Agent's `ai_tool` input.
|
|
85
|
+
- Fix: connect it. Check the workflow JSON `connections` block has `"<tool_name>": { "ai_tool": [[{ "node": "AI Agent", "type": "ai_tool", "index": 0 }]] }`.
|
|
86
|
+
|
|
87
|
+
4. **Name violates `[A-Za-z0-9_]+`**. Spaces, hyphens, and emoji in the tool name cause silent skip on v1.1+.
|
|
88
|
+
- Fix: rename to `snake_case_only`.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Error 5: LLM sends malformed `query`
|
|
93
|
+
|
|
94
|
+
**Symptom**: your `JSON.parse(query)` throws, or fields come through as wrong types.
|
|
95
|
+
|
|
96
|
+
**Causes**:
|
|
97
|
+
- You're in unstructured mode and the description is ambiguous, so the LLM invents a format.
|
|
98
|
+
- You asked for a JSON string but the LLM sent a natural-language sentence.
|
|
99
|
+
- Numeric fields arrive as strings because the LLM serialized them that way.
|
|
100
|
+
|
|
101
|
+
**Fixes**, in order of preference:
|
|
102
|
+
|
|
103
|
+
1. **Switch to structured mode**. Set `specifyInputSchema: true` and define fields. The LLM now gets a typed schema and n8n validates before your code runs.
|
|
104
|
+
|
|
105
|
+
2. **Give a concrete example in the description**. LLMs imitate examples well:
|
|
106
|
+
```
|
|
107
|
+
Call with a single JSON string. Example:
|
|
108
|
+
{"price":439900,"down_payment":87980,"interest_rate":6.95}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
3. **Coerce defensively**:
|
|
112
|
+
```javascript
|
|
113
|
+
const params = JSON.parse(query);
|
|
114
|
+
const price = Number(params.price);
|
|
115
|
+
if (!isFinite(price)) throw new Error('price must be numeric');
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Error 6: `"$helpers is not defined"` / `"$input is not defined"`
|
|
121
|
+
|
|
122
|
+
**Cause**: you assumed the Code Tool sandbox exposes the same helpers as the Code node. It doesn't.
|
|
123
|
+
|
|
124
|
+
**Unavailable in Code Tool**:
|
|
125
|
+
- `$input`, `$json`, `$binary`
|
|
126
|
+
- `$node["OtherNode"]`
|
|
127
|
+
- `$helpers.httpRequest()`
|
|
128
|
+
- `$jmespath()`
|
|
129
|
+
- `this.getContext(...)`, `$getWorkflowStaticData(...)`
|
|
130
|
+
- `$fromAI()`
|
|
131
|
+
|
|
132
|
+
**Fix**:
|
|
133
|
+
- Pure computation? Stay in Code Tool, use plain JS.
|
|
134
|
+
- Need HTTP? Move to **HTTP Request Tool** (with `$fromAI()` in URL/body).
|
|
135
|
+
- Need other-node data or credentials? Move to **Call Sub-workflow Tool (`toolWorkflow`)** — its sub-workflow has a full Code node sandbox.
|
|
136
|
+
- Need state across calls? Not possible in Code Tool. Use a sub-workflow that reads/writes a Data Table, Redis, etc.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Error 7: Python-specific — `"name 'query' is not defined"`
|
|
141
|
+
|
|
142
|
+
**Cause**: in Python, the input variable is `_query` (underscore prefix), not `query`.
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
# ❌ Broken
|
|
146
|
+
result = process(query)
|
|
147
|
+
|
|
148
|
+
# ✅ Fixed
|
|
149
|
+
result = process(_query)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Error Prevention Checklist
|
|
155
|
+
|
|
156
|
+
Before saving a Code Tool:
|
|
157
|
+
|
|
158
|
+
- [ ] Tool **name** is snake_case, descriptive, and unique
|
|
159
|
+
- [ ] **Description** tells the LLM when to call it, with an example if unstructured
|
|
160
|
+
- [ ] **No `$fromAI()`** in the code body
|
|
161
|
+
- [ ] **No `$input`, `$json`, `$helpers`** — not in this sandbox
|
|
162
|
+
- [ ] Input read from `query` (JS) or `_query` (Python)
|
|
163
|
+
- [ ] All code paths `return` a string (or a number that auto-converts)
|
|
164
|
+
- [ ] If returning structured data, wrapped in `JSON.stringify(...)`
|
|
165
|
+
- [ ] Wired to an AI Agent via `ai_tool` connection
|
|
166
|
+
- [ ] For multi-field input: either example JSON in description, or `specifyInputSchema: true`
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Debugging tips
|
|
171
|
+
|
|
172
|
+
- **Use the Execution view**, not just the test output. The agent's tool invocation and raw input/output are visible there — you can see exactly what `query` the LLM sent.
|
|
173
|
+
- **Log inside the tool** by including fields in the returned JSON:
|
|
174
|
+
```javascript
|
|
175
|
+
return JSON.stringify({ received_query: query, result: /* ... */ });
|
|
176
|
+
```
|
|
177
|
+
The LLM sees the echo, and you can spot malformed input.
|
|
178
|
+
- **Test the tool without the LLM** by temporarily turning the tool node into a standalone Code node with hard-coded `query`, running it manually, then swapping back.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Input Schema for Code Tool (Structured Mode)
|
|
2
|
+
|
|
3
|
+
How to turn `@n8n/n8n-nodes-langchain.toolCode` into a **DynamicStructuredTool** so the LLM passes typed arguments instead of a free-form string.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Why use a schema?
|
|
8
|
+
|
|
9
|
+
Without a schema, the Code Tool is a LangChain `DynamicTool`:
|
|
10
|
+
- LLM sees: "one string argument called query"
|
|
11
|
+
- You must parse whatever the LLM sends
|
|
12
|
+
- Typos, missing fields, wrong types are your problem at runtime
|
|
13
|
+
|
|
14
|
+
With a schema, the Code Tool becomes a `DynamicStructuredTool`:
|
|
15
|
+
- LLM sees: a typed object with named fields and descriptions
|
|
16
|
+
- Runtime rejects invalid calls before your code runs
|
|
17
|
+
- Numeric fields stay numeric (no more `Number(params.price)` for every field)
|
|
18
|
+
- Tool calls are more reliable — most modern LLMs handle structured tools better than "here's a JSON string please"
|
|
19
|
+
|
|
20
|
+
**Cost**: a little config to define the schema, and the node must be on a version that supports it.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Enabling the schema
|
|
25
|
+
|
|
26
|
+
Set `specifyInputSchema: true` on the `toolCode` parameters. Two schema-definition styles:
|
|
27
|
+
|
|
28
|
+
### Style A: `fromJson` — paste a representative example (v≥1.3, recommended)
|
|
29
|
+
|
|
30
|
+
The easiest. Give n8n an example JSON, and it infers the schema for you.
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"parameters": {
|
|
35
|
+
"name": "calculate_car_loan",
|
|
36
|
+
"description": "Computes monthly car-loan payment using an annuity formula with optional balloon.",
|
|
37
|
+
"language": "javaScript",
|
|
38
|
+
"specifyInputSchema": true,
|
|
39
|
+
"schemaType": "fromJson",
|
|
40
|
+
"jsonSchemaExample": "{\n \"price\": 439900,\n \"down_payment\": 87980,\n \"interest_rate\": 6.95,\n \"months\": 36,\n \"residual_percent\": 50,\n \"setup_fee\": 695,\n \"monthly_admin_fee\": 59\n}",
|
|
41
|
+
"jsCode": "// query is now a validated OBJECT, not a string\nconst { price, down_payment, interest_rate, months, residual_percent, setup_fee = 0, monthly_admin_fee = 0 } = query;\n\nconst principal = price - down_payment;\nconst residual = price * (residual_percent / 100);\nconst r = interest_rate / 100 / 12;\nconst growth = Math.pow(1 + r, months);\nconst base = r === 0\n ? (principal - residual) / months\n : (principal - residual / growth) * r / (1 - 1 / growth);\nconst monthly_payment = base + monthly_admin_fee;\n\nreturn JSON.stringify({\n monthly_payment_sek: Math.round(monthly_payment),\n loan_amount: Math.round(principal)\n});"
|
|
42
|
+
},
|
|
43
|
+
"type": "@n8n/n8n-nodes-langchain.toolCode",
|
|
44
|
+
"typeVersion": 1.3,
|
|
45
|
+
"name": "calculate_car_loan"
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**How it works**: n8n looks at the example, infers `{price: number, down_payment: number, ...}`, and generates a JSON Schema. The LLM sees that schema and passes a validated object.
|
|
50
|
+
|
|
51
|
+
### Style B: `manual` — write the JSON Schema yourself
|
|
52
|
+
|
|
53
|
+
Use when you need descriptions per field, enums, min/max constraints, or optional fields.
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"parameters": {
|
|
58
|
+
"name": "calculate_car_loan",
|
|
59
|
+
"description": "Computes monthly car-loan payment.",
|
|
60
|
+
"language": "javaScript",
|
|
61
|
+
"specifyInputSchema": true,
|
|
62
|
+
"schemaType": "manual",
|
|
63
|
+
"inputSchema": "{\n \"type\": \"object\",\n \"required\": [\"price\", \"down_payment\", \"interest_rate\", \"months\", \"residual_percent\"],\n \"properties\": {\n \"price\": { \"type\": \"number\", \"description\": \"Car price in SEK\" },\n \"down_payment\": { \"type\": \"number\", \"description\": \"Down payment in SEK\" },\n \"interest_rate\": { \"type\": \"number\", \"description\": \"Annual nominal rate in percent, e.g. 6.95\" },\n \"months\": { \"type\": \"integer\", \"minimum\": 1, \"description\": \"Loan term in months\" },\n \"residual_percent\": { \"type\": \"number\", \"minimum\": 0, \"maximum\": 99, \"description\": \"Balloon as % of price\" },\n \"setup_fee\": { \"type\": \"number\", \"default\": 0 },\n \"monthly_admin_fee\": { \"type\": \"number\", \"default\": 0 }\n }\n}",
|
|
64
|
+
"jsCode": "const { price, down_payment, interest_rate, months, residual_percent, setup_fee = 0, monthly_admin_fee = 0 } = query;\n// ... same computation as above ...\nreturn JSON.stringify({ monthly_payment_sek: /*...*/ });"
|
|
65
|
+
},
|
|
66
|
+
"type": "@n8n/n8n-nodes-langchain.toolCode",
|
|
67
|
+
"typeVersion": 1.3,
|
|
68
|
+
"name": "calculate_car_loan"
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**When `manual` is worth it**:
|
|
73
|
+
- You want per-field `description` strings (the LLM reads these)
|
|
74
|
+
- You need `enum` values (e.g. currency: `["SEK", "EUR", "USD"]`)
|
|
75
|
+
- You need numeric constraints (`minimum`, `maximum`)
|
|
76
|
+
- You want to mark fields as optional cleanly
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## How `query` behaves with a schema
|
|
81
|
+
|
|
82
|
+
Source of truth from the ToolCode sandbox:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
const sandbox = new JsTaskRunnerSandbox(workflowMode, ctx, undefined, { query });
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The sandbox always receives `{ query }`. The difference is what `query` holds:
|
|
89
|
+
|
|
90
|
+
| Mode | Type of `query` | How to use |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| No schema | `string` | `JSON.parse(query)` if you want structure |
|
|
93
|
+
| With schema | `object` (validated) | Destructure: `const { price, months } = query;` |
|
|
94
|
+
|
|
95
|
+
In Python, the same applies — `_query` is a string without schema, a dict with schema.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Schema version compatibility
|
|
100
|
+
|
|
101
|
+
- `specifyInputSchema` and `schemaType: "manual"` with `inputSchema`: available in v1.2
|
|
102
|
+
- `schemaType: "fromJson"` with `jsonSchemaExample`: requires v≥1.3
|
|
103
|
+
|
|
104
|
+
Set `typeVersion: 1.3` on the node if you want `fromJson`. Older installs should use `manual`.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Picking a pattern
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
Does your tool need more than one input field?
|
|
112
|
+
├─ No (just a URL, question, text blob)
|
|
113
|
+
│ └─ Unstructured — skip the schema
|
|
114
|
+
├─ Yes, and fields are all typed (numbers, bools, enums)
|
|
115
|
+
│ └─ Structured with fromJson (easiest)
|
|
116
|
+
├─ Yes, and you need constraints or rich descriptions
|
|
117
|
+
│ └─ Structured with manual
|
|
118
|
+
└─ Yes, and fields are complex / reusable across agents
|
|
119
|
+
└─ Use toolWorkflow (sub-workflow tool) instead of toolCode
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Gotcha: schema must be valid JSON
|
|
125
|
+
|
|
126
|
+
`jsonSchemaExample` and `inputSchema` are **strings containing JSON**, not objects. Watch the escaping when you paste them into workflow JSON. If the node won't save or the LLM doesn't see the fields, validate the JSON separately first.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Gotcha: schema changes don't retroactively fix old agent runs
|
|
131
|
+
|
|
132
|
+
If an agent was already started with an unstructured tool and you flip it to structured, the agent's system prompt may still reflect the old contract until it's reloaded. Force a re-run / re-open the agent node after changing schema settings.
|