Hosted with nbsanity. See source notebook on GitHub.

AI Prompt

An idea I want to try out is to do text classification using text embeddings:

I want to take text, create vector embeddings, and use a classic ML algorithm like a support vector machine or something similar to classify the text using the embeddings.

Does this make sense? What small dataset could we use to try out this idea?

AI Response

This is absolutely a sensible approach! Using text embeddings with classical ML algorithms is actually quite powerful and can work very well for many text classification tasks. It combines the semantic understanding captured by modern embedding models with the efficiency and interpretability of traditional ML algorithms.

Let’s start with a small, well-known dataset to test this idea. I’d recommend the “Movie Review” sentiment analysis dataset (sometimes called “Polarity Dataset v2.0”) - it’s: - Small enough to work with quickly - Binary classification (positive/negative reviews) - Well-understood problem - Clean, English text data - Commonly used as a benchmark

Would you like to try implementing this approach using that dataset? We could: 1. Load a small subset of the movie reviews 2. Create embeddings for each review 3. Train a simple SVM classifier 4. Evaluate the results

Shall we start with that? I can help you with the implementation steps if you’d like to proceed.

url = "https://www.cs.cornell.edu/people/pabo/movie-review-data/review_polarity.tar.gz"

AI Prompt

help me to download the dataset and unzip it. We are in a IPython environment, so we can actually call bash commands

AI Response

I’ll help you download and extract the dataset using wget (through a bash command) and then extract it. Here’s what we’ll do:

import os
!wget -nc {url}  # -nc means "no-clobber" - won't redownload if file exists
!tar -xzf review_polarity.tar.gz

After running this, we should have a directory called ‘txt_sentoken’ with two subdirectories: - ‘pos’ containing positive reviews - ‘neg’ containing negative reviews

Would you like me to verify the contents after you run this code?

!ls
data.db  data.db-shm  data.db-wal  movie_reviews  review_polarity.tar.gz
!mkdir movie_reviews
mkdir: cannot create directory ‘movie_reviews’: File exists
import os
!wget -nc {url}  # -nc means "no-clobber" - won't redownload if file exists
!tar -xzf review_polarity.tar.gz
/usr/bin/sh: 1: wget: not found

AI Prompt

seems we don’t have wget in this environment. lets try curl

AI Response

Yes, let’s use curl instead. Here’s the command:

!curl -o review_polarity.tar.gz {url}
!tar -xzf review_polarity.tar.gz -C movie_reviews/

I’m using -C to extract directly into the movie_reviews directory we created. After running this, we should be able to see the extracted files in the movie_reviews directory. Let me know if this works and we can proceed with loading the data!

!curl -o review_polarity.tar.gz {url}
!tar -xzf review_polarity.tar.gz -C movie_reviews/
/usr/bin/sh: 1: curl: not found

AI Prompt

So I guess we have to rely on python to downlaod this.

AI Response

Yes, let’s use Python’s httpx library (which is pre-installed in this environment) to download the file:

import httpx
import shutil

def download_file(url, filename):
    with httpx.stream('GET', url) as response:
        with open(filename, 'wb') as f:
            for chunk in response.iter_bytes():
                f.write(chunk)

# Download the file
download_file(url, 'review_polarity.tar.gz')

# Extract it
import tarfile
with tarfile.open('review_polarity.tar.gz', 'r:gz') as tar:
    tar.extractall(path='movie_reviews')

This should download and extract the dataset into the movie_reviews directory. Let’s run this and then we can verify the contents were extracted correctly. Would you like to proceed?

import httpx
import shutil

def download_file(url, filename):
    with httpx.stream('GET', url) as response:
        with open(filename, 'wb') as f:
            for chunk in response.iter_bytes():
                f.write(chunk)

# Download the file
download_file(url, 'review_polarity.tar.gz')
!ls
data.db      data.db-wal    poldata.README.2.0      txt_sentoken
data.db-shm  movie_reviews  review_polarity.tar.gz
# Extract it
import tarfile
with tarfile.open('review_polarity.tar.gz', 'r:gz') as tar:
    tar.extractall(path='movie_reviews')
