Querying a knowledge base
Asgard implements RAG (Retrieval-Augmented Generation) with a knowledge base and the Retrieve Knowledge processor, so the AI answers from the documents and data you have uploaded.
How it works
The RAG query happens entirely on the workflow server, and the API you call is exactly the one you call to send an ordinary message:
the user asks a question
↓
the Asgard API (the same endpoint)
↓
Retrieve Knowledge processor (finds the relevant content in the knowledge base)
↓
LLM (generates the answer from what was retrieved)
↓
the SSE stream
You do not change how you call the API. Add a Retrieve Knowledge processor to the workflow, point it at a knowledge base, and RAG works from there.
Before you start
Set these up on the Asgard platform first:
- Create a knowledge base — upload PDFs, text files, web pages
- Configure an embedding model — pick the vectorising model, for example OpenAI text-embedding-3-small
- Add a Retrieve Knowledge processor to the workflow — set the retrieval strategy and which knowledge base to use
- Publish the app — which gives you the namespace and bot-provider-name
For the detailed steps see knowledge base settings.
How to phrase the question
How a question is phrased makes a real difference to what the knowledge base retrieves:
| Advice | Example |
|---|---|
| Use concrete keywords | ✅ How many working days does a refund take? |
| Avoid a vague question | ❌ Tell me everything |
| One topic at a time | ✅ How long is the warranty on product A? |
| Give the context | ✅ I am an enterprise customer. What is the contract renewal process? |
cURL
curl -X POST "https://api.asgard-ai.com/generic/ns/your-namespace/bot-provider/your-bot-provider/message/sse" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your-api-key" \
-d '{
"customChannelId": "kb-query-channel-001",
"customMessageId": "kb-msg-001",
"text": "What documents do I need for a refund, and what is the process?",
"action": "NONE"
}'
JavaScript
const BASE_URL = 'https://api.asgard-ai.com';
const NAMESPACE = 'your-namespace';
const BOT_PROVIDER = 'your-bot-provider';
const API_KEY = process.env.ASGARD_API_KEY;
/**
* Ask the knowledge base a question and get a RAG answer
* @param {string} question - the question
* @param {string} channelId - the channel id; the same channel keeps the conversation memory
*/
async function queryKnowledgeBase(question, channelId = 'kb-channel-001') {
const url = `${BASE_URL}/generic/ns/${NAMESPACE}/bot-provider/${BOT_PROVIDER}/message/sse`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-KEY': API_KEY,
},
body: JSON.stringify({
customChannelId: channelId,
text: question,
action: 'NONE',
}),
});
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let fullAnswer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (!line.startsWith('data:')) continue;
const jsonStr = line.slice(5).trim();
if (!jsonStr) continue;
try {
const event = JSON.parse(jsonStr);
if (event.eventType === 'asgard.message.delta') {
const delta = event.fact.messageDelta.message.text;
fullAnswer += delta;
// print as it arrives, for the typewriter effect
process.stdout.write(delta);
}
if (event.eventType === 'asgard.run.done') {
console.log('\n\nQuery complete');
return fullAnswer;
}
} catch (_) {}
}
}
return fullAnswer;
}
// usage: asking follow-up questions on one channelId, which keeps the memory
async function main() {
const channelId = `kb-session-${Date.now()}`;
console.log('Question 1:');
await queryKnowledgeBase('What documents do I need for a refund?', channelId);
console.log('\n\nQuestion 2, a follow-up:');
await queryKnowledgeBase('Roughly how many working days does it take?', channelId);
}
main().catch(console.error);
Python
import requests
import json
import os
import time
BASE_URL = "https://api.asgard-ai.com"
NAMESPACE = "your-namespace"
BOT_PROVIDER = "your-bot-provider"
API_KEY = os.environ.get("ASGARD_API_KEY")
def query_knowledge_base(question: str, channel_id: str) -> str:
"""
Ask the Asgard knowledge base a question and get a RAG answer.
Args:
question: the question
channel_id: the channel id; the same channel keeps the conversation memory
Returns:
The full answer text
"""
url = f"{BASE_URL}/generic/ns/{NAMESPACE}/bot-provider/{BOT_PROVIDER}/message/sse"
headers = {
"Content-Type": "application/json",
"X-API-KEY": API_KEY,
}
payload = {
"customChannelId": channel_id,
"text": question,
"action": "NONE",
}
full_answer = ""
with requests.post(url, headers=headers, json=payload, stream=True) as response:
response.raise_for_status()
for line in response.iter_lines():
if not line:
continue
decoded = line.decode("utf-8")
if not decoded.startswith("data:"):
continue
json_str = decoded[5:].strip()
if not json_str:
continue
try:
event = json.loads(json_str)
event_type = event.get("eventType")
if event_type == "asgard.message.delta":
delta = event["fact"]["messageDelta"]["message"]["text"]
full_answer += delta
print(delta, end="", flush=True)
elif event_type == "asgard.run.done":
print("\n")
break
except json.JSONDecodeError:
pass
return full_answer
if __name__ == "__main__":
channel_id = f"kb-session-{int(time.time())}"
questions = [
"What documents do I need for a refund?",
"Roughly how many working days does a refund take?",
"Can I still apply after the refund window has closed?",
]
for i, question in enumerate(questions, 1):
print(f"\nQuestion {i}: {question}")
print("Answer: ", end="")
answer = query_knowledge_base(question, channel_id)
print(f"({len(answer)} characters)")
Reading the sources out of the response
When the workflow is set to return the knowledge base sources, they arrive in the template field of the asgard.message.complete event:
if (event.eventType === 'asgard.message.complete') {
const message = event.fact.messageComplete.message;
// the full answer text
const answerText = message.text;
// the knowledge base sources, if the workflow is set to return them
const template = message.template;
if (template && template.sources) {
console.log('Sources:');
template.sources.forEach((source, i) => {
console.log(` ${i + 1}. ${source.title} — ${source.url || source.fileName}`);
});
}
}
What template contains depends on how you configured the workflow. See Message Template.
Next
- Handling a streamed response — building the typewriter effect
- Webhook integration — triggering a workflow automatically
- Knowledge base settings — uploading and managing the documents