> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gloo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Building a Knowledge-Grounded Chatbot with RAG

> Learn how to ground AI responses in your own content using Gloo AI Grounded Completions with Retrieval-Augmented Generation (RAG).

From time immemorial, well let's say late 2022, we've all had the experience of AI models confidently responding to a question with nonsense or made-up facts. That's a term known as "hallucinating". It happens when models lack the knowledge needed and are incentivized to try and be helpful. At other times, their responses are simply vague and unhelpful.

The good news? There's a fix! This tutorial walks you through how to use Gloo AI's Grounded Completions API with RAG (Retrieval-Augmented Generation). You'll see how giving your AI the right context makes all the difference.

<Info>
  **Key Problem**: Without grounding, AI models may hallucinate answers about your organization, products, or services.

  **The Solution**: Grounded Completions uses RAG to retrieve relevant content from YOUR publisher before generating responses. This tutorial shows how grounding on your own content transforms generic AI into an accurate, source-backed assistant.
</Info>

## Prerequisites

Before starting, ensure you have:

* A Gloo AI Studio account
* Your Client ID and Client Secret from the [API Credentials page](/studio/manage-api-credentials)
* A Publisher created with content uploaded in [Gloo Studio](https://studio.ai.gloo.com)

<Tip>
  **New to Publishers?** Check out the [Upload Files to Data
  Engine](/tutorials/upload-files) recipe to learn how to create a Publisher and
  upload your documents using Gloo AI APIs.
</Tip>

***

## What You'll Build

A 2-step comparison that shows you how RAG grounding works:

1. **Non-grounded (Step 1)**: Generic model knowledge, may hallucinate
2. **Grounded on your publisher (Step 2)**: Your specific content, accurate and source-backed

This comparison shows the dramatic difference grounding on your own content makes.

### Example: Bezalel Ministries

For this tutorial, we've cooked up a fictional organization called Bezalel Ministries. They're a faith-based creative group who produce biblically-accurate artwork and educational resources. They're delightful.

We've created 5 sample documents that cover everything from their hiring process to their educational programs and research methodology.

Why use a made-up org? Because it lets us show you exactly how the API works without any real-world baggage. Plus, Bezalel Ministries is more interesting than "Company X" or "Acme Corp".

**What the comparison reveals**:

* **Step 1 (Non-grounded)**: Generic hiring advice with no knowledge of Bezalel
* **Step 2 (Publisher grounded)**: Accurate details about Bezalel's 3-phase selection journey from their actual docs

<Card title="Working Code Sample" icon="github" href="https://github.com/GlooDeveloper/gloo-ai-docs-cookbook/tree/main/completions-grounded">
  Follow along with complete working examples in all 6 languages (JavaScript, TypeScript, Python, PHP, Go, Java). Includes the 5 Bezalel sample content files.

  Setup and testing instructions are provided later.
</Card>

***

## Step 1: Understanding the Hallucination Problem

Without grounding, when you ask about specific organizational information, the model relies on general knowledge and may:

* Provide generic answers that don't match your reality
* Make up plausible-sounding but incorrect information
* Say "I don't have information" even though you've uploaded relevant content

### The Non-Grounded Request

This is a standard Completions V2 call:

<CodeGroup>
  ```python Python theme={null}
  import requests

  def make_non_grounded_request(query, token):
      """Standard completion WITHOUT RAG - may hallucinate."""

      api_url = "https://platform.ai.gloo.com/ai/v2/chat/completions"
      headers = {
          "Authorization": f"Bearer {token}",
          "Content-Type": "application/json"
      }

      payload = {
          "messages": [{"role": "user", "content": query}],
          "auto_routing": True,
          "max_tokens": 500
      }

      response = requests.post(api_url, headers=headers, json=payload)
      response.raise_for_status()
      return response.json()

  # Example query
  query = "What is Bezalel Ministries' hiring process?"
  result = make_non_grounded_request(query, access_token)
  print(result['choices'][0]['message']['content'])
  # Result: Generic hiring advice, not specific to Bezalel
  ```

  ```javascript JavaScript theme={null}
  async function makeNonGroundedRequest(query, token) {
    // Standard completion WITHOUT RAG - may hallucinate

    const apiUrl = 'https://platform.ai.gloo.com/ai/v2/chat/completions';

    const payload = {
      messages: [{ role: 'user', content: query }],
      auto_routing: true,
      max_tokens: 500,
    };

    const response = await fetch(apiUrl, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(payload),
    });

    return response.json();
  }

  // Example query
  const query = "What is Bezalel Ministries' hiring process?";
  const result = await makeNonGroundedRequest(query, accessToken);
  console.log(result.choices[0].message.content);
  // Result: Generic hiring advice, not specific to Bezalel
  ```

  ```typescript TypeScript theme={null}
  interface CompletionResponse {
    choices: Array<{
      message: {
        content: string;
      };
    }>;
    sources_returned?: boolean;
    model?: string;
  }

  async function makeNonGroundedRequest(
    query: string,
    token: string,
  ): Promise<CompletionResponse> {
    // Standard completion WITHOUT RAG - may hallucinate

    const apiUrl = 'https://platform.ai.gloo.com/ai/v2/chat/completions';

    const payload = {
      messages: [{ role: 'user', content: query }],
      auto_routing: true,
      max_tokens: 500,
    };

    const response = await fetch(apiUrl, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(payload),
    });

    return response.json();
  }

  // Example query
  const query = "What is Bezalel Ministries' hiring process?";
  const result = await makeNonGroundedRequest(query, accessToken);
  console.log(result.choices[0].message.content);
  // Result: Generic hiring advice, not specific to Bezalel
  ```

  ```php PHP theme={null}
  <?php

  function makeNonGroundedRequest($query, $token) {
      // Standard completion WITHOUT RAG - may hallucinate

      $apiUrl = "https://platform.ai.gloo.com/ai/v2/chat/completions";

      $payload = [
          "messages" => [["role" => "user", "content" => $query]],
          "auto_routing" => true,
          "max_tokens" => 500
      ];

      $ch = curl_init($apiUrl);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_POST, true);
      curl_setopt($ch, CURLOPT_HTTPHEADER, [
          "Authorization: Bearer " . $token,
          "Content-Type: application/json"
      ]);
      curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));

      $response = curl_exec($ch);
      curl_close($ch);

      return json_decode($response, true);
  }

  // Example query
  $query = "What is Bezalel Ministries' hiring process?";
  $result = makeNonGroundedRequest($query, $accessToken);
  echo $result['choices'][0]['message']['content'];
  // Result: Generic hiring advice, not specific to Bezalel
  ```

  ```go Go theme={null}
  package main

  import (
      "bytes"
      "encoding/json"
      "net/http"
  )

  type CompletionRequest struct {
      Messages    []Message `json:"messages"`
      AutoRouting bool      `json:"auto_routing"`
      MaxTokens   int       `json:"max_tokens"`
  }

  type Message struct {
      Role    string `json:"role"`
      Content string `json:"content"`
  }

  type CompletionResponse struct {
      Choices []struct {
          Message struct {
              Content string `json:"content"`
          } `json:"message"`
      } `json:"choices"`
      SourcesReturned bool   `json:"sources_returned,omitempty"`
      Model          string `json:"model,omitempty"`
  }

  func makeNonGroundedRequest(query, token string) (*CompletionResponse, error) {
      // Standard completion WITHOUT RAG - may hallucinate

      apiURL := "https://platform.ai.gloo.com/ai/v2/chat/completions"

      payload := CompletionRequest{
          Messages: []Message{
              {Role: "user", Content: query},
          },
          AutoRouting: true,
          MaxTokens:   500,
      }

      jsonData, _ := json.Marshal(payload)
      req, _ := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonData))
      req.Header.Set("Authorization", "Bearer "+token)
      req.Header.Set("Content-Type", "application/json")

      client := &http.Client{}
      resp, err := client.Do(req)
      if err != nil {
          return nil, err
      }
      defer resp.Body.Close()

      var result CompletionResponse
      json.NewDecoder(resp.Body).Decode(&result)
      return &result, nil
  }

  // Example usage
  func main() {
      query := "What is Bezalel Ministries' hiring process?"
      result, _ := makeNonGroundedRequest(query, accessToken)
      fmt.Println(result.Choices[0].Message.Content)
      // Result: Generic hiring advice, not specific to Bezalel
  }
  ```

  ```java Java theme={null}
  import java.net.http.*;
  import java.net.URI;
  import com.google.gson.*;

  public class NonGroundedRequest {

      public static JsonObject makeNonGroundedRequest(String query, String token)
              throws Exception {
          // Standard completion WITHOUT RAG - may hallucinate

          String apiUrl = "https://platform.ai.gloo.com/ai/v2/chat/completions";

          JsonObject message = new JsonObject();
          message.addProperty("role", "user");
          message.addProperty("content", query);

          JsonArray messages = new JsonArray();
          messages.add(message);

          JsonObject payload = new JsonObject();
          payload.add("messages", messages);
          payload.addProperty("auto_routing", true);
          payload.addProperty("max_tokens", 500);

          HttpClient client = HttpClient.newHttpClient();
          HttpRequest request = HttpRequest.newBuilder()
              .uri(URI.create(apiUrl))
              .header("Authorization", "Bearer " + token)
              .header("Content-Type", "application/json")
              .POST(HttpRequest.BodyPublishers.ofString(payload.toString()))
              .build();

          HttpResponse<String> response = client.send(request,
              HttpResponse.BodyHandlers.ofString());

          return JsonParser.parseString(response.body()).getAsJsonObject();
      }

      // Example query
      public static void main(String[] args) throws Exception {
          String query = "What is Bezalel Ministries' hiring process?";
          JsonObject result = makeNonGroundedRequest(query, accessToken);

          String content = result.getAsJsonArray("choices")
              .get(0).getAsJsonObject()
              .getAsJsonObject("message")
              .get("content").getAsString();

          System.out.println(content);
          // Result: Generic hiring advice, not specific to Bezalel
      }
  }
  ```
</CodeGroup>

**What you'll see**: A generic response about hiring processes, or possibly "I don't have specific information about Bezalel Ministries." The model doesn't know about your organization because it wasn't trained on your content.

***

## Step 2: Grounding on Your Publisher

Now let's use the `/grounded` endpoint with the `rag_publisher` parameter to ground on YOUR specific content.

### The Publisher Grounded Request

This adds two key changes from Step 1: the `/grounded` endpoint and the `rag_publisher` parameter:

<CodeGroup>
  ```python Python theme={null}
  import requests

  def make_publisher_grounded_request(query, token, publisher_name, sources_limit=3):
      """Grounded on YOUR publisher - accurate, source-backed."""

      api_url = "https://platform.ai.gloo.com/ai/v2/chat/completions/grounded"
      headers = {
          "Authorization": f"Bearer {token}",
          "Content-Type": "application/json"
      }

      payload = {
          "messages": [{"role": "user", "content": query}],
          "auto_routing": True,
          "rag_publisher": publisher_name,  # KEY: This grounds on YOUR content
          "sources_limit": sources_limit,
          "max_tokens": 500
      }

      response = requests.post(api_url, headers=headers, json=payload)
      response.raise_for_status()
      return response.json()

  # Same query, now grounded on YOUR publisher
  query = "What is Bezalel Ministries' hiring process?"
  result = make_publisher_grounded_request(query, access_token, "Bezalel")
  print(result['choices'][0]['message']['content'])
  print(f"Sources used: {result.get('sources_returned')}")
  # Result: Detailed answer about Bezalel's 3-phase selection journey
  # Sources used: True
  ```

  ```javascript JavaScript theme={null}
  async function makePublisherGroundedRequest(
    query,
    token,
    publisherName,
    sourcesLimit = 3,
  ) {
    // Grounded on YOUR publisher - accurate, source-backed

    const apiUrl = 'https://platform.ai.gloo.com/ai/v2/chat/completions/grounded';

    const payload = {
      messages: [{ role: 'user', content: query }],
      auto_routing: true,
      rag_publisher: publisherName, // KEY: This grounds on YOUR content
      sources_limit: sourcesLimit,
      max_tokens: 500,
    };

    const response = await fetch(apiUrl, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(payload),
    });

    return response.json();
  }

  // Same query, now grounded on YOUR publisher
  const query = "What is Bezalel Ministries' hiring process?";
  const result = await makePublisherGroundedRequest(
    query,
    accessToken,
    'Bezalel',
  );
  console.log(result.choices[0].message.content);
  console.log(`Sources used: ${result.sources_returned}`);
  // Result: Detailed answer about Bezalel's 3-phase selection journey
  // Sources used: true
  ```

  ```typescript TypeScript theme={null}
  interface PublisherGroundedRequest {
    messages: Array<{ role: string; content: string }>;
    auto_routing: boolean;
    rag_publisher: string;
    sources_limit: number;
    max_tokens: number;
  }

  async function makePublisherGroundedRequest(
    query: string,
    token: string,
    publisherName: string,
    sourcesLimit: number = 3,
  ): Promise<GroundedResponse> {
    // Grounded on YOUR publisher - accurate, source-backed

    const apiUrl = 'https://platform.ai.gloo.com/ai/v2/chat/completions/grounded';

    const payload: PublisherGroundedRequest = {
      messages: [{ role: 'user', content: query }],
      auto_routing: true,
      rag_publisher: publisherName, // KEY: This grounds on YOUR content
      sources_limit: sourcesLimit,
      max_tokens: 500,
    };

    const response = await fetch(apiUrl, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(payload),
    });

    return response.json();
  }

  // Same query, now grounded on YOUR publisher
  const query = "What is Bezalel Ministries' hiring process?";
  const result = await makePublisherGroundedRequest(
    query,
    accessToken,
    'Bezalel',
  );
  console.log(result.choices[0].message.content);
  console.log(`Sources used: ${result.sources_returned}`);
  // Result: Detailed answer about Bezalel's 3-phase selection journey
  // Sources used: true
  ```

  ```php PHP theme={null}
  <?php

  function makePublisherGroundedRequest($query, $token, $publisherName, $sourcesLimit = 3) {
      // Grounded on YOUR publisher - accurate, source-backed

      $apiUrl = "https://platform.ai.gloo.com/ai/v2/chat/completions/grounded";

      $payload = [
          "messages" => [["role" => "user", "content" => $query]],
          "auto_routing" => true,
          "rag_publisher" => $publisherName,  // KEY: This grounds on YOUR content
          "sources_limit" => $sourcesLimit,
          "max_tokens" => 500
      ];

      $ch = curl_init($apiUrl);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_POST, true);
      curl_setopt($ch, CURLOPT_HTTPHEADER, [
          "Authorization: Bearer " . $token,
          "Content-Type: application/json"
      ]);
      curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));

      $response = curl_exec($ch);
      curl_close($ch);

      return json_decode($response, true);
  }

  // Same query, now grounded on YOUR publisher
  $query = "What is Bezalel Ministries' hiring process?";
  $result = makePublisherGroundedRequest($query, $accessToken, "Bezalel");
  echo $result['choices'][0]['message']['content'] . "\n";
  echo "Sources used: " . ($result['sources_returned'] ? 'true' : 'false');
  // Result: Detailed answer about Bezalel's 3-phase selection journey
  // Sources used: true
  ```

  ```go Go theme={null}
  package main

  import (
      "bytes"
      "encoding/json"
      "fmt"
      "net/http"
  )

  type PublisherGroundedRequest struct {
      Messages     []Message `json:"messages"`
      AutoRouting  bool      `json:"auto_routing"`
      RagPublisher string    `json:"rag_publisher"`  // KEY: This grounds on YOUR content
      SourcesLimit int       `json:"sources_limit"`
      MaxTokens    int       `json:"max_tokens"`
  }

  func makePublisherGroundedRequest(query, token, publisherName string, sourcesLimit int) (*GroundedResponse, error) {
      // Grounded on YOUR publisher - accurate, source-backed

      apiURL := "https://platform.ai.gloo.com/ai/v2/chat/completions/grounded"

      payload := PublisherGroundedRequest{
          Messages: []Message{
              {Role: "user", Content: query},
          },
          AutoRouting:  true,
          RagPublisher: publisherName,
          SourcesLimit: sourcesLimit,
          MaxTokens:    500,
      }

      jsonData, _ := json.Marshal(payload)
      req, _ := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonData))
      req.Header.Set("Authorization", "Bearer "+token)
      req.Header.Set("Content-Type", "application/json")

      client := &http.Client{}
      resp, err := client.Do(req)
      if err != nil {
          return nil, err
      }
      defer resp.Body.Close()

      var result GroundedResponse
      json.NewDecoder(resp.Body).Decode(&result)
      return &result, nil
  }

  // Example usage
  func main() {
      query := "What is Bezalel Ministries' hiring process?"
      result, _ := makePublisherGroundedRequest(query, accessToken, "Bezalel", 3)
      fmt.Println(result.Choices[0].Message.Content)
      fmt.Printf("Sources used: %v\n", result.SourcesReturned)
      // Result: Detailed answer about Bezalel's 3-phase selection journey
      // Sources used: true
  }
  ```

  ```java Java theme={null}
  import java.net.http.*;
  import java.net.URI;
  import com.google.gson.*;

  public class PublisherGroundedRequest {

      public static JsonObject makePublisherGroundedRequest(
              String query,
              String token,
              String publisherName,
              int sourcesLimit) throws Exception {
          // Grounded on YOUR publisher - accurate, source-backed

          String apiUrl = "https://platform.ai.gloo.com/ai/v2/chat/completions/grounded";

          JsonObject message = new JsonObject();
          message.addProperty("role", "user");
          message.addProperty("content", query);

          JsonArray messages = new JsonArray();
          messages.add(message);

          JsonObject payload = new JsonObject();
          payload.add("messages", messages);
          payload.addProperty("auto_routing", true);
          payload.addProperty("rag_publisher", publisherName);  // KEY: This grounds on YOUR content
          payload.addProperty("sources_limit", sourcesLimit);
          payload.addProperty("max_tokens", 500);

          HttpClient client = HttpClient.newHttpClient();
          HttpRequest request = HttpRequest.newBuilder()
              .uri(URI.create(apiUrl))
              .header("Authorization", "Bearer " + token)
              .header("Content-Type", "application/json")
              .POST(HttpRequest.BodyPublishers.ofString(payload.toString()))
              .build();

          HttpResponse<String> response = client.send(request,
              HttpResponse.BodyHandlers.ofString());

          return JsonParser.parseString(response.body()).getAsJsonObject();
      }

      // Same query, now grounded on YOUR publisher
      public static void main(String[] args) throws Exception {
          String query = "What is Bezalel Ministries' hiring process?";
          JsonObject result = makePublisherGroundedRequest(query, accessToken, "Bezalel", 3);

          String content = result.getAsJsonArray("choices")
              .get(0).getAsJsonObject()
              .getAsJsonObject("message")
              .get("content").getAsString();

          boolean sourcesReturned = result.get("sources_returned").getAsBoolean();

          System.out.println(content);
          System.out.println("Sources used: " + sourcesReturned);
          // Result: Detailed answer about Bezalel's 3-phase selection journey
          // Sources used: true
      }
  }
  ```
</CodeGroup>

**What you'll see**: A detailed, accurate response about Bezalel's specific 3-phase selection journey, pulled directly from their uploaded content. The `sources_returned: true` flag confirms RAG found and used relevant sources.

<Info>
  **Why this query works**: The Bezalel publisher contains a document titled "bezalel\_hiring\_process.txt" that describes their selection journey in detail. When you ground on this publisher, the RAG system retrieves this document before generating the response.

  For your own use case, design queries that match your uploaded content. If you upload product documentation, ask product questions. If you upload HR policies, ask HR questions.
</Info>

### Side-by-Side Comparison

| Step                  | Endpoint                           | rag\_publisher | sources\_returned | Response Quality        |
| --------------------- | ---------------------------------- | -------------- | ----------------- | ----------------------- |
| 1. Non-grounded       | `/ai/v2/chat/completions`          | N/A            | N/A               | Generic/may hallucinate |
| 2. Publisher grounded | `/ai/v2/chat/completions/grounded` | Your publisher | `true`            | Accurate, specific      |

***

## Quick Reference: Publisher & Query Examples

Different content types require different query approaches:

| Content Type              | Publisher Example | Good Query Examples                                                                                       | Why It Works                                        |
| ------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| **Organizational Docs**   | "Bezalel"         | "What is the hiring process?"<br />"What benefits do employees get?"                                      | Documents contain policy/process descriptions       |
| **Bible Study Resources** | "FaithLibrary"    | "What study guides are available on the Book of Romans?"<br />"How do I start a small group Bible study?" | Study materials answer how-to and topical questions |
| **Pastoral Care Support** | "CareMinistry"    | "What counseling services are available?"<br />"How do I request prayer support?"                         | Ministry docs answer member care questions          |
| **Educational Content**   | "CourseCatalog"   | "What courses are available on Python?"<br />"What are the prerequisites for Course 101?"                 | Course descriptions match query topics              |

**Key Pattern**: Your queries should directly relate to the information in your uploaded documents.

***

## Step 3: Comparing Side-by-Side

The most powerful way to demonstrate RAG's value is to compare both approaches. Here's how to build a comparison function:

<CodeGroup>
  ```python Python theme={null}
  def compare_responses(query, token, publisher_name):
      """Compare non-grounded vs publisher-grounded responses."""

      print(f"\nQuery: {query}")
      print("=" * 80)

      # Step 1: Non-grounded
      print("\n🔹 STEP 1: NON-GROUNDED Response (Generic Model Knowledge):")
      print("-" * 80)
      non_grounded = make_non_grounded_request(query, token)
      print(non_grounded['choices'][0]['message']['content'])
      print(f"\n📊 Sources used: {non_grounded.get('sources_returned', False)}")

      print("\n" + "=" * 80 + "\n")

      # Step 2: Publisher grounded
      print("🔹 STEP 2: GROUNDED on Your Publisher (Your Specific Content):")
      print("-" * 80)
      publisher_grounded = make_publisher_grounded_request(query, token, publisher_name)
      print(publisher_grounded['choices'][0]['message']['content'])
      print(f"\n📊 Sources used: {publisher_grounded.get('sources_returned', False)}")
      print(f"📊 Model: {publisher_grounded.get('model')}")

  # Run comparison
  compare_responses(
      "What is Bezalel Ministries' hiring process?",
      access_token,
      "Bezalel"
  )
  ```

  ```javascript JavaScript theme={null}
  async function compareResponses(query, token, publisherName) {
    // Compare non-grounded vs publisher-grounded responses

    console.log(`\nQuery: ${query}`);
    console.log('='.repeat(80));

    // Step 1: Non-grounded
    console.log('\n🔹 STEP 1: NON-GROUNDED Response (Generic Model Knowledge):');
    console.log('-'.repeat(80));
    const nonGrounded = await makeNonGroundedRequest(query, token);
    console.log(nonGrounded.choices[0].message.content);
    console.log(`\n📊 Sources used: ${nonGrounded.sources_returned || false}`);

    console.log('\n' + '='.repeat(80) + '\n');

    // Step 2: Publisher grounded
    console.log('🔹 STEP 2: GROUNDED on Your Publisher (Your Specific Content):');
    console.log('-'.repeat(80));
    const publisherGrounded = await makePublisherGroundedRequest(
      query,
      token,
      publisherName,
    );
    console.log(publisherGrounded.choices[0].message.content);
    console.log(`\n📊 Sources used: ${publisherGrounded.sources_returned}`);
    console.log(`📊 Model: ${publisherGrounded.model}`);
  }

  // Run comparison
  await compareResponses(
    "What is Bezalel Ministries' hiring process?",
    accessToken,
    'Bezalel',
  );
  ```

  ```typescript TypeScript theme={null}
  async function compareResponses(
    query: string,
    token: string,
    publisherName: string,
  ): Promise<void> {
    // Compare non-grounded vs publisher-grounded responses

    console.log(`\nQuery: ${query}`);
    console.log('='.repeat(80));

    // Step 1: Non-grounded
    console.log('\n🔹 STEP 1: NON-GROUNDED Response (Generic Model Knowledge):');
    console.log('-'.repeat(80));
    const nonGrounded = await makeNonGroundedRequest(query, token);
    console.log(nonGrounded.choices[0].message.content);
    console.log(`\n📊 Sources used: ${nonGrounded.sources_returned || false}`);

    console.log('\n' + '='.repeat(80) + '\n');

    // Step 2: Publisher grounded
    console.log('🔹 STEP 2: GROUNDED on Your Publisher (Your Specific Content):');
    console.log('-'.repeat(80));
    const publisherGrounded = await makePublisherGroundedRequest(
      query,
      token,
      publisherName,
    );
    console.log(publisherGrounded.choices[0].message.content);
    console.log(`\n📊 Sources used: ${publisherGrounded.sources_returned}`);
    console.log(`📊 Model: ${publisherGrounded.model}`);
  }

  // Run comparison
  await compareResponses(
    "What is Bezalel Ministries' hiring process?",
    accessToken,
    'Bezalel',
  );
  ```

  ```php PHP theme={null}
  <?php

  function compareResponses($query, $token, $publisherName) {
      // Compare non-grounded vs publisher-grounded responses

      echo "\nQuery: $query\n";
      echo str_repeat("=", 80) . "\n";

      // Step 1: Non-grounded
      echo "\n🔹 STEP 1: NON-GROUNDED Response (Generic Model Knowledge):\n";
      echo str_repeat("-", 80) . "\n";
      $nonGrounded = makeNonGroundedRequest($query, $token);
      echo $nonGrounded['choices'][0]['message']['content'] . "\n";
      $sourcesUsed = $nonGrounded['sources_returned'] ?? false;
      echo "\n📊 Sources used: " . ($sourcesUsed ? 'true' : 'false') . "\n";

      echo "\n" . str_repeat("=", 80) . "\n\n";

      // Step 2: Publisher grounded
      echo "🔹 STEP 2: GROUNDED on Your Publisher (Your Specific Content):\n";
      echo str_repeat("-", 80) . "\n";
      $publisherGrounded = makePublisherGroundedRequest($query, $token, $publisherName);
      echo $publisherGrounded['choices'][0]['message']['content'] . "\n";
      echo "\n📊 Sources used: " . ($publisherGrounded['sources_returned'] ? 'true' : 'false') . "\n";
      echo "📊 Model: " . $publisherGrounded['model'] . "\n";
  }

  // Run comparison
  compareResponses(
      "What is Bezalel Ministries' hiring process?",
      $accessToken,
      "Bezalel"
  );
  ```

  ```go Go theme={null}
  func compareResponses(query, token, publisherName string) {
      // Compare non-grounded vs publisher-grounded responses

      fmt.Printf("\nQuery: %s\n", query)
      fmt.Println(strings.Repeat("=", 80))

      // Step 1: Non-grounded
      fmt.Println("\n🔹 STEP 1: NON-GROUNDED Response (Generic Model Knowledge):")
      fmt.Println(strings.Repeat("-", 80))
      nonGrounded, _ := makeNonGroundedRequest(query, token)
      fmt.Println(nonGrounded.Choices[0].Message.Content)
      fmt.Printf("\n📊 Sources used: %v\n", nonGrounded.SourcesReturned)

      fmt.Println("\n" + strings.Repeat("=", 80) + "\n")

      // Step 2: Publisher grounded
      fmt.Println("🔹 STEP 2: GROUNDED on Your Publisher (Your Specific Content):")
      fmt.Println(strings.Repeat("-", 80))
      publisherGrounded, _ := makePublisherGroundedRequest(query, token, publisherName, 3)
      fmt.Println(publisherGrounded.Choices[0].Message.Content)
      fmt.Printf("\n📊 Sources used: %v\n", publisherGrounded.SourcesReturned)
      fmt.Printf("📊 Model: %s\n", publisherGrounded.Model)
  }

  // Example usage
  func main() {
      compareResponses(
          "What is Bezalel Ministries' hiring process?",
          accessToken,
          "Bezalel",
      )
  }
  ```

  ```java Java theme={null}
  public class GroundedCompletionsDemo {

      public static void compareResponses(String query, String token, String publisherName)
              throws Exception {
          // Compare non-grounded vs publisher-grounded responses

          System.out.println("\nQuery: " + query);
          System.out.println("=".repeat(80));

          // Step 1: Non-grounded
          System.out.println("\n🔹 STEP 1: NON-GROUNDED Response (Generic Model Knowledge):");
          System.out.println("-".repeat(80));
          JsonObject nonGrounded = makeNonGroundedRequest(query, token);
          String nonGroundedContent = nonGrounded.getAsJsonArray("choices")
              .get(0).getAsJsonObject()
              .getAsJsonObject("message")
              .get("content").getAsString();
          System.out.println(nonGroundedContent);

          boolean nonGroundedSources = nonGrounded.has("sources_returned")
              && nonGrounded.get("sources_returned").getAsBoolean();
          System.out.println("\n📊 Sources used: " + nonGroundedSources);

          System.out.println("\n" + "=".repeat(80) + "\n");

          // Step 2: Publisher grounded
          System.out.println("🔹 STEP 2: GROUNDED on Your Publisher (Your Specific Content):");
          System.out.println("-".repeat(80));
          JsonObject publisherGrounded = makePublisherGroundedRequest(query, token, publisherName, 3);
          String publisherGroundedContent = publisherGrounded.getAsJsonArray("choices")
              .get(0).getAsJsonObject()
              .getAsJsonObject("message")
              .get("content").getAsString();
          System.out.println(publisherGroundedContent);

          boolean publisherGroundedSources = publisherGrounded.get("sources_returned").getAsBoolean();
          String model = publisherGrounded.get("model").getAsString();
          System.out.println("\n📊 Sources used: " + publisherGroundedSources);
          System.out.println("📊 Model: " + model);
      }

      // Example usage
      public static void main(String[] args) throws Exception {
          compareResponses(
              "What is Bezalel Ministries' hiring process?",
              accessToken,
              "Bezalel"
          );
      }
  }
  ```
</CodeGroup>

### Example Output

```
Query: What is Bezalel Ministries' hiring process?
================================================================================

🔹 STEP 1: NON-GROUNDED Response (Generic Model Knowledge):
--------------------------------------------------------------------------------
I cannot provide specific details about the internal hiring process for Bezalel
Ministries, as that information is not publicly available...

📊 Sources used: false

================================================================================

🔹 STEP 2: GROUNDED on Your Publisher (Your Specific Content):
--------------------------------------------------------------------------------
Bezalel Ministries views its hiring process as discerning a divine calling,
modeling their approach on God's selection of Bezalel in Exodus. Their process
includes three phases: calling alignment through structured interviews about
spiritual gifts, skills assessment with portfolio reviews, and a discernment
period where candidates spend time with the team...

📊 Sources used: true
📊 Model: gloo-google-gemini-2.5-pro
```

Notice the difference:

* **Step 1 (Non-grounded)**: Generic or "I don't have specific information"
* **Step 2 (Publisher grounded)**: Detailed, accurate answer from Bezalel's actual documentation

***

## Testing Your Implementation

This tutorial provides two testing paths:

1. **Quick Start (Recommended)**: Test the Bezalel example to validate your setup
2. **Your Own Content**: Adapt the code to use your publisher and content

***

### Phase 1: Quick Start with Bezalel Example

First, validate your setup works by running the provided Bezalel example.

#### Setup

1. **Install dependencies** for your language (see cookbook READMEs)
2. **Configure `.env`** with your credentials:
   ```bash theme={null}
   GLOO_CLIENT_ID=your_client_id
   GLOO_CLIENT_SECRET=your_client_secret
   PUBLISHER_NAME=Bezalel
   ```
3. **Create the Bezalel publisher** in [Gloo Studio](https://studio.ai.gloo.com):
   * Create a new publisher named "Bezalel" (exact name, case-sensitive)
   * Upload the 5 sample content files from the [cookbook's `content/` directory](https://github.com/GlooDeveloper/gloo-ai-docs-cookbook/tree/main/completions-grounded/content)
   * Wait for all documents to show "Completed" status before proceeding

#### Run the Comparison

Each language implementation includes a ready-to-run comparison demo:

<CodeGroup>
  ```bash Python theme={null}
  python -m venv venv
  source venv/bin/activate  # On Windows: venv\Scripts\activate
  pip install -r requirements.txt
  python main.py
  ```

  ```bash JavaScript theme={null}
  npm install
  npm start
  ```

  ```bash TypeScript theme={null}
  npm install
  npm start
  ```

  ```bash PHP theme={null}
  composer install
  php index.php
  ```

  ```bash Go theme={null}
  go mod download
  go run main.go
  ```

  ```bash Java theme={null}
  mvn clean install
  mvn exec:java
  ```
</CodeGroup>

#### What to Look For

When testing with ANY publisher (Bezalel or your own):

✅ **Step 1 (Non-grounded)**:

* Generic response not specific to your content
* Example: "A typical hiring process involves..." (for any hiring query)

✅ **Step 2 (Publisher grounded)**:

* Specific, accurate information from YOUR uploaded documents
* `sources_returned: true` (if this is false, see troubleshooting)
* Example: For Bezalel → "3-phase selection journey with calling alignment..."
* Example: For your product docs → "The enterprise plan is \$X/month and includes..."

If Step 2 fails or returns generic responses, see the [Troubleshooting](#troubleshooting) section.

***

### Phase 2: Adapt to Your Own Content

Once the Bezalel example works, adapt it to your own use case.

#### Step 1: Prepare Your Content

Before modifying code, ensure you have:

1. **Created a Publisher** in [Gloo Studio](https://studio.ai.gloo.com)
   * See [Upload Files to Data Engine](/tutorials/upload-files) recipe for detailed instructions
2. **Uploaded relevant documents** (PDFs, text files, Word docs, etc.)
   * Upload content that answers the questions your users will ask
   * Example: HR policies for HR questions, product docs for product questions
3. **Note your Publisher name** (you'll need this for the code)

<Tip>
  **Content Quality Matters**: The more relevant and well-structured your
  content, the better your grounded responses will be. Each document should
  focus on a specific topic area.
</Tip>

#### Step 2: Update the Code

Modify two areas in the code:

**A. Update Environment Variables**

In your `.env` file:

```bash theme={null}
# Before
PUBLISHER_NAME=Bezalel

# After
PUBLISHER_NAME=MyCompanyDocs
```

**B. Update Queries**

Design queries that match your uploaded content:

```python theme={null}
# Before (Bezalel example)
queries = [
    "What is Bezalel Ministries' hiring process?",
    "What educational resources does Bezalel Ministries provide?",
]

# After (your content - example: e-commerce)
queries = [
    "What is the return policy for damaged items?",
    "How do I track my order?",
]

# After (your content - example: technical docs)
queries = [
    "How do I configure SSL certificates?",
    "What are the system requirements for installation?",
]
```

#### Step 3: Run and Validate

Run your modified code using the same commands from Phase 1.

**Interpreting Results**:

✅ **Success indicators**:

* Step 2 shows `sources_returned: true`
* Step 2 response contains specific information from your documents
* Step 2 is noticeably more accurate than Step 1

⚠️ **If Step 2 is generic or wrong**:

* Check that your publisher name matches exactly (case-sensitive)
* Verify content is uploaded and indexed in Studio
* Ensure your queries are relevant to your content
* Try increasing `sources_limit` (change 3 to 5 or 10)

#### Step 3: Refine Your Queries

If results aren't accurate:

1. **Make queries more specific**: "What is the pricing?" → "What is the enterprise plan pricing for annual subscriptions?"
2. **Match content structure**: If your document is titled "Installation Guide," ask installation questions
3. **Test incrementally**: Start with one query, verify it works, then add more
4. **Check sources**: If available in the API response, inspect which documents were retrieved

<Tip>
  **Iterative Testing**: RAG quality improves with iteration. Test different
  queries, refine your content, and adjust `sources_limit` until you get optimal
  results.
</Tip>

***

## Working Code Sample

<Card title="View Complete Code" icon="github" href="https://github.com/GlooDeveloper/gloo-ai-docs-cookbook/tree/main/completions-grounded">
  Clone or browse the complete working examples for all 6 languages (JavaScript,
  TypeScript, Python, PHP, Go, Java) with setup instructions and Bezalel content
  files.
</Card>

***

## Next Steps

Now that you understand grounded completions:

### Use Your Own Content

See [Phase 2: Adapt to Your Own Content](#phase-2-adapt-to-your-own-content) above for detailed instructions on:

* Preparing and uploading your content
* Updating the code for your publisher
* Designing effective queries
* Validating and refining your results

### Build a Production Chatbot

Extend this recipe:

* Add streaming for real-time responses
* Implement conversation history
* Extract and display source citations
* Handle edge cases (no sources found, publisher not found)
* Add user feedback collection

### Explore Advanced Features

* **Adjust source limits**: Use `sources_limit` to control how many documents are retrieved
* **Search without generation**: Use the [Search API](/api-reference/data/search-publisher-data) for RAG without completion
* **Multi-turn conversations**: Maintain context across multiple grounded queries
* **Hybrid approaches**: Mix grounded and non-grounded based on query type

***

## Troubleshooting

### Publisher Not Found Error

```
Error: Publisher 'YourPublisher' not found
```

**Solutions**:

* Verify publisher name is **exact match** (case-sensitive) to Studio
* Confirm publisher exists at [Gloo Studio](https://studio.ai.gloo.com)
* Check you're using the correct account/credentials

### No Sources Returned

```
sources_returned: false  (in grounded request)
```

**Solutions**:

* Verify content is uploaded to the publisher in Studio
* Check that your query is relevant to uploaded content
* Increase `sources_limit` (try 5-10 for complex queries)
* Ensure content has finished indexing in Studio

### Authentication Errors

```
401 Unauthorized
```

**Solutions**:

* Verify `GLOO_CLIENT_ID` and `GLOO_CLIENT_SECRET` are correct
* Get fresh credentials from [API Credentials page](https://studio.ai.gloo.com/settings/api-keys)
* Check token expiration—implementation auto-refreshes tokens

### Generic Responses in Grounded Mode

If Step 2 (publisher grounded) responses still seem generic:

* Check `sources_returned` flag—should be `true` for Step 2
* Verify you're using the `rag_publisher` parameter
* Confirm the right content is uploaded to your publisher in Studio
* Try more specific queries that match your content
* Review content in Studio to ensure it's indexed

### Step 2 Isn't Using My Content

If Step 2 returns `sources_returned: false` or generic responses:

**Check Publisher Name**:

* Publisher names are **case-sensitive**: "MyPublisher" ≠ "mypublisher"
* Verify exact name in [Gloo Studio](https://studio.ai.gloo.com)
* Check for typos in your code or `.env` file

**Verify Content is Indexed**:

* Log into Gloo Studio
* Navigate to your Publisher
* Confirm documents show "Completed" status (not "Pending")
* Wait 5-10 minutes after upload before testing

**Query-Content Mismatch**:

* Your query might not match your content
* Example: Asking "What is the pricing?" when you only uploaded a "User Guide"
* Solution: Ask questions your documents can answer

**Increase sources\_limit**:

* Default is 3 sources
* Try 5-10 for complex queries: `sources_limit: 10`
* More sources = broader retrieval, better coverage

**Content Quality Issues**:

* Scanned PDFs may have poor OCR
* Very short documents (\<100 words) may not be retrieved
* Highly technical jargon may need more context

***

## Additional Resources

* [Completions V2 API Guide](/api-guides/completions-v2) - Complete API documentation
* [Grounded Completions API Reference](/api-reference/completions/v2/grounded) - Full parameter details
* [Upload Files Tutorial](/tutorials/upload-files) - Setting up Publishers and uploading documents
* [Search API Reference](/api-reference/data/search-publisher-data) - RAG without generation
* [Authentication Tutorial](/tutorials/authentication) - OAuth2 setup details