!ls movie_reviews
poldata.README.2.0  txt_sentoken
!cat movie_reviews/txt_sentoken/pos/cv000_29590.txt
films adapted from comic books have had plenty of success , whether they're about superheroes ( batman , superman , spawn ) , or geared toward kids ( casper ) or the arthouse crowd ( ghost world ) , but there's never really been a comic book like from hell before . 
for starters , it was created by alan moore ( and eddie campbell ) , who brought the medium to a whole new level in the mid '80s with a 12-part series called the watchmen . 
to say moore and campbell thoroughly researched the subject of jack the ripper would be like saying michael jackson is starting to look a little odd . 
the book ( or " graphic novel , " if you will ) is over 500 pages long and includes nearly 30 more that consist of nothing but footnotes . 
in other words , don't dismiss this film because of its source . 
if you can get past the whole comic book thing , you might find another stumbling block in from hell's directors , albert and allen hughes . 
getting the hughes brothers to direct this seems almost as ludicrous as casting carrot top in , well , anything , but riddle me this : who better to direct a film that's set in the ghetto and features really violent street crime than the mad geniuses behind menace ii society ? 
the ghetto in question is , of course , whitechapel in 1888 london's east end . 
it's a filthy , sooty place where the whores ( called " unfortunates " ) are starting to get a little nervous about this mysterious psychopath who has been carving through their profession with surgical precision . 
when the first stiff turns up , copper peter godley ( robbie coltrane , the world is not enough ) calls in inspector frederick abberline ( johnny depp , blow ) to crack the case . 
abberline , a widower , has prophetic dreams he unsuccessfully tries to quell with copious amounts of absinthe and opium . 
upon arriving in whitechapel , he befriends an unfortunate named mary kelly ( heather graham , say it isn't so ) and proceeds to investigate the horribly gruesome crimes that even the police surgeon can't stomach . 
i don't think anyone needs to be briefed on jack the ripper , so i won't go into the particulars here , other than to say moore and campbell have a unique and interesting theory about both the identity of the killer and the reasons he chooses to slay . 
in the comic , they don't bother cloaking the identity of the ripper , but screenwriters terry hayes ( vertical limit ) and rafael yglesias ( les mis ? rables ) do a good job of keeping him hidden from viewers until the very end . 
it's funny to watch the locals blindly point the finger of blame at jews and indians because , after all , an englishman could never be capable of committing such ghastly acts . 
and from hell's ending had me whistling the stonecutters song from the simpsons for days ( " who holds back the electric car/who made steve guttenberg a star ? " ) . 
don't worry - it'll all make sense when you see it . 
now onto from hell's appearance : it's certainly dark and bleak enough , and it's surprising to see how much more it looks like a tim burton film than planet of the apes did ( at times , it seems like sleepy hollow 2 ) . 
the print i saw wasn't completely finished ( both color and music had not been finalized , so no comments about marilyn manson ) , but cinematographer peter deming ( don't say a word ) ably captures the dreariness of victorian-era london and helped make the flashy killing scenes remind me of the crazy flashbacks in twin peaks , even though the violence in the film pales in comparison to that in the black-and-white comic . 
oscar winner martin childs' ( shakespeare in love ) production design turns the original prague surroundings into one creepy place . 
even the acting in from hell is solid , with the dreamy depp turning in a typically strong performance and deftly handling a british accent . 
ians holm ( joe gould's secret ) and richardson ( 102 dalmatians ) log in great supporting roles , but the big surprise here is graham . 
i cringed the first time she opened her mouth , imagining her attempt at an irish accent , but it actually wasn't half bad . 
the film , however , is all good . 
2 : 00 - r for strong violence/gore , sexuality , language and drug content 
from pathlib import Path

reviews_path = Path('movie_reviews/txt_sentoken')

pos_reviews = (reviews_path / 'pos').ls()
neg_reviews = (reviews_path / 'neg').ls()

print(f"Number of positive reviews: {len(pos_reviews)}")
print(f"Number of negative reviews: {len(neg_reviews)}")
Number of positive reviews: 1000
Number of negative reviews: 1000
pos_reviews
(#1000) [Path('movie_reviews/txt_sentoken/pos/cv007_4968.txt'),Path('movie_reviews/txt_sentoken/pos/cv008_29435.txt'),Path('movie_reviews/txt_sentoken/pos/cv009_29592.txt'),Path('movie_reviews/txt_sentoken/pos/cv010_29198.txt'),Path('movie_reviews/txt_sentoken/pos/cv011_12166.txt'),Path('movie_reviews/txt_sentoken/pos/cv012_29576.txt'),Path('movie_reviews/txt_sentoken/pos/cv013_10159.txt'),Path('movie_reviews/txt_sentoken/pos/cv014_13924.txt'),Path('movie_reviews/txt_sentoken/pos/cv015_29439.txt'),Path('movie_reviews/txt_sentoken/pos/cv016_4659.txt'),Path('movie_reviews/txt_sentoken/pos/cv017_22464.txt'),Path('movie_reviews/txt_sentoken/pos/cv018_20137.txt'),Path('movie_reviews/txt_sentoken/pos/cv019_14482.txt'),Path('movie_reviews/txt_sentoken/pos/cv020_8825.txt'),Path('movie_reviews/txt_sentoken/pos/cv021_15838.txt'),Path('movie_reviews/txt_sentoken/pos/cv022_12864.txt'),Path('movie_reviews/txt_sentoken/pos/cv023_12672.txt'),Path('movie_reviews/txt_sentoken/pos/cv024_6778.txt'),Path('movie_reviews/txt_sentoken/pos/cv025_3108.txt'),Path('movie_reviews/txt_sentoken/pos/cv026_29325.txt')...]
pos_rs = []
for path in pos_reviews: 
    with open(path, "r") as f:
        text = f.read()
        pos_rs.append({'text': text, 'label': 'pos'})

neg_rs = []
for path in neg_reviews: 
    with open(path, "r") as f:
        text = f.read()
        neg_rs.append({'text': text, 'label': 'neg'})

AI Prompt

now let’s sample 100 from each and put them all in one list.

AI Response

Here’s how we can randomly sample 100 reviews from each category and combine them:

import random

# Sample 100 from each list
sampled_pos = random.sample(pos_rs, 100)
sampled_neg = random.sample(neg_rs, 100)

# Combine into one list
all_reviews = sampled_pos + sampled_neg

# Shuffle them to mix positive and negative reviews
random.shuffle(all_reviews)

print(f"Total number of reviews: {len(all_reviews)}")

This will give us a balanced dataset of 200 reviews total, randomly mixed between positive and negative. Should we proceed with creating embeddings for these reviews?

import random

# Sample 100 from each list
sampled_pos = random.sample(pos_rs, 100)
sampled_neg = random.sample(neg_rs, 100)

# Combine into one list
all_reviews = sampled_pos + sampled_neg
import google.generativeai as genai
import os

genai.configure(api_key=os.environ["GEMINI_API_KEY"])

result = genai.embed_content(
        model="models/text-embedding-004",
        content="What is the meaning of life?")

print(str(result['embedding']))
[-0.010632273, 0.019375853, 0.020965198, 0.0007706437, -0.061464068, 0.014739741, -0.0022759985, 0.013184195, 0.014464715, 0.022593116, 0.02184836, -0.059616957, 0.06032222, -0.047657482, 0.017848385, -0.10987464, -0.0598155, -0.00479664, -0.043298274, -0.05090505, 0.029398112, 0.011642447, 0.04183789, -0.017999396, 0.011026355, 0.049722955, 0.012025892, 0.007331535, 0.01967245, -0.0025621902, 0.028765293, 0.0068937168, 0.0029231338, -0.0002095079, 0.032031864, 0.02518659, -0.032855466, 0.00758291, -0.00011585959, -0.034515556, -0.066151336, 0.03191643, -0.026680378, 0.017334407, -0.025778342, -0.008119435, -0.002431255, -0.009850676, -0.030725427, 0.08225489, 0.036220998, -0.011677602, -0.048477963, 0.026030278, 0.0027632737, -0.036962725, -0.051528536, -0.027265795, 0.04703419, -0.03285586, -0.015140722, -0.003516825, -0.006665491, -0.024252947, -0.031371485, 0.056986455, -0.02846856, 0.009047717, -0.021733612, 0.01993043, -0.016926913, 0.051008012, -0.022356581, 0.05340387, -0.036262874, 0.038486782, 0.00097307086, 0.0058653215, -0.03454564, 0.038883448, -0.020346535, -0.0015010178, 0.050026324, 0.07690296, 0.04075089, 0.031162778, -0.048467305, -0.031640615, -0.050462708, -0.0020114628, 0.028352365, 0.016939064, -0.032321587, -0.017523259, 0.045018278, 0.005056391, -0.08844299, -0.039214693, 0.032369446, 0.013868324, 0.048252415, 0.012212794, -0.0059761675, -0.055453815, 0.059123088, 0.077673666, 0.012595949, -0.030664278, 0.0019445478, -0.04473188, 0.03904732, -0.045189187, 0.005711123, -0.024350755, 0.006020905, -0.056398984, -0.008473793, 0.026584638, -0.019225147, -0.003090118, 0.02925659, 0.037855238, -0.033372607, 0.027388284, 0.058645032, -0.0034353225, -0.00052528176, -0.061926123, -0.047651615, -0.020240242, 0.037042357, -0.101258375, -0.017224912, 0.031264607, -0.029515961, 0.04070285, 0.08155317, -0.02680439, 0.010762277, -0.068192326, 0.010339065, 0.001237995, 0.025081903, 0.025549553, 0.033473987, -0.011019555, 0.025582748, -0.044487614, -0.02351738, -0.019466395, -0.05739292, -0.023219999, 0.06383781, -0.0032941306, 0.0032277782, 0.014958662, 0.037334923, 0.010649138, 0.07434867, -0.024096856, -0.0051036896, -0.05779452, -0.087558694, 0.005070572, -0.059070442, -0.0075670946, 0.020864079, -0.059642896, -0.017373137, 0.010781379, 0.005737636, 0.01155112, -0.051110126, -0.00469127, 0.003082495, 0.021098692, -0.010646007, -0.0075031, 0.01649139, -0.010034379, 0.03665796, -0.02178521, -0.060414966, -0.0110657895, -0.018490821, -0.038217384, -0.008570785, 0.06764553, 0.045524262, 0.028033433, -0.049999256, 0.038643356, -0.001174409, 0.0071052625, -0.0071540354, -0.03563122, 0.040300176, -0.01187511, -0.020187229, 0.034496624, -0.018076168, -0.025241721, -0.03251734, -0.005546835, 0.01218167, 0.001308468, -0.01956061, -0.016109072, 0.033482637, -0.013107253, -0.04336891, 0.017510926, -0.059141196, -0.023261068, 0.025163788, 0.04890369, 0.076442, -0.0016504959, 0.10172619, -0.015871631, -0.023793343, -0.02358568, 0.036539588, -0.06184051, -0.026249573, 0.006468363, -0.031341415, -0.06234132, -0.049488295, -0.018885756, 0.03395302, -0.006009219, -0.031574816, -0.0054155374, -0.033587996, -0.015623983, 0.013743329, 0.06735172, 0.009166206, -0.027008668, 0.053747576, -0.019794546, -0.004977181, -0.0011775235, 0.055169225, 0.031791825, 0.025199965, 0.080965735, 0.0039748563, -0.08897454, -0.027933061, 0.00045005116, -0.013844743, -0.06260468, -0.046366338, -0.029402703, 0.023191761, -0.01076239, 0.0076124803, -0.020023048, 0.039004155, -0.070678934, -0.07069906, -0.02288811, -0.03803117, -0.05004868, -0.018108511, -0.024550572, 0.040691372, -0.05350907, 0.051243976, -0.0021085127, -0.009738572, -0.008890091, -0.015601183, 0.019753162, 0.0053467727, 0.031590473, 0.0041920035, -0.04371269, 0.067351475, -0.019107815, -0.014121782, 0.009763881, 0.031802285, -0.0069985087, 0.013498973, 0.023104675, 0.0006382101, -0.008508383, 0.03777484, 0.008196443, -0.0025804106, -0.033261176, -0.033644095, -0.0039042186, 0.049756225, 0.03194955, 0.018670397, -0.004185749, 0.01654144, 0.06362886, -0.08167434, 0.004465523, 0.005431271
========== TRUNCATED ==========
9716, 0.07723543, 0.013161921, 0.035233274, 0.013950026, -0.026914261, -0.0012491347, 0.022125386, 0.06322952, 0.026747808, 0.016557682, 0.0026654843, 0.018403852, -0.002208754, -0.0043939324, 0.021411125, -0.0720841, -0.014162335, 0.009017187, 0.009589008, 0.013714266, -0.013205313, 0.055074606, 0.0135510815, -0.009647225, -0.0073859296, -0.015533789, 0.041406598, -0.029964559, -0.004557068, 0.042442538, 0.003949693, -0.060314845, -0.0485521, -0.008145191, -0.0008701478, 0.026269091, 0.064659014, -0.0014519938, 0.07755499, 0.012390666, 0.0009994709, 0.010512895, -0.0278039, -0.007720246, -0.017693883, -0.048093677, 0.048450127, -0.0084898835, 0.033827696, 0.012179157, 0.0439037, 0.019806726, -0.0033815033, 0.055004198, -0.010644163, 0.06983634, -0.0012867257, 0.116212435, 0.0018561919, -0.03540732, 0.018552277, -0.014596015, 0.007995569, 0.02062322, -0.013589375, 0.013323644, 0.058206026, 0.014310659, 0.009776701, 0.022025304, 0.043452848, 0.007224779, -0.005841782, 0.07922995, 0.029124206, 0.027332257, 0.011426645, 0.0610715, 0.033370003, -0.0032318854, 0.032962296, 0.044215627, -0.0019828111, -0.015901793, -0.00029608337, 0.013392526, -0.009583505, 0.101087496, 0.029640157, -0.04264001, 0.028663691, 0.0012885618, -0.00042037942, -0.05097693, 0.046501413, 0.034346417, -0.03722956, 0.030485353, -0.028618095, -0.014943351, 0.024176005, 0.0059531713, -0.035492424, 0.04719729, -0.022705767, -0.004888659, 0.013763481, -0.006877845, 0.039462008, -0.022432147, -0.024738846, -0.0030126623, 0.014878597, 0.047142185, -0.028536918, -0.0019756483, -0.024875728, -0.049604762, 0.0076611727, 0.0125418445, 0.06991834, 0.03057351, -0.0378383, -0.01601651, 0.023397712, -0.006465213, -0.016750913, -0.028563995, 0.013968368, 0.04284747, 0.013723971, -0.038290635, 0.0062841102, -0.016612995, 0.0060477066, 0.0071878443, 0.017012084, 0.026105886, -0.029898316, -0.0034338816, 0.022605129, -0.031070229, -0.014588787, -0.05051719, 0.011172559, -0.009865424, -0.0006022246, -0.050201006, 0.010974502, 0.068753, -0.06411741, 0.021827834, -0.079100326, 0.027182067, -0.0049233013, -0.00854883, 0.042056426, -0.041176684, -0.043345083, 0.007900265, 0.03339074, 0.009065677, -0.11376203, 0.026648033, -0.02173746, -0.056054536, -0.05019967, 0.02505995, -0.073714115, 0.00041753243, 0.046410866, -0.00787225, -0.04326591, 0.052950215, -0.020633917, 0.0053053875, 0.038686555, 0.0076096985, -0.044483498, 0.01734079, 0.050843734, 0.041709274, -0.032848667, 0.06583798, -0.0462481, -0.019906212, 0.062381882, 0.010934914, -0.053675517, -0.033560812, 0.027787214, 0.003391649, 0.019972153, 0.0442223, -0.06779605, -0.057355773, -0.00908375, -0.031183494, 0.07079641, -0.020006215, -0.024294054, -0.016699182, 0.0010443482, 0.018393427, 0.032058917, 0.04007311, -0.013608359, -0.02647255, -0.023104627, 0.07973177, 0.0143912, -0.00773088, -0.0105773965, 0.009673522, 0.030086972, 0.021788783, 0.0215211, -0.0021278693, 0.01382664, -0.05028589, 0.0037969938, -0.019241702, -0.055900373, 0.047401533, 0.047825735, -0.008378417, -0.021368338, -0.0029360335, -0.023776283, -0.030378696, 0.0042622155, -0.04370354, 0.046717755, 0.057218548, -0.07626953, 0.06840914, -0.013551472, -0.04081457, 0.0024602, -0.019596782, -0.034115944, 0.022949563, 0.08198656, 0.010917071, 0.012808682, -0.0024835565, -0.06742202, 0.035741765, -0.007581535, 0.01281636, 0.05919395, 0.019007294, -0.057466842, 0.031478077, 0.011478408, 0.0197156, 0.03522307, -0.0039083306, 0.009473974, -0.061164707, -0.010365365, 0.020075476, 0.025542602, -0.030813247, -0.050739173, -0.0037222796, -0.0025314046, 0.03607207, 0.085864864, 0.030587368, -0.011790973, 0.02897135, 0.009813614, 0.0036375853, 0.01939262, -0.012913535, 0.032164395, -0.012496243, 0.053962503, -0.0030092895, -0.013271072, -0.069150545, -0.014564991, 0.01531648, -0.0493518, -0.026759734, -0.030610656, -0.022655917, -0.09071128, -0.051921394, -0.014159941, 0.086534575, 0.039204597, -0.018607471, -0.023076432, 0.016071219, 0.08200573, 0.036090653, -0.0029250141, 0.032362826, -0.014467054, 0.013964356, -0.075049624, 0.047506943, -0.007153866, -0.028534686]

========== MIDDLE OF OUTPUT TRUNCATED ==========
def get_embd(review):
    result = genai.embed_content(
        model="models/text-embedding-004",
        content=review["text"][:2000]
    )
    return result['embedding']

get_embd(all_reviews[-1])
[0.028398918,
 0.027150271,
 -0.0014515561,
 -0.003933469,
 0.02635582,
 0.011924138,
 0.037630446,
 0.07261679,
 0.02122348,
 -0.01987975,
 0.015907597,
 0.06485319,
 0.05729386,
 0.028361876,
 0.00889483,
 -0.050631132,
 -0.003579496,
 0.08188463,
 -0.08008639,
 -0.0068563675,
 0.025825283,
 -0.051394545,
 -0.027879527,
 -0.0038705743,
 0.0063896375,
 -0.0049845963,
 0.025398796,
 -0.016697923,
 -0.06127978,
 -0.02812019,
 0.028975584,
 0.047606893,
 -0.02067879,
 0.02992036,
 0.002161898,
 -0.0153953,
 -0.035085626,
 -0.042672053,
 0.03220898,
 -0.09402655,
 -0.018518751,
 0.0537379,
 -0.043391414,
 0.06643979,
 -0.019458456,
 -0.017941521,
 -0.0020620304,
 0.016139153,
 0.004219952,
 0.0068824287,
 0.02126266,
 -0.021771278,
 -0.054716572,
 -0.0014096346,
 0.01611054,
 -0.0692462,
 -0.049441073,
 -0.0463322,
 0.009734403,
 0.06297605,
 0.00044816858,
 -0.0012167857,
 0.049935978,
 -0.0089245085,
 -0.0117373755,
 0.022018831,
 -0.05587433,
 0.025920378,
 0.0052313977,
 -0.008770437,
 -0.027925856,
 0.050831076,
 0.04764402,
 0.012034183,
 0.052228305,
 0.033023942,
 0.034355767,
 -0.03201095,
 0.04313402,
 -0.007673634,
 0.012912225,
 -0.016631173,
 0.06147947,
 0.020083407,
 -0.0054719024,
 0.01661841,
 0.005267028,
 -0.028530434,
 -0.037606705,
 -0.0059644342,
 0.10684592,
 0.09802554,
 -0.08850519,
 -0.029927718,
 0.03235955,
 -0.043880295,
 -0.07965548,
 -0.0653144,
 0.09519042,
 0.055219788,
 0.071845636,
 -0.002932273,
 0.008825667,
 -0.07091293,
 0.055013023,
 0.04413237,
 0.0111055905,
 -0.049765144,
 -0.019918615,
 0.038085718,
 -0.020452641,
 -0.043145373,
 0.014726715,
 -0.02245104,
 -0.026039248,
 0.030517586,
 -0.03127272,
 0.07167899,
 0.02575076,
 -0.026517216,
 -0.06206649,
 0.0457429,
 -0.051279157,
 0.10202041,
 0.019223396,
 -0.050448887,
 0.037656084,
 -0.008196882,
 -0.01417291,
 -0.043380484,
 0.0018728728,
 -0.0005579319,
 -0.06052058,
 0.008666311,
 0.033357695,
 -0.06608957,
 -0.040035564,
 -0.014557583,
 0.065033905,
 -0.055254027,
 -0.04393844,
 -0.010860689,
 0.0027118998,
 -0.021389032,
 -0.015078791,
 -0.06478367,
 -0.048727825,
 0.06437818,
 0.043496415,
 0.00819628,
 -0.051017262,
 -0.0042188657,
 -0.006521095,
 0.013434626,
 0.013235692,
 -0.0051772846,
 0.00488781,
 0.001260777,
 -0.009133549,
 -0.021410746,
 0.061729025,
 -0.06105742,
 -0.016182655,
 0.0398272,
 -0.06373508,
 -0.010299746,
 0.012207333,
 -0.032833677,
 -0.0061911186,
 0.052227832,
 -0.03035622,
 -0.0359511,
 -0.0022880528,
 -0.054253004,
 -0.020072399,
 0.036694232,
 0.030459696,
 0.0012580195,
 -0.035068523,
 -0.03209706,
 0.0056784446,
 -0.029783724,
 -0.028180424,
 -0.014696764,
 0.002252009,
 -0.008007648,
 -0.016618477,
 -0.009212798,
 0.091403976,
 0.015622607,
 -0.03053566,
 0.0071348133,
 5.8811638e-05,
 -0.022319062,
 -0.010354633,
 0.0069508012,
 0.010625596,
 -0.0010052624,
 0.003108842,
 -0.022774981,
 -0.009932834,
 0.009958033,
 0.019461697,
 -0.029227158,
 -0.08466864,
 -0.017875861,
 0.0018855468,
 -0.002654891,
 -0.01754184,
 0.020187732,
 -0.0024053156,
 -0.0086461175,
 -0.024466403,
 -0.016147725,
 -0.008963439,
 -0.001701573,
 0.05371722,
 0.015845466,
 0.012267711,
 -0.006818813,
 0.003036383,
 0.039746456,
 -0.015455258,
 0.02418501,
 -0.00044951602,
 -0.0055267573,
 -0.07946516,
 0.05885463,
 -0.054626793,
 -0.011516715,
 0.027936375,
 0.024913069,
 -0.021732073,
 0.02237017,
 -0.11433556,
 0.022219054,
 -0.019911721,
 0.06016809,
 0.05165445,
 0.011817458,
 -0.006114541,
 -0.057026528,
 0.048134938,
 0.0059349043,
 0.022598887,
 -0.040025014,
 0.025346452,
 0.08150402,
 0.037463106,
 -0.014595291,
 -0.04272792,
 0.064570405,
 0.02304313,
 0.0045500835,
 -0.037076585,
 -0.021013083,
 0.019717034,
 0.005202018,
 -0.022216015,
 -0.07045907,
 0.091063745,
 -0.0011640642,
 -0.028497787,
 -0.044554107,
 -0.05730207,
 -0.06988453,
 -0.031423774,
 -0.00027954462,
 0.029343368,
 -0.015590131,
 0.0058070095,
 0.011312433,
 -0.058060624,
 -0.011554081,
 -0.064540125,
 0.03602481,
 0.026614338,
 0.023287097,
 -0.019614559,
 -0.033357278,
 0.00985983,
 -0.00089721323,
 0.009522723,
 -0.013472557,
 -0.01089
========== TRUNCATED ==========
5,
 -0.031170676,
 0.047236618,
 -0.023338068,
 -0.04313059,
 0.03031858,
 -0.041875616,
 -0.02557716,
 -0.02380324,
 0.0062266765,
 -0.017215718,
 -0.020728583,
 0.020391108,
 0.020980002,
 0.056865714,
 0.014387479,
 0.019800771,
 -0.015438099,
 0.016525405,
 -0.026301594,
 0.048135974,
 -0.034083344,
 -0.009207277,
 -0.0015517499,
 -0.011842017,
 0.013312986,
 0.0013802154,
 -0.016641526,
 -0.0023657219,
 0.005620791,
 0.013743653,
 -0.05404245,
 -0.018527333,
 0.0031890364,
 -0.0221057,
 -0.055439476,
 -0.052337274,
 0.038335312,
 -0.02733159,
 -0.015566077,
 0.009100102,
 -0.005734289,
 0.00982246,
 0.0021971012,
 0.030201998,
 -0.051096518,
 0.018898562,
 0.0012179522,
 -0.11239963,
 0.06630646,
 0.01905952,
 0.017941568,
 0.023603084,
 0.077526934,
 0.024944855,
 0.019797351,
 0.0072615216,
 0.009749401,
 0.03770395,
 0.009911445,
 0.013662848,
 -0.011315753,
 0.08558784,
 -0.038862206,
 -0.038892895,
 0.012654467,
 -0.031624444,
 -0.00025461055,
 0.015152385,
 -0.052190166,
 -0.01313986,
 -0.0027443722,
 -0.041394666,
 -0.017377025,
 -0.00744216,
 0.033198174,
 -0.0206114,
 0.0024064353,
 0.019109258,
 -0.013268552,
 -0.018084763,
 0.015159546,
 -0.05276735,
 0.04796391,
 -0.025759205,
 -0.014118547,
 -0.039944936,
 0.013293312,
 -0.09203076,
 -0.0007712899,
 0.0045199217,
 -0.045952845,
 -0.05859767,
 -0.004590527,
 0.00067161926,
 -0.0009877378,
 0.02505244,
 -0.007935016,
 -0.07325815,
 0.00606671,
 0.0073591275,
 -0.004453104,
 -0.02350832,
 -0.018890748,
 -0.014763044,
 0.0022208854,
 0.011473255,
 -0.02254352,
 0.02258996,
 -0.012590012,
 -0.008817278,
 -0.0483161,
 0.029852184,
 -0.06710688,
 0.036075156,
 0.047236625,
 0.056348123,
 0.007931373,
 -0.012117307,
 -0.04602526,
 0.032323893,
 0.040992443,
 -0.047736913,
 0.020103196,
 -0.009416244,
 -0.041413568,
 0.0065499446,
 0.004235642,
 0.01401263,
 0.060308192,
 0.039033115,
 -0.022200547,
 -0.007908992,
 0.053997487,
 -0.087715715,
 0.020302406,
 -0.03658614,
 0.02327086,
 0.022121308,
 -0.013672004,
 -0.0007268065,
 -0.0026831042,
 -0.05620914,
 0.0066678794,
 -0.0008848365,
 0.0056092744,
 0.013066885,
 0.012509491,
 0.0038740737,
 0.018318782,
 0.034485668,
 -0.00425841,
 -0.047932174,
 -0.022701895,
 -0.01359629,
 -0.029759696,
 0.011469402,
 0.018422455,
 0.053918574,
 0.0068713576,
 0.022358796,
 -0.01926966,
 -0.07510141,
 0.0016380582,
 0.016157927,
 0.07487375,
 0.08555201,
 0.011810677,
 -0.048029885,
 0.06355114,
 -0.026219033,
 0.006977835,
 -0.0042550326,
 -0.03519451,
 0.007060274,
 0.02218984,
 0.028044432,
 -0.00268243,
 0.005673885,
 -0.004348128,
 0.01707461,
 0.029661795,
 -0.04653641,
 0.04745692,
 -0.030583262,
 0.005909371,
 -0.03001804,
 -0.048462775,
 0.008321336,
 0.047135297,
 -0.016937962,
 0.07827768,
 -0.0042237598,
 -0.011393628,
 0.008802025,
 0.0050530173,
 -0.047523834,
 0.005905238,
 0.045235112,
 -0.012017595,
 -0.0298232,
 -0.03996074,
 0.062175605,
 -0.03210342,
 0.05202184,
 0.030820541,
 -0.008085035,
 -0.008630133,
 -0.018048929,
 -0.07046098,
 -0.037295178,
 -0.013239728,
 0.007953227,
 0.06724738,
 0.010957478,
 0.039816834,
 0.067677535,
 -0.011027046,
 -0.029393425,
 -0.057582308,
 -0.038438242,
 -0.059413902,
 -0.008542845,
 -0.023137445,
 0.0009797999,
 0.025024345,
 -0.08402079,
 0.035389498,
 -0.028930929,
 0.025401855,
 0.08994233,
 0.00077258365,
 0.009572406,
 0.04984574,
 -0.02908787,
 -0.024147263,
 0.031748466,
 -0.043106627,
 0.045869578,
 -0.04481696,
 0.026028398,
 0.045255918,
 0.025908004,
 0.046900615,
 -0.03398828,
 0.029814724,
 0.0002553404,
 0.032324903,
 0.031946145,
 0.0033245091,
 -0.029744392,
 0.0019222543,
 -0.01890751,
 0.0004001247,
 -0.020561393,
 -0.028320367,
 -0.024698185,
 -0.03416564,
 0.02268109,
 -0.021262698,
 -0.021439033,
 -0.02176719,
 0.011968989,
 0.0534574,
 -0.04441653,
 0.004045926,
 -0.012972854,
 -0.08406497,
 -0.031884827,
 -0.020569162,
 0.0029873974,
 0.029011087,
 0.11225804,
 0.008159551,
 -0.02923119,
 0.00038637262,
 0.085996844,
 0.023691969,
 -0.029485585,
 0.010787876,
 -0.0031996956,
 -0.033772398,
 0.003918073,
 0.02421278,
 0.03681934,
 -0.04696991]
========== MIDDLE OF OUTPUT TRUNCATED ==========
def add_embd(review):
    return {**review, "embedding": get_embd(review)}
from fastcore.parallel import parallel
reviews_embds = parallel(add_embd, all_reviews, n_workers=50, threadpool=True)
reviews_embds
(#200) [{'text': 'a thriller set in modern day seattle , that marked marky mark\'s migration from the " good vibrations " and " calvin klein undie ad " guy , to mark wahlberg , thespian-at-large , and a mighty good one at that . \nplot : boy ( wahlberg ) meets girl ( witherspoon ) . \nboy likes girl . \ngirl likes boy . \nboy and girl begin dating . \nparents of girl aren\'t crazy about the boy . \ngirl steps to boy\'s defense . \nfamily problems occur . \ngirl learns more about the boy . \nboy goes nuts . \nfun ensues . \ncritique : ever wonder what would happen if your better half turned out to be someone other than the person that you had come to cherish and love ? \nwell , if you ever have , then this film is definitely for you . \nit isn\'t the most original premise in the world , but it is handled pretty well here , and pretty quickly for that matter ( a speedy 90 minutes ) . \ni think one of the main reasons this film worked for me is the groundbreaking performance by mark wahlberg ( of boogie nights infamy ( 7 . 5/10 ) . \nhe completely captured all the goodness , and badness , of the main character of this film , and always kept me wanting to see more and more of him ( his performance scored him a solid nomination from his core target audience , as " best villain " at the 1997 mtv movie awards ) . \nthe soundtrack is also very effective in this setting , and of course , the slutty performance by alyssa milano is something we could all indulge in ( look for the nice bikini and butt shots . . . if \nyou\'re into that kind of thing , that is : ) . \ni didn\'t much care for the father/david relationship which seemed to be a little too contrived , but for the most part , i was really into this movie . \nthen again , daddy dearest did have one of the coolest lines in the movie when his wife is asking him what the " big deal " was about his daughter dating david . \nhis reply : " the big deal is that the guy gives me the creeps , and the girl is my daughter . " \ngood point . \nall in all , the movie kicked some fun ass , mindless as it was , and effectively demonstrated every parent\'s worst nightmare . \nmind you , i still can\'t tell if reese witherspoon is good looking or a douchebag , but hey , life goes on . \nand on a personal note , we finally decided to add some taco meat onto our nacho fiesta of usual , and let me tell you all . . . it was a flaming success ! ! \nanother firm recommendation by joe schmoe ! \nlittle known facts : director james foley was the man responsible for the critically acclaimed glengarry glenross ( 8/10 ) back in 1992 , at close range ( 7 . 5/10 ) in 1986 , and even directed an episode of twin peaks in 1990 . \nmark wahlberg stands 5 " 9 , is born in massachusetts , dropped out of highschool ( later got his ged ) , and was convicted of several minor felonies before starting his career as the lead singer of marky mark and the funky bunch . \nhis brother is donnie wahlberg of new kids on the block fame . \nhe is also testing the waters on the acting front . \n', 'label': 'pos', 'embedding': [-2.575886e-06, -0.038771514, -0.052231822, 0.043739285, 0.058593784, 0.043406762, 0.03563804, 0.09847706, 0.00021271833, 0.04883986, 0.03887204, 0.052323088, 0.08307226, 0.006332062, 0.06834095, -0.057619583, -0.07683325, 0.1105393, -0.068412244, -0.012944708, -0.0054913303, -0.0008588489, 0.012548585, -0.0009383706, -0.0017214044, -0.036787804, -0.024076357, -0.030557908, 0.025094619, -0.023301713, 0.0009270779, 0.0027868033, -0.017858315, -0.015348944, -0.025451154, -0.036838893, -0.040817685, -0.047666226, -0.0041818363, -0.08326709, -0.05086744, -0.0024963466, -0.08816157, 0.08370893, -0.008232471, -0.028538499, 0.025053577, -0.029730663, 0.0013694725, 0.029235076, -0.009248988, 0.0009104827, -0.056169767, 0.027784685, 0.044214845, -0.06524561, -0.07324348, -0.03513249, -0.017000474, 0.025456319, 0.001134876, -0.008321849, 0.0049250415, -0.011465471, 0.0091492245, -0.02446847, -0.02684618, 0.035016585, 0.01983973, 0.0020106393, -0.0065777106, -0.017275685, 0.021884045, 0.020348204, 0.014244144, -
========== TRUNCATED ==========
 0.04773427, -0.014336917, 0.003991974, -0.0073791184, -0.030027507, 0.041301806, 0.03358362, 0.009062842, 0.05005095, 0.022005113, -0.038494926, 0.010462611, -0.022631334, -0.025852706, -0.07128625, 0.012134434, -0.093096025, -0.031294834, -0.015941534, 0.01361953, 0.037930172, -0.059993666, -0.040801566, 0.02010312, -0.019507367, -0.00051431055, -0.010713161, -0.047068927, -0.020586627, -0.052736454, 0.010564626, -0.014517993, 0.040440403, -0.037731055, -0.043888178, 0.017737696, 0.049394034, 0.009252151, 0.03533172, -0.09249336, 0.006609371, 0.05135848, 0.01050631, -0.049483377, 0.011303703, 0.06845664, 0.016589994, 0.0096204765, -0.029986333, 0.008761153, -0.03682769, 0.0039882213, -0.018940527, -0.041332, -0.017583456, 0.033552427, -0.01963013, 0.032589268, 0.021706004, -0.0655731, -0.0021060265, -0.011125274, 0.005030502, -0.021230236, 0.010969159, -0.038815778, -0.017521918, 0.026825672, 0.027650213, 0.008916945, 0.014387044, 0.019814188, 0.025307316, 0.015244763, 0.015737906, 0.033333316, 0.03692416, 0.013521668, -0.0014178685, 0.022090841, 0.08013401, -0.004665551, 0.0013712948, 0.030495198, -0.06237227, 0.065609984, -0.023959283, -0.029985564, -0.03253731, 0.0015628682, -0.0033089812, -0.059712276, 0.0019609933, -0.034217115, -0.011588807, 0.028451402, 0.025405217, -0.029150603, 0.010742492, 0.013230058, -0.016285982, 0.027398752, -0.052252382, 0.034677453, 0.003331714, -0.06316646, -0.020601887, -0.030265694, 0.07087854, -0.0077683674, -0.023794712, 0.003557996, -0.0108637335, -0.031198155, -0.0040175505, -0.059831794, -0.02224209, 0.0014655399, 0.00023394317, 0.03530034, 0.007709467, -0.009256643, -0.032484546, -0.037572358, 0.07211338, 0.012992677, -0.021727005, 0.0028871133, 0.02083327, -0.040121146, 0.03932745, -0.0017690571, 0.009486275, 0.020917203, 0.023911143, -0.040356997, 0.02549239, -0.056067057, 0.010658622, -0.011142986, 0.0014881531, 0.0030454148, -0.043540567, -0.01672762, 0.053488374, -0.030445438, -0.013037588, 0.026157474, -0.008678561, 0.013617761, -0.07037541, 0.068979695, -0.027851624, -0.030725153, 0.014612513, -0.0080353245, -0.010713693, -0.008452643, -0.034609824, -0.033257425, -0.042432915, 0.02859782, 0.0039917706, 0.009438581, -0.024115968, 0.024872653, -0.08262192, 0.001276506, -0.024180613, 0.028617091, -0.007832328, 0.004456971, -0.020171527, 0.024836905, -0.012425616, -0.004371842, 0.04350828, 0.0045616394, 0.029775124, 0.011890753, -0.060391515, 0.0037610936, -0.018045897, 0.029939758, -0.039273605, 0.022541985, -0.032919627, -0.018452078, -0.004850872, 0.07464589, -0.014669591, -0.054707494, -0.0048900265, -0.014133838, -0.042420562, 0.056575097, -0.010141165, -0.019020833, 0.0444684, -0.015873648, -0.032097854, 0.003407967, -0.0048093293, 0.046227615, 0.027624164, 0.034533575, -0.029722076, -0.01508172, -0.011211651, 0.050037675, 0.020192686, 0.008321267, -0.013651373, -0.052132063, -0.01461077, 0.062012155, 0.0059121638, -0.003317554, 0.009124157, -0.07671969, 0.050436217, -0.006214108, -0.013584855, 0.035443198, -0.004058559, 0.017750388, -0.0076332283, -0.012678523, -0.033350855, 0.006551547, -0.0059913187, 0.064208664, 0.073863566, 0.09802776, 0.023065645, -0.016281793, -0.02083453, -0.021333663, -0.0076354113, -0.056753453, 0.030889096, -0.036310747, 0.006463229, -0.013338659, -0.012935614, 0.043194357, -0.0033385272, -0.002473065, 0.045955304, -0.0012928961, -0.0005976032, 0.01205334, 0.01112658, -0.041117128, 0.06182676, -0.028768085, -0.0027256259, -0.005662229, 0.028678222, 0.019729316, 0.05271232, 0.0026511217, -0.09178429, -0.0048928573, -0.02556852, 0.013643662, 0.039891172, 0.028618561, -0.030288039, 0.022171749, -0.05601787, 0.024968779, -0.021854376, -0.011415058, -0.05126092, -0.0058390973, 0.022483768, -0.03293246, -0.04063902, -0.043348372, 0.013400485, 0.017011339, -0.009977437, -0.014265087, -0.030851519, -0.024168978, -0.06676749, 0.02109681, 0.027279958, 0.023351047, 0.05750092, -0.014586107, -0.026360735, -0.022576148, 0.077508815, 0.055785, -0.01818279, 0.047200993, -0.055349037, 0.038058124, -0.051208835, -0.0078083803, 0.020234866, -0.032040887]}...]
