{
  "name": "InsightsLM - Chat",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "2fabf43f-6e6e-424b-8e93-9150e9ce7d6c",
        "authentication": "headerAuth",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -780,
        -320
      ],
      "id": "8720544c-77dd-4c95-85aa-bc46792f8f52",
      "name": "Webhook",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "model": "qwen3:8b-q4_K_M",
        "options": {
          "format": "json"
        }
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOllama",
      "typeVersion": 1,
      "position": [
        -120,
        100
      ],
      "id": "ae40613a-1713-4c65-a204-85ad4e05a93f",
      "name": "Ollama Chat Model",
      "credentials": {
        "ollamaApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsonSchemaExample": "{\n\t\"search_query\": \"<ADD>\"\n}",
        "autoFix": true
      },
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [
        40,
        -80
      ],
      "id": "6f097c3f-fe56-4636-9c75-ea1ec3d17b7e",
      "name": "Structured Output Parser1"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=User Query: {{ $('Webhook').item.json.body.message }}\n\nConversation History: {{ JSON.stringify($json.data.slice(0,10))  }}",
        "hasOutputParser": true,
        "messages": {
          "messageValues": [
            {
              "message": "=Based on the provided user query and taking into context the conversation history, output a serach query that we can send to our vector database to retrieve relevant chunks of text"
            }
          ]
        },
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        -40,
        -320
      ],
      "id": "81189f62-f5f9-4396-bb80-22f3c9591c31",
      "name": "Generate Search Query"
    },
    {
      "parameters": {
        "mode": "load",
        "tableName": {
          "__rl": true,
          "value": "documents",
          "mode": "list",
          "cachedResultName": "documents"
        },
        "prompt": "={{ $json.output.search_query }}",
        "topK": 10,
        "options": {
          "metadata": {
            "metadataValues": [
              {
                "name": "notebook_id",
                "value": "={{ $('Webhook').item.json.body.session_id }}"
              }
            ]
          }
        }
      },
      "type": "@n8n/n8n-nodes-langchain.vectorStoreSupabase",
      "typeVersion": 1.3,
      "position": [
        320,
        -320
      ],
      "id": "e6d5c9fa-a280-4b09-bc33-42945a42df7b",
      "name": "Supabase Vector Store1",
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "model": "nomic-embed-text:latest"
      },
      "type": "@n8n/n8n-nodes-langchain.embeddingsOllama",
      "typeVersion": 1,
      "position": [
        380,
        -100
      ],
      "id": "c10decbe-3270-427f-be01-a1bf122286b2",
      "name": "Embeddings Ollama",
      "credentials": {
        "ollamaApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "aggregate": "aggregateAllItemData",
        "options": {}
      },
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1,
      "position": [
        960,
        -320
      ],
      "id": "b6668ad1-3257-4e51-b34b-efe843e8cf2c",
      "name": "Aggregate"
    },
    {
      "parameters": {
        "jsonSchemaExample": "{\n\t\"output\": [\n      {\"sentence_paragraph_with_citation\": \"<ADD>\"},\n      {\"sentence_paragraph_with_citation\": \"<ADD>\"}\n    ]\n}",
        "autoFix": true
      },
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [
        1340,
        -120
      ],
      "id": "4538519d-5d0f-4ed4-9d9f-597d64df3811",
      "name": "Structured Output Parser2"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=# User Query\n{{ $('Webhook').item.json.body.message }}\n\n# Conversation History \n{{ $('Aggregate1').item.json.data }}\n\n# Retrieved Chunks from Knowledgebase\n{{ JSON.stringify($json.data) }}",
        "hasOutputParser": true,
        "messages": {
          "messageValues": [
            {
              "message": "=# ROLE\n\nYou are tasked with answering a users question, in the context of a conversation, using provided chunks of information. \n\nYour goal is to provide an accurate answer from these chunks while citing your sources using the following syntax. \n\nBe as detailed as possible\n\n[0] \n\nWhere the number refers to the chunk_id of the specifc chunks that contains the information.\n\n## Example:\n\nMastering the foundational techniques for your serve, forehand, and backhand is crucial for developing overall tennis proficiency, as these techniques directly impact your ability to generate power, control, consistency, accuracy, spin, leverage, and prevent injuries.[2]\n\nThe serve is detailed through five steps: Grip, Ball Toss, Trophy Position, Pronation, and Follow Through and Finish [0][7]\n\nGrip: Using the continental grip (also known as the chopper grip) is fundamental for serve proficiency. It is the same grip used for slices, forehand and backhand volleys, and the overhead smash. [9]\n\nThe citation(s) should appear at the end of the sentence or paragraph where the information is used.\n\n# IMPORTANT\n\nOutput in JSON format\nImportant: Only based your answers on information in the provided chunks from the vector store or conversation history\nImportant: If you cannot answer the question using the provided chunks, set the response value as \"Sorry I don't know\".\nImportant: You MUST trigger the \"Supabase Vector Store\" tool every time"
            }
          ]
        },
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        1220,
        -320
      ],
      "id": "d7858d60-9fb0-4046-9b46-3b38aaa0b026",
      "name": "Generate Response"
    },
    {
      "parameters": {
        "model": "qwen3:8b-q4_K_M",
        "options": {
          "temperature": 0.4,
          "format": "json"
        }
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOllama",
      "typeVersion": 1,
      "position": [
        1200,
        80
      ],
      "id": "518def0e-733e-4a59-b76e-b949d4946a9c",
      "name": "Ollama Chat Model1",
      "credentials": {
        "ollamaApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Loop over input items and transform the citation format\nfor (const item of $input.all()) {\n  // Assuming chunks are available in item.json.chunks \n  // If chunks come from a different input, modify this line accordingly\n  const chunks = $('Aggregate').first().json.data || [];\n  \n  if (item.json.output && Array.isArray(item.json.output)) {\n    const transformedOutput = [];\n    \n    for (const outputItem of item.json.output) {\n      if (outputItem.sentence_paragraph_with_citation) {\n        const textWithCitations = outputItem.sentence_paragraph_with_citation;\n        \n        // Extract citation indices using regex to find [0], [1], etc.\n        const citationMatches = textWithCitations.match(/\\[(\\d+)\\]/g) || [];\n        const citationIndices = citationMatches.map(match => parseInt(match.replace(/[\\[\\]]/g, '')));\n        \n        // Remove citations from text to get clean text\n        const cleanText = textWithCitations.replace(/\\s*\\[\\d+\\]/g, '').trim();\n        \n        // Build citations array\n        const citations = [];\n        const processedIndices = new Set(); // To avoid duplicates\n        \n        for (const citationId of citationIndices) {\n          if (!processedIndices.has(citationId)) {\n            // Find chunk by chunk_id instead of using array index\n            const chunkItem = chunks.find(chunkItem => chunkItem.chunk_id === citationId);\n            \n            if (chunkItem && chunkItem.document) {\n              const chunk = chunkItem.document;\n              const metadata = chunk.metadata;\n              \n              citations.push({\n                chunk_index: citationId,\n                chunk_source_id: metadata.source_id,\n                chunk_lines_from: metadata.loc.lines.from,\n                chunk_lines_to: metadata.loc.lines.to\n              });\n              processedIndices.add(citationId);\n            }\n          }\n        }\n        \n        transformedOutput.push({\n          text: cleanText,\n          citations: citations\n        });\n      }\n    }\n    \n    // Create the exact output format you specified\n    item.json.output = {\n      type: \"ai\",\n      content: JSON.stringify({\n        output: transformedOutput\n      }),\n      tool_calls: [],\n      additional_kwargs: {},\n      response_metadata: {},\n      invalid_tool_calls: []\n    };\n  }\n}\n\nreturn $input.all();"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1580,
        -320
      ],
      "id": "648bd6ac-84d4-48da-bc33-7c8768f97c2c",
      "name": "Create Output Structure with Citations"
    },
    {
      "parameters": {
        "tableId": "n8n_chat_histories",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "session_id",
              "fieldValue": "={{ $('Webhook').item.json.body.session_id }}"
            },
            {
              "fieldId": "message",
              "fieldValue": "={{ $json.human }}"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        2040,
        -320
      ],
      "id": "1e45e3e9-f9f4-43e0-b2ef-46ec66a03e6d",
      "name": "Save Human Query",
      "alwaysOutputData": true,
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "tableId": "n8n_chat_histories",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "session_id",
              "fieldValue": "={{ $('Webhook').item.json.body.session_id }}"
            },
            {
              "fieldId": "message",
              "fieldValue": "={{ $('Create Output Structure with Citations').item.json.output }}"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        2460,
        -320
      ],
      "id": "8ca8de19-928e-440e-b548-5d38828e1805",
      "name": "Save AI Response",
      "alwaysOutputData": true,
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "amount": 1
      },
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        2260,
        -320
      ],
      "id": "0c66af07-1f85-45df-a72a-3e23c570b6ff",
      "name": "Wait"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "106e9fbb-8fe2-4bc0-8f91-50687c4ac2dd",
              "name": "human",
              "value": "={\"type\": \"human\", \"content\": {{ JSON.stringify(($('Webhook').item.json.body.message)) }}, \"additional_kwargs\": {}, \"response_metadata\": {}}",
              "type": "object"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1800,
        -320
      ],
      "id": "9c75dec2-7ed4-41d1-8914-6f61541f9e32",
      "name": "Edit Fields"
    },
    {
      "parameters": {
        "jsCode": "// Loop over input items and add 'id' as the first field\n$input.all().forEach((item, index) => {\n  // Create a new object with id first, then spread the existing properties\n  item.json = {\n    chunk_id: index + 1,\n    ...item.json\n  };\n});\n\nreturn $input.all();"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        680,
        -320
      ],
      "id": "adc1814e-e4f8-4eea-895d-ae82c74dd90b",
      "name": "Code"
    },
    {
      "parameters": {
        "operation": "getAll",
        "tableId": "n8n_chat_histories",
        "returnAll": true,
        "filters": {
          "conditions": [
            {
              "keyName": "session_id",
              "condition": "eq",
              "keyValue": "={{ $json.body.session_id }}"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        -560,
        -320
      ],
      "id": "e5526eaa-09e4-46c2-9c75-0b6600014ec0",
      "name": "Fetch Chat History",
      "alwaysOutputData": true,
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "aggregate": "aggregateAllItemData",
        "options": {}
      },
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1,
      "position": [
        -320,
        -320
      ],
      "id": "51ef9ec1-8f89-4835-991f-bde7373121d8",
      "name": "Aggregate1"
    },
    {
      "parameters": {
        "content": "[![The AI Automators](https://www.theaiautomators.com/wp-content/uploads/2025/03/gray-logo.png)](https://www.theaiautomators.com/)\n## InsightsLM Local - Chat\nhttps://github.com/theaiautomators/insights-lm-public\nhttps://github.com/theaiautomators/insights-lm-local-package",
        "height": 260,
        "width": 320,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -780,
        -680
      ],
      "id": "3be1ba88-4f52-4f17-8444-3c05fc98df3f",
      "name": "Sticky Note8"
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Fetch Chat History",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ollama Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Generate Search Query",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Structured Output Parser1",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser1": {
      "ai_outputParser": [
        [
          {
            "node": "Generate Search Query",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Generate Search Query": {
      "main": [
        [
          {
            "node": "Supabase Vector Store1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Embeddings Ollama": {
      "ai_embedding": [
        [
          {
            "node": "Supabase Vector Store1",
            "type": "ai_embedding",
            "index": 0
          }
        ]
      ]
    },
    "Supabase Vector Store1": {
      "main": [
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate": {
      "main": [
        [
          {
            "node": "Generate Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser2": {
      "ai_outputParser": [
        [
          {
            "node": "Generate Response",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Ollama Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "Generate Response",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Structured Output Parser2",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Generate Response": {
      "main": [
        [
          {
            "node": "Create Output Structure with Citations",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Output Structure with Citations": {
      "main": [
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Human Query": {
      "main": [
        [
          {
            "node": "Wait",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait": {
      "main": [
        [
          {
            "node": "Save AI Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields": {
      "main": [
        [
          {
            "node": "Save Human Query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code": {
      "main": [
        [
          {
            "node": "Aggregate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Chat History": {
      "main": [
        [
          {
            "node": "Aggregate1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate1": {
      "main": [
        [
          {
            "node": "Generate Search Query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "847a2686-5464-4cad-b823-517b26de76d9",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "fMscTRsbgnrUFZ2S",
  "tags": [
    {
      "createdAt": "2025-07-01T11:04:36.330Z",
      "updatedAt": "2025-07-01T11:04:36.330Z",
      "id": "Wki6TfWIHzQoLAWa",
      "name": "TheAIAutomators.com"
    }
  ]
}