From cf735148c6485d58f60a29fce4ab6f7f09076265 Mon Sep 17 00:00:00 2001 From: Jamesb Date: Wed, 6 Mar 2024 07:13:42 +0000 Subject: [PATCH] fix: misc bug fixes and email format improvements --- .../answersQuestion/answersQuestion.ts | 53 +++++---- .../findStartOfAnswer/findStartOfAnswer.ts | 49 ++++++-- .../findStartOfAnswerYouTube.ts | 13 +- .../prompts/findStartOfAnswerYouTubePrompt.ts | 64 +++++----- .../prompts/titleClip/titleClip.ts | 52 ++++---- packages/cli/src/youtube/transcript.ts | 8 +- packages/client/src/components/AdminPage.tsx | 21 +++- .../src/lib/email/RecommendationsEmail.tsx | 111 ++++++++++-------- .../server/src/tasks/twitterPipeline.saga.ts | 48 ++++---- 9 files changed, 240 insertions(+), 179 deletions(-) diff --git a/packages/cli/src/recommender/prompts/answersQuestion/answersQuestion.ts b/packages/cli/src/recommender/prompts/answersQuestion/answersQuestion.ts index fd6e760..ada2b15 100644 --- a/packages/cli/src/recommender/prompts/answersQuestion/answersQuestion.ts +++ b/packages/cli/src/recommender/prompts/answersQuestion/answersQuestion.ts @@ -1,30 +1,39 @@ import { Prompt } from "prompt-iteration-assistant"; -import { answersQuestionInputSchema } from "./schemas/answersQuestionInputSchema"; +import { + AnswersQuestionInput, + answersQuestionInputSchema, +} from "./schemas/answersQuestionInputSchema"; import { answersQuestionOutputSchema } from "./schemas/answersQuestionOutputSchema"; import { answersQuestionPrompt } from "./prompts/answersQuestionPrompt"; export const ANSWERS_QUESTION = "Answers Question"; -export const answersQuestion = () => - new Prompt({ - name: ANSWERS_QUESTION, - description: - "Check whether the text contains the start of an answer to the question", - prompts: [answersQuestionPrompt], - model: "gpt-4", - input: answersQuestionInputSchema, - output: answersQuestionOutputSchema, - }); - -if (require.main === module) { - (async () => { - const res = await answersQuestion().run({ - stream: false, - promptVariables: { - text: "", - question: "How does chain of thought prompting work", - }, +class AnswersQuestion extends Prompt< + typeof answersQuestionInputSchema, + typeof answersQuestionOutputSchema +> { + constructor() { + super({ + name: ANSWERS_QUESTION, + description: + "Check whether the text contains the start of an answer to the question", + prompts: [answersQuestionPrompt], + model: "gpt-4", + input: answersQuestionInputSchema, + output: answersQuestionOutputSchema, }); - console.log(res); - })(); + } + async execute(args: AnswersQuestionInput) { + try { + return this.run({ + stream: false, + promptVariables: args, + }); + } catch (e) { + console.error(e); + return { answersQuestion: false }; + } + } } + +export const answersQuestion = () => new AnswersQuestion(); diff --git a/packages/cli/src/recommender/prompts/findStartOfAnswer/findStartOfAnswer.ts b/packages/cli/src/recommender/prompts/findStartOfAnswer/findStartOfAnswer.ts index 5d7ed69..061f918 100644 --- a/packages/cli/src/recommender/prompts/findStartOfAnswer/findStartOfAnswer.ts +++ b/packages/cli/src/recommender/prompts/findStartOfAnswer/findStartOfAnswer.ts @@ -13,17 +13,42 @@ import { findStartOfAnswerPrompt } from "./prompts/findStartOfAnswerPrompt"; export const FIND_START_OF_ANSWER = "Find Start Of Answer"; -export const findStartOfAnswer = () => - new Prompt({ - name: FIND_START_OF_ANSWER, - description: "Find the start of an answer to a question in some text", - prompts: [findStartOfAnswerPrompt], - model: "gpt-4", - input: findStartOfAnswerInputSchema, - output: findStartOfAnswerOutputSchema, - exampleData: [], - }); +class FindStartOfAnswer extends Prompt< + typeof findStartOfAnswerInputSchema, + typeof findStartOfAnswerOutputSchema +> { + constructor() { + super({ + name: FIND_START_OF_ANSWER, + description: "Find the start of an answer to a question in some text", + prompts: [findStartOfAnswerPrompt], + model: "gpt-4", + input: findStartOfAnswerInputSchema, + output: findStartOfAnswerOutputSchema, + exampleData: [], + }); + } -if (require.main === module) { - (async () => {})(); + async execute(args: { + question: string; + text: string; + openPipeRequestTags?: RequestTagsWithoutName; + enableOpenPipeLogging?: boolean; + }) { + const promptVariables: FindStartOfAnswerInput = { + text: args.text, + question: args.question, + }; + try { + return this.run({ + stream: false, + promptVariables, + }); + } catch (e) { + console.error(e); + return { quotedAnswer: null }; + } + } } + +export const findStartOfAnswer = () => new FindStartOfAnswer(); diff --git a/packages/cli/src/recommender/prompts/findStartOfAnswerYouTube/findStartOfAnswerYouTube.ts b/packages/cli/src/recommender/prompts/findStartOfAnswerYouTube/findStartOfAnswerYouTube.ts index 3957dac..ec0fff5 100644 --- a/packages/cli/src/recommender/prompts/findStartOfAnswerYouTube/findStartOfAnswerYouTube.ts +++ b/packages/cli/src/recommender/prompts/findStartOfAnswerYouTube/findStartOfAnswerYouTube.ts @@ -43,10 +43,15 @@ ${cue.text} .join(`\n---\n`), question: args.question, }; - return this.run({ - stream: false, - promptVariables, - }); + try { + return this.run({ + stream: false, + promptVariables, + }); + } catch (e) { + console.error(e); + return { cueId: null }; + } } } diff --git a/packages/cli/src/recommender/prompts/findStartOfAnswerYouTube/prompts/findStartOfAnswerYouTubePrompt.ts b/packages/cli/src/recommender/prompts/findStartOfAnswerYouTube/prompts/findStartOfAnswerYouTubePrompt.ts index e278793..152becf 100644 --- a/packages/cli/src/recommender/prompts/findStartOfAnswerYouTube/prompts/findStartOfAnswerYouTubePrompt.ts +++ b/packages/cli/src/recommender/prompts/findStartOfAnswerYouTube/prompts/findStartOfAnswerYouTubePrompt.ts @@ -1,5 +1,11 @@ -import { CandidatePrompt, ChatMessage } from "prompt-iteration-assistant"; +import { + CandidatePrompt, + ChatMessage, + toCamelCase, +} from "prompt-iteration-assistant"; import { FindStartOfAnswerYouTubeInput } from "../schemas/findStartOfAnswerYouTubeInputSchema"; +import { FindStartOfAnswerYouTubeOutput } from "../schemas/findStartOfAnswerYouTubeOutputSchema"; +import { FIND_START_OF_ANSWER_YOUTUBE } from "../findStartOfAnswerYouTube"; export const findStartOfAnswerYouTubePrompt = new CandidatePrompt({ @@ -9,41 +15,35 @@ export const findStartOfAnswerYouTubePrompt = ChatMessage.system( ` # Instructions -- Given a question from the user, evalutate whether the beginning of the answer is in the transcript. -- If the beginning of the answer is in the transcript, return the ID of the transcript cure where the answer starts. -- The answer doesn't need to be complete, just the start of it. +- Given a question from the user, evaluate whether the beginning of the answer is in the transcript. +- If the beginning of the answer is in the transcript, return the ID of the transcript cue where the answer starts. - If the beginning of the answer is not in the transcript, return null. `.trim() ), - // ChatMessage.user( - // ` - // # Transcript - - // # Question - // What is the best way to learn a new language? - // `.trim() - // ), - // ChatMessage.assistant(null, { - // name: toCamelCase(FIND_START_OF_ANSWER_YOUTUBE), - // arguments: { - // answersQuestion: true, - // cueId: 0, - // }, - // }), - // ChatMessage.user( - // ` - // # Transcript + ChatMessage.user( + `# Transcript +ID: 0 +Hello, my name is John. +--- +ID: 1 +I am a software developer interested in learning new languages. +--- +ID: 2 +The best way to learn a new language is through +--- +ID: 3 +immersion. - // # Question - // How does chain of thought prompting work? - // `.trim() - // ), - // ChatMessage.assistant(null, { - // name: toCamelCase(FIND_START_OF_ANSWER_YOUTUBE), - // arguments: { - // answersQuestion: false, - // }, - // }), +# Question +What is the best way to learn a new language? +`.trim() + ), + ChatMessage.assistant(null, { + name: toCamelCase(FIND_START_OF_ANSWER_YOUTUBE), + arguments: { + cueId: 2, + }, + }), ChatMessage.user( ` # Transcript diff --git a/packages/cli/src/recommender/prompts/titleClip/titleClip.ts b/packages/cli/src/recommender/prompts/titleClip/titleClip.ts index cef858e..6a68fb5 100644 --- a/packages/cli/src/recommender/prompts/titleClip/titleClip.ts +++ b/packages/cli/src/recommender/prompts/titleClip/titleClip.ts @@ -1,29 +1,39 @@ import { Prompt } from "prompt-iteration-assistant"; import { titleClipPrompt } from "./prompts/titleClipPrompt"; -import { titleClipInputSchema } from "./schemas/titleClipInputSchema"; +import { + TitleClipInput, + titleClipInputSchema, +} from "./schemas/titleClipInputSchema"; import { titleClipOutputSchema } from "./schemas/titleClipOutputSchema"; export const TITLE_CLIP = "Title Clip"; -export const titleClip = () => - new Prompt({ - name: TITLE_CLIP, - description: "Give the clip a short title", - prompts: [titleClipPrompt], - model: "gpt-3.5-turbo", - input: titleClipInputSchema, - output: titleClipOutputSchema, - }); +class TitleClip extends Prompt< + typeof titleClipInputSchema, + typeof titleClipOutputSchema +> { + constructor() { + super({ + name: TITLE_CLIP, + description: "Give the clip a short title", + prompts: [titleClipPrompt], + model: "gpt-3.5-turbo", + input: titleClipInputSchema, + output: titleClipOutputSchema, + }); + } -if (require.main === module) { - (async () => { - // const res = await answersQuestion().run({ - // stream: false, - // // promptVariables: { - // // text: "", - // // question: "How does chain of thought prompting work", - // // }, - // }); - // console.log(res); - })(); + async execute(args: TitleClipInput) { + try { + return this.run({ + stream: false, + promptVariables: args, + }); + } catch (e) { + console.error(e); + return { title: "" }; + } + } } + +export const titleClip = () => new TitleClip(); diff --git a/packages/cli/src/youtube/transcript.ts b/packages/cli/src/youtube/transcript.ts index be2378f..a53af22 100644 --- a/packages/cli/src/youtube/transcript.ts +++ b/packages/cli/src/youtube/transcript.ts @@ -1,12 +1,6 @@ import chalk from "chalk"; import { exec, execSync } from "child_process"; -import { - existsSync, - readFileSync, - readdirSync, - writeFile, - writeFileSync, -} from "fs"; +import { existsSync, readFileSync, writeFileSync } from "fs"; import path from "path"; import { dataFolder } from "../filesystem"; import { parseSync, stringifySync } from "subtitle"; diff --git a/packages/client/src/components/AdminPage.tsx b/packages/client/src/components/AdminPage.tsx index 16f5c19..ddc4000 100644 --- a/packages/client/src/components/AdminPage.tsx +++ b/packages/client/src/components/AdminPage.tsx @@ -14,7 +14,7 @@ import { TableRow, } from "@mui/material"; import React from "react"; -import { sortBy } from "remeda"; +import { sortBy, last } from "remeda"; dayjs.extend(relativeTime); @@ -22,6 +22,8 @@ export const AdminPage = () => { const [pipelines, setPipelines] = useState(); const [force, setForce] = useState(0); + const [hideSuccessful, setHideSuccessful] = useState(false); + useEffect(() => { // Function to fetch data const fetchData = () => { @@ -46,6 +48,13 @@ export const AdminPage = () => { + @@ -65,15 +74,17 @@ export const AdminPage = () => { - {sortBy(pipelines, (x) => dayjs(x.createdAt).valueOf()).map( - (pipeline) => ( + {sortBy(pipelines, (x) => dayjs(x.createdAt).valueOf()) + .filter((p) => + hideSuccessful ? last(p.tasks)?.name !== "done" : true + ) + .map((pipeline) => ( - ) - )} + ))}
diff --git a/packages/server/src/lib/email/RecommendationsEmail.tsx b/packages/server/src/lib/email/RecommendationsEmail.tsx index f67c7b2..3558251 100644 --- a/packages/server/src/lib/email/RecommendationsEmail.tsx +++ b/packages/server/src/lib/email/RecommendationsEmail.tsx @@ -130,7 +130,7 @@ export default function RecommendationsEmail({ className="mx-auto my-20 text-center" /> - + New Recommendations
@@ -157,61 +157,68 @@ export default function RecommendationsEmail({
{ // Loop through the recommendations and create a section for each query - Object.entries(input.clips).map(([query, clusters], i) => { + Object.entries(input.clips).map(([_, clusters], i) => { return ( -
+
{ // Loop through the clusters and create a section for each question Object.entries(clusters).map(([question, clips], i) => { + if (clips.length === 0) return null; return ( - +
{question} - {clips.map((clip) => { - if (clip.type === "article") { - return ( - - - {clip.articleTitle} - - "{truncate(clip.text, 300)}" - - ); - } else { - return ( - - +
    + {clips.map((clip, i) => { + if (clip.type === "article") { + return ( +
  1. + + {clip.articleTitle} + - {clip.summarizedTitle} ( - {clip.videoTitle}) + "{truncate(clip.text, 450)}" - - - - +
  2. + ); + } else { + return ( +
  3. + + + {clip.summarizedTitle} ( + {clip.videoTitle}) + + + + + + + + + "{truncate(clip.text, 300)}" - - "{truncate(clip.text, 300)}" - - ); - } - })} - +
  4. + ); + } + })} +
+
); }) } @@ -224,10 +231,16 @@ export default function RecommendationsEmail({
- Give Feedback + + Give Feedback + - Manage Preferences + + Manage Preferences +
diff --git a/packages/server/src/tasks/twitterPipeline.saga.ts b/packages/server/src/tasks/twitterPipeline.saga.ts index f4bf7bb..ff4d1e5 100644 --- a/packages/server/src/tasks/twitterPipeline.saga.ts +++ b/packages/server/src/tasks/twitterPipeline.saga.ts @@ -408,7 +408,7 @@ export const twitterPipeline = new Saga( transcriptFile: fetchResult, }; }), - { concurrency: 3 } + { concurrency: 5 } ) ); resultsWithTranscripts.push({ @@ -513,34 +513,25 @@ export const twitterPipeline = new Saga( helpers.logInfo("Cleaning clips..."); const tasks = Object.entries(clips).flatMap(([question, clips]) => { return clips.flatMap((clip) => async () => { - const answersQ = await answersQuestion().run({ - promptVariables: { - question: clip.question, - text: clip.text, - }, - stream: false, + const answersQ = await answersQuestion().execute({ + question: clip.question, + text: clip.text, }); if (!answersQ.answersQuestion) { return null; } if (clip.type === "article") { - const result = await findStartOfAnswer().run({ - promptVariables: { - question, - text: clip.text, - }, - stream: false, + const result = await findStartOfAnswer().execute({ + question, + text: clip.text, }); if (result?.quotedAnswer) { const match = nearestSubstring(result.quotedAnswer, clip.text); if (match.bestMatch && match.bestScore > 0.8) { - const summarizedTitle = await titleClip().run({ - promptVariables: { - clip: clip.text, - videoTitle: clip.articleTitle, - question: clip.question, - }, - stream: false, + const summarizedTitle = await titleClip().execute({ + clip: clip.text, + videoTitle: clip.articleTitle, + question: clip.question, }); return { ...clip, @@ -558,13 +549,10 @@ export const twitterPipeline = new Saga( }); if (result?.cueId != null) { const newCues = clip.cues.slice(result.cueId); - const summarizedTitle = await titleClip().run({ - promptVariables: { - clip: clip.text, - videoTitle: clip.videoTitle, - question: clip.question, - }, - stream: false, + const summarizedTitle = await titleClip().execute({ + clip: clip.text, + videoTitle: clip.videoTitle, + question: clip.question, }); return { ...clip, @@ -656,4 +644,10 @@ export const twitterPipeline = new Saga( helpers.logInfo("Email sent"); } }, + }) + .addStep({ + name: "done", + run: async (_, __, helpers) => { + helpers.logInfo("Pipeline complete"); + }, });