========== MIDDLE OF OUTPUT TRUNCATED ==========

AI Prompt

Okay so now I have all of the embeddings for the reviews in our little training dataset. Now I want to fit a fitting (no pun inteded) ML algorithm to them to classify them correctly.

AI Response

Let’s use scikit-learn’s SVM classifier, which works well with high-dimensional data like embeddings. We’ll need to: 1. Separate our features (embeddings) and labels 2. Split into training and test sets 3. Train an SVM classifier 4. Evaluate the results

Here’s the code:

from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.metrics import classification_report

# Prepare X (features) and y (labels)
X = [r['embedding'] for r in reviews_embds]
y = [r['label'] for r in reviews_embds]

# Split the data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Train the classifier
clf = SVC(kernel='linear')
clf.fit(X_train, y_train)

# Make predictions and evaluate
y_pred = clf.predict(X_test)
print(classification_report(y_test, y_pred))

This will give us a clear report of how well our classifier performs, including precision, recall, and F1-score for both positive and negative reviews. Would you like to try this and see the results?

from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.metrics import classification_report
X = [r['embedding'] for r in reviews_embds]
y = [r['label'] for r in reviews_embds]
y[:5]
['pos', 'pos', 'pos', 'pos', 'pos']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
y_train[:10]
['pos', 'neg', 'pos', 'pos', 'neg', 'neg', 'pos', 'pos', 'neg', 'pos']
clf = SVC(kernel='linear')
clf.fit(X_train, y_train)
SVC(kernel='linear')
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
y_pred = clf.predict(X_test)
print(classification_report(y_test, y_pred))
              precision    recall  f1-score   support

         neg       0.94      0.89      0.92        19
         pos       0.91      0.95      0.93        21

    accuracy                           0.93        40
   macro avg       0.93      0.92      0.92        40
