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 →
{
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "document-qa",
"options": {}
},
"id": "webhook",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
250,
300
]
},
{
"parameters": {
"model": "llama3",
"operation": "embedding",
"input": "={{ $json.question }}",
"options": {}
},
"id": "generate_question_embedding",
"name": "Generate Question Embedding",
"type": "n8n-nodes-base.ollama",
"typeVersion": 1,
"position": [
480,
300
]
},
{
"parameters": {
"url": "=http://qdrant:6333/collections/documents/points/search",
"method": "POST",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "vector",
"value": "={{ $node[\"Generate Question Embedding\"].json.embedding }}"
},
{
"name": "limit",
"value": "={{ $json.max_results || 5 }}"
},
{
"name": "with_payload",
"value": true
},
{
"name": "filter",
"value": "={{ $json.document_ids && $json.document_ids.length > 0 ? {\"must\": [{\"key\": \"document_id\", \"match\": {\"any\": $json.document_ids}}]} : {} }}"
}
]
},
"options": {}
},
"id": "search_documents",
"name": "Search Documents",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [
700,
300
]
},
{
"parameters": {
"functionCode": "// Extract relevant information from search results\nconst results = $input.json.result || [];\n\n// Format the context from retrieved documents\nlet context = '';\nlet sources = [];\n\nif (results && results.length > 0) {\n results.forEach((item, index) => {\n if (item.payload && item.payload.text) {\n context += `Document ${index + 1}: ${item.payload.text}\\n\\n`;\n \n // Add to sources\n sources.push({\n document_id: item.payload.document_id || 'unknown',\n document_title: item.payload.document_title || 'Unknown Document',\n page: item.payload.page || null,\n text: item.payload.text.substring(0, 200) + '...'\n });\n }\n });\n}\n\nreturn {\n context,\n sources,\n question: $input.first().json.question,\n conversation_id: $input.first().json.conversation_id || null\n};"
},
"id": "prepare_context",
"name": "Prepare Context",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
920,
300
]
},
{
"parameters": {
"model": "llama3",
"operation": "chat",
"messages": {
"values": [
{
"role": "system",
"content": "You are a helpful document assistant. Your task is to answer questions based on the provided document context. If the context doesn't contain the information needed to answer the question, say that you don't have enough information. Always base your answers on the provided context and be specific about where the information comes from. If you quote or reference specific information, mention which document it's from."
},
{
"role": "user",
"content": "=Context information:\n{{ $json.context }}\n\nQuestion: {{ $json.question }}"
}
]
},
"options": {
"temperature": 0.3
}
},
"id": "generate_answer",
"name": "Generate Answer",
"type": "n8n-nodes-base.ollama",
"typeVersion": 1,
"position": [
1140,
300
]
},
{
"parameters": {
"functionCode": "// Determine confidence based on response\nconst responseText = $input.json.message.content;\nconst lowerResponse = responseText.toLowerCase();\n\n// Check for phrases indicating the AI couldn't answer\nconst lowConfidence = [\n \"i don't have enough information\",\n \"i don't know\",\n \"i'm not sure\",\n \"cannot provide\",\n \"unable to assist\",\n \"not mentioned in the context\",\n \"not specified in the documents\"\n].some(phrase => lowerResponse.includes(phrase));\n\n// Calculate confidence score\nlet confidence = lowConfidence ? 0.3 : 0.85;\n\nreturn {\n answer: responseText,\n sources: $input.first().json.sources || [],\n confidence,\n conversation_id: $input.first().json.conversation_id || null\n};"
},
"id": "format_answer",
"name": "Format Answer",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
1360,
300
]
},
{
"parameters": {
"keepOnlySet": true,
"values": {
"string": [
{
"name": "answer",
"value": "={{ $json.answer }}"
}
],
"number": [
{
"name": "confidence",
"value": "={{ $json.confidence }}"
}
],
"array": [
{
"name": "sources",
"value": "={{ $json.sources }}"
}
]
},
"options": {}
},
"id": "set",
"name": "Set",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [
1580,
300
]
}
],
"connections": {
"webhook": {
"main": [
[
{
"node": "generate_question_embedding",
"type": "main",
"index": 0
}
]
]
},
"generate_question_embedding": {
"main": [
[
{
"node": "search_documents",
"type": "main",
"index": 0
}
]
]
},
"search_documents": {
"main": [
[
{
"node": "prepare_context",
"type": "main",
"index": 0
}
]
]
},
"prepare_context": {
"main": [
[
{
"node": "generate_answer",
"type": "main",
"index": 0
}
]
]
},
"generate_answer": {
"main": [
[
{
"node": "format_answer",
"type": "main",
"index": 0
}
]
]
},
"format_answer": {
"main": [
[
{
"node": "set",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Document-Qa-Agent. Uses ollama, httpRequest. Webhook trigger; 7 nodes.
Source: https://github.com/konfio/Nova.K-AI-Starter-Kit/blob/ceb30794de099859c8309b0e632ed8046543e341/examples/document-qa/document-qa-agent.json — 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.
VEP WAPP. Uses openAi, lmChatOpenAi, toolCalculator, agent. Webhook trigger; 100 nodes.
⏺ 🚀 How it works
L&D_AgentsAI_ATIVO. Uses httpRequest, agent, googleCalendarTool, toolSerpApi. Webhook trigger; 93 nodes.
ANIS_HUB 1. Uses gmail, googleDrive, googleSheets, httpRequest. Webhook trigger; 89 nodes.
CLINICAINTEGRAL_secretary. Uses postgres, mcpClientTool, googleDriveTool, toolWorkflow. Webhook trigger; 89 nodes.