AutomationFlowsAI & RAG › Route AI Prompts Between Anthropic, Google Gemini, Mistral and Openai

Route AI Prompts Between Anthropic, Google Gemini, Mistral and Openai

ByTriple 8 Labs @triple8labs on n8n.io

This sub-workflow is called via Execute Workflow and routes a prompt to Anthropic, Google Gemini, Mistral, or OpenAI, then normalizes the model output into a single response field while estimating token usage and cost. Receives input from another workflow via the Execute…

Event trigger★★★★☆ complexity17 nodesExecute Workflow TriggerHTTP Request
AI & RAG Trigger: Event Nodes: 17 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #16330 — we link there as the canonical source.

This workflow follows the Execute Workflow Trigger → HTTP Request recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "h0ZOAW0PzyTkjUvN",
  "meta": {
    "builderVariant": "mcp",
    "aiBuilderAssisted": true
  },
  "name": "Route AI prompts to Anthropic, Google Gemini, Mistral, or OpenAI",
  "tags": [
    {
      "id": "Fur9Q2nIpUv2w23M",
      "name": "triple8labs",
      "createdAt": "2026-06-07T20:22:00.276Z",
      "updatedAt": "2026-06-07T20:22:00.276Z"
    }
  ],
  "nodes": [
    {
      "id": "8367556b-c4ff-44b9-a05c-1d47478e9e91",
      "name": "Sticky Note f15b7efd",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        -800
      ],
      "parameters": {
        "width": 904,
        "height": 2020,
        "content": "## Route AI prompts to Anthropic, Google Gemini, Mistral, or OpenAI\n\n### How it works\n\nCall this workflow from any workflow via **Execute Workflow** (sub-workflow) to route a prompt to Anthropic, Google Gemini, Mistral, or OpenAI. The response is normalised into a single `llm_response` field regardless of provider.\n\n**Minimum payload:**\n```\n{ \"userPrompt\": \"Your prompt here\" }\n```\n\n**All supported fields:**\n```\n{\n  \"userPrompt\":      \"...\",           // required\n  \"systemPrompt\":    \"...\",           // optional\n  \"llm_provider\":    \"google\",      // anthropic | google | mistral | openai\n  \"google_model\":    \"gemini-3.5-flash\",\n  \"anthropic_model\": \"claude-haiku-4-5-20251001\",\n  \"mistral_model\":   \"mistral-medium-latest\",\n  \"openai_model\":    \"gpt-5.4-mini\",\n  \"temperature\":     0.5,           // 0.0 - 1.0 (omitted for o-series reasoning models)\n  \"max_tokens\":      5000,          // integer or numeric string - both accepted\n  \"response_format\": \"json\"         // json | text\n}\n```\n\n**Returns** the original payload plus:\n| Field | Description |\n|---|---|\n| `llm_response` | Model text output, or `[API ERROR] ...` on failure |\n| `model_used` | Model that handled the request |\n| `provider_used` | Provider that handled the request |\n| `llm_response_length` | Character length of `llm_response` |\n| `llm_response_empty` | `true` if response is empty or an API error occurred |\n| `_input_tokens` | Input token count - actual from API if available, otherwise estimated |\n| `_output_tokens` | Output token count - actual from API if available, otherwise estimated |\n| `_estimated_cost_usd` | Estimated cost in USD. `null` if model not in pricing table |\n\n**Note on `response_format`:** For Google Gemini, `json` enforces `application/json` MIME type at the API level. For all other providers, it controls response parsing only - use your prompt to request JSON output.\n\n### Setup\n\n**1. Update the CONFIG node** with your preferred defaults:\n- `DEFAULT_PROVIDER` - which LLM to use when the caller does not specify\n- `DEFAULT_*_MODEL` - default model per provider\n- `DEFAULT_TEMPERATURE` / `DEFAULT_MAX_TOKENS` / `DEFAULT_RESPONSE_FORMAT`\n\n**2. Link credentials** for each provider you plan to use:\n- Anthropic API -> **Anthropic Chat**\n- Google AI (PaLM) API -> **Google Chat** *(the credential is named \"PaLM\" in n8n but works with current Gemini models)*\n- Mistral Cloud API -> **Mistral Chat**\n- OpenAI API -> **OpenAI Chat**\n\nCredentials for unused providers can be left unset.\n\n**3. Activate the workflow** before calling it. Inactive workflows cannot be reached via Execute Workflow.\n\n### Troubleshooting\n\n**\"userPrompt is required\"** - The calling workflow did not pass a `userPrompt` field. Field names are case-sensitive - `prompt` or `user_prompt` will not work.\n\n**\"Prompt too large\"** - Estimated token count exceeded 70% of the model's context limit. Shorten the prompt, reduce `DEFAULT_MAX_TOKENS`, or switch to a model with a larger context window.\n\n**`llm_response_empty: true`** - An API call failed. The `llm_response` field contains `[API ERROR] ...` with details. Common causes: invalid or missing credentials, rate limit (429), unsupported model name.\n\n**`_estimated_cost_usd` is null** - The model is not in the Cost Tracking node's pricing table. Add the model and its rates to the `COSTS` object in that node.\n\n### Notes\n- Prompt content is coerced to string and stripped of null bytes. Content-based filtering is the caller's responsibility.\n- Pricing in the Cost Tracking node is approximate. Verify against provider pricing pages and update the `COSTS` table as needed."
      },
      "typeVersion": 1
    },
    {
      "id": "0808e3b4-68e7-4ec1-8aff-a60edd941179",
      "name": "Sticky Note 6202f1fd",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        1232
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 400,
        "content": "## 1) Initialise"
      },
      "typeVersion": 1
    },
    {
      "id": "389f6971-5e09-4d0b-bbc3-b8ac8e0efa6d",
      "name": "Sticky Note acfb1d75",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        464,
        1232
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 400,
        "content": "## 2) Normalise & validate"
      },
      "typeVersion": 1
    },
    {
      "id": "78685cb6-d14b-44d4-ae5a-32b71850e543",
      "name": "Sticky Note 42d01980",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        928,
        944
      ],
      "parameters": {
        "color": 7,
        "width": 688,
        "height": 920,
        "content": "## 3) Route to provider"
      },
      "typeVersion": 1
    },
    {
      "id": "c476de12-71dc-4169-b1e1-48889e18e0ac",
      "name": "Sticky Note cf57dd67",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1632,
        1232
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 400,
        "content": "## 4) Merge & return"
      },
      "typeVersion": 1
    },
    {
      "id": "12d446f9-4d1a-4d64-9737-0701b85b4d0c",
      "name": "Execute Workflow Trigger",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "position": [
        80,
        1392
      ],
      "parameters": {
        "inputSource": "passthrough"
      },
      "typeVersion": 1.1
    },
    {
      "id": "fe3678aa-7a9e-4f10-a545-a414421f0186",
      "name": "CONFIG",
      "type": "n8n-nodes-base.set",
      "position": [
        304,
        1392
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "def_prov",
              "name": "DEFAULT_PROVIDER",
              "type": "string",
              "value": "google"
            },
            {
              "id": "def_anth",
              "name": "DEFAULT_ANTHROPIC_MODEL",
              "type": "string",
              "value": "claude-haiku-4-5-20251001"
            },
            {
              "id": "def_goog",
              "name": "DEFAULT_GOOGLE_MODEL",
              "type": "string",
              "value": "gemini-3.5-flash"
            },
            {
              "id": "def_mist",
              "name": "DEFAULT_MISTRAL_MODEL",
              "type": "string",
              "value": "mistral-medium-latest"
            },
            {
              "id": "def_oai",
              "name": "DEFAULT_OPENAI_MODEL",
              "type": "string",
              "value": "gpt-5.4-mini"
            },
            {
              "id": "def_temp",
              "name": "DEFAULT_TEMPERATURE",
              "type": "number",
              "value": 0.5
            },
            {
              "id": "def_tok",
              "name": "DEFAULT_MAX_TOKENS",
              "type": "number",
              "value": 5000
            },
            {
              "id": "def_fmt",
              "name": "DEFAULT_RESPONSE_FORMAT",
              "type": "string",
              "value": "json"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "726c3278-6dbb-4c84-9087-bc011c084fb4",
      "name": "Normalize Input",
      "type": "n8n-nodes-base.code",
      "position": [
        528,
        1392
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Normalize Input\nconst triggerData = $('Execute Workflow Trigger').first().json || {};\nconst j = { ...triggerData, ...($json || {}) };\n\nif (!j.userPrompt) throw new Error('userPrompt is required');\n\nfunction sanitizePrompt(val) {\n  if (val == null) return '';\n  return String(val).replace(/\\x00/g, '');\n}\n\nconst llm_provider = (j.llm_provider || j.DEFAULT_PROVIDER || 'google').toLowerCase();\n\nlet model;\nif (llm_provider === 'anthropic') {\n  model = j.anthropic_model || j.DEFAULT_ANTHROPIC_MODEL || 'claude-haiku-4-5-20251001';\n} else if (llm_provider === 'google') {\n  model = j.google_model || j.DEFAULT_GOOGLE_MODEL || 'gemini-3.5-flash';\n} else if (llm_provider === 'mistral') {\n  model = j.mistral_model || j.DEFAULT_MISTRAL_MODEL || 'mistral-medium-latest';\n} else if (llm_provider === 'openai') {\n  model = j.openai_model || j.DEFAULT_OPENAI_MODEL || 'gpt-5.4-mini';\n}\n\nconst temperature = typeof j.temperature === 'number'   ? j.temperature\n                  : typeof j.temperature === 'string'   ? parseFloat(j.temperature) || 0.5\n                  : typeof j.DEFAULT_TEMPERATURE === 'number' ? j.DEFAULT_TEMPERATURE\n                  : 0.5;\n\nconst max_tokens = parseInt(j.max_tokens, 10)\n  || parseInt(j.DEFAULT_MAX_TOKENS, 10)\n  || 5000;\n\nconst response_format = j.response_format || j.DEFAULT_RESPONSE_FORMAT || 'json';\nconst systemPrompt    = sanitizePrompt(j.systemPrompt);\nconst userPrompt      = sanitizePrompt(j.userPrompt);\n\nconst isReasoningModel = llm_provider === 'openai' && /^o[0-9]/i.test(model);\nconst isNewOpenAI      = llm_provider === 'openai' && /^gpt-5/i.test(model);\n\nconst provider_body = {\n  model,\n  ...(!isReasoningModel && { temperature }),\n  ...((isNewOpenAI || isReasoningModel) ? { max_completion_tokens: max_tokens } : { max_tokens }),\n  messages: [\n    { role: 'system', content: systemPrompt },\n    { role: 'user',   content: userPrompt }\n  ]\n};\n\nreturn {\n  json: {\n    ...j,\n    llm_provider,\n    response_format,\n    model_used:    model,\n    provider_used: llm_provider,\n    provider_body\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "972a68a3-d8e0-4bf7-89ee-e682e6fe5515",
      "name": "Validate Token Budget",
      "type": "n8n-nodes-base.code",
      "position": [
        752,
        1392
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "function estimateTokens(text, provider) {\n  if (!text) return 0;\n  const str = String(text);\n  if (provider === 'openai' || provider === 'anthropic') {\n    const words = str.trim() ? str.trim().split(/\\s+/).length : 0;\n    return Math.ceil(words * 1.3);\n  } else if (provider === 'google') {\n    return Math.ceil(str.length / 3.5);\n  }\n  return Math.ceil(str.length / 4);\n}\n\nconst provider      = ($json.llm_provider || 'google').toLowerCase();\nconst systemContent = $json.provider_body?.messages?.[0]?.content || '';\nconst userContent   = $json.provider_body?.messages?.[1]?.content || '';\n\nconst systemTokens = estimateTokens(systemContent, provider);\nconst userTokens   = estimateTokens(userContent,   provider);\nconst totalInput   = systemTokens + userTokens;\n\nconst MODEL_LIMITS = {\n  'claude-haiku-4-5-20251001':  200000,\n  'claude-haiku-4-5':           200000,\n  'claude-sonnet-4-6':         1000000,\n  'claude-sonnet-4-5-20250929': 200000,\n  'claude-sonnet-4-5':          200000,\n  'claude-opus-4-8':           1000000,\n  'claude-opus-4-7':           1000000,\n  'claude-fable-5':            1000000,\n  'gemini-3.5-flash':          1000000,\n  'gemini-3-flash':            1000000,\n  'gemini-2.5-flash':          1000000,\n  'gemini-2.5-flash-lite':     1000000,\n  'gpt-5.5':                   1000000,\n  'gpt-5.4':                   1000000,\n  'gpt-5.4-mini':               400000,\n  'gpt-4o':                     128000,\n  'gpt-4o-mini':                128000,\n  'mistral-medium-latest':      128000,\n  'mistral-small-latest':       128000,\n  'mistral-large-latest':       128000,\n};\n\nconst model = $json.provider_body?.model || '';\nconst limit = MODEL_LIMITS[model] || 32000;\n\nif (totalInput > limit * 0.7) {\n  throw new Error(\n    `Prompt too large: ~${totalInput} estimated tokens (model limit: ${limit}). ` +\n    `Reduce prompt length or switch to a larger model.`\n  );\n}\n\nreturn { json: { ...$json, _token_estimate: totalInput } };"
      },
      "typeVersion": 2
    },
    {
      "id": "24080dcb-f852-46d9-bf70-5cbf24f16852",
      "name": "Which Provider?",
      "type": "n8n-nodes-base.switch",
      "position": [
        976,
        1360
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "anthropic",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "d1c1e6e7-06b7-446c-9ebc-1cbf73e7eca0",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.llm_provider }}",
                    "rightValue": "anthropic"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "google",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "2252afb1-5f07-4b84-89ee-1d036e70ec3b",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.llm_provider }}",
                    "rightValue": "google"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "mistral",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "36979d2f-1458-4777-b3b7-df7e0279227b",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.llm_provider }}",
                    "rightValue": "mistral"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "openai",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "f260d701-b399-452f-b4ad-6c60c6282a45",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.llm_provider }}",
                    "rightValue": "openai"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {
          "ignoreCase": true
        }
      },
      "typeVersion": 3.2
    },
    {
      "id": "9af34dfa-c19a-4c28-9844-3e9e70a1032f",
      "name": "Anthropic Chat",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        1312,
        1104
      ],
      "parameters": {
        "url": "https://api.anthropic.com/v1/messages",
        "method": "POST",
        "options": {
          "timeout": 300000,
          "response": {
            "response": {
              "fullResponse": true
            }
          }
        },
        "jsonBody": "={{ JSON.stringify({ model: $json.provider_body.model, max_tokens: $json.provider_body.max_tokens, temperature: $json.provider_body.temperature, system: $json.provider_body?.messages?.[0]?.content || '', messages: [ { role: 'user', content: [ { type: 'text', text: $json.provider_body?.messages?.[1]?.content || '' } ] } ] }) }}",
        "sendBody": true,
        "jsonHeaders": "={{ JSON.stringify({ 'Content-Type': 'application/json', 'anthropic-version': '2023-06-01' }) }}",
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "specifyHeaders": "json",
        "nodeCredentialType": "anthropicApi"
      },
      "typeVersion": 3
    },
    {
      "id": "8b231a4a-bda0-4c6a-9fac-013d56e8b469",
      "name": "Merge Provider Response",
      "type": "n8n-nodes-base.code",
      "position": [
        1696,
        1392
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Merge Provider Response\nconst idx  = typeof $itemIndex === 'number' ? $itemIndex : 0;\nconst orig = ($items('Validate Token Budget')?.[idx]?.json) || {};\n\nconst base = $json?.body ?? $json ?? {};\n\nfunction extractContentFromJSON(str) {\n  if (!str) return str;\n  let clean = str.replace(/^```(json)?\\n?/i, '').replace(/\\n?```$/i, '').trim();\n  try {\n    const parsed = JSON.parse(clean);\n    if (typeof parsed === 'object' && parsed !== null) {\n      if (parsed.output)      return parsed.output;\n      if (parsed.response)    return parsed.response;\n      if (parsed.content)     return parsed.content;\n      if (parsed.story)       return parsed.story;\n      if (parsed.text)        return parsed.text;\n      if (parsed.story_bible) return parsed.story_bible;\n      return clean;\n    }\n    return clean;\n  } catch (e) {\n    clean = clean.replace(/(?<!:)(\\s)\"([a-zA-Z0-9])/g, '$1\\\\\"$2');\n    clean = clean.replace(/([a-zA-Z0-9\\.?!])\"(?![,\\}\\]:])/g, '$1\\\\\"');\n    return clean;\n  }\n}\n\nlet outputText   = '';\nlet rawErrorBody = '';\n\nif (base.candidates?.[0]?.content?.parts?.[0]?.text) {\n  outputText = base.candidates[0].content.parts[0].text;\n}\nelse if (Array.isArray(base.content) && base.content[0]?.text) {\n  outputText = base.content[0].text;\n}\nelse if (base.choices?.[0]?.message?.content) {\n  outputText = base.choices[0].message.content;\n}\nelse if (base.text)                                        outputText = base.text;\nelse if (base.content && typeof base.content === 'string') outputText = base.content;\nelse if (base.message?.content)                            outputText = base.message.content;\nelse if (base.output)                                      outputText = base.output;\nelse if (base.error) {\n  rawErrorBody = '[API ERROR] ' + JSON.stringify(base.error);\n} else {\n  const raw = JSON.stringify($json);\n  if (raw && raw.length > 2) rawErrorBody = '[UNEXPECTED RESPONSE] ' + raw.substring(0, 300);\n}\n\nconst trimmed         = outputText ? outputText.trim() : '';\nconst isJSON          = trimmed.startsWith('{') || trimmed.startsWith('[') || trimmed.includes('```json');\nconst callerWantsJson = (orig.response_format || '').toLowerCase().includes('json');\n\nif (isJSON) {\n  if (callerWantsJson) {\n    outputText = trimmed.replace(/^```(json)?\\n?/i, '').replace(/\\n?```$/i, '').trim();\n  } else {\n    outputText = extractContentFromJSON(outputText);\n  }\n}\n\nlet _actual_input_tokens  = null;\nlet _actual_output_tokens = null;\n\nif (base.usage) {\n  if (typeof base.usage.input_tokens === 'number') {\n    _actual_input_tokens  = base.usage.input_tokens;\n    _actual_output_tokens = base.usage.output_tokens    ?? null;\n  }\n  else if (typeof base.usage.prompt_tokens === 'number') {\n    _actual_input_tokens  = base.usage.prompt_tokens;\n    _actual_output_tokens = base.usage.completion_tokens ?? null;\n  }\n}\nelse if (base.usageMetadata) {\n  _actual_input_tokens  = base.usageMetadata.promptTokenCount      ?? null;\n  _actual_output_tokens = base.usageMetadata.candidatesTokenCount  ?? null;\n}\n\ndelete orig.provider_body;\n\nreturn {\n  json: {\n    ...orig,\n    llm_response:         outputText || rawErrorBody,\n    _actual_input_tokens,\n    _actual_output_tokens,\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "e39b37a7-7926-404d-a120-b242e9dfd133",
      "name": "Google Chat",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        1312,
        1296
      ],
      "parameters": {
        "url": "=https://generativelanguage.googleapis.com/v1beta/models/{{$json.provider_body.model}}:generateContent",
        "body": "={{ JSON.stringify({ \"contents\": [{ \"role\": \"user\", \"parts\": [{ \"text\": $json.provider_body?.messages?.[1]?.content || \"\" }] }], \"systemInstruction\": { \"parts\": [{ \"text\": $json.provider_body?.messages?.[0]?.content || \"\" }] }, \"generationConfig\": { \"temperature\": $json.provider_body.temperature, \"maxOutputTokens\": $json.provider_body.max_tokens, \"responseMimeType\": ($json.response_format || \"\").toLowerCase().includes(\"json\") ? \"application/json\" : \"text/plain\" } }) }}",
        "method": "POST",
        "options": {
          "timeout": 300000,
          "response": {
            "response": {
              "fullResponse": true
            }
          }
        },
        "sendBody": true,
        "contentType": "raw",
        "jsonHeaders": "={{ JSON.stringify({ 'Content-Type': 'application/json' }) }}",
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "rawContentType": "application/json",
        "specifyHeaders": "json",
        "nodeCredentialType": "googlePalmApi"
      },
      "typeVersion": 4.3
    },
    {
      "id": "001aefdf-2257-4c00-95dd-26277bf9bb6a",
      "name": "Mistral Chat",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        1312,
        1488
      ],
      "parameters": {
        "url": "https://api.mistral.ai/v1/chat/completions",
        "method": "POST",
        "options": {
          "timeout": 300000,
          "response": {
            "response": {
              "fullResponse": true
            }
          }
        },
        "jsonBody": "={{ $json.provider_body }}",
        "sendBody": true,
        "jsonHeaders": "={{ JSON.stringify({ 'Content-Type': 'application/json' }) }}",
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "specifyHeaders": "json",
        "nodeCredentialType": "mistralCloudApi"
      },
      "typeVersion": 3
    },
    {
      "id": "38377ebd-df47-4c47-bfec-f51e94b25242",
      "name": "OpenAI Chat",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        1312,
        1680
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/chat/completions",
        "method": "POST",
        "options": {
          "timeout": 300000,
          "response": {
            "response": {
              "fullResponse": true
            }
          }
        },
        "jsonBody": "={{ $json.provider_body }}",
        "sendBody": true,
        "jsonHeaders": "={{ JSON.stringify({ 'Content-Type': 'application/json' }) }}",
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "specifyHeaders": "json",
        "nodeCredentialType": "openAiApi"
      },
      "typeVersion": 3
    },
    {
      "id": "cbf91aba-8388-4e9f-8fd6-a1d1fe24c1dc",
      "name": "Cost Tracking",
      "type": "n8n-nodes-base.code",
      "position": [
        1904,
        1392
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const COSTS = {\n  anthropic: {\n    'claude-haiku-4-5-20251001':  { input: 0.001,    output: 0.005   },\n    'claude-haiku-4-5':           { input: 0.001,    output: 0.005   },\n    'claude-sonnet-4-6':          { input: 0.003,    output: 0.015   },\n    'claude-sonnet-4-5-20250929': { input: 0.003,    output: 0.015   },\n    'claude-sonnet-4-5':          { input: 0.003,    output: 0.015   },\n    'claude-opus-4-8':            { input: 0.005,    output: 0.025   },\n    'claude-opus-4-7':            { input: 0.005,    output: 0.025   },\n    'claude-fable-5':             { input: 0.01,     output: 0.05    },\n  },\n  google: {\n    'gemini-3.5-flash':           { input: 0.0003,   output: 0.0025  },\n    'gemini-3-flash':             { input: 0.001,    output: 0.004   },\n    'gemini-2.5-flash':           { input: 0.0003,   output: 0.0025  },\n    'gemini-2.5-flash-lite':      { input: 0.0001,   output: 0.0004  },\n  },\n  openai: {\n    'gpt-5.5':                    { input: 0.005,    output: 0.02    },\n    'gpt-5.4':                    { input: 0.005,    output: 0.02    },\n    'gpt-5.4-mini':               { input: 0.0003,   output: 0.0012  },\n    'gpt-4o':                     { input: 0.0025,   output: 0.01    },\n    'gpt-4o-mini':                { input: 0.00015,  output: 0.0006  },\n  },\n  mistral: {\n    'mistral-medium-latest':      { input: 0.0004,   output: 0.002   },\n    'mistral-small-latest':       { input: 0.0001,   output: 0.0003  },\n    'mistral-large-latest':       { input: 0.002,    output: 0.006   },\n  },\n};\n\nconst provider    = $json.provider_used || '';\nconst model       = $json.model_used    || '';\nconst llmResponse = $json.llm_response  || '';\n\nconst inputTokens  = $json._actual_input_tokens\n  ?? ($json._token_estimate || 0);\nconst outputTokens = $json._actual_output_tokens\n  ?? Math.ceil(llmResponse.length / 4);\n\nconst rates = COSTS[provider]?.[model];\nconst estimatedCostUsd = rates\n  ? (inputTokens / 1000) * rates.input + (outputTokens / 1000) * rates.output\n  : null;\n\nreturn {\n  json: {\n    ...$json,\n    llm_response_length: llmResponse.length,\n    llm_response_empty:  llmResponse.length === 0,\n    _input_tokens:       inputTokens,\n    _output_tokens:      outputTokens,\n    _estimated_cost_usd: estimatedCostUsd,\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "7b8431ae-0ec1-4932-ac62-0378b7317326",
      "name": "Return",
      "type": "n8n-nodes-base.code",
      "position": [
        2080,
        1392
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const idx = typeof $itemIndex === 'number' ? $itemIndex : 0;\nconst orig = ($items('Execute Workflow Trigger')?.[idx]?.json) || {};\n\nreturn { json: { ...orig, ...$json } };"
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": true,
    "executionOrder": "v1"
  },
  "versionId": "fc9e2858-641d-4dfc-96b9-fc5107ea53f0",
  "nodeGroups": [],
  "connections": {
    "CONFIG": {
      "main": [
        [
          {
            "node": "Normalize Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Chat": {
      "main": [
        [
          {
            "node": "Merge Provider Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat": {
      "main": [
        [
          {
            "node": "Merge Provider Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mistral Chat": {
      "main": [
        [
          {
            "node": "Merge Provider Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cost Tracking": {
      "main": [
        [
          {
            "node": "Return",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Anthropic Chat": {
      "main": [
        [
          {
            "node": "Merge Provider Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Input": {
      "main": [
        [
          {
            "node": "Validate Token Budget",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Which Provider?": {
      "main": [
        [
          {
            "node": "Anthropic Chat",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Chat",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Mistral Chat",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "OpenAI Chat",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Token Budget": {
      "main": [
        [
          {
            "node": "Which Provider?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Provider Response": {
      "main": [
        [
          {
            "node": "Cost Tracking",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute Workflow Trigger": {
      "main": [
        [
          {
            "node": "CONFIG",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This sub-workflow is called via Execute Workflow and routes a prompt to Anthropic, Google Gemini, Mistral, or OpenAI, then normalizes the model output into a single response field while estimating token usage and cost. Receives input from another workflow via the Execute…

Source: https://n8n.io/workflows/16330/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

[2/2] KNN classifier (lands dataset). Uses httpRequest, stickyNote, executeWorkflowTrigger. Event-driven trigger; 18 nodes.

HTTP Request, Execute Workflow Trigger
AI & RAG

Workflows from the webinar "Build production-ready AI Agents with Qdrant and n8n".

HTTP Request, Execute Workflow Trigger
AI & RAG

[3/3] Anomaly detection tool (crops dataset). Uses stickyNote, httpRequest, executeWorkflowTrigger. Event-driven trigger; 17 nodes.

HTTP Request, Execute Workflow Trigger
AI & RAG

Workflows from the webinar "Build production-ready AI Agents with Qdrant and n8n".

HTTP Request, Execute Workflow Trigger
AI & RAG

works with selfhosted Supabase

Execute Workflow Trigger, Form Trigger, HTTP Request