Skip to content

Commit 86e66b2

Browse files
Merge pull request #9 from AIObjectives/f/claude
support Claude
2 parents a3e737a + 30a4d24 commit 86e66b2

File tree

9 files changed

+125
-38
lines changed

9 files changed

+125
-38
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ node_modules/
22
**/.cache/*
33
.DS_Store
44
google-credentials.json
5-
.env*
5+
.env*
6+
.vscode/

README.md

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ export OPENAI_API_KEY=sk-something-something
1515
export OPENAI_API_KEY_PASSWORD=some-password
1616
```
1717

18+
Likewise for Claude:
19+
20+
```
21+
export ANTHROPIC_API_KEY=sk-something-something
22+
export ANTHROPIC_API_KEY_PASSWORD=some-password
23+
```
24+
1825
If a team member has already set up a Google Cloud Storage project, add the keys for that project to the same `.env` file. Otheriwse,
1926
see the "Setting up a Google Cloud instance" section for how to set up a project.
2027

@@ -71,33 +78,33 @@ export type SourceRow = {
7178
timestamp?: string; // timestamp in the video
7279
};
7380
```
81+
7482
## Provided client
7583

7684
Open `localhost:8080/` to see the example of client provided.
7785
The client is written in plain html/css/js in the `public` folder.
7886

79-
8087
## Setting up a Google Cloud instance
8188

8289
### Set up Google Cloud storage & services
8390

8491
First create a new storage bucket:
92+
8593
- Create a Google Cloud project and a Google Cloud Storage bucket
8694
- Add `GCLOUD_STORAGE_BUCKET=name-of-your-bucket` to your `.env`
8795
- Make sure this bucket has public access so that anyone can read from it (to
88-
make your reports accessible from your browser):
89-
- Turn off the "Prevent public access" protect
90-
- In the "Permissions" tab, click "Grant access." Add the principal `allUsers`
91-
and assign the role `Storage Object User`.
96+
make your reports accessible from your browser): - Turn off the "Prevent public access" protect - In the "Permissions" tab, click "Grant access." Add the principal `allUsers`
97+
and assign the role `Storage Object User`.
9298

9399
Then create a service account for this bucket:
100+
94101
- In the "IAM & Admin" view, select "Service Accounts" from the left menu, and
95102
then click "Create service account"
96103
- Give this account the "Editor" role
97104
- Create keys for this account and download them as a json file:
98-
- Save this file as `./google-credentials.json`
99-
- Encode this using by running the command `base64 -i ./google-credentials.json`
100-
- Put this in a variable `GOOGLE_CREDENTIALS_ENCODED` in your `.env`
105+
- Save this file as `./google-credentials.json`
106+
- Encode this using by running the command `base64 -i ./google-credentials.json`
107+
- Put this in a variable `GOOGLE_CREDENTIALS_ENCODED` in your `.env`
101108

102109
Your .env file should now look like this:
103110

@@ -134,13 +141,13 @@ Note: the first deploy with fail if you haven't set the .env variables as descri
134141
Your cloud instance is now ready to use!
135142

136143
To upload a new image (e.g. after a fresh git pull), deploy a new docker image to the same instance:
144+
137145
- Run `./bin/docker-build-gcloud.sh`
138146
- Open the google console and search for gloud cloud run
139147
- Find your project and the `tttc-light-js` app
140148
- Click "EDIT AND DEPLOY NEW VERSION"
141149
- Find the new docker image that you just pushed from the list of available images
142150

143-
144151
## Using docker locally (not recommended)
145152

146153
There should be no need to use docker for local development, except when working on the Dockerfile or testing something docker-related, in which case you might want to use these scripts:

package-lock.json

Lines changed: 43 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"typescript": "^5.3.3"
2727
},
2828
"dependencies": {
29+
"@anthropic-ai/sdk": "^0.20.2",
2930
"@google-cloud/storage": "^7.7.0",
3031
"axios": "^1.6.7",
3132
"cors": "^2.8.5",

src/gpt.ts

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,74 @@
11
import OpenAI from "openai";
2+
import Anthropic from "@anthropic-ai/sdk";
23

34
import { Tracker, Cache } from "./types";
45

5-
export const testGPT = async (apiKey: string) => {
6-
const openai = new OpenAI({ apiKey });
7-
await openai.chat.completions.create({
8-
messages: [{ role: "user", content: "hi" }],
9-
model: "gpt-4-turbo-preview",
10-
});
11-
};
12-
136
export const gpt = async (
7+
model: String,
148
apiKey: string,
159
cacheKey: string,
1610
system: string,
1711
user: string,
1812
tracker: Tracker,
1913
cache?: Cache
2014
) => {
21-
const openai = new OpenAI({ apiKey });
2215
if (cache && cache.get(cacheKey)) return cache.get(cacheKey);
2316
const start = Date.now();
24-
const completion = await openai.chat.completions.create({
25-
messages: [
26-
{ role: "system", content: system },
27-
{ role: "user", content: user },
28-
],
29-
model: "gpt-4-turbo-preview",
30-
response_format: { type: "json_object" },
31-
});
32-
const { prompt_tokens, completion_tokens } = completion.usage!;
17+
18+
let message: string;
19+
let finish_reason: string;
20+
let prompt_tokens: number;
21+
let completion_tokens: number;
22+
23+
// OPENAI GPT
24+
if (model.startsWith("gpt")) {
25+
const openai = new OpenAI({ apiKey });
26+
const completion = await openai.chat.completions.create({
27+
messages: [
28+
{ role: "system", content: system },
29+
{ role: "user", content: user },
30+
],
31+
model: model as any,
32+
...(model.startsWith("gpt-4-turbo")
33+
? { response_format: { type: "json_object" } }
34+
: {}),
35+
});
36+
prompt_tokens = completion.usage!.prompt_tokens;
37+
completion_tokens = completion.usage!.completion_tokens;
38+
finish_reason = completion.choices[0].finish_reason;
39+
message = completion.choices[0].message.content!;
40+
}
41+
42+
// ANTHROPIC CLAUDE
43+
else if (model.startsWith("claude")) {
44+
const anthropic = new Anthropic({ apiKey });
45+
const completion = await anthropic.messages.create({
46+
model: model as any,
47+
system,
48+
max_tokens: 1024,
49+
messages: [{ role: "user", content: user }],
50+
});
51+
prompt_tokens = completion.usage.input_tokens;
52+
completion_tokens = completion.usage.output_tokens;
53+
finish_reason = completion.stop_reason || "stop";
54+
message = completion.content[0].text;
55+
}
56+
57+
// not supporting other models yet
58+
else {
59+
throw new Error(`Unknown model: ${model}`);
60+
}
61+
3362
const cost =
3463
prompt_tokens * (10 / 1000000) + completion_tokens * (30 / 1000000);
3564
tracker.costs += cost;
3665
tracker.prompt_tokens += prompt_tokens;
3766
tracker.completion_tokens += completion_tokens;
38-
const { finish_reason, message } = completion.choices[0];
3967
if (finish_reason !== "stop") {
40-
console.log(completion);
4168
console.log(message);
42-
throw new Error("gpt 4 turbo stopped early!");
69+
throw new Error("the AI stopped early!");
4370
} else {
44-
const result = JSON.parse(message.content!);
71+
const result = JSON.parse(message);
4572
if (cache) cache.set(cacheKey, result);
4673
const _s = ((Date.now() - start) / 1000).toFixed(1);
4774
const _c = cost.toFixed(2);

src/pipeline.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
} from "./types";
1818

1919
const defaultOptions = {
20+
model: "gpt-4-turbo-preview",
2021
data: [],
2122
title: "",
2223
question: "",
@@ -86,6 +87,7 @@ async function pipeline(
8687
console.log("Step 1: generating taxonomy of topics and subtopics");
8788

8889
const { taxonomy }: { taxonomy: Taxonomy } = await gpt(
90+
options.model,
8991
options.apiKey!,
9092
"taxonomy",
9193
systemMessage(options),
@@ -101,6 +103,7 @@ async function pipeline(
101103
await Promise.all(
102104
batch.map(async ({ id, comment }) => {
103105
const { claims } = await gpt(
106+
options.model,
104107
options.apiKey!,
105108
"claims_from_" + id,
106109
systemMessage(options),
@@ -150,6 +153,7 @@ async function pipeline(
150153
for (const topic of taxonomy) {
151154
for (const subtopic of topic.subtopics) {
152155
const { nesting } = await gpt(
156+
options.model,
153157
options.apiKey!,
154158
"nesting_" +
155159
subtopic.subtopicName

src/server.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import 'dotenv/config'
1+
import "dotenv/config";
22
import express from "express";
33
import cors from "cors";
44
import pipeline from "./pipeline";
55
import html from "./html";
6-
import { testGPT } from "./gpt";
76
import { Options } from "./types";
87
import { getUrl, storeHtml } from "./storage";
98
import { uniqueSlug, formatData, placeholderFile } from "./utils";
@@ -25,7 +24,7 @@ app.post("/generate", async (req, res) => {
2524
config.googleSheet.url,
2625
config.googleSheet.pieChartColumns,
2726
config.googleSheet.filterEmails,
28-
config.googleSheet.oneSubmissionPerEmail,
27+
config.googleSheet.oneSubmissionPerEmail
2928
);
3029
config.data = formatData(data);
3130
config.pieCharts = pieCharts;
@@ -34,14 +33,16 @@ app.post("/generate", async (req, res) => {
3433
throw new Error("Missing data");
3534
}
3635
config.data = formatData(config.data);
36+
// allow users to use our keys if they provided the password
3737
if (config.apiKey === process.env.OPENAI_API_KEY_PASSWORD) {
38-
// allow users to use our keys if they provided the password
3938
config.apiKey = process.env.OPENAI_API_KEY!;
39+
} else if (config.apiKey === process.env.ANTHROPIC_API_KEY_PASSWORD) {
40+
config.apiKey = process.env.ANTHROPIC_API_KEY!;
4041
}
42+
4143
if (!config.apiKey) {
42-
throw new Error("Missing OpenAI API key");
44+
throw new Error("Missing API key");
4345
}
44-
await testGPT(config.apiKey); // will fail is key is invalid
4546
config.filename = config.filename || uniqueSlug(config.title);
4647
const url = getUrl(config.filename);
4748
await storeHtml(config.filename, placeholderFile());

src/test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ async function main() {
3838
const data = await loadSource();
3939
const json = await pipeline(
4040
{
41+
model: "claude-3-opus-20240229",
4142
apiKey: process.env.OPENAI_API_KEY!,
4243
data,
4344
pieCharts: [

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export type PieChart = {
1212
};
1313

1414
export type Options = {
15+
model?: string;
1516
apiKey?: string;
1617
data?: SourceRow[];
1718
title: string;
@@ -28,6 +29,7 @@ export type Options = {
2829
url: string;
2930
pieChartColumns?: string[];
3031
filterEmails?: string[];
32+
oneSubmissionPerEmail?: boolean;
3133
};
3234
};
3335

0 commit comments

Comments
 (0)