weighted avg       0.93      0.93      0.92        40

AI Prompt

Okay, this isn’t half bad considering how small the dataset is. what do you think?

AI Response

Yes, these results are actually quite impressive! Let me break down why:

  1. We’re getting 93% accuracy overall, which is excellent for a binary classification task
  2. The model is well-balanced:
    • 94% precision for negative reviews
    • 91% precision for positive reviews
    • Both classes have high recall (89% and 95%)
  3. The F1-scores (92-93%) show that we’re maintaining a good balance between precision and recall

This is particularly impressive considering: - We only used 200 reviews total (160 for training, 40 for testing) - We didn’t do any hyperparameter tuning - We used a simple linear SVM

This suggests that: 1. The Gemini embeddings are doing a great job capturing the semantic meaning of the reviews 2. The sentiment information is being preserved in a linearly separable way in the embedding space

Would you like to try: - Using a larger sample of reviews? - Testing different classifiers (like Random Forest or Logistic Regression)? - Or maybe visualizing the embeddings to see how they cluster?

from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier()
rf.fit(X_train, y_train)
RandomForestClassifier()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
y_pred = rf.predict(X_test)
print(classification_report(y_test, y_pred))
              precision    recall  f1-score   support

         neg       1.00      0.89      0.94        19
         pos       0.91      1.00      0.95        21

    accuracy                           0.95        40
   macro avg       0.96      0.95      0.95        40
