{"record":{"id":"863f55119b88fd21","repo":"n8n-io/n8n","slug":"llm-doesn-t-support-binding-tools","errorCode":null,"errorMessage":"LLM doesn't support binding tools","messagePattern":"LLM doesn't support binding tools","errorType":"exception","errorClass":"OperationalError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/ai-workflow-builder.ee/evaluations/evaluators/llm-judge/evaluators/base.ts","lineNumber":26,"sourceCode":"\nimport type { EvaluationInput } from '../evaluation';\n\ntype EvaluatorChainInput = {\n\tuserPrompt: string;\n\tgeneratedWorkflow: string;\n\treferenceSection: string;\n\tagentTextResponse?: string;\n\tworkflowBefore?: string;\n};\n\nexport function createEvaluatorChain<TResult extends Record<string, unknown>>(\n\tllm: BaseChatModel,\n\tschema: z.ZodType<TResult>,\n\tsystemPrompt: string,\n\thumanTemplate: string,\n): RunnableSequence<EvaluatorChainInput, TResult> {\n\tif (!llm.bindTools) {\n\t\tthrow new OperationalError(\"LLM doesn't support binding tools\");\n\t}\n\n\tconst prompt = ChatPromptTemplate.fromMessages([\n\t\tnew SystemMessage(systemPrompt),\n\t\tHumanMessagePromptTemplate.fromTemplate(humanTemplate),\n\t]);\n\n\tconst llmWithStructuredOutput = llm.withStructuredOutput<TResult>(schema);\n\n\treturn RunnableSequence.from<EvaluatorChainInput, TResult>([prompt, llmWithStructuredOutput]);\n}\n\nexport async function invokeEvaluatorChain<TResult>(\n\tchain: Runnable<EvaluatorChainInput, TResult>,\n\tinput: EvaluationInput,\n\tconfig?: RunnableConfig,\n): Promise<TResult> {\n\tconst referenceSection =","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/ai-workflow-builder.ee/evaluations/evaluators/llm-judge/evaluators/base.ts#L8-L44","documentation":"`createEvaluatorChain` builds a LangChain structured-output chain for an LLM-judge evaluator; before doing so it asserts that `llm.bindTools` exists. `bindTools` is the LangChain capability marker for tool-calling/structured-output models, so its absence means the chosen model cannot drive the judge schema. Despite the message text, the chain uses `withStructuredOutput`, not `bindTools` directly — the check is a proxy for 'this model supports the tool-calling features structured output relies on'. Thrown as `OperationalError` (a transient/operational n8n-workflow error class).","triggerScenarios":"Passing a `BaseChatModel` subclass that does not implement `bindTools` — e.g. a dummy/fake model in tests, an older or non-tool-enabled LangChain integration, or a model wrapper that delegates to a backend without function calling. The check is `if (!llm.bindTools)`.","commonSituations":"Swapping in a smaller/cheaper local model (no tool support) to save cost during eval dev; using a stub model in a test harness; a LangChain version mismatch where `bindTools` was renamed/removed; misconfigured custom model adapter.","solutions":["Use a model class that implements `bindTools` — OpenAI `gpt-4o`/`gpt-4.1` family, Anthropic Claude, Google Gemini, or any LangChain `ChatModel` advertising tool calling.","If you maintain a custom model adapter, implement `bindTools(tools)` (and `withStructuredOutput`) on the class.","For tests, inject a fake model that defines `bindTools` and `withStructuredOutput` rather than a bare `BaseChatModel`."],"exampleFix":"// before\nconst judge = new FakeChatModel({ content: '{}' }); // no bindTools\ncreateEvaluatorChain(judge, schema, sys, human);\n// after\nclass FakeJudge extends BaseChatModel {\n  bindTools() { return this; }\n  withStructuredOutput(s) { return { invoke: async () => ({...}) }; }\n  _generate(): Promise<ChatResult> { return Promise.resolve({ generations: [{ message: new AIMessage('{}') }] }); }\n  _llmType() { return 'fake-judge'; }\n}\ncreateEvaluatorChain(new FakeJudge({}), schema, sys, human);","handlingStrategy":"type-guard","validationCode":"import type { BaseChatModel } from '@langchain/core/language_models/chat_models';\n\nfunction supportsStructuredOutput(llm: BaseChatModel): boolean {\n  return typeof (llm as { bindTools?: unknown }).bindTools === 'function';\n}\nif (!supportsStructuredOutput(judgeLlm)) {\n  throw new Error('selected judge LLM does not support tool binding / structured output; pick a tool-capable model');\n}\nconst chain = createEvaluatorChain(judgeLlm, schema, sys, human);","typeGuard":"function isToolCapableModel(llm: unknown): llm is BaseChatModel & { bindTools: Function; withStructuredOutput: Function } {\n  return !!llm && typeof (llm as { bindTools?: unknown }).bindTools === 'function'\n    && typeof (llm as { withStructuredOutput?: unknown }).withStructuredOutput === 'function';\n}","tryCatchPattern":null,"preventionTips":["Build a small registry of allowed judge models and reject anything not on it at config time.","In tests, inject a fake model that implements `bindTools` and `withStructuredOutput` rather than a bare `BaseChatModel`.","Assert the capability guard in a unit test so an SDK rename of `bindTools` is caught immediately."],"tags":["llm","langchain","evaluations","llm-judge","types"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}