Skip to main content
A complete example of question and answer against an Asgard RAG knowledge base

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:

  1. Create a knowledge base — upload PDFs, text files, web pages
  2. Configure an embedding model — pick the vectorising model, for example OpenAI text-embedding-3-small
  3. Add a Retrieve Knowledge processor to the workflow — set the retrieval strategy and which knowledge base to use
  4. 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:

AdviceExample
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}`);
});
}
}
info

What template contains depends on how you configured the workflow. See Message Template.


Next​