← Go Back

How I Built an AI Tool That Writes Product Manuals as PDFs

Oct 08, 2025 6min
#golang #ai #pdf

Ive been working on an AI search product lately, and one thing I needed was proper product manuals. At first, I was creating all the PDFs manually which got repetitive fast. So, to save time (and honestly out of curiosity), I built a small program that generates manuals automatically using AI.

For this project, I decided to use my new go-to language Go (Golang).
 You might wonder why I didnt use my main language, PHP. Well, I just wanted to try something new and have some fun experimenting.

To handle the AI part, I used the package github.com/openai/openai-go/v3
for making requests to OpenAIs API.
For PDF generation, I went with github.com/signintech/gopdf
, which is great for creating simple PDFs (nothing fancy in design just clean and functional).
And finally, I used github.com/joho/godotenv
to load environment variables because yeah, Id rather not leak my API keys while testing with ChatGPT.

Since I dont have much time to manually create a PDF manual for every new product I add, I started thinking of ways to automate the process. Ive seen a lot of people use AI to speed up their work and in my case, its just a side project so an idea popped into my head:
Why not build a program that generates the manuals for me?

I brainstormed a bunch of approaches but decided to stick with something simple you know, KISS (Keep It Simple, Stupid).
The idea was straightforward: take a product name and build a prompt using some predefined, general-purpose questions.

Of course, before jumping into code, I had to figure out how real companies actually write their product manuals. So I did a bit of research (with some help from GPT, of course).

Once I had the structure product name + predefined questions it was time to start cooking. (Ive always wanted to say that!)

064bbb93053de2823fd0b1a7d827dfbb.jpg 104.95 KB
let's start by getting product name using this function: 

func GetProductName() string {
fmt.Print("Enter product name: ")
defer fmt.Println("Name was received")
var productName string
_, err := fmt.Scan(&productName)
if err != nil {
log.Fatal(err)
}
return productName
}

then ,  I created a Question struct to hold all the predefined questions: 

type Question struct {
ID       int
Question string
Type     string
}

And a function to return all the questions:

func GetQuestions() []Question {
return []Question{
{ID: 1, Question: "What product is for", Type: "text"},
{ID: 2, Question: "What feature this product has", Type: "list"},
{ID: 3, Question: "How to setup this product", Type: "text"},
{ID: 4, Question: "How to maintaine this product", Type: "text"},
{ID: 5, Question: "How to use this product", Type: "text"},
{ID: 6, Question: "What are the technical specifications", Type: "list"},
{ID: 7, Question: "What materials is the product made from", Type: "text"},
{ID: 8, Question: "What safety precautions should be followed", Type: "list"},
{ID: 9, Question: "What warranty does the product have", Type: "text"},
{ID: 10, Question: "What are common issues and troubleshooting steps", Type: "list"},
{ID: 11, Question: "What accessories or spare parts are available", Type: "list"},
{ID: 12, Question: "How to store the product when not in use", Type: "text"},
{ID: 13, Question: "How long is the expected lifespan of the product", Type: "text"},
{ID: 14, Question: "Is the product eco-friendly or recyclable", Type: "text"},
{ID: 15, Question: "Who to contact for support or repairs", Type: "text"},
}
}

Next, I built a function to generate a prompt that I can send to OpenAI:

func gptPromptBuilder(productName string) string {
var builder strings.Builder
builder.WriteString(fmt.Sprintf("Generate answers for the following questions about %s.\n", productName))
builder.WriteString("Return your response **only** as a valid JSON object, no markdown, no explanations.\n")
builder.WriteString("Each key must be the exact question, and each value the detailed answer.\n\n")
builder.WriteString("Questions:\n")
for _, q := range GetQuestions() {
builder.WriteString(fmt.Sprintf("- %s?\n", q.Question))
}
return builder.String()
}

Then , I wrote a function to call the OpenAI API using my prompt:

func OpenAiClient(prompt string) (*responses.Response, error) {
client := openai.NewClient(option.WithAPIKey(os.Getenv("OPENAI_API_KEY")))
params := responses.ResponseNewParams{
Model:           openai.ChatModelGPT4oMini,
Temperature:     openai.Float(0.7),
MaxOutputTokens: openai.Int(2000),
Input: responses.ResponseNewParamsInputUnion{
OfString: openai.String(prompt),
},
}
resp, err := client.Responses.New(context.TODO(), params)
if err != nil {
log.Fatalf("Error calling OpenAI API: %v", err)
}
return resp, err
}

next i will use the return response func OpenAiClient to make a map with question as key and answers as value
so i can map over them and printing question and answer like in the following code:

myQuestionsAndAnswers := make(map[string]string)
    if err := json.Unmarshal([]byte(strings.TrimSpace(response.OutputText())), &myQuestionsAndAnswers); err != nil {
        log.Fatalf(" Failed to parse GPT JSON: %v\nResponse was:\n%s", err, strings.TrimSpace(response.OutputText()))
    }

I used gopdf to generate the PDF. First, initialize the instance and set the font:

pdf := gopdf.GoPdf{}
pdf.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4})

fontPath := os.Getenv("FONT_PATH")
if _, err := os.Stat(fontPath); os.IsNotExist(err) {
log.Fatalf("Font path does not exist: %s", fontPath)
}
pdf.AddTTFFont("ARIAL", fontPath)
pdf.SetFont("ARIAL", "", 12)

I set some basic parameters for layout:

pageMargin := 40.0
lineHeight := 16.0
pageHeight := gopdf.PageSizeA4.H
maxWidth := gopdf.PageSizeA4.W - 2*pageMargin

Then iterate over the questions and answers, creating pages as needed:

pdf.AddPage()
y := pageMargin

for _, q := range questions {
answer := myQuestionsAndAnswers[q.Question]

pdf.SetXY(pageMargin, y)
pdf.SetFont("ARIAL", "", 14)
pdf.Text(fmt.Sprintf("%s:", q.Question))
y += lineHeight * 1.5

pdf.SetFont("ARIAL", "", 12)
lines := helpers.SplitTextToLines(&pdf, answer, maxWidth)
for _, line := range lines {
if y+lineHeight > pageHeight-pageMargin {
pdf.AddPage()
y = pageMargin
}
pdf.SetXY(pageMargin, y)
pdf.Text(line)
y += lineHeight
}
y += lineHeight
}

Finally, save the PDF:

pdfName := fmt.Sprintf("%s_manual.pdf", productName)
pdf.WritePdf(pdfName)

now this was for me my first experienc making ai do something for me like .

 The next step is to make this code available through my API so that a PDF manual can be generated automatically for any new product created. Ill cover that in another article. 

Thank you!

789f3be7d72ca06a5e0506792e13d570.jpg 104.73 KB