[Step 1-A-1/4] Build an AI PDF Summarizer — Environment Setup & Your First API Call (Developer Edition)

📌Expert Workflows · Step 1: AI PDF Summarizer · Edition A (Developer) · Part 1 of 4

Review the Expert Workflows roadmap for the currently published prerequisites and project path.

🛠️ Gather These First

  • Python 3.10+ andpip
  • An API key from a provider that currently documents a free or trial allowance and supports an OpenAI-compatible endpoint. Free access, rate limits, supported models and billing requirements can change, so check the provider’s official documentation before choosing one.
  • A terminal and a code editor
  • One PDF to test with — start with a text-based one, not a scan

How to Use This Troubleshooting Tutorial

  • 🔴 BEFORE shows an intentionally incomplete or flawed example so readers can reproduce and understand a representative problem.

  • 🧠 CAUSE explains why the problem occurs.

  • 🟢 AFTER presents the corrected implementation for this lesson.

  • ✅ VERIFICATION explains how to confirm that the correction works.

📝 Step 1: Project Structure and a Virtual Environment

Start with a structure you won’t have to undo later:

mkdir pdf-summarizer
cd pdf-summarizer
python -m venv .venv

# macOS/Linux
source .venv/bin/activate
# Windows (PowerShell)
.venv\Scripts\Activate.ps1

pip install openai pypdf python-dotenv

Three dependencies, each with one job:openaiis the client SDK (it speaks theOpenAI-compatible chat-completions protocolthat some providers implement),pypdfreads PDFs, andpython-dotenvloads your secrets from a file instead of hard-coding them. The reason this matters for portability: because so many providers implement the same protocol, some compatible providers may require only configuration changes, while authentication, endpoints, request fields, and supported features can still differ in.env— you are not locking yourself into a single vendor, which is exactly the kind of decision that saves a rewrite six months later.

Now create the files:

pdf-summarizer/
├── .env          # secrets — never committed
├── .gitignore
├── summarize.py
└── sample.pdf

.env:

AI_API_KEY=your_key_here
AI_BASE_URL=https://your-provider.example.com/api/v1
AI_MODEL=your-model-name

.gitignore— write thisbeforeyour first commit, not after:

.venv/
.env
__pycache__/
*.pdf

That.envline is the single most important line in this file. Committing an API key to a public repository can expose it to automated scanning and misuse quickly. Treat any committed key as compromised, revoke or rotate it immediately, and remove it from the repository history where appropriate.

📝 Step 2: The Connection Test

Before touching PDFs, prove you can reach the model. Createsummarize.py:

import os
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv() API_KEY = os.getenv("AI_API_KEY") BASE_URL = os.getenv("AI_BASE_URL") MODEL = os.getenv("AI_MODEL") missing = [name for name, value in { "AI_API_KEY": API_KEY, "AI_BASE_URL": BASE_URL, "AI_MODEL": MODEL, }.items() if not value] if missing: raise SystemExit("Missing required setting(s): " + ", ".join(missing))
client = OpenAI(api_key=API_KEY, base_url=BASE_URL, timeout=30.0)
response = client.chat.completions.create( model=MODEL, messages=[{"role": "user", "content": "Reply with exactly: connection ok"}], ) print(response.choices[0].message.content)

Run it:

python summarize.py

If configuration, provider access and the request all succeed, the requested output is shown below. A different or empty response must be inspected; loading variables alone does not prove a live connection. Optional temperature is omitted because support varies by model.

connection ok

If you do not see this, stop here. The error scenario below is intentional: it shows how the script responds when the `.env` file is unavailable to the running program. The failure is part of the lesson, not a sign that the walkthrough has gone wrong.

That explicit missing-settings guard is not defensive padding—it converts a confusing downstream failure into a clear message. The controlled example below shows the kind of configuration error that can otherwise waste an hour.


📋 Provider Access and Compatibility Checklist

Before you commit to a provider, sanity-check it against the four things that actually determine whether a provider plan is usable for this build. Keep this table — it’s the checklist that saves you from picking a provider you’ll abandon in part 3:

What to check Why it matters here Where to find it
Current quota and billing requirements A 40-page PDF becomes several chunked requests (part 3)       Provider pricing page
Context window (tokens) Determines how big a chunk you can send Model card / docs
OpenAI-compatible endpoint      Check the supported request fields and response shape Provider API docs
Base URL path A possible cause of a 404 (/v1vs/api/v1) Provider quickstart

The one that catches people later is the daily request limit: a chunked summary can involve one callper chunk, additional reduction calls and retries, so a provider that looks generous for chat can run out mid-summary. Checking this now, before you build, is a five-minute decision that prevents a part-3 surprise.

⚠️ Troubleshooting — The API key that “was definitely there”

Understand the symptom

The following failure is intentional. It is included so you can observe what happens when the program cannot load the `.env` file.

1. Keep the original code exactly as shown in Step 2.
2. Temporarily rename `.env` to `.env.bak`.
3. Run the script again:

python summarize.py

4. Read the error message.
5. Rename `.env.bak` back to `.env`.
6. Apply the corrected code in the AFTER section.
7. Run the script again and confirm:

connection ok

For this exact missing-settings result, none of the three variables may already exist in the process environment or another discovered .env. Renaming the file alone may therefore not reproduce it. The downloadable offline test uses an isolated temporary folder and cleared test environment without touching your real configuration.

🔴 BEFORE

Missing required setting(s): AI_API_KEY, AI_BASE_URL, AI_MODEL

🤔 Cause

In this controlled test, the failure is simple: `.env` has been renamed to `.env.bak`, so the program cannot find a file with the expected name. The key may still exist inside `.env.bak`, but that does not help because the program is looking for `.env`.

