Skip to main content
Ctrl+K
Kellogg Research Support holds weekly office hours at the Kellogg Global Hub, Room 5422, every Wednesday from 10:00 a.m. to 12:00 p.m.
Kellogg School of Management
Research Support
Northwestern University - Home Kellogg School of Management
Research Support
Northwestern University - Home

Fundamentals

  • Getting Help
  • What Does KRS Offer?
  • Introduction to Kellogg Linux Cluster

Computing

  • Kellogg Linux Cluster
    • Kellogg Linux Cluster Overview
    • KLC User Guide
      • Access KLC
      • SSH
      • FastX
      • VS Code Workflow
      • KLC OnDemand
      • KLC Filesystem
      • Data Transfer
      • Edit Files on KLC
      • Using Git on KLC
      • Conda/Mamba Environments
      • Launching Jobs
      • Using tmux
      • Monitoring & Managing Jobs
      • LLM API Usage
      • Open Source LLMs (Interactive)
    • Datasets on KLC
      • CME
      • Comscore
      • Cotality (formerly CoreLogic)
      • EDGAR
      • Nielsen Marketing Data
      • PATSTAT
    • Quest Documentation
    • Quest Data Security Guidance
  • Kellogg Linux Cluster Reserve
    • When to Use KLC Reserve
    • GPU Concepts
    • Submitting SLURM Jobs
    • GPU Jobs
    • High-Memory Jobs
    • vLLM on GPUs
    • Ollama on GPUs
  • KLC Node Resources

Datasets

  • Kellogg Data Hosting
    • WRDS
      • CRSP Example
      • TAQ Example
      • Revelio Labs Example
    • Athena (AWS)
      • Comscore
      • Cotality (formerly CoreLogic)
      • Equifax
      • PATSTAT
    • Redivis
      • L2 VM2 Uniform
      • Numerator
      • OpenAlex
      • ProxyCurl
  • Kellogg Research Support Datasets

Tutorials

  • Research the Right Way Workshop
  • Natural Language Processing
    • Deterministic Extraction
    • Quantifying Features
    • Paid LLMs for Text Analysis
    • Open-Source LLMs for Text Analysis
    • Validation and Rigor
  • Qualitative Coding with LLMs
  • OpenAI and LLMs
    • Understanding LLMs
    • OpenAI API
    • GitHub Copilot
    • Application Workflow
  • Open Source LLMs

Paid LLMs for Text Analysis

Contents

  • Do LLMs Always Help?
  • Basic API Pattern
  • Logging
  • Testing LLM Outputs
  • Demo: Classifying Enron Emails
  • Demo: Classifying 10-K Risk Disclosures
  • Next Steps

Paid LLMs for Text Analysis#

This tutorial covers when and how to use paid LLM APIs (OpenAI, Anthropic, Google) for text analysis research. It complements the rule-based methods in Tutorial 1 and Tutorial 2.

Do LLMs Always Help?#

LLMs are powerful but not always the right tool. The key question is: does the task require understanding context and nuance, or can a rule-based method handle it?

Scenario

Better Approach

Extract a known pattern (dates, identifiers)

Regex or NLP pipeline

Handle negation (“not satisfied”)

LLM wins

Classify with new/unseen terminology

LLM wins

Detect sarcasm or irony

Classic NLP can win

Interpret complex nested clauses

LLM wins

High-volume, deterministic extraction

Dictionary/TF-IDF

Advantages of paid LLMs:

  • Easy to use — send a request, receive structured output

  • State-of-the-art contextual understanding

Disadvantages:

  • Costly — usage-based pricing scales quickly

  • Not transparent — model weights and training data are opaque

  • Difficult to reproduce — models update without notice

Basic API Pattern#

The same pattern works across providers. Below is OpenAI, but Anthropic (Claude) and Google (Gemini) follow a nearly identical structure.

import os
from openai import OpenAI

# Load key from file (never hardcode it)
KEY_PATH = "/kellogg/proj/<your-netid>/keys/openai-key.txt"
with open(os.path.expanduser(KEY_PATH)) as f:
    client = OpenAI(api_key=f.read().strip())

