Back to Blog
3 min read

Building a Custom GPT for Your Documentation with Azure OpenAI

One of the most practical AI applications is a custom chatbot that answers questions about your documentation. Here’s a complete guide to building one with Azure OpenAI.

Architecture

User Question -> Azure OpenAI -> Search Your Docs -> Generate Answer
                    |
              System Prompt (defines persona)
                    |
              Azure AI Search (retrieves relevant docs)

Step 1: Prepare Your Documentation

import os
from azure.ai.formrecognizer import DocumentAnalysisClient
from azure.core.credentials import AzureKeyCredential

def extract_text_from_docs(folder_path: str) -> list[dict]:
    """Extract text from various document formats."""
    documents = []

    for filename in os.listdir(folder_path):
        filepath = os.path.join(folder_path, filename)

        if filename.endswith('.md'):
            with open(filepath, 'r') as f:
                content = f.read()
        elif filename.endswith('.pdf'):
            content = extract_pdf_with_form_recognizer(filepath)
        else:
            continue

        documents.append({
            "id": filename,
            "content": content,
            "source": filename,
            "chunk_id": 0
        })

    return documents

def chunk_documents(documents: list[dict], chunk_size: int = 1000) -> list[dict]:
    """Split documents into smaller chunks for better retrieval."""
    chunks = []

    for doc in documents:
        content = doc["content"]
        words = content.split()

        for i in range(0, len(words), chunk_size):
            chunk_content = " ".join(words[i:i + chunk_size])
            chunks.append({
                "id": f"{doc['id']}_chunk_{i // chunk_size}",
                "content": chunk_content,
                "source": doc["source"],
                "chunk_id": i // chunk_size
            })

    return chunks
from azure.search.documents import SearchClient
from azure.search.documents.indexes import SearchIndexClient
from azure.search.documents.indexes.models import (
    SearchIndex, SearchField, SearchFieldDataType,
    VectorSearch, HnswAlgorithmConfiguration, VectorSearchProfile
)

def create_search_index(index_client: SearchIndexClient, index_name: str):
    """Create search index with vector search capability."""
    fields = [
        SearchField(name="id", type=SearchFieldDataType.String, key=True),
        SearchField(name="content", type=SearchFieldDataType.String, searchable=True),
        SearchField(name="source", type=SearchFieldDataType.String, filterable=True),
        SearchField(
            name="content_vector",
            type=SearchFieldDataType.Collection(SearchFieldDataType.Single),
            searchable=True,
            vector_search_dimensions=1536,
            vector_search_profile_name="default-profile"
        )
    ]

    vector_search = VectorSearch(
        algorithms=[HnswAlgorithmConfiguration(name="default-algorithm")],
        profiles=[VectorSearchProfile(name="default-profile", algorithm_configuration_name="default-algorithm")]
    )

    index = SearchIndex(name=index_name, fields=fields, vector_search=vector_search)
    index_client.create_or_update_index(index)

Step 3: Create the Chat Interface

from openai import AzureOpenAI
from azure.search.documents import SearchClient

class DocumentChatbot:
    def __init__(self, openai_client: AzureOpenAI, search_client: SearchClient):
        self.openai = openai_client
        self.search = search_client
        self.system_prompt = """You are a helpful assistant that answers questions about our documentation.

Rules:
1. Only answer based on the provided context
2. If the answer isn't in the context, say "I don't have information about that"
3. Cite the source document when answering
4. Be concise but complete"""

    async def chat(self, user_question: str) -> str:
        # Retrieve relevant documents
        context = await self._retrieve_context(user_question)

        # Generate answer
        response = await self.openai.chat.completions.create(
            model="gpt-4",
            messages=[
                {"role": "system", "content": self.system_prompt},
                {"role": "user", "content": f"Context:\n{context}\n\nQuestion: {user_question}"}
            ],
            temperature=0.7,
            max_tokens=500
        )

        return response.choices[0].message.content

    async def _retrieve_context(self, query: str, top_k: int = 5) -> str:
        # Get embedding for query
        embedding = await self._get_embedding(query)

        # Search
        results = self.search.search(
            search_text=query,
            vector_queries=[{
                "vector": embedding,
                "k_nearest_neighbors": top_k,
                "fields": "content_vector"
            }],
            top=top_k
        )

        # Format context
        context_parts = []
        for result in results:
            context_parts.append(f"[{result['source']}]: {result['content']}")

        return "\n\n".join(context_parts)

Step 4: Deploy as API

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()
chatbot = DocumentChatbot(openai_client, search_client)

class ChatRequest(BaseModel):
    question: str

class ChatResponse(BaseModel):
    answer: str

@app.post("/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
    answer = await chatbot.chat(request.question)
    return ChatResponse(answer=answer)

This pattern works for any documentation. The key is good chunking, quality embeddings, and a well-crafted system prompt that keeps the bot focused on your content.

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.