This workflow follows the Agent → Chat Trigger 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 →
{
"name": "ai_chat_agent",
"nodes": [
{
"parameters": {
"promptType": "define",
"text": "={{ $json.chatInput }}",
"hasOutputParser": true,
"needsFallback": true,
"options": {
"systemMessage": "<instructions>\n<persona>\nYou are Stonewalker: a mystical, concise, and helpful guide who uncovers hidden, interesting places. Speak with clarity, wisdom, and brevity.\n</persona>\n\n<goal>\nHelp the user discover unique places of interest using available tools. If the user\u2019s input is incomplete or unnormalized, make an educated guess, normalize as needed, or ask a clarifying question if confidence is low. \nAlways output a persona-driven message to the user and a JSON array of relevant items, each with a decimal \u201cStonewalker Stars\u201d rating (your confidence they\u2019ll enjoy it).\n</goal>\n\n<steps>\n1. Assess if the input contains both a valid intent and a valid location (or if either must be guessed/clarified).\n - You may use your history as context to determine what the user is trying to communicate.\n2. Normalize both intent and location for tool use. \n - If you must guess, be explicit and acknowledge this in your response.\n3. Search using ALL available tools. \n - If no relevant results, inform the user and help broaden their search.\n4. For each result, assign a \u201cStonewalker Stars\u201d score (0\u20131) using the official scoring formula.\n - Drop any result that scores 0 for relevance.\n5. Return all relevant data in the structured response. If one or more items do not apply, simply output an empty array, object, or empty string to ensure you pass validation.\n</steps>\n\n<tools class=\"constraints\">\nYou should always attempt to call every tool with a timeout set. Do not wait for an extended amount of time for results to return but you should at least try to retrieve some data from each tool.\n\nWhen outputting data from multiple tools, be sure that it is all in a single list. The results should be normalized so you can include the basic requirements and additional extras that are relevant.\n</tools>\n\n\n<user-input-normalization class=\"constraints\">\nNever pass user input directly to any tool. Always normalize first. \nIf intent is unclear, do your best but acknowledge the guess and ask for clarification.\nBe aware that intent and location may not always appear in the same order. The user may output location first and then intent. It is up to you to know which is which.\n</user-input-normalization>\n\n<intent-normalization class=\"constraints\">\nThe user\u2019s \u201cintent\u201d may be any noun or adjective-noun (e.g., \u201cmystical scary graveyards\u201d). \n- Encourage unique and specific requests, but preserve their exact wording (adjectives and noun together as a single string).\n- Only modify to clarify or combine, never split into separate keywords.\n<example>\nUser: \u201cmystical scary graveyards\u201d \nNormalized: \u201cmystical scary graveyards\u201d\n</example>\n</intent-normalization>\n\n<location-normalization class=\"constraints\">\nNormalize any location input (city, region, or coordinates) to: `<city>, <state code>, <country>`. \nIf needed, infer from common abbreviations or misspellings.\n<example>\nUser: \u201cAtl\u201d \nNormalized: \u201cAtlanta, GA, United States\u201d\n</example>\n</location-normalization>\n\n<output class=\"constraints\">\nFollowing the structured output example, always output at minimum: \n1. Your concise, mystical message to the user (persona-driven) \n2. The user's 'intent' after you parsed so it can be stored in the cache\n3. The normalized 'location' given by the user to store in the cache.\n4. A JSON array of areas of interest as returned by the tool, each with a decimal `score` in [0,1] representing your \u201cStonewalker Stars\u201d confidence in their fit for the user\u2019s intent. Listed with most relevant first.\n\nYou must always return valid JSON fenced by a markdown code block. Do not return any additional text.\n<example-schema-return>\n```json\n{\n \"user_intent\": \"quiet, beach-like spots to sit by the water\",\n \"user_location\": \"Tallapoosa, GA\",\n \"response\": \"Tallapoosa isn\u2019t coastal, but you\u2019ve got mellow shorelines within a day-use drive. Try Yellowjacket Beach on West Point Lake for a true sand-under-toes vibe, or Lake Harding\u2019s small sandy pull-offs for a quieter read-by-the-water afternoon. Pack snacks, bug spray, and cash for day-use where posted.\",\n \"places\": [\n {\n \"name\": \"Yellowjacket Beach, West Point Lake\",\n \"description\": \"Managed day-use area with a sandy swim beach, restrooms, and picnic tables on a large reservoir.\",\n \"address\": \"Yellowjacket Creek Recreation Area, LaGrange, GA 30240\",\n \"city\": \"LaGrange\",\n \"state\": \"GA\",\n \"country\": \"US\",\n \"coordinates\": { \"lat\": 32.935, \"lng\": -85.129 },\n \"url\": \"https://example.com/west-point-lake-yellowjacket\",\n \"tags\": [\"lake\", \"swim beach\", \"picnic\", \"family-friendly\"],\n \"stonewalker_stars\": 4.4,\n \"confidence\": 0.87,\n \"source\": \"google_places\",\n \"source_id\": \"west_point_yellowjacket_beach\",\n \"cached_at\": \"2025-09-01T01:10:00Z\"\n },\n {\n \"name\": \"Lake Harding (select sandy pull-offs)\",\n \"description\": \"Reservoir on the Chattahoochee with small informal sandy edges; good for low-key shoreline lounging.\",\n \"address\": \"Lake Harding, GA/AL border\",\n \"city\": \"Salem\",\n \"state\": \"AL\",\n \"country\": \"US\",\n \"coordinates\": { \"lat\": 32.682, \"lng\": -85.125 },\n \"url\": \"https://example.com/lake-harding-shore\",\n \"tags\": [\"lake\", \"quiet\", \"shore access\"],\n \"stonewalker_stars\": 3.9,\n \"confidence\": 0.78,\n \"source\": \"google_maps\",\n \"source_id\": \"lake_harding_misc_shore\",\n \"cached_at\": \"2025-09-01T01:10:00Z\"\n }\n ]\n}\n```\n</example-schema-return>\n</output>\n\n<stonewalker-scoring class=\"instructions\">\nUNDERFOOT SCORING (always run after retrieving items)\n- For each item, calculate a `score` in [0,1] using:\n - **relevance** ([0,1]): Text match to user intent + location in title/description/categories.\n - **distance_component** ([0,1]):\n - 0\u20135 miles: ~1.0\n - 5\u201315 miles: ~0.7\n - 15\u201335 miles: ~0.4\n - >35 miles: ~0.15\n - missing: 0.5\n - **app_fit** ([0,1]):\n - High (0.8\u20131.0): public, safe, experience-forward (tours, landmarks, museums, parks, historic sites, oddities, festivals, etc.)\n - Low (0\u20130.2): private, unsafe, services (funeral homes, hospitals, offices, explicit/adult, logistics, generic retail, etc.)\n - Special: Cemeteries medium if historic/tour; low otherwise.\n- Final: `score = clamp01(0.5*relevance + 0.2*distance_component + 0.3*app_fit)`\n- ALWAYS attach `score` to each item (do not invent items/fields, use URL as key).\n- Consistently apply rules and do not return results that score 0.\n</stonewalker-scoring>\n</instructions>\n",
"maxIterations": 5,
"returnIntermediateSteps": false
}
},
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 2.2,
"position": [
336,
960
],
"id": "0968cb4e-b08c-4ea0-8316-492b853dc8ee",
"name": "chat_agent",
"onError": "continueRegularOutput"
},
{
"parameters": {
"model": {
"__rl": true,
"value": "gpt-4.1-mini",
"mode": "list",
"cachedResultName": "gpt-4.1-mini"
},
"options": {
"maxTokens": 512,
"temperature": 0.4
}
},
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.2,
"position": [
112,
1184
],
"id": "c8b14b74-d852-4452-91d3-e9eb0dd09b1e",
"name": "gpt_4.1_mini",
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"message": "={{ $json.text }}\n",
"waitUserReply": false,
"options": {
"memoryConnection": true
}
},
"type": "@n8n/n8n-nodes-langchain.chat",
"typeVersion": 1,
"position": [
1568,
1056
],
"id": "268279a9-5f55-4737-a24e-5786bca7f3bd",
"name": "respond_to_chat",
"onError": "continueErrorOutput"
},
{
"parameters": {
"model": {
"__rl": true,
"value": "gpt-4.1",
"mode": "list",
"cachedResultName": "gpt-4.1"
},
"options": {
"maxTokens": 512,
"temperature": 0.3
}
},
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.2,
"position": [
240,
1184
],
"id": "6d4b1c1b-6629-4e06-8b19-c20a10925e97",
"name": "gpt_4.1_full",
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"workflowId": {
"__rl": true,
"value": "wRCvCmRGcILIoLjy",
"mode": "list",
"cachedResultName": "Underfoot \u2014 wf_error_notifications"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"json": "={{ $input.query }}",
"callingFlow": "={{ `$$json.callingWorkflow.name: $execution.id` }}"
},
"matchingColumns": [],
"schema": [
{
"id": "json",
"displayName": "json",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "object"
},
{
"id": "callingFlow",
"displayName": "callingFlow",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "string"
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": true
},
"options": {
"waitForSubWorkflow": false
}
},
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [
1632,
864
],
"id": "59262f79-432b-403d-b51a-3265e55118f0",
"name": "call_discord_error_notifier",
"onError": "continueRegularOutput"
},
{
"parameters": {
"public": true,
"initialMessages": "Welcome to Underfoot \ud83e\udea8 Where are you headed and what do you hope to uncover there? The stones will tell us exactly where to go.",
"options": {
"inputPlaceholder": "I'd love to find the oldest grave that exists in Salem.",
"subtitle": "Uncover hidden gems with the legendary Stonewalker",
"title": "Underfoot",
"responseMode": "responseNodes"
}
},
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
"typeVersion": 1.3,
"position": [
-336,
960
],
"id": "a982f942-cc47-466f-9a89-c710ed0244d6",
"name": "hosted_chat_trigger",
"onError": "continueRegularOutput"
},
{
"parameters": {
"description": "Use this tool to search Google for web pages, places, or events matching the user\u2019s query. Provide only when the user requests information that can be found through a general web search (news, local businesses, things to do, etc.). Do not use for specialized databases or tasks requiring structured/official data.",
"workflowId": {
"__rl": true,
"value": "So4ty17033VQwdOO",
"mode": "list",
"cachedResultName": "Underfoot \u2014 wf_serp_search_tool"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"intent": "={{ $fromAI('intent', `the thing or place the user wants info about`, 'string') }}",
"location": "={{ $fromAI('location', `the city, state, and country to look for events in`, 'string') }}"
},
"matchingColumns": [],
"schema": [
{
"id": "intent",
"displayName": "intent",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "string"
},
{
"id": "location",
"displayName": "location",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "string"
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.2,
"position": [
496,
1184
],
"id": "32721a92-725d-4fb7-be5e-7af958305e87",
"name": "call_google_serp"
},
{
"parameters": {
"httpMethod": "POST",
"path": "af58e54e-336c-4a32-bda0-430b6e447076",
"responseMode": "responseNode",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [
-336,
528
],
"id": "ffbfbbc4-048f-45af-8db6-39891d4cb84a",
"name": "webhook_trigger",
"onError": "continueRegularOutput"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json.response }}",
"options": {
"responseCode": 200
}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.4,
"position": [
-112,
304
],
"id": "b21695e4-2b41-44c9-9ae3-7f819bbab2e1",
"name": "respond_to_webhook"
},
{
"parameters": {},
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"typeVersion": 1.3,
"position": [
368,
1184
],
"id": "4cba12d8-ccd9-4c83-9850-34bc6dc71c2a",
"name": "agent_memory"
},
{
"parameters": {
"jsCode": "// Code (Run Once for All Items)\n// Pretty-print normalized agent output into clean Markdown for the chat UI,\n// even when `places` is empty or the message still contains an embedded JSON blob.\n//\n// Expected input per item (from normalizer):\n// { message, intent, location, places, intermediateSteps? }\n// Output per item: { text: \"<markdown>\" }\n\nconst items = $input.all();\n\n// ---------- helpers ----------\nfunction extractFirstJsonBlock(text = \"\") {\n const s = String(text);\n const objStart = s.indexOf(\"{\");\n if (objStart === -1) return null;\n // find the last } and try to cut there (we're not parsing here\u2014just removing blob from prose)\n const objEnd = s.lastIndexOf(\"}\");\n if (objEnd > objStart) return s.slice(objStart, objEnd + 1);\n return null;\n}\n\nfunction stripEmbeddedJson(text = \"\") {\n const block = extractFirstJsonBlock(text);\n if (!block) return text?.trim() ?? \"\";\n const start = text.indexOf(block);\n if (start === -1) return text?.trim() ?? \"\";\n const before = text.slice(0, start).trimEnd();\n const after = text.slice(start + block.length).trimStart();\n return (before || after || text).trim();\n}\n\nfunction fmtAddress(p) {\n const addr = [p.address, [p.city, p.state].filter(Boolean).join(\", \"), p.country]\n .filter(Boolean)\n .join(\", \");\n return addr || null;\n}\n\nfunction renderStars(score) {\n if (typeof score !== \"number\" || !isFinite(score)) return null;\n const five = Math.round(score * 5 * 10) / 10; // 0-5, 1 decimal\n const raw = Math.round(score * 100) / 100; // 0-1, 2 decimals\n return `\u2b50 ${five}/5 (Stonewalker ${raw})`;\n}\n\nfunction fmtPlace(p) {\n const name = p.url ? `[${p.name}](${p.url})` : `**${p.name}**`;\n const bits = [];\n\n if (p.description) bits.push(p.description);\n\n const addr = fmtAddress(p);\n if (addr) bits.push(addr);\n\n const stars = renderStars(p.stonewalker_stars ?? p.confidence);\n if (stars) bits.push(stars);\n\n if (Array.isArray(p.tags) && p.tags.length) bits.push(p.tags.map(t => `\\`${t}\\``).join(\" \"));\n\n return `- ${name}${bits.length ? \" \u2014 \" + bits.join(\" \u00b7 \") : \"\"}`;\n}\n\nfunction makeTitle(intent, location) {\n const i = intent ? intent.charAt(0).toUpperCase() + intent.slice(1) : null;\n if (i && location) return `### ${i} in ${location}`;\n if (location) return `### Results for ${location}`;\n if (i) return `### ${i}`;\n return `### Result`;\n}\n\n// ---------- main ----------\nconst out = items.map((item) => {\n const src = item.json ?? item;\n\n const message = src.output.response;\n const intent = src.intent ?? src.user_intent ?? null;\n const location = src.location ?? src.user_location ?? null;\n const places = Array.isArray(src.output.places) ? src.output.places : [];\n\n let md = `${makeTitle(intent, location)}\\n\\n`;\n\n if (message) md += `${message}\\n\\n`;\n\n if (intent || location) {\n md += `**Intent:** ${intent ?? \"\u2014\"} \\n**Location:** ${location ?? \"\u2014\"}\\n\\n`;\n }\n\n if (places.length) {\n md += places.map(fmtPlace).join(\"\\n\");\n } else {\n md += `_No specific places were returned by the tools this round. Adjust the intent (e.g., \u201ccoffee shops\u201d vs \u201ccoffee\u201d), narrow or broaden the location, or run the search again to fetch venue details._`;\n }\n\n return { text: md };\n});\n\nreturn out;\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1344,
864
],
"id": "d95e7934-0947-4909-ab08-41922a2eaf61",
"name": "pretty_print_results",
"onError": "continueErrorOutput"
},
{
"parameters": {
"workflowId": {
"__rl": true,
"value": "IAKagbbjRUADr5n4",
"mode": "list",
"cachedResultName": "Underfoot \u2014 wf_update_all_caches"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"intent": "={{ $json.user_intent }}",
"location": "={{ $json.user_location }}"
},
"matchingColumns": [],
"schema": [
{
"id": "intent",
"displayName": "intent",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "string"
},
{
"id": "location",
"displayName": "location",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "string"
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": true
},
"options": {
"waitForSubWorkflow": false
}
},
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [
1344,
1104
],
"id": "6eb208f6-b242-4611-ad1b-070d56d8268d",
"name": "send_off_whispers_in_the_ether",
"onError": "continueErrorOutput"
},
{
"parameters": {
"jsonSchemaExample": "{\n \"user_intent\": \"quiet, beach-like spots to sit by the water\",\n \"user_location\": \"Tallapoosa, GA\",\n \"response\": \"Tallapoosa isn\u2019t coastal, but you\u2019ve got mellow shorelines within a day-use drive. Try Yellowjacket Beach on West Point Lake for a true sand-under-toes vibe, or Lake Harding\u2019s small sandy pull-offs for a quieter read-by-the-water afternoon. Pack snacks, bug spray, and cash for day-use where posted.\",\n \"places\": [\n {\n \"name\": \"Yellowjacket Beach, West Point Lake\",\n \"description\": \"Managed day-use area with a sandy swim beach, restrooms, and picnic tables on a large reservoir.\",\n \"address\": \"Yellowjacket Creek Recreation Area, LaGrange, GA 30240\",\n \"city\": \"LaGrange\",\n \"state\": \"GA\",\n \"country\": \"US\",\n \"coordinates\": { \"lat\": 32.935, \"lng\": -85.129 },\n \"url\": \"https://example.com/west-point-lake-yellowjacket\",\n \"tags\": [\"lake\", \"swim beach\", \"picnic\", \"family-friendly\"],\n \"stonewalker_stars\": 4.4,\n \"confidence\": 0.87,\n \"source\": \"google_places\",\n \"source_id\": \"west_point_yellowjacket_beach\",\n \"cached_at\": \"2025-09-01T01:10:00Z\"\n },\n {\n \"name\": \"Lake Harding (select sandy pull-offs)\",\n \"description\": \"Reservoir on the Chattahoochee with small informal sandy edges; good for low-key shoreline lounging.\",\n \"address\": \"Lake Harding, GA/AL border\",\n \"city\": \"Salem\",\n \"state\": \"AL\",\n \"country\": \"US\",\n \"coordinates\": { \"lat\": 32.682, \"lng\": -85.125 },\n \"url\": \"https://example.com/lake-harding-shore\",\n \"tags\": [\"lake\", \"quiet\", \"shore access\"],\n \"stonewalker_stars\": 3.9,\n \"confidence\": 0.78,\n \"source\": \"google_maps\",\n \"source_id\": \"lake_harding_misc_shore\",\n \"cached_at\": \"2025-09-01T01:10:00Z\"\n }\n ]\n}",
"autoFix": true,
"customizeRetryPrompt": true,
"prompt": "Instructions:\n--------------\n{instructions}\n--------------\nCompletion:\n--------------\n{completion}\n--------------\n\nAbove, the Completion did not satisfy the constraints given in the Instructions.\nError:\n--------------\n{error}\n--------------\n\nPlease try again. Please only respond with an answer that satisfies the constraints laid out in the Instructions:\nYou are an OCD organizer and love making sure everything in exactly where it's supposed to be. Nothing extra, nothing missing is your motto \u2014 no more, no less, but just right.\n\nYour goal is to format the input given to you identical to the example json format. Substituting empty arrays and objects in place of nulls. Use your best judgement to determine which field goes where and above all \u2014 return the format exactly as it is defined without modifications, parsing, encoding, or any other formatting. Simple data in, simple data out. Everyone is happy."
},
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"typeVersion": 1.3,
"position": [
752,
1184
],
"id": "5b6d9803-5ef4-43e7-9545-b8450464f072",
"name": "structured_optput"
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"leftValue": "={{ $('set_self_hosted_chat').item }}",
"rightValue": "self-hosted-checkmark-devtools",
"operator": {
"type": "object",
"operation": "exists",
"singleValue": true
},
"id": "01c7d074-ad29-431e-adcd-e40fda30c58c"
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "checkmark-devtools"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "1e07c96a-a9f1-48bb-bea7-eef79095943e",
"leftValue": "={{ $('set_src_sys_id_hosted_chat').item }}",
"rightValue": "n8n-cloud-hosted",
"operator": {
"type": "object",
"operation": "exists",
"singleValue": true
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "n8n-cloud-hosted"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.2,
"position": [
-336,
304
],
"id": "c4106aa4-4209-4004-9edd-5631fd3ea342",
"name": "determine_return_method"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "d31c30b9-76e3-4934-a4a2-a23c9faa8207",
"name": "source_system",
"value": "={{ \"self-hosted-checkmark-devtools\" }}",
"type": "string"
},
{
"id": "f4e003de-9151-4158-9103-6c790a69db9b",
"name": "sessionId",
"value": "={{ $json.sessionId || uuid() }}",
"type": "string"
}
]
},
"includeOtherFields": true,
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-112,
528
],
"id": "d606ed97-54dd-4aff-a2cd-8de35ebba64f",
"name": "set_self_hosted_chat",
"onError": "continueRegularOutput"
},
{
"parameters": {
"mode": "raw",
"jsonOutput": "{\n \"src_system_id\": \"n8n-cloud-hosted\"\n}",
"includeOtherFields": true,
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-112,
960
],
"id": "445d1bbb-6322-4c15-b87e-d176ab188e92",
"name": "set_src_sys_id_hosted_chat",
"onError": "continueRegularOutput"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": false,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "b6a4f573-16d8-4a53-bc72-854789db1c94",
"leftValue": "={{ $json.output.places }}",
"rightValue": "",
"operator": {
"type": "number",
"operation": "exists",
"singleValue": true
}
},
{
"id": "6e190d89-578e-424c-a222-bdec64903ca7",
"leftValue": "={{ $json.output.places.length }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "gt"
}
}
],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {
"ignoreCase": true
}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
1120,
960
],
"id": "53e6ae1b-3fa2-4921-9240-5c313a46d309",
"name": "check_if_found",
"onError": "continueRegularOutput"
},
{
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-mini"
},
"options": {
"maxTokens": 256,
"responseFormat": "json_object",
"temperature": 0.1
}
},
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.2,
"position": [
832,
1392
],
"id": "3ed8935b-fb41-4839-95a7-6387d03f3cb8",
"name": "gpt-4.1-mini",
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"useCustomSchema": true,
"schema": "underfoot",
"operation": "getAll",
"tableId": "search_cache",
"limit": 10,
"filters": {
"conditions": [
{
"keyName": "title",
"condition": "ilike",
"keyValue": "=*{{ $fromAI('intent') }}*"
},
{
"keyName": "description",
"condition": "ilike",
"keyValue": "=*{{ $fromAI('intent') }}*"
},
{
"keyName": "user_intent",
"condition": "ilike",
"keyValue": "=*{{ $fromAI('intent') }}*"
},
{
"keyName": "user_location",
"condition": "ilike",
"keyValue": "=*{{ $fromAI('location') }}*"
}
]
}
},
"type": "n8n-nodes-base.supabaseTool",
"typeVersion": 1,
"position": [
624,
1184
],
"id": "0c92657d-0b31-41d7-a2fc-1cd8e09af8ac",
"name": "search_cache",
"credentials": {
"supabaseApi": {
"name": "<your credential>"
}
}
}
],
"connections": {
"chat_agent": {
"main": [
[
{
"node": "check_if_found",
"type": "main",
"index": 0
}
]
]
},
"gpt_4.1_mini": {
"ai_languageModel": [
[
{
"node": "chat_agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"gpt_4.1_full": {
"ai_languageModel": [
[
{
"node": "chat_agent",
"type": "ai_languageModel",
"index": 1
}
]
]
},
"respond_to_chat": {
"main": [
[]
]
},
"hosted_chat_trigger": {
"main": [
[
{
"node": "set_src_sys_id_hosted_chat",
"type": "main",
"index": 0
}
]
]
},
"call_google_serp": {
"ai_tool": [
[
{
"node": "chat_agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"webhook_trigger": {
"main": [
[
{
"node": "set_self_hosted_chat",
"type": "main",
"index": 0
}
]
]
},
"agent_memory": {
"ai_memory": [
[
{
"node": "chat_agent",
"type": "ai_memory",
"index": 0
}
]
]
},
"pretty_print_results": {
"main": [
[
{
"node": "respond_to_chat",
"type": "main",
"index": 0
}
],
[
{
"node": "call_discord_error_notifier",
"type": "main",
"index": 0
}
]
]
},
"send_off_whispers_in_the_ether": {
"main": [
[],
[
{
"node": "call_discord_error_notifier",
"type": "main",
"index": 0
}
]
]
},
"structured_optput": {
"ai_outputParser": [
[
{
"node": "chat_agent",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"determine_return_method": {
"main": [
[],
[
{
"node": "respond_to_webhook",
"type": "main",
"index": 0
}
]
]
},
"set_self_hosted_chat": {
"main": [
[]
]
},
"set_src_sys_id_hosted_chat": {
"main": [
[
{
"node": "chat_agent",
"type": "main",
"index": 0
}
]
]
},
"check_if_found": {
"main": [
[
{
"node": "send_off_whispers_in_the_ether",
"type": "main",
"index": 0
},
{
"node": "pretty_print_results",
"type": "main",
"index": 0
}
],
[
{
"node": "pretty_print_results",
"type": "main",
"index": 0
}
]
]
},
"gpt-4.1-mini": {
"ai_languageModel": [
[
{
"node": "structured_optput",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"search_cache": {
"ai_tool": [
[
{
"node": "chat_agent",
"type": "ai_tool",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"callerPolicy": "workflowsFromSameOwner",
"errorWorkflow": "g0RA1ILyuSMHbt6q"
},
"versionId": "9100d552-745d-4e38-99a6-dfdd2d8a159c",
"meta": {
"templateCredsSetupCompleted": true
},
"id": "Yogrk8C57eV2wMpk",
"tags": [
{
"createdAt": "2025-08-29T04:02:35.168Z",
"updatedAt": "2025-08-29T04:02:35.168Z",
"id": "4g2r1ALvxirC7hxK",
"name": "n8nbrightdatachallenge"
},
{
"createdAt": "2025-08-29T04:02:50.893Z",
"updatedAt": "2025-08-29T04:02:50.893Z",
"id": "Mx3hjhTFNPLHvKnk",
"name": "devchallenge"
}
]
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
openAiApisupabaseApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
ai_chat_agent. Uses agent, lmChatOpenAi, chat, chatTrigger. Chat trigger; 19 nodes.
Source: https://gist.github.com/anchildress1/cab1237affe75f0bed6629faeb940f2c — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
by Varritech Technologies
Who is this workflow for? This workflow is designed for SEO analysts, content creators, marketing agencies, and developers who need to index a website and then interact with its content as if it were
This Chatbot automates the process of discovering job openings and generating tailored job application emails.
Job Application PredictLeads & ScrapeGraph AI. Uses chatTrigger, lmChatOpenAi, mcpClientTool, memoryBufferWindow. Chat trigger; 32 nodes.
This workflow implements an advanced AI-powered system for generating, and executing Claude Skills stored on GitHub.