07/07/2026 03:00am

Golang The Series EP.158: Context Injection Techniques in RAG Architecture
#Golang
#Go AI
#Qdrant
#Context Injection
#Vector Database
#OpenAI
#GPT-4o
#Hallucination
Welcome to EP.158 of Superdev Academy! In our last episode, we built an automated Document Ingestion Pipeline to extract text from PDF files and store them as vectors in Qdrant.
Today, we are taking our RAG (Retrieval-Augmented Generation) architecture to the next level with a core technique: Context Injection. Essentially, this means taking the relevant text segments (Chunks) retrieved via semantic search and feeding them directly into the LLM's prompt along with the user's query. By providing this explicit context, we force models like GPT-4o to answer only based on our data, effectively eliminating hallucinations or the AI making things up.พ
Context Injection Flow in the Backend (Go)
To give you a clear picture of how our Golang backend processes data when a user asks a question, let's break down the 3 core steps:
Plaintext
[User submits a question]
│
▼
1. Vector Search (Qdrant) ──> The system searches the vector DB and retrieves the top 3 most relevant text chunks.
│
▼
2. Context Injection ───────> The backend stitches the "User Question" + "Top 3 Chunks" together into a single Prompt Template.
│
▼
3. Call LLM API (OpenAI) ───> The context-rich prompt is sent to the AI ➡️ The model generates an accurate response based strictly on our data.
Designing a Prompt Template to Stop Hallucinations
Once we pull the relevant chunks from our database, the next step is combining them into a final prompt. The secret to a bulletproof RAG system is keeping the model on a tight leash using strict System Prompt rules:
"Answer the question using ONLY the provided context. If the answer cannot be found within the context, reply with 'Sorry, this information is not available in the system.' Do not make up or assume any facts."
This approach is vital for security and ensures production-grade reliability for enterprise applications.
For Go (Golang) developers, our go-to method at Superdev Academy for assembling these backend prompts is the trusted fmt.Sprintf for simple structures. If your prompt grows longer or requires complex variables, switching to text/template will keep your code clean and manageable.
Step-by-Step Go Code for RAG
Dumping a massive block of code can be overwhelming. Let's break main.go into 4 digestible parts so you can easily trace the context injection flow.
1. Initialize Clients
First, we set up our clients to communicate with both OpenAI (for Embeddings and LLM completion) and Qdrant (for Vector Search).
Go
package main
import (
"context"
"fmt"
"log"
"os"
"strings"
"github.com/qdrant/go-client/qdrant"
"github.com/sashabaranov/go-openai"
)
func main() {
ctx := context.Background()
userQuery := "How many vacation days do new employees get?"
// Fetch API Key from Environment Variables to secure credentials
apiKey := os.Getenv("OPENAI_API_KEY")
if apiKey == "" {
log.Fatal("Please set your OPENAI_API_KEY environment variable")
}
openaiClient := openai.NewClient(apiKey)
// Connect to Qdrant via gRPC Port (Default: 6334)
qdrantClient, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
if err != nil {
log.Fatalf("Qdrant connection failed: %v", err)
}
defer qdrantClient.Close()
2. Query Embedding
Before querying the database, we must convert the user's natural language question into a 1,536-dimensional vector using the text-embedding-3-small model—a concept we covered back in EP.156.
Go
embReq := openai.EmbeddingRequest{
Input: []string{userQuery},
Model: openai.SmallEmbedding3Small,
}
embResp, err := openaiClient.CreateEmbeddings(ctx, embReq)
if err != nil {
log.Fatalf("Embedding failed: %v", err)
}
queryVector := embResp.Data[0].Embedding
3. Vector Search & Extraction
We feed the query vector into our ai_knowledge_base collection, limiting results to the top 3 closest matches (Top-3). We then loop through the payload to extract the raw text and join them into a single string.
Go
searchLimit := uint64(3)
searchResp, err := qdrantClient.Query(ctx, &qdrant.QueryPoints{
CollectionName: "ai_knowledge_base",
Query: qdrant.NewQuery(queryVector...),
Limit: &searchLimit,
})
if err != nil {
log.Fatalf("Qdrant query failed: %v", err)
}
// Extract raw text chunks and join them with newlines
var contextChunks []string
for _, point := range searchResp {
if contentValue, exists := point.Payload["content"]; exists {
content := contentValue.GetStringValue()
contextChunks = append(contextChunks, fmt.Sprintf("- %s", content))
}
}
injectedContext := strings.Join(contextChunks, "\n\n")
4. Context Injection & Call GPT-4o
This is where the magic happens. We inject the gathered injectedContext into our prompt layout and invoke OpenAI's Chat Completion API.
Go
systemPrompt := "You are an internal company assistant. Your job is to answer employee questions using ONLY the provided Context. Do not use external knowledge. If the answer isn't in the Context, reply with 'Sorry, this information is not available in the system.'"
// Inject Context and Query using fmt.Sprintf
userPromptWithContext := fmt.Sprintf(`Answer the question below based strictly on the provided reference data.
[Context Data]
%s
[Question]
%s
Answer:`, injectedContext, userQuery)
// Send the context-rich prompt to OpenAI
chatResp, err := openaiClient.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
Model: openai.GPT4o,
Messages: []openai.ChatCompletionMessage{
{Role: openai.ChatMessageRoleSystem, Content: systemPrompt},
{Role: openai.ChatMessageRoleUser, Content: userPromptWithContext},
},
})
if err != nil {
log.Fatalf("Chat completion failed: %v", err)
}
fmt.Println("🤖 AI Response (Verified by internal docs):")
fmt.Println(chatResp.Choices[0].Message.Content)
}
What Gophers Need to Watch Out For: Context Window and Pricing
While Context Injection delivers highly accurate answers, scaling it in production comes with two main challenges that senior developers look out for:
Token Bloat (Spiraling Costs): Keep in mind that even if a user asks a short one-sentence question, your backend will append 3 to 5 dense text chunks (potentially thousands of tokens) to the prompt. This means input token costs can scale up rapidly alongside user traction.
Max Context Limit (API Breakers): If you pull too much data and exceed the model's maximum Context Window, the OpenAI API will throw an error. To prevent this, always pair your setup with proper Token Management (we highly recommend calculating and trimming text using the
tiktoken-golibrary covered in EP.149).
🎯 Daily Mission: Stress-Test Your Prompts
Take the code from this episode and run it against the company handbook PDF you ingested during EP.157. Then, throw a curveball by asking something completely unrelated—like "What is the traditional recipe for Pad Thai?" or "How do I fix a broken washing machine?"
What to look for: Does the model follow your strict system prompt constraints and return your fallback error string? Or does it break character and pull public data from the internet? Tweak your prompt template until your bot behaves predictably 100% of the time!
💬 FAQ
If a semantic search returns a very low score, should we still inject it into the prompt?
Absolutely not. In production-grade code, you should always implement a logic check on point.Score. If the similarity score falls below your defined threshold (e.g., lower than 0.50), skip it. Injecting irrelevant filler data confuses the LLM and drastically increases the chance of a hallucination.
Does appending raw text chunks sequentially cause the model to ignore details in the middle?
Yes, this is a proven phenomenon in AI research. It is known as the "Lost in the Middle" problem. Most LLMs tend to pay closer attention to the beginning and end of a prompt, glossing over details buried in the middle. Advanced workarounds involve using a Reranking step to ensure the most critical chunks sit at the top and bottom of your context stack before injection.
📝 Summary
In EP.158, we completed a vital phase of our RAG architecture: performing vector lookups via Qdrant, sanitizing and formatting the payloads into a strict prompt structure, and handling the OpenAI response loop safely for a production environment.
We have successfully connected external documents to our LLM. However, in the real world, production data isn't just vectors; you still have relational assets like employee IDs, departments, or financial balances sitting inside traditional databases.
🚀 Coming Up Next in EP.159: In our next episode, we will evolve our system into a hybrid powerhouse: "Hybrid Search: Combining SQL and Vector Search in a Single Go Project." See you there!
Follow Superdev Academy on all platforms:
🔵 Facebook: Superdev Academy Thailand
🎬 YouTube: Superdev Academy Channel
📸 Instagram: @superdevacademy
🎬 TikTok: @superdevacademy
🌐 Website: superdevacademy.com