Files and JSON with AI: Turn a Folder of Reviews into Data
Hands-on lab · IDE in your browser

Files and JSON with AI: Turn a Folder of Reviews into Data

Read a folder of customer reviews with Python's pathlib, summarise each one with a language model, ask the model for JSON and parse it safely even when it adds fences or chatter, save and reload results as JSON, and process the whole folder so one empty or broken file is recorded instead of stopping the run.

Time
50 min
Checked steps
5
Level
Beginner
Setup
None
Read step 1

Hands-on labs require Pro · $29.99/mo · cancel anytime

Lab cockpit50 min · 5 stepsSession running
1 / 5 steps passingOne call per file: summarise a review · step 2 of 5
reviews.py▶ Run✓ Check
def read_review(path):    """The file's text with spaces and blank lines trimmed from both ends."""    return Path(path).read_text(encoding="utf-8").strip()  # ---------- Step 2: one model call per file ----------def summarise(text, client=None):    """A one-sentence summary of one review."""    client = client or CLIENT         
TerminalOutput

The job

Brightline Books keeps customer reviews as text files in an inbox folder, one file per review. Nobody has time to read them all, and the owner wants to know which ones are unhappy and whether price keeps coming up. You write reviews.py: it finds the files, reads them, asks the model for a summary and for sentiment, topic and a price flag as JSON, and writes everything to results.json. The folder is realistic, so it holds a notes file that is not a review, an old archive folder and one empty file.

5 steps, each checked when you finish it

A check runs your work at the end of every step. Hints and the full solution are there if you get stuck.

  1. 1

    Find and read the files

    Open the inbox/ folder in the file tree.

    You writelist_reviews()read_review()
  2. 2

    One call per file: summarise a review

    The pattern under most document work is simple: the instructions go in the system message, the document goes in the user message, and you make one call per document.

    You writesummarise()
  3. 3

    Save and load JSON

    Results that live only in a Python variable vanish when the script ends.

    You writesave_json()load_json()
  4. 4

    Ask the model for JSON, and do not trust it blindly

    A summary is for people.

    You writeparse_json_reply()extract()
  5. 5

    The whole folder, and one bad file

    Now put it together: every review in, one results.json out.

    You writeprocess_folder()

Step 1 as it appears in the lab

The lab’s own text. The hint and the solution stay inside the lab.

Step 1: Find and read the files

Open the inbox/ folder in the file tree. Twelve reviews, and three things a real folder always has: a file that is not a review (notes.md), an old subfolder (archive/) and a file with nothing in it. Your script has to pick exactly the right files before any AI is involved; a model that summarises the staff notes is a bug you cannot see.

Python's pathlib treats paths as objects rather than strings:

  • Path("inbox") is the folder. Path("inbox") / "review-01.txt" is a file inside it.
  • .glob("*.txt") finds the entries in that folder whose names end in .txt. It does not look inside subfolders (rglob would).
  • .read_text(encoding="utf-8") returns the whole file as a string. Say the encoding every time: without it Python uses the computer's default, which differs between machines and breaks on accented letters.
  • .name is the file name without the folder: review-01.txt.
Do this

1. Write list_reviews(folder): return sorted(Path(folder).glob("*.txt")). sorted gives the same order on every computer.

2. Write read_review(path): read the file as UTF-8 and .strip() it, which removes spaces and blank lines at both ends. An empty file becomes "".

3. Run. It lists each file with its word count and first words.

reviews.py, the file you edit63 lines
"""Turn a folder of customer reviews into one JSON file the shop can use."""
import json
from pathlib import Path

from openai import OpenAI

CLIENT = OpenAI()
MODEL = "meta/llama-3.1-8b-instruct"
SENTIMENTS = ("positive", "negative", "mixed")


# ---------- Step 1: find and read the files ----------
def list_reviews(folder):
    """Every .txt file directly inside `folder`, sorted by name, as Path objects."""
    # TODO (Step 1): return sorted(Path(folder).glob("*.txt"))
    raise NotImplementedError("Step 1: write list_reviews()")


def read_review(path):
    """The file's text with spaces and blank lines trimmed from both ends."""
    # TODO (Step 1): read the file as UTF-8 text and strip whitespace from both ends.
    raise NotImplementedError("Step 1: write read_review()")


# ---------- Step 2: one model call per file ----------
def summarise(text, client=None):
    """A one-sentence summary of one review."""
    client = client or CLIENT
    raise NotImplementedError("summarise() arrives in Step 2")


# ---------- Step 3: save and load JSON ----------
def save_json(data, path):
    """Write any list/dict to `path` as readable JSON (indented, accents kept as they are)."""
    raise NotImplementedError("save_json() arrives in Step 3")


def load_json(path):
    """Read a JSON file back into Python lists and dicts."""
    raise NotImplementedError("load_json() arrives in Step 3")


# ---------- Step 4: ask for JSON, and parse it defensively ----------
EXTRACT_PROMPT = """Read the customer review and reply with a JSON object only, no other text:
{"sentiment": "positive" | "negative" | "mixed", "topic": "<two or three words>", "mentions_price": true | false}"""


def parse_json_reply(reply):
    """Turn a model reply that should contain one JSON object into a dict.
    Copes with ```json fences and with chatter before or after the object."""
    raise NotImplementedError("parse_json_reply() arrives in Step 4")


def extract(text, client=None):
    """Ask the model for sentiment, topic and mentions_price; return them as a dict."""
    client = client or CLIENT
    raise NotImplementedError("extract() arrives in Step 4")


# ---------- Step 5: the whole folder, without one bad file stopping the rest ----------
def process_folder(folder, out_path, client=None):
    """One record per .txt file, written to out_path as a JSON list. Returns the list."""
    raise NotImplementedError("process_folder() arrives in Step 5")
Provided for you:inbox/archive/review-old-2019.txtinbox/notes.mdinbox/review-01.txtinbox/review-02.txtinbox/review-03.txtinbox/review-04.txtinbox/review-05.txtinbox/review-06.txtinbox/review-07.txtinbox/review-08.txtinbox/review-09.txtinbox/review-10.txtinbox/review-11.txtinbox/review-12.txttry_it.py

Frequently asked questions

Why not just use json.loads on the model's reply?

Models often wrap JSON in markdown code fences or add a short sentence before it, and json.loads fails on both. The lab's parser cuts the reply down to the outermost braces, parses that, and checks that the fields hold allowed values before trusting them.

What does ensure_ascii=False do?

By default Python's json module writes accented and non-Latin characters as escape codes such as \u00e9. With ensure_ascii=False the file keeps them as readable characters. Both load back to the same text.

Why catch every exception in the folder loop when other labs say not to?

The goal of a batch run is that one bad file does not lose the other results, and each error is written into that file's record rather than hidden. In a single call you want your own bugs to crash loudly; in a batch you want them recorded next to the file that caused them.

Is structured output with Pydantic covered?

The next foundations lab, Structured Output with Pydantic, replaces the hand-written checks here with a schema, validation errors and automatic retries.

Why files and JSON come before everything else in AI work

Almost every useful AI script starts with files on disk and ends with structured data another program can read. In between is a model call whose reply is text, even when you asked for JSON. Knowing how to find the right files, read them with the right encoding, get a machine-readable answer out of a model and save it as JSON is the skill under document processing, data extraction and most automation. In this lab you use pathlib to list and read a folder of reviews, call a hosted model once per file, and learn why a JSON reply has to be parsed defensively: the model may wrap it in code fences or add a sentence before it. You validate the fields, save results with readable indentation and accented characters intact, and run the whole folder so that an empty file becomes an error record instead of a crash.