In a real project, the same symptom can come from a misplaced or misnamed file, a typo in the variable name, or loading the file after `os.getenv()` runs. The important rule is that the environment file must be loaded before `API_KEY`, `BASE_URL`, and `MODEL` are read. The corrected code below makes the intended `.env` location explicit by anchoring it to the folder containing `summarize.py`.

A second version of this same trap: puttingload_dotenv()aftertheos.getenv()calls. Python executes top to bottom; the variables are read before the file is loaded, so you getNonewith a perfectly correct.env.

🟢 AFTER

First restore `.env` by renaming `.env.bak` back to `.env`.

Now update the original `summarize.py` file. Do not replace the entire file. Make only these two changes:

1. Add `from pathlib import Path` to the import section.
2. Replace the original `load_dotenv()` line with the path-anchored version below.

The new loading code must appear before the `os.getenv()` lines.

import os
from pathlib import Path
from dotenv import load_dotenv
from openai import OpenAI

ENV_PATH = Path(__file__).resolve().parent / ".env"
load_dotenv(dotenv_path=ENV_PATH)

Path(__file__).resolve().parentis the folder containing the script, regardless of your terminal’s location. Add a one-line diagnostic while you’re debugging:

print("env loaded from:", ENV_PATH, "| exists:", ENV_PATH.exists())

💡 Teaching note: This is the same failure pattern represented by the example user’s composite scenario. It is included to make the debugging lesson concrete; it is not presented as a personal incident by the author. The path-anchored loader makes the intended `.env` location explicit and easier to verify.


📝 Step 3: Reading the Two Failures That Look Identical

Once the key loads, the next wall is authentication and routing errors — and beginners routinely misdiagnose them because both arrive as ugly tracebacks. Learn the difference now and you’ll save hours across every remaining project in this course:

Error What it usually means First thing to check
401 Unauthorized The key is wrong, expired, or not yet activated Check provider, project, permissions and accidental spaces; rotate if compromised
404 Not Found Themodel nameorbase URL pathis wrong Compare both against the provider’s docs, exactly
429 Too Many Requests Rate or quota limit hit Check current quota and billing status; wait or adjust the plan if appropriate
Connection error Network, proxy, or SSL problem Check network and proxy settings without disabling certificate verification

The 404 case is the sneaky one. Every provider publishes a slightly different base path (/v1,/api/v1,/api/paas/v4), and the SDK appends its own route to whatever you give it. If a trailing slash or a missing segment leaves you pointing at a URL that doesn’t exist, the SDK reports a 404 thatlookslike “the model doesn’t exist” — and you’ll waste twenty minutes checking model names that were fine all along.

Isolate one request: First run this small Python script and inspect the provider’s error code. A .env loaded inside Python does not set your shell variables, so copying a shell command with $AI_API_KEY can test an empty key instead. Some compatible services do not expose a /models endpoint; a failed list request does not prove that chat completion is unavailable, and a successful list does not prove access to the selected model.

Compare the endpoint and model with the provider’s own quickstart. Never paste credentials into a public diagnostic report. The SDK documentation describes configuration and error handling; compatibility with a third-party service must be checked separately.

⚠️ The Part People Skip (Don’t)

When an unverified API connection, PDF extraction step, and prompt are combined, one failure creates several possible causes. Verify one layer at a time with the smallest test that can prove it works.

Teaching takeaway: Verify one layer at a time and keep the smallest diagnostic script that proves each layer works. A successful connection test is evidence for that request only; it does not guarantee future quota, model availability, or identical output.

✅ What You Have Now

A clean project with an isolated virtual environment, secrets stored outside your code and excluded from Git, a path-anchored.envloader that works no matter where you run the script from, and a small connection-check script ready for a live test with a provider you configure. That’s the whole foundation — and every remaining part builds directly on this file.

💡 Go One Level Deeper

Add a--checkflag tosummarize.pyso the connection test stays available as a subcommand rather than something you delete. Also worth reading before part 3:how models actually process your text— tokens and context windows stop being trivia the moment you feed a 40-page PDF into a request.

❓ Try This Yourself

Getconnection okprinting on your machine, then deliberately break it: rename.envto.env.bakand run the script again. Confirm you get your own clear error message instead of a cryptic SDK traceback. Knowing what your failure looks like is what makes part 2 fast.

🔗 Next in This Series

[Step 1-A-2/4] Extracting Text From Any PDF — Including the Ones That Fight Back, where we pull text out withpypdf, handle the scanned PDFs that return nothing at all, and fix the encoding bug that turns non-English text into question marks.


Written and edited by G. Troy. See the editorial standards for sourcing and AI-assistance practices.

Last updated: August 9, 2026

Security, cost, and compatibility note

Use a restricted project and a non-sensitive test prompt. Never print or commit credentials. Confirm current provider pricing, rate limits, data use, endpoint compatibility, and SDK documentation. Revoke any exposed key and treat outputs as unverified drafts.


Reproducible local verification — 12 September 2026

Download the PDF foundations scripts, synthetic PDFs, extracted text and actual results. Run python verify.py after installing the dependencies listed in README.md. Ten checks passed on Python 3.11.4 with pypdf 6.16.1, ReportLab 5.0.1, python-dotenv 1.0.1 and openai 2.38.0.

The setup checks reproduced the exact missing-settings message and path-anchored loading with a stubbed client. The PDF checks generated and extracted a real synthetic PDF, wrote UTF-8 output, examined two blank pages, and reproduced a real encoding failure. No live provider request, OCR or AI-quality test was performed.

SHA-256: c6f6194be951576aca40e8fd7e8997154e5b0b5efea5713e0252e58c77c540c6. The public download was checked against the local archive. connection_check.py is the completed AFTER setup script; save it as summarize.py to follow this series. pdf_reader.py is the runnable extraction companion for Part 2.