Build a Financial Documents Assistant using Qdrant and Mistral.ai
Build a robust financial documents assistant that intelligently answers questions based on your uploaded files, leveraging the power of Qdrant for vector storage and Mistral.ai for advanced embeddings and chat capabilities. This workflow begins with a Local File Trigger, monitoring a specified directory for new or updated financial documents. Upon detection, the Read File node processes the document content, which is then transformed into numerical representations by the Embeddings Mistral Cloud node. The Default Data Loader and Recursive Character Text Splitter prepare this data for efficient retrieval, storing it in Qdrant via the Qdrant Vector Store node. When a user interacts with the Chat Trigger, the Question and Answer Chain, powered by the Mistral Cloud Chat Model and a Vector Store Retriever, consults the Qdrant database to provide accurate, context-aware answers from your financial documents. This setup allows financial analysts, legal teams, or anyone handling sensitive documents to quickly extract information, understand complex reports, and automate query responses, significantly reducing manual search time and improving decision-making accuracy. The workflow also intelligently handles document updates by searching for and deleting existing points in Qdrant before re-embedding and storing the new version, ensuring your knowledge base is always current and relevant.
Workflow JSON
{"meta": {"instanceId": "26ba763460b97c249b82942b23b6384876dfeb9327513332e743c5f6219c2b8e"}, "nodes": [{"id": "c5525f47-4d91-4b98-87bb-566b90da64a1", "name": "Local File Trigger", "type": "n8n-nodes-base.localFileTrigger", "position": [660, 700], "parameters": {"path": "/home/node/host_mount/local_file_search", "events": ["add", "change", "unlink"], "options": {"awaitWriteFinish": true}, "triggerOn": "folder"}, "typeVersion": 1}, {"id": "804334d6-e34d-40d1-9555-b331ffe66f6f", "name": "When clicking \"Test workflow\"", "type": "n8n-nodes-base.manualTrigger", "position": [664.5766613599001, 881.8474780113352], "parameters": {}, "typeVersion": 1}, {"id": "7ab0e284-b667-4d1f-8ceb-fb05e4081a06", "name": "Set Variables", "type": "n8n-nodes-base.set", "position": [840, 700], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "35ea70c4-8669-4975-a68d-bbaa094713c0", "name": "directory", "type": "string", "value": "/home/node/BankStatements"}, {"id": "1d081d19-ff4e-462a-9cbe-7af2244bf87f", "name": "file_added", "type": "string", "value": "={{ $json.event === 'add' && $json.path || ''}}"}, {"id": "18f8dc03-51ca-48c7-947f-87ce8e1979bf", "name": "file_changed", "type": "string", "value": "={{ $json.event === 'change' && $json.path || '' }}"}, {"id": "65074ff7-037b-4b3b-b2c3-8a61755ab43b", "name": "file_deleted", "type": "string", "value": "={{ $json.event === 'unlink' && $json.path || '' }}"}, {"id": "9a1902e7-f94d-4d1f-9006-91c67354d3e8", "name": "qdrant_collection", "type": "string", "value": "local_file_search"}]}}, "typeVersion": 3.3}, {"id": "76173972-ceca-43a4-b85f-00b41f774304", "name": "Sticky Note", "type": "n8n-nodes-base.stickyNote", "position": [580, 460], "parameters": {"color": 7, "width": 665.0909497859384, "height": 596.8351502261468, "content": "## Step 1. Select the target folder\n[Read more about local file trigger](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.localfiletrigger)\n\nIn this workflow, we'll monitor a specific folder on disk that n8n has access to. Since we're using docker, we can either use the n8n volume or mount a folder from the host machine.\n\nThe local file trigger is useful to execute the workflow whenever changes are made to our target folder."}, "typeVersion": 1}, {"id": "eda839f7-dde4-4d1f-9fe6-692df4ac7282", "name": "Sticky Note4", "type": "n8n-nodes-base.stickyNote", "position": [184.57666135990007, 461.84747801133517], "parameters": {"width": 372.51107341403605, "height": 356.540665091993, "content": "## Try It Out!\n### This workflow does the following:\n* Monitors a target folder for changes using the local file trigger\n* Synchronises files in the target folder with their vectors in Qdrant\n* Mistral AI is used to create a Q&A AI agent on all files in the target folder\n\n### Need Help?\nJoin the [Discord](https://discord.com/invite/XPKeKXeB7d) or ask in the [Forum](https://community.n8n.io/)!\n\nHappy Hacking!"}, "typeVersion": 1}, {"id": "f82f6de0-af8f-4fdf-a733-f59ba4fed02f", "name": "Read File", "type": "n8n-nodes-base.readWriteFile", "position": [1340, 1120], "parameters": {"options": {}, "fileSelector": "={{ $json.file_added }}"}, "typeVersion": 1}, {"id": "7354a080-051b-479f-97b1-49cc0c14c9d8", "name": "Embeddings Mistral Cloud", "type": "@n8n/n8n-nodes-langchain.embeddingsMistralCloud", "position": [1720, 1280], "parameters": {"options": {}}, "credentials": {"mistralCloudApi": {"id": "", "name": "[Your mistralCloudApi]"}}, "typeVersion": 1}, {"id": "a1ad45ff-a882-4aed-82e2-cad2483cf4e8", "name": "Default Data Loader", "type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader", "position": [1820, 1280], "parameters": {"options": {"metadata": {"metadataValues": [{"name": "filter_by_filename", "value": "={{ $json.file_location }}"}, {"name": "filter_by_created_month", "value": "={{ $now.year + '-' + $now.monthShort }}"}, {"name": "filter_by_created_week", "value": "={{ $now.year + '-' + $now.monthShort + '-W' + $now.weekNumber }}"}]}}, "jsonData": "={{ $json.data }}", "jsonMode": "expressionData"}, "typeVersion": 1}, {"id": "0b0e29b9-8873-4074-94dc-9f0364c28835", "name": "Recursive Character Text Splitter", "type": "@n8n/n8n-nodes-langchain.textSplitterRecursiveCharacterTextSplitter", "position": [1840, 1400], "parameters": {"options": {}}, "typeVersion": 1}, {"id": "c0555ba6-a1bd-4aa9-a340-a9c617f8e6db", "name": "Prepare Embedding Document", "type": "n8n-nodes-base.set", "position": [1520, 1120], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "41a1d4ca-e5a5-4fb9-b249-8796ae759b33", "name": "data", "type": "string", "value": "=## file location\n{{ [$json.directory, $json.fileName].join('/') }}\n## file created\n{{ $now.toISO() }}\n## file contents\n{{ $input.item.binary.data.data.base64Decode() }}"}, {"id": "c091704d-b81c-448b-8c90-156ef568b871", "name": "file_location", "type": "string", "value": "={{ [$json.directory, $json.fileName].join('/') }}"}]}}, "typeVersion": 3.3}, {"id": "ffe8c363-0809-4d21-aa8f-34b0fc2dc57f", "name": "Chat Trigger", "type": "@n8n/n8n-nodes-langchain.chatTrigger", "position": [2280, 680], "webhookId": "37587fe0-b8db-4012-90a7-1f65b9bfd0df", "parameters": {}, "typeVersion": 1}, {"id": "8d958669-60be-4bb2-80fc-2a6c7c7bfae6", "name": "Question and Answer Chain", "type": "@n8n/n8n-nodes-langchain.chainRetrievalQa", "position": [2500, 680], "parameters": {}, "typeVersion": 1.3}, {"id": "f143e438-8176-4923-a866-3f9a2a16793d", "name": "Mistral Cloud Chat Model", "type": "@n8n/n8n-nodes-langchain.lmChatMistralCloud", "position": [2500, 840], "parameters": {"model": "mistral-small-2402", "options": {}}, "credentials": {"mistralCloudApi": {"id": "", "name": "[Your mistralCloudApi]"}}, "typeVersion": 1}, {"id": "06dd8f4c-3b66-43e0-85c8-ec222e275f87", "name": "Vector Store Retriever", "type": "@n8n/n8n-nodes-langchain.retrieverVectorStore", "position": [2620, 840], "parameters": {}, "typeVersion": 1}, {"id": "2fdabcb5-a7a7-4e02-8c1b-9190e2e52385", "name": "Embeddings Mistral Cloud1", "type": "@n8n/n8n-nodes-langchain.embeddingsMistralCloud", "position": [2620, 1080], "parameters": {"options": {}}, "credentials": {"mistralCloudApi": {"id": "", "name": "[Your mistralCloudApi]"}}, "typeVersion": 1}, {"id": "e5664534-de07-481f-87dd-68d7d0715baa", "name": "Remap for File_Added Flow", "type": "n8n-nodes-base.set", "position": [1920, 700], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "840219e1-ed47-4b00-83fd-6b3c0bd71650", "name": "file_added", "type": "string", "value": "={{ $('Set Variables').item.json.file_changed }}"}]}}, "typeVersion": 3.3}, {"id": "1fd14832-aafe-4d72-b4f2-7afc72df97dc", "name": "Search For Existing Point", "type": "n8n-nodes-base.httpRequest", "position": [1340, 280], "parameters": {"url": "=http://qdrant:6333/collections/{{ $('Set Variables').item.json.qdrant_collection }}/points/scroll", "method": "POST", "options": {}, "jsonBody": "={\n \"filter\": {\n \"must\": [\n {\n \"key\": \"metadata.filter_by_filename\",\n \"match\": {\n \"value\": \"{{ $json.file_changed }}\"\n }\n }\n ]\n },\n \"limit\": 1,\n \"with_payload\": false,\n \"with_vector\": false\n}", "sendBody": true, "specifyBody": "json", "authentication": "predefinedCredentialType", "nodeCredentialType": "qdrantApi"}, "credentials": {"qdrantApi": {"id": "", "name": "[Your qdrantApi]"}}, "typeVersion": 4.2}, {"id": "b5fa817f-82d6-41dd-9817-4c1dd9137b76", "name": "Has Existing Point?", "type": "n8n-nodes-base.if", "position": [1520, 280], "parameters": {"options": {}, "conditions": {"options": {"leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "0392bac0-8fb5-406b-b59f-575edf5ab30d", "operator": {"type": "array", "operation": "notEmpty", "singleValue": true}, "leftValue": "={{ $json.result.points }}", "rightValue": ""}]}}, "typeVersion": 2}, {"id": "b0fa4fa4-5d1b-4a12-b8ba-a10d71f31f94", "name": "Delete Existing Point", "type": "n8n-nodes-base.httpRequest", "position": [1720, 700], "parameters": {"url": "=http://qdrant:6333/collections/{{ $('Set Variables').item.json.qdrant_collection }}/points/delete", "method": "POST", "options": {}, "sendBody": true, "authentication": "predefinedCredentialType", "bodyParameters": {"parameters": [{"name": "points", "value": "={{ $json.result.points.map(point => point.id) }}"}]}, "nodeCredentialType": "qdrantApi"}, "credentials": {"qdrantApi": {"id": "", "name": "[Your qdrantApi]"}}, "typeVersion": 4.2}, {"id": "5408adfe-4d6b-407c-aac7-e87c9b1a1592", "name": "Search For Existing Point1", "type": "n8n-nodes-base.httpRequest", "position": [1340, 700], "parameters": {"url": "=http://qdrant:6333/collections/{{ $('Set Variables').item.json.qdrant_collection }}/points/scroll", "method": "POST", "options": {}, "jsonBody": "={\n \"filter\": {\n \"must\": [\n {\n \"key\": \"metadata.filter_by_filename\",\n \"match\": {\n \"value\": \"{{ $json.file_changed }}\"\n }\n }\n ]\n },\n \"limit\": 1,\n \"with_payload\": false,\n \"with_vector\": false\n}", "sendBody": true, "specifyBody": "json", "authentication": "predefinedCredentialType", "nodeCredentialType": "qdrantApi"}, "credentials": {"qdrantApi": {"id": "", "name": "[Your qdrantApi]"}}, "typeVersion": 4.2}, {"id": "fac43587-0d24-4d6e-a0d5-8cc8f9615967", "name": "Has Existing Point?1", "type": "n8n-nodes-base.if", "position": [1520, 700], "parameters": {"options": {}, "conditions": {"options": {"leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "0392bac0-8fb5-406b-b59f-575edf5ab30d", "operator": {"type": "array", "operation": "notEmpty", "singleValue": true}, "leftValue": "={{ $json.result.points }}", "rightValue": ""}]}}, "typeVersion": 2}, {"id": "010baacd-fac1-4cc1-86bf-9d6ef11916fe", "name": "Delete Existing Point1", "type": "n8n-nodes-base.httpRequest", "position": [1700, 280], "parameters": {"url": "=http://qdrant:6333/collections/{{ $('Set Variables').item.json.qdrant_collection }}/points/delete", "method": "POST", "options": {}, "sendBody": true, "authentication": "predefinedCredentialType", "bodyParameters": {"parameters": [{"name": "points", "value": "={{ $json.result.points.map(point => point.id) }}"}]}, "nodeCredentialType": "qdrantApi"}, "credentials": {"qdrantApi": {"id": "", "name": "[Your qdrantApi]"}}, "typeVersion": 4.2}, {"id": "2d6fb29c-2fac-41de-9ad0-cc781b246378", "name": "Handle File Event", "type": "n8n-nodes-base.switch", "position": [1000, 700], "parameters": {"rules": {"values": [{"outputKey": "file_deleted", "conditions": {"options": {"leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "a1f6d86a-9805-4d0e-ac70-90c9cf0ad339", "operator": {"type": "string", "operation": "notEmpty", "singleValue": true}, "leftValue": "={{ $json.file_deleted }}", "rightValue": ""}]}, "renameOutput": true}, {"outputKey": "file_changed", "conditions": {"options": {"leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "d15cde67-b5b0-4676-b4fb-ead749147392", "operator": {"type": "string", "operation": "notEmpty", "singleValue": true}, "leftValue": "={{ $json.file_changed }}", "rightValue": ""}]}, "renameOutput": true}, {"outputKey": "file_added", "conditions": {"options": {"leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"operator": {"type": "string", "operation": "notEmpty", "singleValue": true}, "leftValue": "={{ $json.file_added }}", "rightValue": ""}]}, "renameOutput": true}]}, "options": {}}, "typeVersion": 3}, {"id": "da91b2aa-613c-4e3e-af83-fbd3bb7e922e", "name": "Sticky Note1", "type": "n8n-nodes-base.stickyNote", "position": [1280, 123.92779403575491], "parameters": {"color": 7, "width": 847.032584995578, "height": 335.8400964393443, "content": "## Step 2. When files are removed, the vector point is cleared.\n[Learn how to delete points using the Qdrant API](https://qdrant.tech/documentation/concepts/points/#delete-points)\n\nTo keep our vectorstore relevant, we'll implement a simple synchronisation system whereby documents deleted from the local file folder are also purged from Qdrant. This can be simply achieved using Qdrant APIs."}, "typeVersion": 1}, {"id": "2f9f5b2b-6504-4b27-a0c4-f3373df352df", "name": "Sticky Note2", "type": "n8n-nodes-base.stickyNote", "position": [1280, 480], "parameters": {"color": 7, "width": 855.9952607674757, "height": 433.01782147687817, "content": "## Step 3. When files are updated, the vector point is updated.\n[Learn how to delete points using the Qdrant API](https://qdrant.tech/documentation/concepts/points/#delete-points)\n\nSimilarly to the files deleted branch, when we encounter a change in a file we'll update the matching vector point in Qdrant to ensure our vector store stays relevant. Here, we can achieve this my deleting the existing vector point and creating it anew with the updated bank statement."}, "typeVersion": 1}, {"id": "38128b7f-d0f2-405c-a7de-662df812c344", "name": "Sticky Note3", "type": "n8n-nodes-base.stickyNote", "position": [1280, 940], "parameters": {"color": 7, "width": 846.8204626627492, "height": 629.9714759033081, "content": "## Step 4. When new files are added, add them to Qdrant Vectorstore.\n[Read more about the Qdrant node](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.vectorstoreqdrant)\n\nUsing Qdrant, we'll able to create a simple yet powerful RAG based application for our bank statements. One of Qdrant's most powerful features is its filtering system, we'll use it to manage the synchronisation of our local file system and Qdrant."}, "typeVersion": 1}, {"id": "e85e2a30-e775-42fe-a12a-ac5de4eb4673", "name": "Sticky Note5", "type": "n8n-nodes-base.stickyNote", "position": [2180, 491.43199269284935], "parameters": {"color": 7, "width": 744.4578330639196, "height": 759.7908149448928, "content": "## Step 5. Create AI Agent expert on historic bank statements \n[Read more about the Question & Answer Chain](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.chainretrievalqa)\n\nFinally, let's use a Question & Answer AI node to combine the Mistral AI model and Qdrant as the vector store retriever to create a local expert for all our bank statements questions. "}, "typeVersion": 1}, {"id": "7b29b0b9-ffee-4456-b036-9b39400d2b31", "name": "Qdrant Vector Store", "type": "@n8n/n8n-nodes-langchain.vectorStoreQdrant", "position": [1700, 1120], "parameters": {"mode": "insert", "options": {}, "qdrantCollection": {"__rl": true, "mode": "id", "value": "={{ $('Set Variables').item.json.qdrant_collection }}"}}, "credentials": {"qdrantApi": {"id": "", "name": "[Your qdrantApi]"}}, "typeVersion": 1}, {"id": "1857bebb-b492-415e-96c8-235329bfd28a", "name": "Qdrant Vector Store1", "type": "@n8n/n8n-nodes-langchain.vectorStoreQdrant", "position": [2620, 960], "parameters": {"qdrantCollection": {"__rl": true, "mode": "id", "value": "BankStatements"}}, "credentials": {"qdrantApi": {"id": "", "name": "[Your qdrantApi]"}}, "typeVersion": 1}], "pinData": {}, "connections": {"Read File": {"main": [[{"node": "Prepare Embedding Document", "type": "main", "index": 0}]]}, "Chat Trigger": {"main": [[{"node": "Question and Answer Chain", "type": "main", "index": 0}]]}, "Set Variables": {"main": [[{"node": "Handle File Event", "type": "main", "index": 0}]]}, "Handle File Event": {"main": [[{"node": "Search For Existing Point", "type": "main", "index": 0}], [{"node": "Search For Existing Point1", "type": "main", "index": 0}], [{"node": "Read File", "type": "main", "index": 0}]]}, "Local File Trigger": {"main": [[{"node": "Set Variables", "type": "main", "index": 0}]]}, "Default Data Loader": {"ai_document": [[{"node": "Qdrant Vector Store", "type": "ai_document", "index": 0}]]}, "Has Existing Point?": {"main": [[{"node": "Delete Existing Point1", "type": "main", "index": 0}]]}, "Has Existing Point?1": {"main": [[{"node": "Delete Existing Point", "type": "main", "index": 0}]]}, "Qdrant Vector Store1": {"ai_vectorStore": [[{"node": "Vector Store Retriever", "type": "ai_vectorStore", "index": 0}]]}, "Delete Existing Point": {"main": [[{"node": "Remap for File_Added Flow", "type": "main", "index": 0}]]}, "Vector Store Retriever": {"ai_retriever": [[{"node": "Question and Answer Chain", "type": "ai_retriever", "index": 0}]]}, "Embeddings Mistral Cloud": {"ai_embedding": [[{"node": "Qdrant Vector Store", "type": "ai_embedding", "index": 0}]]}, "Mistral Cloud Chat Model": {"ai_languageModel": [[{"node": "Question and Answer Chain", "type": "ai_languageModel", "index": 0}]]}, "Embeddings Mistral Cloud1": {"ai_embedding": [[{"node": "Qdrant Vector Store1", "type": "ai_embedding", "index": 0}]]}, "Remap for File_Added Flow": {"main": [[{"node": "Read File", "type": "main", "index": 0}]]}, "Search For Existing Point": {"main": [[{"node": "Has Existing Point?", "type": "main", "index": 0}]]}, "Prepare Embedding Document": {"main": [[{"node": "Qdrant Vector Store", "type": "main", "index": 0}]]}, "Search For Existing Point1": {"main": [[{"node": "Has Existing Point?1", "type": "main", "index": 0}]]}, "When clicking \"Test workflow\"": {"main": [[{"node": "Set Variables", "type": "main", "index": 0}]]}, "Recursive Character Text Splitter": {"ai_textSplitter": [[{"node": "Default Data Loader", "type": "ai_textSplitter", "index": 0}]]}}}How to Import This Workflow
- 1Copy the workflow JSON above using the Copy Workflow JSON button.
- 2Open your n8n instance and go to Workflows.
- 3Click Import from JSON and paste the copied workflow.
Don't have an n8n instance? Start your free trial at n8nautomation.cloud
Related Templates
Text to Speech (OpenAI)
Converts text into natural-sounding speech using OpenAI's Text-to-Speech API. It sends your input text to OpenAI and receives an audio file in return. This is useful for creating audio versions of articles, generating voiceovers for videos, or providing accessibility features for web content. Quickly transform written content into engaging audio.
LangChain - Example - Code Node Example
Explore a basic LangChain agent that answers questions using a custom tool. This workflow connects n8n's AI nodes and custom code nodes to OpenAI for language model interactions. It's useful for developers building custom AI assistants or researchers experimenting with agentic workflows. This saves development time by providing a ready-to-use example of a LangChain agent.
AI-Powered Candidate Shortlisting Automation for ERPNext
Automate AI-powered candidate shortlisting for ERPNext job applications. This workflow connects ERPNext, Google Gemini, WhatsApp, and Outlook to process resumes, evaluate candidates, and communicate outcomes. Recruiters and HR departments can use this to efficiently screen applicants, automatically reject unqualified candidates, and send acceptance notifications. It significantly reduces manual review time and streamlines the hiring process.