Build a RAG knowledge-base agent
Retrieve the right passages first, then answer from a bounded context. This small version uses Qwen embeddings and a grounded DeepSeek response.
https://aiwave.live/v1 as the OpenAI-compatible Base URL. The live catalog checked on 2026-09-25 lists qwen3.7-text-embedding and deepseek-v4-flash. Keep your key in AIWAVE_API_KEY, never in source control.Why this pattern
Sending an entire knowledge base on every question is difficult to inspect and scale. Retrieval-augmented generation separates the work: rank the relevant passages, then ask the model to answer only from those passages. This recipe embeds four fictional help-center entries on each run so the first version stays easy to understand.
1. The 60-second version
Create a key in AIWave Console, then run:
export AIWAVE_API_KEY="sk-your-api-key" python -m pip install openai python rag_knowledge_base.py "How long are password reset links valid?"
Save the following as rag_knowledge_base.py:
import math
import os
import sys
from openai import OpenAI
BASE_URL = os.environ.get("AIWAVE_BASE_URL", "https://aiwave.live/v1")
CHAT_MODEL = os.environ.get("AIWAVE_CHAT_MODEL", "deepseek-v4-flash")
EMBEDDING_MODEL = "qwen3.7-text-embedding"
KNOWLEDGE_BASE = [
{"title": "Password reset", "text": "Password reset links expire after 30 minutes. Request a new link after expiry."},
{"title": "API key rotation", "text": "Create a replacement key, update the client, verify one bounded request, then revoke the old key."},
{"title": "Invoice timing", "text": "Usage appears in the Console request record after the request completes. Review the dated rate before forecasting."},
{"title": "Support handoff", "text": "For an unresolved account issue, include the request ID and error class, never the API key or request body."},
]
def cosine_similarity(left, right):
numerator = sum(a * b for a, b in zip(left, right))
left_norm = math.sqrt(sum(value * value for value in left))
right_norm = math.sqrt(sum(value * value for value in right))
if not left_norm or not right_norm:
return 0.0
return numerator / (left_norm * right_norm)
def retrieve(client, question, top_k=2):
texts = [item["text"] for item in KNOWLEDGE_BASE] + [question]
response = client.embeddings.create(model=EMBEDDING_MODEL, input=texts)
vectors = sorted(response.data, key=lambda item: item.index)
question_vector = vectors[-1].embedding
scored = []
for item, vector in zip(KNOWLEDGE_BASE, vectors[:-1]):
scored.append((cosine_similarity(vector.embedding, question_vector), item))
scored.sort(key=lambda pair: pair[0], reverse=True)
return [item for _score, item in scored[:top_k]]
def answer(question):
client = OpenAI(api_key=os.environ["AIWAVE_API_KEY"], base_url=BASE_URL)
passages = retrieve(client, question)
context = "\n\n".join(
f"[{index}] {item['title']}: {item['text']}"
for index, item in enumerate(passages, start=1)
)
response = client.chat.completions.create(
model=CHAT_MODEL,
messages=[
{"role": "system", "content": "Answer only from the supplied context. Cite supporting passages as [1], [2]. If the context is insufficient, say so."},
{"role": "user", "content": f"Context:\n{context}\n\nQuestion: {question}"},
],
max_tokens=180,
)
return response.choices[0].message.content or "No answer returned."
if __name__ == "__main__":
if len(sys.argv) != 2:
raise SystemExit('Usage: python rag_knowledge_base.py "your question"')
print(answer(sys.argv[1]))Validation: run python -m py_compile rag_knowledge_base.py, then ask the password-reset question. The response should cite one of the retrieved passages. The script never prints the API key.
2. One-run cost model
Illustrative estimate using the dated base rates fetched from Pricing on 2026-09-25. The rows are effective 2026-08-27; the account-group multiplier controls the applied charge.
| Component | Assumption | Estimate |
|---|---|---|
| Embeddings | 4 passages + 1 question, about 170 input tokens × $0.111585556 / 1M | $0.00002 |
| Answer | About 430 input × $0.638 / 1M + 80 output × $1.914 / 1M | $0.00043 |
| Complete run | One embedding request plus one grounded chat request | About $0.00045 |
| Source | Pricing JSON, fetched 2026-09-25 | Recheck before scaling |
This is a planning estimate, not a billing promise. Actual tokens, model, account group, and retries determine the charge.
3. Extend it safely
- Precompute corpus embeddings with a document revision instead of embedding every passage on every question.
- Add a similarity threshold and hand off when no passage is relevant enough.
- Store document titles, source URLs, and revision dates beside each chunk.
- Batch embedding requests and cap retrieved passages before the chat call.
- Recheck model IDs and rates on Models and Pricing before changing the defaults.
4. Common failures
401: checkAIWAVE_API_KEYand the exact Base URL; see Request Recovery.402: check Console balance and the selected account group before retrying.429: use bounded backoff and lower concurrency; see Request Recovery.400: inspect the embedding input shape, model ID, and token limits; see Client setup.5xxor timeout: retain the request ID when returned and retry only within a bounded policy.
Next steps
Self-check
- Uses the live Base URL and model IDs checked on 2026-09-25.
- Includes a copy-ready 60-second path and complete Python script.
- Includes a dated, assumption-based cost table.
- Links common error classes to recovery guidance.
- Uses
?ref=agents-6on next-step links. - Contains no customer data, internal data, or provider credentials.