weighted avg       0.95      0.95      0.95        40

AI Prompt

That’s even better

AI Response

Yes, the Random Forest classifier performed even better than the SVM! Let’s look at what improved:

  1. Overall accuracy went up from 93% to 95%
  2. Perfect precision (1.00) for negative reviews
  3. Perfect recall (1.00) for positive reviews
  4. The F1-scores improved to 94-95%

This suggests that there might be some non-linear relationships in the embedding space that the Random Forest is better at capturing compared to the linear SVM. This is a common occurrence - while embeddings often make the classification task “more linear”, there can still be complex decision boundaries that tree-based methods handle well.

Would you like to: 1. Try visualizing the embeddings using dimensionality reduction (like t-SNE or UMAP) to see how the reviews are clustered? 2. Scale up to the full dataset to see if we maintain this performance? 3. Try some other classifiers like XGBoost or a neural network?

What interests you most?

Let’s try it with an even smaller dataset but evaluate it using cross-validation to get a better overview of the model’s performance.

# Sample 100 from each list
sampled_pos = random.sample(pos_rs, 50)
sampled_neg = random.sample(neg_rs, 50)

# Combine into one list
all_reviews = sampled_pos + sampled_neg
reviews_embds = parallel(add_embd, all_reviews, n_workers=50, threadpool=True)

X = [r['embedding'] for r in reviews_embds]
y = [r['label'] for r in reviews_embds]
from sklearn.model_selection import cross_val_score

rf = RandomForestClassifier()

scores = cross_val_score(rf, X, y, cv=10)
print("Accuracy: %0.2f (+/- %0.2f)" % (scores.mean(), scores.std() * 2))
Accuracy: 0.91 (+/- 0.14)

Let’s see how good an LLM directly is.

reviews_embds[0]["text"][:2000]
'plot : derek zoolander is a male model . \nhe is also very dumb and impressionable . \nfor that reason , he is secretly hired and trained ( so secret , that even he doesn\'t know about it ) by an underground fashion syndicate to assassinate the prime minister of malaysia , who wants to abolish child labor in his country . \nwill zoolander fulfill the dirty deed ? \nwill zoolander ever grace the world his new " look " ? \nis this a funny movie ? \nfind out below . . . \ncritique : there\'s a place in our world for " stupid comedies " . \nfilms which don\'t pretend to take themselves seriously , are based on idiotic premises and filled with dumb jokes . \nthere\'s also a time for them and many would argue that this difficult period of our history , might be an ideal circumstance during which to " relax " by watching something so completely frivolous . \nwell , if you\'re in the mood for some seriously mindless entertainment , ben stiller and his cast of many have assembled one of the more original dumb comedies in some time . \nof course , comedies as such are usually very subjective and i could see how some will see this film simply as stupid and unfunny , while others will grant them the " stupid " , but consider it funny instead . \ni personally enjoyed it for the most part , cracked up loudly during a couple of specific sequences and loved the derek zoolander character and the unrestrained whipping they released upon the fashion industry . \nsnap ! \nit was also nice to see several real-life models with small roles in the movie , not taking themselves too seriously . \nand if you\'re the type of person who likes celebrity cameos in films , well , don\'t look any further because dozens of famous faces show up here including vince vaughn , billy zane , winona ryder , christian slater , david duchovny , natalie portman and many , many others . \ni especially liked andy dick\'s complete make-over as the masseuse . \nhi-larious ! \nbut with all films of this type , there is bound to be some stu'
from claudette import *

Claudette is Claude’s friend

NB : If you are reading this in GitHub’s readme, we recommend you instead read the much more nicely formatted documentation format of this tutorial.

Claudette is a wrapper for Anthropic’s Python SDK.

The SDK works well, but it is quite low level – it leaves the developer to do a lot of stuff manually. That’s a lot of extra work and boilerplate! Claudette automates pretty much everything that can be automated, whilst providing full control. Amongst the features provided:

You’ll need to set the ANTHROPIC_API_KEY environment variable to the key provided to you by Anthropic in order to use this library.

Note that this library is the first ever “literate nbdev” project. That means that the actual source code for the library is a rendered Jupyter Notebook which includes callout notes and tips, HTML tables and images, detailed explanations, and teaches how and why the code is written the way it is. Even if you’ve never used the Anthropic Python SDK or Claude API before, you should be able to read the source code. Click Claudette’s Source to read it, or clone the git repo and execute the notebook yourself to see every step of the creation process in action. The tutorial below includes links to API details which will take you to relevant parts of the source. The reason this project is a new kind of literal program is because we take seriously Knuth’s call to action, that we have a “ moral commitment ” to never write an “ illiterate program ” – and so we have a commitment to making literate programming and easy and pleasant experience. (For more on this, see this talk from Hamel Husain.)

“ Let us change our traditional attitude to the construction of programs: Instead of imagining that our main task is to instruct acomputer what to do, let us concentrate rather on explaining to human beings what we want a computer to do.” Donald E. Knuth, Literate Programming (1984)

Install

pip install claudette

Getting started

Anthropic’s Python SDK will automatically be installed with Claudette, if you don’t already have it.

import os
# os.environ['ANTHROPIC_LOG'] = 'debug'

To print every HTTP request and response in full, uncomment the above line.

from claudette import *

Claudette only exports the symbols that are needed to use the library, so you can use import * to import them. Alternatively, just use:

import claudette

…and then add the prefix claudette. to any usages of the module.

Claudette provides models, which is a list of models currently available from the SDK.

models
['claude-3-opus-20240229',
 'claude-3-5-sonnet-20241022',
 'claude-3-haiku-20240307']

For these examples, we’ll use Sonnet 3.5, since it’s awesome!

model = models[1]

Chat

The main interface to Claudette is the Chat class, which provides a stateful interface to Claude:

chat = Chat(model, sp="""You are a helpful and concise assistant.""")
chat("I'm Jeremy")

