memorium/lib/openai.ts

231 lines
6.8 KiB
TypeScript
Raw Permalink Normal View History

2025-01-18 00:46:05 +01:00
import OpenAI from "https://deno.land/x/openai@v4.69.0/mod.ts";
import { zodResponseFormat } from "https://deno.land/x/openai@v4.69.0/helpers/zod.ts";
2023-08-01 21:35:21 +02:00
import { OPENAI_API_KEY } from "@lib/env.ts";
import { hashString } from "@lib/helpers.ts";
2025-01-06 16:14:29 +01:00
import { createCache } from "@lib/cache.ts";
2025-01-25 00:00:04 +01:00
import { recipeResponseSchema } from "@lib/recipeSchema.ts";
2023-08-01 21:35:21 +02:00
2025-01-06 16:14:29 +01:00
const openAI = OPENAI_API_KEY && new OpenAI({ apiKey: OPENAI_API_KEY });
interface MovieRecommendation {
year: number;
title: string;
}
const cache = createCache<MovieRecommendation[]>("movie-recommendations");
2023-08-01 21:35:21 +02:00
function extractListFromResponse(response?: string): string[] {
if (!response) return [];
return response
.split(/[\n,]/)
.map((line) => line.trim())
.filter((line) => !line.endsWith(":"))
.map((line) => line.replace(/^[^\s]*/, "").trim())
.filter((line) => line.length > 2);
}
2023-08-01 21:35:21 +02:00
export async function summarize(content: string) {
if (!openAI) return;
2024-06-21 11:16:01 +02:00
const chatCompletion = await openAI.chat.completions.create({
2023-08-01 21:35:21 +02:00
model: "gpt-3.5-turbo",
messages: [
{
2024-06-21 11:16:01 +02:00
role: "user",
content:
`Please summarize the article in one sentence as short as possible: ${
content.slice(0, 2000)
}`,
2023-08-01 21:35:21 +02:00
},
],
});
return chatCompletion.choices[0].message.content;
}
export async function shortenTitle(content: string) {
if (!openAI) return;
2024-06-21 11:16:01 +02:00
const chatCompletion = await openAI.chat.completions.create({
2023-08-01 21:35:21 +02:00
model: "gpt-3.5-turbo",
messages: [
{
2024-06-21 11:16:01 +02:00
role: "system",
content:
"You shorten the title of the provided website as much as possible, don't rewrite it, just remove unneccesary information. Please remove for example any mention of the name of the website.",
},
{
role: "user",
content: content.slice(0, 2000),
2023-08-01 21:35:21 +02:00
},
],
});
return chatCompletion.choices[0].message.content;
}
export async function extractAuthorName(content: string) {
if (!openAI) return;
2024-06-21 11:16:01 +02:00
const chatCompletion = await openAI.chat.completions.create({
2023-08-01 21:35:21 +02:00
model: "gpt-3.5-turbo",
messages: [
{
2024-06-21 11:16:01 +02:00
role: "system",
content:
"You extract the name of the author from the given text. If you are not able to extract the name, please respond with 'not found' and do not include any other output.",
},
{
role: "user",
content: content.slice(0, 2000),
2023-08-01 21:35:21 +02:00
},
],
});
const author = chatCompletion.choices[0].message.content;
if (
author?.toLowerCase().includes("not") &&
author?.toLowerCase().includes("found")
) return "";
return author;
2023-08-01 21:35:21 +02:00
}
export async function createGenres(
type: string,
description: string,
title = "unknown",
) {
2023-09-08 13:33:29 +02:00
if (!openAI) return;
2024-06-21 11:42:27 +02:00
const chatCompletion = await openAI.chat.completions.create({
2023-09-08 13:33:29 +02:00
model: "gpt-3.5-turbo",
messages: [
{
2024-06-21 11:16:01 +02:00
role: "system",
content:
`you create some keywords that can be used in a recommendation system. The keywords are based on a ${type} description or title. If you do not know the title, take into account the description aswell. Create a range of keywords from very specific ones that describe the general vibe. ${
title ? `The name of the ${type} is ${title}` : ""
2024-06-21 11:16:01 +02:00
}. Return a list of around 20 keywords seperated by commas`,
2023-09-08 13:33:29 +02:00
},
2023-09-08 15:24:07 +02:00
{
2024-06-21 11:16:01 +02:00
role: "user",
content: description.slice(0, 2000),
2023-09-08 13:33:29 +02:00
},
],
});
const res = chatCompletion.choices[0].message.content?.toLowerCase();
return extractListFromResponse(res)
2023-09-08 13:33:29 +02:00
.map((v) => v.replaceAll(" ", "-"));
}
export async function createKeywords(
type: string,
description: string,
title = "unknown",
) {
if (!openAI) return;
2024-06-21 11:42:27 +02:00
const chatCompletion = await openAI.chat.completions.create({
model: "gpt-3.5-turbo",
messages: [
{
"role": "system",
"content":
`you create some keywords that can be used in a recommendation system. The keywords are based on a ${type} description or title. If you do not know the title, take into account the description aswell. Create a range of keywords from very specific ones that describe the general vibe.
title: ${title}
2024-06-21 11:16:01 +02:00
return a list of around 20 keywords seperated by commas
`,
},
{
"role": "user",
2024-06-21 11:16:01 +02:00
"content": description.slice(0, 2000).replaceAll("\n", " "),
},
],
});
const res = chatCompletion.choices[0].message.content?.toLowerCase();
return extractListFromResponse(res)
.map((v) => v.replaceAll(" ", "-"));
}
2025-01-06 16:14:29 +01:00
export const getMovieRecommendations = async (
keywords: string,
exclude: string[],
) => {
if (!openAI) return;
const cacheId = `movierecs:${hashString(`${keywords}:${exclude.join()}`)}`;
if (cache.has(cacheId)) return cache.get(cacheId);
const chatCompletion = await openAI.chat.completions.create({
model: "gpt-3.5-turbo",
messages: [
{
role: "user",
content:
`Could you recommend me 10 movies based on the following attributes:
${keywords}
The movies should be similar to but not include ${
2025-01-06 16:14:29 +01:00
exclude.join(", ")
} or remakes of that.
respond with a plain unordered list each item starting with the year the movie was released and then the title of the movie seperated by a -`,
2025-01-06 16:14:29 +01:00
},
],
});
2025-01-06 16:14:29 +01:00
const res = chatCompletion.choices[0].message.content?.toLowerCase();
2025-01-06 16:14:29 +01:00
if (!res) return;
2025-01-06 16:14:29 +01:00
const recommendations = res.split("\n").map((entry) => {
const [year, ...title] = entry.split("-");
2025-01-06 16:14:29 +01:00
return {
year: parseInt(year.trim()),
title: title.join(" ").replaceAll('"', "").trim(),
};
}).filter((y) => !Number.isNaN(y.year));
2025-01-06 16:14:29 +01:00
cache.set(cacheId, recommendations);
2025-01-06 16:14:29 +01:00
return recommendations;
};
2023-08-01 21:35:21 +02:00
export async function createTags(content: string) {
if (!openAI) return;
2024-06-21 11:42:27 +02:00
const chatCompletion = await openAI.chat.completions.create({
2023-08-01 21:35:21 +02:00
model: "gpt-3.5-turbo",
messages: [
{
2024-06-21 11:16:01 +02:00
role: "system",
content:
2023-08-01 21:35:21 +02:00
"Please respond with a list of genres the corresponding article falls into, dont include any other informations, just a comma seperated list of top 5 categories. Please respond only with tags that make sense even if there are less than five.",
},
2024-06-21 11:16:01 +02:00
{ role: "user", content: content.slice(0, 2000) },
2023-08-01 21:35:21 +02:00
],
});
const res = chatCompletion.choices[0].message.content?.toLowerCase();
return extractListFromResponse(res).map((v) => v.replaceAll(" ", "-"));
2023-08-01 21:35:21 +02:00
}
2025-01-18 00:46:05 +01:00
export async function extractRecipe(content: string) {
if (!openAI) return;
const completion = await openAI.beta.chat.completions.parse({
model: "gpt-4o-2024-08-06",
temperature: 0.1,
messages: [
{
role: "system",
content: "Extract the recipe information from the provided markdown.",
},
{ role: "user", content },
],
response_format: zodResponseFormat(recipeResponseSchema, "recipe-v2"),
2025-01-18 00:46:05 +01:00
});
2025-01-19 19:49:24 +01:00
return recipeResponseSchema.parse(completion.choices[0].message.parsed);
2025-01-18 00:46:05 +01:00
}