SYSTEM_PROMPT = "You are a completion engine. Predict the next word based on context."
USER_PROMPT   = "Complete this sentence in 1 word: I should have taken more..."

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user",   "content": USER_PROMPT},
    ],
    temperature=0,
    seed=42,
)
print(response.choices[0].message.content)

For a script that toggles between Anthropic, OpenAI, and Google, see the krs-openai-cookbook GitHub repo .

Logging#

Always log your runs. LLM outputs are non-deterministic, models update over time, and you need a record for reproducibility.

Log at minimum: timestamp, model version, system prompt, user prompt, and the raw response.

import csv, os, datetime

def log_to_csv(csv_path, model, ctx_window, sys_prompt, usr_prompt, response, test_result):
    os.makedirs(os.path.dirname(csv_path), exist_ok=True)
    file_exists = os.path.isfile(csv_path)
    with open(csv_path, mode="a", newline="") as f:
        writer = csv.writer(f)
        if not file_exists:
            writer.writerow(["timestamp", "model", "ctx_window",
                             "system_prompt", "user_prompt", "response", "test_result"])
        writer.writerow([
            datetime.datetime.now(), model, ctx_window,
            sys_prompt, usr_prompt, response, test_result,
        ])

Testing LLM Outputs#

Because LLMs can introduce errors, hallucinations, and biases, embed automated checks into your pipeline.

A simple approach: validate the grammatical structure of outputs using spaCy.

import spacy

nlp = spacy.load("en_core_web_sm")

def get_pos_tag(text: str) -> str:
    """Return POS tag of the first non-punctuation token."""
    doc = nlp(text)
    for token in doc:
        if not token.is_punct:
            return token.tag_
    return "UNKNOWN"

def test_is_noun(response_text: str) -> str:
    tag = get_pos_tag(response_text.strip())
    return "PASS" if tag.startswith("NN") else f"FAIL ({tag})"

# Example
print(test_is_noun("statistics"))   # → PASS (NN)
print(test_is_noun("running"))      # → FAIL (VBG)

Run tests on a known gold standard before processing the full dataset. See Tutorial 5: Validation and Rigor for a full framework.

Demo: Classifying Enron Emails#

Enron emails are a commonly used benchmark dataset. The goal: classify an email’s tone or topic.

def classify_email(email_text: str, client: OpenAI) -> str:
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {
                "role": "system",
                "content": (
                    "You are an expert at classifying corporate email communications. "
                    "Classify the tone as one of: [Neutral, Positive, Negative, Urgent]. "
                    "Return only the label."
                ),
            },
            {"role": "user", "content": email_text},
        ],
        temperature=0,
        seed=42,
    )
    return response.choices[0].message.content.strip()

Demo: Classifying 10-K Risk Disclosures#

For financial text (e.g., SEC 10-K filings), classify risk factor paragraphs:

RISK_CATEGORIES = ["Market Risk", "Operational Risk", "Regulatory Risk",
                   "Litigation Risk", "Other"]

def classify_risk(paragraph: str, client: OpenAI) -> str:
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {
                "role": "system",
                "content": (
                    f"You are a financial analyst. Classify the following risk disclosure "
                    f"paragraph into exactly one of: {RISK_CATEGORIES}. Return only the label."
                ),
            },
            {"role": "user", "content": paragraph},
        ],
        temperature=0,
        seed=42,
    )
    return response.choices[0].message.content.strip()

Next Steps#

  • Tutorial 4: Open-Source LLMs — run local models on KLC for privacy and reproducibility

  • Tutorial 5: Validation and Rigor — validating LLM outputs for academic research

  • Qualitative Coding with LLMs — rubric-based qualitative coding with structured output and intercoder reliability

  • OpenAI API Guide — full setup and best practices

previous

Quantifying Features

next

Open-Source LLMs for Text Analysis

Contents
  • Do LLMs Always Help?
  • Basic API Pattern
  • Logging
  • Testing LLM Outputs
  • Demo: Classifying Enron Emails
  • Demo: Classifying 10-K Risk Disclosures
  • Next Steps
Last updated: 2026-09-03
© Kellogg School of Management | Kellogg Research Support Disclaimer