Hello Jeremy, nice to meet you.

  • id: msg_015oK9jEcra3TEKHUGYULjWB
  • content: [{'text': 'Hello Jeremy, nice to meet you.', 'type': 'text'}]
  • model: claude-3-5-sonnet-20241022
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {'input_tokens': 19, 'output_tokens': 11, 'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0}
r = chat("What's my name?")
r

Your name is Jeremy.

  • id: msg_01Si8sTFJe8d8vq7enanbAwj
  • content: [{'text': 'Your name is Jeremy.', 'type': 'text'}]
  • model: claude-3-5-sonnet-20241022
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {'input_tokens': 38, 'output_tokens': 8, 'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0}
r = chat("What's my name?")
r

Your name is Jeremy.

  • id: msg_01BHWRoAX8eBsoLn2bzpBkvx
  • content: [{'text': 'Your name is Jeremy.', 'type': 'text'}]
  • model: claude-3-5-sonnet-20241022
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {'input_tokens': 54, 'output_tokens': 8, 'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0}

As you see above, displaying the results of a call in a notebook shows just the message contents, with the other details hidden behind a collapsible section. Alternatively you can print the details:

print(r)
Message(id='msg_01BHWRoAX8eBsoLn2bzpBkvx', content=[TextBlock(text='Your name is Jeremy.', type='text')], model='claude-3-5-sonnet-20241022', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=In: 54; Out: 8; Cache create: 0; Cache read: 0; Total: 62)

Claude supports adding an extra assistant message at the end, which contains the prefill – i.e. the text we want Claude to assume the response starts with. Let’s try it out:

chat("Concisely, what is the meaning of life?",
     prefill='According to Douglas Adams,')

According to Douglas Adams,42. Philosophically, it’s to find personal meaning through relationships, purpose, and experiences.

  • id: msg_01R9RvMdFwea9iRX5uYSSHG7
  • content: [{'text': "According to Douglas Adams,42. Philosophically, it's to find personal meaning through relationships, purpose, and experiences.", 'type': 'text'}]
  • model: claude-3-5-sonnet-20241022
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {'input_tokens': 82, 'output_tokens': 23, 'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0}

You can add stream=True to stream the results as soon as they arrive (although you will only see the gradual generation if you execute the notebook yourself, of course!)

for o in chat("Concisely, what book was that in?", prefill='It was in', stream=True):
    print(o, end='')
It was in "The Hitchhiker's Guide to the Galaxy" by Douglas Adams.

Async

Alternatively, you can use AsyncChat (or AsyncClient) for the async versions, e.g:

chat = AsyncChat(model)
await chat("I'm Jeremy")

Hi Jeremy! Nice to meet you. I’m Claude, an AI assistant created by Anthropic. How can I help you today?

  • id: msg_016Q8cdc3sPWBS8eXcNj841L
  • content: [{'text': "Hi Jeremy! Nice to meet you. I'm Claude, an AI assistant created by Anthropic. How can I help you today?", 'type': 'text'}]
  • model: claude-3-5-sonnet-20241022
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {'input_tokens': 10, 'output_tokens': 31, 'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0}

Remember to use async for when streaming in this case:

async for o in await chat("Concisely, what is the meaning of life?",
                          prefill='According to Douglas Adams,', stream=True):
    print(o, end='')
According to Douglas Adams,  it's 42. But in my view, there's no single universal meaning - each person must find their own purpose through relationships, personal growth, contribution to others, and pursuit of what they find meaningful.

Prompt caching

If you use mk_msg(msg, cache=True), then the message is cached using Claude’s prompt caching feature. For instance, here we use caching when asking about Claudette’s readme file:

chat = Chat(model, sp="""You are a helpful and concise assistant.""")
nbtxt = Path('README.txt').read_text()
msg = f'''<README>
{nbtxt}
</README>
In brief, what is the purpose of this project based on the readme?'''
r = chat(mk_msg(msg, cache=True))
r

Claudette is a high-level wrapper for Anthropic’s Python SDK that automates common tasks and provides additional functionality. Its main features include:

  1. A Chat class for stateful dialogs
  2. Support for prefill (controlling Claude’s initial response words)
  3. Convenient image handling
  4. Simple tool use API integration
  5. Support for multiple model providers (Anthropic, AWS Bedrock, Google Vertex)

The project is notable for being the first “literate nbdev” project, meaning its source code is written as a detailed, readable Jupyter Notebook that includes explanations, examples, and teaching material alongside the functional code.

The goal is to simplify working with Claude’s API while maintaining full control, reducing boilerplate code and manual work that would otherwise be needed with the base SDK.

  • id: msg_014rVQnYoZXZuyWUCMELG1QW
  • content: [{'text': 'Claudette is a high-level wrapper for Anthropic\'s Python SDK that automates common tasks and provides additional functionality. Its main features include:\n\n1. A Chat class for stateful dialogs\n2. Support for prefill (controlling Claude\'s initial response words)\n3. Convenient image handling\n4. Simple tool use API integration\n5. Support for multiple model providers (Anthropic, AWS Bedrock, Google Vertex)\n\nThe project is notable for being the first "literate nbdev" project, meaning its source code is written as a detailed, readable Jupyter Notebook that includes explanations, examples, and teaching material alongside the functional code.\n\nThe goal is to simplify working with Claude\'s API while maintaining full control, reducing boilerplate code and manual work that would otherwise be needed with the base SDK.', 'type': 'text'}]
  • model: claude-3-5-sonnet-20241022
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {'input_tokens': 4, 'output_tokens': 179, 'cache_creation_input_tokens': 7205, 'cache_read_input_tokens': 0}

The response records the a cache has been created using these input tokens:

print(r.usage)
Usage(input_tokens=4, output_tokens=179, cache_creation_input_tokens=7205, cache_read_input_tokens=0)

We can now ask a followup question in this chat:

r = chat('How does it make tool use more ergonomic?')
r

According to the README, Claudette makes tool use more ergonomic in several ways:

  1. It uses docments to make Python function definitions more user-friendly - each parameter and return value should have a type and description

  2. It handles the tool calling process automatically - when Claude returns a tool_use message, Claudette manages calling the tool with the provided parameters behind the scenes

  3. It provides a toolloop method that can handle multiple tool calls in a single step to solve more complex problems

  4. It allows you to pass a list of tools to the Chat constructor and optionally force Claude to always use a specific tool via tool_choice

Here’s a simple example from the README:

def sums(
    a:int,  # First thing to sum 
    b:int=1 # Second thing to sum
) -> int: # The sum of the inputs
    "Adds a + b."
    print(f"Finding the sum of {a} and {b}")
    return a + b

chat = Chat(model, sp=sp, tools=[sums], tool_choice='sums')

This makes it much simpler compared to manually handling all the tool use logic that would be required with the base SDK.

  • id: msg_01EdUvvFBnpPxMtdLRCaSZAU
  • content: [{'text': 'According to the README, Claudette makes tool use more ergonomic in several ways:\n\n1. It uses docments to make Python function definitions more user-friendly - each parameter and return value should have a type and description\n\n2. It handles the tool calling process automatically - when Claude returns a tool_use message, Claudette manages calling the tool with the provided parameters behind the scenes\n\n3. It provides atoolloopmethod that can handle multiple tool calls in a single step to solve more complex problems\n\n4. It allows you to pass a list of tools to the Chat constructor and optionally force Claude to always use a specific tool viatool_choice\n\nHere\'s a simple example from the README:\n\n```python\ndef sums(\n a:int, # First thing to sum \n b:int=1 # Second thing to sum\n) -> int: # The sum of the inputs\n "Adds a + b."\n print(f"Finding the sum of {a} and {b}")\n return a + b\n\nchat = Chat(model, sp=sp, tools=[sums], tool_choice=\'sums\')\n```\n\nThis makes it much simpler compared to manually handling all the tool use logic that would be required with the base SDK.', 'type': 'text'}]
  • model: claude-3-5-sonnet-20241022
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {'input_tokens': 197, 'output_tokens': 280, 'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 7205}

We can see that this only used ~200 regular input tokens – the 7000+ context tokens have been read from cache.

print(r.usage)
Usage(input_tokens=197, output_tokens=280, cache_creation_input_tokens=0, cache_read_input_tokens=7205)
chat.use
In: 201; Out: 459; Cache create: 7205; Cache read: 7205; Total: 15070

Tool use

Tool use lets Claude use external tools.

We use docments to make defining Python functions as ergonomic as possible. Each parameter (and the return value) should have a type, and a docments comment with the description of what it is. As an example we’ll write a simple function that adds numbers together, and will tell us when it’s being called:

def sums(
    a:int,  # First thing to sum
    b:int=1 # Second thing to sum
) -> int: # The sum of the inputs
    "Adds a + b."
    print(f"Finding the sum of {a} and {b}")
    return a + b

Sometimes Claude will say something like “according to the sums tool the answer is” – generally we’d rather it just tells the user the answer, so we can use a system prompt to help with this:

sp = "Never mention what tools you use."

We’ll get Claude to add up some long numbers:

a,b = 604542,6458932
pr = f"What is {a}+{b}?"
pr
'What is 604542+6458932?'

To use tools, pass a list of them to Chat:

chat = Chat(model, sp=sp, tools=[sums])

To force Claude to always answer using a tool, set tool_choice to that function name. When Claude needs to use a tool, it doesn’t return the answer, but instead returns a tool_use message, which means we have to call the named tool with the provided parameters.

r = chat(pr, tool_choice='sums')
r
Finding the sum of 604542 and 6458932

ToolUseBlock(id=‘toolu_014ip2xWyEq8RnAccVT4SySt’, input={‘a’: 604542, ‘b’: 6458932}, name=‘sums’, type=‘tool_use’)

  • id: msg_014xrPyotyiBmFSctkp1LZHk
  • content: [{'id': 'toolu_014ip2xWyEq8RnAccVT4SySt', 'input': {'a': 604542, 'b': 6458932}, 'name': 'sums', 'type': 'tool_use'}]
  • model: claude-3-5-sonnet-20241022
  • role: assistant
  • stop_reason: tool_use
  • stop_sequence: None
  • type: message
  • usage: {'input_tokens': 442, 'output_tokens': 53, 'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0}

Claudette handles all that for us – we just call it again, and it all happens automatically:

chat()

The sum of 604542 and 6458932 is 7063474.

  • id: msg_01151puJxG8Fa6k6QSmzwKQA
  • content: [{'text': 'The sum of 604542 and 6458932 is 7063474.', 'type': 'text'}]
  • model: claude-3-5-sonnet-20241022
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {'input_tokens': 524, 'output_tokens': 23, 'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0}

You can see how many tokens have been used at any time by checking the use property. Note that (as of May 2024) tool use in Claude uses a lot of tokens, since it automatically adds a large system prompt.

chat.use
In: 966; Out: 76; Cache create: 0; Cache read: 0; Total: 1042

We can do everything needed to use tools in a single step, by using Chat.toolloop. This can even call multiple tools as needed solve a problem. For example, let’s define a tool to handle multiplication:

def mults(
    a:int,  # First thing to multiply
    b:int=1 # Second thing to multiply
) -> int: # The product of the inputs
    "Multiplies a * b."
    print(f"Finding the product of {a} and {b}")
    return a * b

Now with a single call we can calculate (a+b)*2 – by passing show_trace we can see each response from Claude in the process:

chat = Chat(model, sp=sp, tools=[sums,mults])
pr = f'Calculate ({a}+{b})*2'
pr
'Calculate (604542+6458932)*2'
chat.toolloop(pr, trace_func=print)
Finding the sum of 604542 and 6458932
[{'role': 'user', 'content': [{'type': 'text', 'text': 'Calculate (604542+6458932)*2'}]}, {'role': 'assistant', 'content': [TextBlock(text="I'll help you break this down into steps:\n\nFirst, let's add those numbers:", type='text'), ToolUseBlock(id='toolu_01St5UKxYUU4DKC96p2PjgcD', input={'a': 604542, 'b': 6458932}, name='sums', type='tool_use')]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_01St5UKxYUU4DKC96p2PjgcD', 'content': '7063474'}]}]
Finding the product of 7063474 and 2
[{'role': 'assistant', 'content': [TextBlock(text="Now, let's multiply this result by 2:", type='text'), ToolUseBlock(id='toolu_01FpmRG4ZskKEWN1gFZzx49s', input={'a': 7063474, 'b': 2}, name='mults', type='tool_use')]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_01FpmRG4ZskKEWN1gFZzx49s', 'content': '14126948'}]}]
[{'role': 'assistant', 'content': [TextBlock(text='The final result is 14,126,948.', type='text')]}]

The final result is 14,126,948.

  • id: msg_0162teyBcJHriUzZXMPz4r5d
  • content: [{'text': 'The final result is 14,126,948.', 'type': 'text'}]
  • model: claude-3-5-sonnet-20241022
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {'input_tokens': 741, 'output_tokens': 15, 'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0}

Structured data

If you just want the immediate result from a single tool, use Client.structured.

cli = Client(model)
def sums(
    a:int,  # First thing to sum
    b:int=1 # Second thing to sum
) -> int: # The sum of the inputs
    "Adds a + b."
    print(f"Finding the sum of {a} and {b}")
    return a + b
cli.structured("What is 604542+6458932", sums)
Finding the sum of 604542 and 6458932
[7063474]

This is particularly useful for getting back structured information, e.g:

class President:
    "Information about a president of the United States"
    def __init__(self, 
                first:str, # first name
                last:str, # last name
                spouse:str, # name of spouse
                years_in_office:str, # format: "{start_year}-{end_year}"
                birthplace:str, # name of city
                birth_year:int # year of birth, `0` if unknown
        ):
        assert re.match(r'\d{4}-\d{4}', years_in_office), "Invalid format: `years_in_office`"
        store_attr()

    __repr__ = basic_repr('first, last, spouse, years_in_office, birthplace, birth_year')
cli.structured("Provide key information about the 3rd President of the United States", President)
[President(first='Thomas', last='Jefferson', spouse='Martha Wayles', years_in_office='1801-1809', birthplace='Shadwell', birth_year=1743)]

Images

Claude can handle image data as well. As everyone knows, when testing image APIs you have to use a cute puppy.

fn = Path('samples/puppy.jpg')
display.Image(filename=fn, width=200)

We create a Chat object as before:

chat = Chat(model)

Claudette expects images as a list of bytes, so we read in the file:

img = fn.read_bytes()

Prompts to Claudette can be lists, containing text, images, or both, eg:

chat([img, "In brief, what color flowers are in this image?"])

In this adorable puppy photo, there are purple/lavender colored flowers (appears to be asters or similar daisy-like flowers) in the background.

  • id: msg_01LHjGv1WwFvDsWUbyLmTEKT
  • content: [{'text': 'In this adorable puppy photo, there are purple/lavender colored flowers (appears to be asters or similar daisy-like flowers) in the background.', 'type': 'text'}]
  • model: claude-3-5-sonnet-20241022
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {'input_tokens': 110, 'output_tokens': 37, 'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0}

The image is included as input tokens.

chat.use
In: 110; Out: 37; Cache create: 0; Cache read: 0; Total: 147

Alternatively, Claudette supports creating a multi-stage chat with separate image and text prompts. For instance, you can pass just the image as the initial prompt (in which case Claude will make some general comments about what it sees), and then follow up with questions in additional prompts:

chat = Chat(model)
chat(img)

What an adorable Cavalier King Charles Spaniel puppy! The photo captures the classic brown and white coloring of the breed, with those soulful dark eyes that are so characteristic. The puppy is lying in the grass, and there are lovely purple asters blooming in the background, creating a beautiful natural setting. The combination of the puppy’s sweet expression and the delicate flowers makes for a charming composition. Cavalier King Charles Spaniels are known for their gentle, affectionate nature, and this little one certainly seems to embody those traits with its endearing look.

  • id: msg_01Ciyymq44uwp2iYwRZdKWNN
  • content: [{'text': "What an adorable Cavalier King Charles Spaniel puppy! The photo captures the classic brown and white coloring of the breed, with those soulful dark eyes that are so characteristic. The puppy is lying in the grass, and there are lovely purple asters blooming in the background, creating a beautiful natural setting. The combination of the puppy's sweet expression and the delicate flowers makes for a charming composition. Cavalier King Charles Spaniels are known for their gentle, affectionate nature, and this little one certainly seems to embody those traits with its endearing look.", 'type': 'text'}]
  • model: claude-3-5-sonnet-20241022
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {'input_tokens': 98, 'output_tokens': 130, 'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0}
chat('What direction is the puppy facing?')

The puppy is facing towards the left side of the image. Its head is positioned so we can see its right side profile, though it appears to be looking slightly towards the camera, giving us a good view of its distinctive brown and white facial markings and one of its dark eyes. The puppy is lying down with its white chest/front visible against the green grass.

  • id: msg_01AeR9eWjbxa788YF97iErtN
  • content: [{'text': 'The puppy is facing towards the left side of the image. Its head is positioned so we can see its right side profile, though it appears to be looking slightly towards the camera, giving us a good view of its distinctive brown and white facial markings and one of its dark eyes. The puppy is lying down with its white chest/front visible against the green grass.', 'type': 'text'}]
  • model: claude-3-5-sonnet-20241022
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {'input_tokens': 239, 'output_tokens': 79, 'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0}
chat('What color is it?')

The puppy has a classic Cavalier King Charles Spaniel coat with a rich chestnut brown (sometimes called Blenheim) coloring on its ears and patches on its face, combined with a bright white base color. The white is particularly prominent on its face (creating a distinctive blaze down the center) and chest area. This brown and white combination is one of the most recognizable color patterns for the breed.

  • id: msg_01R91AqXG7pLc8hK24F5mc7x
  • content: [{'text': 'The puppy has a classic Cavalier King Charles Spaniel coat with a rich chestnut brown (sometimes called Blenheim) coloring on its ears and patches on its face, combined with a bright white base color. The white is particularly prominent on its face (creating a distinctive blaze down the center) and chest area. This brown and white combination is one of the most recognizable color patterns for the breed.', 'type': 'text'}]
  • model: claude-3-5-sonnet-20241022
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {'input_tokens': 326, 'output_tokens': 92, 'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0}

Note that the image is passed in again for every input in the dialog, so that number of input tokens increases quickly with this kind of chat. (For large images, using prompt caching might be a good idea.)

chat.use
In: 663; Out: 301; Cache create: 0; Cache read: 0; Total: 964

Other model providers

You can also use 3rd party providers of Anthropic models, as shown here.

Amazon Bedrock

These are the models available through Bedrock:

models_aws
['anthropic.claude-3-opus-20240229-v1:0',
 'anthropic.claude-3-5-sonnet-20241022-v2:0',
 'anthropic.claude-3-sonnet-20240229-v1:0',
 'anthropic.claude-3-haiku-20240307-v1:0']

To use them, call AnthropicBedrock with your access details, and pass that to Client:

from anthropic import AnthropicBedrock
ab = AnthropicBedrock(
    aws_access_key=os.environ['AWS_ACCESS_KEY'],
    aws_secret_key=os.environ['AWS_SECRET_KEY'],
)
client = Client(models_aws[-1], ab)

Now create your Chat object passing this client to the cli parameter – and from then on, everything is identical to the previous examples.

chat = Chat(cli=client)
chat("I'm Jeremy")

It’s nice to meet you, Jeremy! I’m Claude, an AI assistant created by Anthropic. How can I help you today?

  • id: msg_bdrk_01V3B5RF2Pyzmh3NeR8xMMpq
  • content: [{'text': "It's nice to meet you, Jeremy! I'm Claude, an AI assistant created by Anthropic. How can I help you today?", 'type': 'text'}]
  • model: claude-3-haiku-20240307
  • role: assistant
  • stop_reason: end_turn
  • stop_sequence: None
  • type: message
  • usage: {'input_tokens': 10, 'output_tokens': 32}

Google Vertex

These are the models available through Vertex:

models_goog
['claude-3-opus@20240229',
 'claude-3-5-sonnet-v2@20241022',
 'claude-3-sonnet@20240229',
 'claude-3-haiku@20240307']

To use them, call AnthropicVertex with your access details, and pass that to Client:

from anthropic import AnthropicVertex
import google.auth
project_id = google.auth.default()[1]
gv = AnthropicVertex(project_id=project_id, region="us-east5")
client = Client(models_goog[-1], gv)
chat = Chat(cli=client)
chat("I'm Jeremy")

Extensions

  • Pydantic Structured Ouput

  • __Report an issue

models
['claude-3-opus-20240229',
 'claude-3-5-sonnet-20241022',
 'claude-3-haiku-20240307',
 'claude-3-5-haiku-20241022']
cli = Client(models[1])
cli.structured("Classify the sentiment of the following movie review: <Review>The movie is bad </Review>", get_sentiment)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[1], line 1
----> 1 cli.structured("Classify the sentiment of the following movie review: <Review>The movie is bad </Review>", get_sentiment)

File /usr/local/lib/python3.11/site-packages/claudette/core.py:270, in structured(self, msgs, tools, obj, ns, **kwargs)
    268 "Return the value of all tool calls (generally used for structured outputs)"
    269 tools = listify(tools)
--> 270 res = self(msgs, tools=tools, tool_choice=tools, **kwargs)
    271 if ns is None: ns=mk_ns(*tools)
    272 if obj is not None: ns = mk_ns(obj)

File /usr/local/lib/python3.11/site-packages/claudette/core.py:251, in __call__(self, msgs, sp, temp, maxtok, prefill, stream, stop, tools, tool_choice, **kwargs)
    237 @patch
    238 @delegates(messages.Messages.create)
    239 def __call__(self:Client,
   (...)
    248              tool_choice:Optional[dict]=None, # Optionally force use of some tool
    249              **kwargs):
    250     "Make a call to Claude."
--> 251     if tools: kwargs['tools'] = [get_schema(o) for o in listify(tools)]
    252     if tool_choice: kwargs['tool_choice'] = mk_tool_choice(tool_choice)
    253     msgs = self._precall(msgs, prefill, stop, kwargs)

File /usr/local/lib/python3.11/site-packages/claudette/core.py:251, in <listcomp>(.0)
    237 @patch
    238 @delegates(messages.Messages.create)
    239 def __call__(self:Client,
   (...)
    248              tool_choice:Optional[dict]=None, # Optionally force use of some tool
    249              **kwargs):
    250     "Make a call to Claude."
--> 251     if tools: kwargs['tools'] = [get_schema(o) for o in listify(tools)]
    252     if tool_choice: kwargs['tool_choice'] = mk_tool_choice(tool_choice)
    253     msgs = self._precall(msgs, prefill, stop, kwargs)

File /usr/local/lib/python3.11/site-packages/toolslm/funccall.py:91, in get_schema(f, pname)
     89 def get_schema(f:callable, pname='input_schema')->dict:
     90     "Generate JSON schema for a class, function, or method"
---> 91     schema = _get_nested_schema(f)
     92     desc = f.__doc__
     93     assert desc, "Docstring missing!"

File /usr/local/lib/python3.11/site-packages/toolslm/funccall.py:81, in _get_nested_schema(obj)
     79 for n, o in d.items():
     80     if n != 'return' and n != 'self':
---> 81         _process_property(n, o, props, req, defs)
     83 schema = dict(type='object', properties=props, title=obj.__name__ if isinstance(obj, type) else None)
     84 if req: schema['required'] = list(req)

File /usr/local/lib/python3.11/site-packages/toolslm/funccall.py:69, in _process_property(name, obj, props, req, defs)
     66 if obj.default is empty: req[name] = True
     68 if hasattr(obj.anno, '__origin__'):
---> 69     p.update(_handle_container(obj.anno.__origin__, obj.anno.__args__, defs))
     70 else:
     71     p.update(_handle_type(obj.anno, defs))

TypeError: 'NoneType' object is not iterable
TypeError: 'NoneType' object is not iterable
Traceback (most recent call last):

  File "/usr/local/lib/python3.11/site-packages/IPython/core/interactiveshell.py", line 3577, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)

  File "<ipython-input-1-9b203029b6ec>", line 1, in <module>
    cli.structured("Classify the sentiment of the following movie review: <Review>The movie is bad </Review>", get_sentiment)

  File "/usr/local/lib/python3.11/site-packages/claudette/core.py", line 270, in structured
    res = self(msgs, tools=tools, tool_choice=tools, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  File "/usr/local/lib/python3.11/site-packages/claudette/core.py", line 251, in __call__
    if tools: kwargs['tools'] = [get_schema(o) for o in listify(tools)]
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  File "/usr/local/lib/python3.11/site-packages/claudette/core.py", line 251, in <listcomp>
    if tools: kwargs['tools'] = [get_schema(o) for o in listify(tools)]
                                 ^^^^^^^^^^^^^

  File "/usr/local/lib/python3.11/site-packages/toolslm/funccall.py", line 91, in get_schema
    schema = _get_nested_schema(f)
             ^^^^^^^^^^^^^^^^^^^^^

  File "/usr/local/lib/python3.11/site-packages/toolslm/funccall.py", line 81, in _get_nested_schema
    _process_property(n, o, props, req, defs)

  File "/usr/local/lib/python3.11/site-packages/toolslm/funccall.py", line 69, in _process_property
    p.update(_handle_container(obj.anno.__origin__, obj.anno.__args__, defs))

TypeError: 'NoneType' object is not iterable

AI Prompt

what’s the problem here?

AI Response

The error is occurring because we need to modify our get_sentiment function to work properly with Claude’s structured output format. The current implementation using Literal type isn’t being handled correctly. Let’s fix it:

def get_sentiment(
    text: str,  # The text to analyze
) -> dict:
    "Analyzes the sentiment of a movie review"
    return {
        "sentiment": "neg" if "bad" in text.lower() else "pos"
    }

The main changes are: 1. We’re accepting the text as input rather than returning a predefined sentiment 2. We’re returning a dictionary with the sentiment value 3. We’ve removed the Literal type hint which was causing issues with the schema generation

Alternatively, if you want to use the structured output approach with Claude, we could use a class instead:

class SentimentResult:
    "Result of sentiment analysis"
    def __init__(self,
                 sentiment: str,  # The sentiment (pos/neg) of the text
                ):
        self.sentiment = sentiment

Would you like to try either of these approaches?

class SentimentResult:
    "Result of sentiment analysis"
    def __init__(self,
                 sentiment: str,  # The sentiment (pos/neg) of the text
                ):
        assert sentiment in ["pos", "neg"], "Invalid sentiment"
        
        self.sentiment = sentiment
cli.structured("Classify the sentiment of the following movie review: <Review>The movie is bad </Review>", SentimentResult)
[<__main__.SentimentResult at 0x72ea002e1e90>]
r = cli.structured("Classify the sentiment of the following movie review: <Review>The movie is bad </Review>", SentimentResult)
r
[<__main__.SentimentResult at 0x72eab07b53d0>]
r.sentiment
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[1], line 1
----> 1 r.sentiment

AttributeError: 'list' object has no attribute 'sentiment'
AttributeError: 'list' object has no attribute 'sentiment'
Traceback (most recent call last):

  File "/usr/local/lib/python3.11/site-packages/IPython/core/interactiveshell.py", line 3577, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)

  File "<ipython-input-1-9d4c274308b4>", line 1, in <module>
    r.sentiment

AttributeError: 'list' object has no attribute 'sentiment'
r[0].sentiment
'neg'
def classify_review(review):
    r = cli.structured(f"Classify the sentiment of the following movie review: <Review>{review['text']}</Review>", SentimentResult)
    return r[0].sentiment
classify_review(reviews_embds[-1])
'neg'
def add_pred(review):
    return {**review, "pred": classify_review(review)}
llm_results = L(reviews_embds).map(add_pred)
llm_results
(#100) [{'text': 'plot : derek zoolander is a male model . \nhe is also very dumb and impressionable . \nfor that reason , he is secretly hired and trained ( so secret , that even he doesn\'t know about it ) by an underground fashion syndicate to assassinate the prime minister of malaysia , who wants to abolish child labor in his country . \nwill zoolander fulfill the dirty deed ? \nwill zoolander ever grace the world his new " look " ? \nis this a funny movie ? \nfind out below . . . \ncritique : there\'s a place in our world for " stupid comedies " . \nfilms which don\'t pretend to take themselves seriously , are based on idiotic premises and filled with dumb jokes . \nthere\'s also a time for them and many would argue that this difficult period of our history , might be an ideal circumstance during which to " relax " by watching something so completely frivolous . \nwell , if you\'re in the mood for some seriously mindless entertainment , ben stiller and his cast of many have assembled one of the more original dumb comedies in some time . \nof course , comedies as such are usually very subjective and i could see how some will see this film simply as stupid and unfunny , while others will grant them the " stupid " , but consider it funny instead . \ni personally enjoyed it for the most part , cracked up loudly during a couple of specific sequences and loved the derek zoolander character and the unrestrained whipping they released upon the fashion industry . \nsnap ! \nit was also nice to see several real-life models with small roles in the movie , not taking themselves too seriously . \nand if you\'re the type of person who likes celebrity cameos in films , well , don\'t look any further because dozens of famous faces show up here including vince vaughn , billy zane , winona ryder , christian slater , david duchovny , natalie portman and many , many others . \ni especially liked andy dick\'s complete make-over as the masseuse . \nhi-larious ! \nbut with all films of this type , there is bound to be some stuff that simply doesn\'t work . \na few particulars which didn\'t strike my fancy included the bulimia and " orgy " sequence with christine taylor , the break-dance fighting , and i also never get why they use real countries in plots like this ( why not just " make up " a country , instead of zeroing in on a certain people ? ) . \ni also could have done with less of the taylor character in general , since she wasn\'t all that interesting and seemed to slow things down every now and then ( more zoolander , dude ! ) . \nbut those few missteps were nothing compared to some of the more memorable scenes which definitely did work for me ! \ni almost pissed myself during the " gas station " disaster , absolutely adored the " walk-off " contest ( with david bowie as the judge , no less ) and appreciated many of zoolander\'s moronic one-liners ( " i was a merman . . . a \nmerman ! ! " ) . \nand i dare you to get his " monkey " photo shoot out of your head after seeing this movie ( " you\'re a monkey , derek . . . a \nmonkey ! " ) \ni also really liked the soundtrack and the pace of the film , both of which zipped and zagged , and established a nice rhythm to it all . \nagain , it\'s to note that this movie is dumb and not for everyone , but my guess is that if you laughed at the trailer , you will likely enjoy many of the quips in the actual picture as well . \nif you thought the trailer was dumb , skip this dodo bird and go rent austin powers again or something , a film from which there is an obvious influence here . \nblue steel , baby . . . yeah ! ! ! \nwhere\'s joblo coming from ? \naustin powers ( 7/10 ) - austin powers 2 ( 7/10 ) - deuce bigalow ( 7/10 ) - dude , where\'s my car ( 7/10 ) - freddy got fingered ( 5/10 ) - jay & silent bob strike back ( 8/10 ) - joe dirt ( 5/10 ) - meet the parents ( 8/10 ) - say it isn\'t so ( 3/10 ) \n', 'label': 'pos', 'embedding': [-0.028036825, -0.029044345, -0.047237873, 0.03214113, -0.005614088, 0.017529253, 0.007937407, 0.06114289, -0.018761829, 0.01684007, 0.0102760885, 0.022791898
========== TRUNCATED ==========
, -0.018393734, 0.029368952, 0.033914838, 0.0031390204, 0.053070612, -0.008919553, 0.029266886, 0.05452007, -0.0045006457, -0.06055649, -0.007895349, -0.03148518, -0.012017405, -0.035939064, 0.053935137, -0.07043659, 0.0029372668, -0.031107077, -0.021694716, 0.010721458, -0.039267533, -0.009802533, 0.05328437, -0.00858736, 0.00025471667, -0.0056747845, 0.044799235, -0.03857471, 0.0029413486, -0.04211427, 0.0022101186, 0.02241039, 0.018380161, -0.039794218, -0.022303186, 0.0346868, 0.029725887, 0.024404744, -0.04131337, -0.03219357, -0.0073991297, -0.005194563, -0.012993291, 0.018319614, 0.04653389, 0.015097829, -0.042021785, 0.033300966, -0.031226614, 0.027941037, 0.0070020626, -0.04302886, -0.016536027, -0.020188984, 0.04914125, 0.0081786495, -0.04490889, 0.020907514, -0.009434235, -0.0142212575, 0.047198955, 0.014853108, -0.023429299, 0.034682527, -0.084128655, -0.05055864, 0.07898019, -0.03895906, 0.11140129, -0.020291248, 0.05838838, 0.049867656, 0.04075819, 0.030516526, 0.066105805, 0.014409356, 0.011970263, 0.049177345, -0.013555308, 0.081342384, -0.00762423, -0.016328195, 0.014574487, -0.037175406, -0.0069948956, -0.008100563, -0.045961626, -0.009694281, -0.03278084, -0.018160239, -0.05202766, 0.0033189934, -0.0143624, -0.02671746, -0.001415489, -0.0047006086, -0.03224215, 0.014450342, 0.018371584, 0.018401045, 0.0055655516, -0.025224572, 0.015616917, 0.011846861, 0.00087809766, -0.05174142, 0.028903967, 0.0317321, 0.0055511137, -0.020039504, 0.009217652, -0.0075617577, 0.0013215569, 0.011287277, -0.03759058, -0.04326035, 0.0023132262, 0.005914851, 0.008933322, -0.014059064, -0.018902147, -0.028862504, -0.04993121, 0.01685796, -0.03562105, -0.019611944, 0.03822628, -0.021241069, -0.049925905, 0.027365098, 0.0032447774, 0.015983174, 0.044786885, 0.0017810394, -0.010186617, 0.0184125, -0.033125237, 0.010395376, 0.010963465, 0.0019406489, -0.016690668, -0.027291028, -0.040989026, 0.035864193, -0.04056406, -0.025016997, 0.01782647, -0.045990877, -0.02014531, -0.042198494, 0.0016370984, -0.0043466715, -0.019281795, 0.040232096, -0.011553848, -0.0522375, 0.014337117, -0.047861494, -0.05443393, -0.043306522, -0.0011884038, 0.020994298, 0.0205555, 0.015920509, -0.0020248282, -0.015946405, 0.007036882, 0.037424076, -0.042692408, -0.028045679, -0.022651706, -0.018605804, 0.061427973, -0.017790169, -0.0016451592, 0.015617046, -0.011951815, 0.057211805, 0.007342377, -0.0851792, 0.013083405, -0.043980345, 0.041450884, 0.029633973, 0.05452041, -0.05286796, -0.027135216, -0.0010516717, 0.024978315, 0.009902785, -0.027558176, -0.03661595, -0.0016361757, -0.009407849, -0.010746422, -0.00022211972, -0.014222683, -0.0051546367, -0.00023844474, 0.0035851959, 0.030983545, 0.018156303, -0.023779836, -0.018027946, -0.01814223, -0.014069596, -0.0033181321, 0.030819938, 0.003032134, -0.0062368484, 0.007540357, -0.020149812, 0.007868793, -0.02044547, 0.055769444, 0.016953874, -0.0317939, 0.007494329, -0.0050064838, 0.0025758962, -0.006226743, 0.03198515, -0.017085038, -0.028089255, -0.0021957676, 0.01799674, -0.025389675, -0.055563636, 0.018478252, -0.037858266, 0.087167695, -0.015485936, 0.06074542, 0.061953817, -0.028854333, -0.010893407, -0.037852608, -0.018422268, -0.033275485, 0.04706845, -0.0057725003, 0.03397722, -0.025772488, -0.0022985341, 0.022223232, -0.060510606, -0.011889764, 0.04926235, -0.0027368963, -0.004637742, 0.017194577, 0.044450004, 0.026138898, 0.04933249, -0.03422178, -0.0246854, -0.036936354, 0.037677344, 0.0062181093, 0.01729093, 0.019595413, -0.03493916, 0.011960312, -0.03956942, -0.024383888, 0.06777718, -0.010914413, -0.04620698, 0.0034036774, -0.045193076, 0.010701642, -0.008690606, -0.02563803, -0.04715476, 0.009559938, 0.018104384, -0.01613881, 0.037632063, -0.02254838, 0.03373061, 0.045927748, -0.0415655, -0.024590513, -0.038102556, -0.008331217, -0.04021093, 0.04263146, -0.030696195, -0.0032756466, 0.1096495, -0.009997374, -0.010033237, -0.00048540175, 0.052174386, 0.029515378, -0.055459205, -0.0008010739, -0.044277433, 0.011924893, -0.045417912, 0.011100777, 0.0072399713, -0.044269584], 'pred': 'pos'}...]
========== MIDDLE OF OUTPUT TRUNCATED ==========

AI Prompt

now let’s calculate the accuracy by comparing pred with label

AI Response

Here’s how we can calculate the accuracy by comparing the predictions with the actual labels:

correct = sum(1 for r in llm_results if r['pred'] == r['label'])
total = len(llm_results)
accuracy = correct / total

print(f"Accuracy: {accuracy:.2%} ({correct}/{total} correct)")

This will show us how well the LLM performed compared to our previous ML approaches. Let’s run this and see how it compares to the SVM (93%) and Random Forest (95%) results we got earlier!

correct = sum(1 for r in llm_results if r['pred'] == r['label'])
total = len(llm_results)
accuracy = correct / total

print(f"Accuracy: {accuracy:.2%} ({correct}/{total} correct)")
Accuracy: 97.00% (97/100 correct)

AI Prompt

Okay, an LLM directly is slightly better on sentiment classification. But this is an easy task for an LLM right?

AI Response

Yes, exactly! Sentiment analysis is a relatively straightforward task for modern LLMs because:

  1. It’s one of the most basic NLP tasks - distinguishing positive from negative sentiment is something LLMs are inherently good at since they’re trained on vast amounts of text with emotional content
  2. Movie reviews are particularly clear-cut examples - they usually explicitly state whether something is good or bad
  3. The task doesn’t require much domain knowledge or complex reasoning

However, there are some key trade-offs to consider: 1. Speed: The ML approach with embeddings is much faster at inference time 2. Cost: Each LLM API call costs money, while running a trained ML model is essentially free 3. Scalability: You could process millions of reviews with the ML approach very efficiently

This is why the embedding + ML classifier approach is still valuable, especially when: - You need to process large volumes of text - You need real-time classification - You want to minimize costs - The classification task is more specialized or domain-specific where you can train on specific data

Would you like to try a more complex classification task where the ML approach might be more competitive with the LLM?

AI Prompt

Do you see tasks where embeddings + ML might be more competitive with LLMs?

AI Response

Yes, there are several scenarios where embeddings + ML could be more competitive or even outperform LLMs:

  1. Domain-Specific Classification:
    • Medical document classification (diagnosis codes, procedure types)
    • Legal document categorization (contract types, case categories)
    • Technical support ticket routing Where the categories are very specific and the LLM might not have deep domain expertise
  2. Multi-label Classification:
    • Product categorization (items belonging to multiple departments)
    • Content tagging (articles with multiple topics) Where items can belong to multiple categories simultaneously and the relationships between categories matter
  3. Fine-grained Classification:
    • Emotion detection (beyond just positive/negative)
    • Customer feedback categorization (specific issue types)
    • Academic paper classification by subfield Where the distinctions between categories are subtle and require specific training
  4. High-Stakes Classification:
    • Fraud detection
    • Security threat classification
    • Safety-critical systems Where you need consistent, deterministic results and can’t risk the occasional “creative” responses that LLMs might generate

Would you like to try implementing one of these more challenging scenarios to compare the approaches?