Demystifying RAG: Build a Private AI Database Assistant in
CodeIgniter 3
Have you ever asked an AI model (like
ChatGPT or Gemini) a highly specific question about your private business data,
only for it to confidently make up a completely false answer?
In the AI world, this is called hallucination.
Standard AI models are trained on public
internet data up to a specific cutoff date. They don’t know about your active
restaurant listings, your active coupon codes today, or your company's latest
GST bills.
So how do we solve this? The answer is RAG (Retrieval-Augmented Generation).
In this comprehensive guide, we will
break down exactly what RAG is, how it works in plain English, and how to
implement it step-by-step inside a classic CodeIgniter
3 PHP application.
1. What on Earth is RAG? (The Open-Book Analogy)
Imagine you are sitting in a classroom
taking a highly specialized history exam:
·
Traditional AI (Closed-Book): The
teacher asks you: "What was the GST
bill total for Restaurant X in May 2026?". You have to rely purely on
your memory. If you don't know, you either fail or guess wildly.
·
RAG AI (Open-Book): Before you answer,
the teacher allows you to walk over to a bookshelf, grab the specific folder
marked "Restaurant X Invoices - May 2026", read it, and then write
down the perfect answer based directly on that folder.
RAG
turns the AI into an open-book exam taker. It
retrieves relevant articles or database records first, hands them to the AI,
and says, "Answer the user's
question using ONLY this information."
2. The Three Steps of RAG
Every RAG system, from simple search bars
to complex enterprise systems, operates in three phases:
graph TD
A[Step 1: Data Ingestion]
-->|Chunk & Embed| B[(Vector Database)]
C[User Asks Question] -->|Step 2:
Retrieval| D[Search Vector DB]
D -->|Find Matches| E[Inject
Context into Prompt]
E -->|Step 3: Generation| F[LLM
eg. Gemini/GPT]
F -->|Accurate Answer| G[User
Receives Response]
Phase A: Ingestion (Storing Knowledge)
Computers don't understand English the
way humans do. To make text searchable by "meaning," we convert text
paragraphs into a long list of numbers called Embeddings (or Vectors). We save these numbers into a Vector Database (like Pinecone, Qdrant,
or PGVector).
Phase B: Retrieval (Finding Knowledge)
When a user asks: "What active coupons do we have today?", the system
converts that question into a vector and searches the Vector Database for the
most mathematically similar matches.
Phase C: Generation (Answering with Knowledge)
The system grabs those matching text
snippets, pastes them into a prompt alongside the original question, and sends
it to the AI. The AI reads the custom data and returns a perfectly accurate
answer.
3. Implementing RAG in CodeIgniter 3
Let's build a fully functioning RAG
assistant in CodeIgniter 3 using:
·
OpenAI API (to handle text generation
and embeddings).
·
Pinecone API (a hosted cloud vector
database with a free tier).
Step 1: Create the
RAG Library (application/libraries/RagService.php)
This reusable library handles
communication between your server, OpenAI, and Pinecone using PHP cURL.
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class RagService {
private $openai_key =
'YOUR_OPENAI_API_KEY';
private $pinecone_key =
'YOUR_PINECONE_API_KEY';
private $pinecone_host =
'https://your-index-url.pinecone.io'; // Your index host from Pinecone console
/**
* Convert plain text into list of
numbers (Vector Embeddings)
*/
public function get_embedding($text)
{
$url =
'https://api.openai.com/v1/embeddings';
$data = [
'input' => $text,
'model' =>
'text-embedding-3-small'
];
$response =
$this->call_api($url, $data, $this->openai_key);
return
isset($response['data'][0]['embedding']) ? $response['data'][0]['embedding'] :
null;
}
/**
* Search the Pinecone Vector DB for
semantically similar text
*/
public function
query_vectors($vector, $top_k = 3) {
$url = $this->pinecone_host .
'/query';
$data = [
'vector' => $vector,
'topK' => $top_k,
'includeMetadata' => true
];
$headers = [
'Api-Key: ' .
$this->pinecone_key,
'Content-Type:
application/json'
];
$response =
$this->call_api($url, $data, null, $headers);
$context_chunks = [];
if (isset($response['matches']))
{
foreach ($response['matches']
as $match) {
if
(isset($match['metadata']['text'])) {
$context_chunks[] =
$match['metadata']['text'];
}
}
}
return $context_chunks;
}
/**
* Send context + question to
GPT/Gemini for the final grounded answer
*/
public function ask_llm($question,
$context_array) {
$url =
'https://api.openai.com/v1/chat/completions';
$context_text =
implode("\n\n---\n\n", $context_array);
$prompt = "You are a helpful
business assistant. Answer the user's question using ONLY the provided context
below. "
. "If the answer
cannot be found in the context, say 'I do not have enough information to answer
that.'\n\n"
. "=== CONTEXT
===\n" . $context_text . "\n===============\n\n"
. "=== QUESTION
===\n" . $question;
$data = [
'model' => 'gpt-4o-mini',
'messages' => [
['role' => 'user',
'content' => $prompt]
],
'temperature' => 0.3
];
$response =
$this->call_api($url, $data, $this->openai_key);
return
isset($response['choices'][0]['message']['content']) ?
$response['choices'][0]['message']['content'] : 'Error generating response';
}
/**
* Push new business knowledge into
the Vector DB
*/
public function insert_knowledge($id,
$text, $metadata = []) {
$embedding =
$this->get_embedding($text);
if (!$embedding) return false;
$url = $this->pinecone_host .
'/vectors/upsert';
$metadata['text'] = $text;
$data = [
'vectors' => [
[
'id' =>
(string)$id,
'values' =>
$embedding,
'metadata' =>
$metadata
]
]
];
$headers = [
'Api-Key: ' .
$this->pinecone_key,
'Content-Type:
application/json'
];
$response =
$this->call_api($url, $data, null, $headers);
return
isset($response['upsertedCount']) && $response['upsertedCount'] > 0;
}
private function call_api($url,
$post_data, $bearer_token = null, $custom_headers = []) {
$ch = curl_init($url);
$headers = [];
if ($bearer_token) {
$headers[] = 'Authorization:
Bearer ' . $bearer_token;
$headers[] = 'Content-Type:
application/json';
}
if (!empty($custom_headers)) {
$headers = $custom_headers;
}
curl_setopt($ch,
CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST,
true);
curl_setopt($ch,
CURLOPT_POSTFIELDS, json_encode($post_data));
curl_setopt($ch,
CURLOPT_HTTPHEADER, $headers);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response,
true);
}
}
Step 2: Create the Controller (application/controllers/Assistant.php)
This controller maps the incoming user
web requests to the RAG service.
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Assistant extends CI_Controller {
public function __construct() {
parent::__construct();
$this->load->library('ragservice');
}
/**
* Endpoint to ask a question
* Route: /assistant/ask
*/
public function ask() {
$question =
$this->input->post('question');
if (empty($question)) {
echo json_encode(['error'
=> 'Question is required']);
return;
}
// 1. Get embedding for the
user's question
$vector =
$this->ragservice->get_embedding($question);
// 2. Query Vector DB for
relevant context
$context =
$this->ragservice->query_vectors($vector, 3);
// 3. Send to LLM to generate the
grounded answer
$answer =
$this->ragservice->ask_llm($question, $context);
// 4. Return results as JSON
$this->output
->set_content_type('application/json')
->set_output(json_encode([
'answer' => $answer,
'retrieved_sources' =>
$context
]));
}
/**
* Endpoint to upload/sync your
restaurant database to Vector database
* Route: /assistant/sync_db
*/
public function sync_db() {
// Example: Querying active
coupons from MySQL to index in Pinecone
// $coupons =
$this->db->get_where('coupons', ['status' =>
'active'])->result_array();
$sample_records = [
['id' => 'coupon_1', 'text'
=> 'Coupon "SUMMER50" gives a 50% discount on food orders above
$20. Valid until August 2026.'],
['id' => 'restaurant_1',
'text' => 'Bistro Hub is located at 123 Main St. It is currently active and
open from 9 AM to 10 PM daily.'],
['id' => 'tax_1', 'text'
=> 'For the 2026-2027 Financial Year, GST bills accrued a total liability of
$14,520.20 as of May 2026.']
];
$count = 0;
foreach ($sample_records as
$record) {
$success =
$this->ragservice->insert_knowledge($record['id'], $record['text']);
if ($success) $count++;
}
echo "Successfully indexed
{$count} records into the AI's Vector DB!";
}
}
4. How the Magic Happens Under the Hood
Let's test our new CodeIgniter RAG
assistant:
1.
Ingestion: You hit /assistant/sync_db. This uploads your restaurant and tax knowledge into Pinecone as
embeddings.
2.
User Query: A user types: "Is Bistro Hub open right now, and what
discount codes can I use?"
3.
Retrieval: The RagService looks
up Pinecone. It retrieves:
·
Match 1: "Bistro Hub is located at
123 Main St. It is currently active..."
·
Match 2: "Coupon 'SUMMER50' gives a
50% discount..."
4.
Generation: GPT/Gemini reads this
information and crafts a beautiful response:
"Yes,
Bistro Hub is currently active and open daily from 9 AM to 10 PM. You can use
the coupon code SUMMER50 to get a
50% discount on orders above $20!"
5. Key Best Practices for a Great RAG System
·
Automatic Syncing (CRON Jobs): Instead
of manually syncing data, set up a CI3 CLI Command or CRON job to read updated
rows from your MySQL database every night and sync them into the Vector DB.
·
Keep Data Chunks Small: Don't throw a
whole 100-page manual into one vector. Break it down into paragraphs or
sentences (chunks) so the search matches are exact and the LLM prompts remain
small and cheap.
·
Clean Metadata: Always attach metadata
tags (like restaurant_id, date, category) to your vectors. This allows you to filter search results by date
or category before querying.
Conclusion
RAG is a game-changer for building
intelligent, context-aware web apps. By connecting your legacy CodeIgniter 3 database to modern AI and
Vector DB APIs, you can build incredibly powerful chatbot assistants, invoice
analyzers, or automated customer support systems that never hallucinate.
Best of all, you can start building it entirely for free today!
0 comments:
Post a Comment