Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support latest model gpt-4-turbo + important fixes #114

Merged
merged 1 commit into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/common/RecommendedTasks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Button, VStack, Text } from "@chakra-ui/react";
import { useAppState } from "../state/store";

const tasks = [
'Post on twitter.com with content "an automated post from WebWand by @NormalComputing" If I\'m not logged in, fail the task and wait for me to log in.',
'Post on twitter.com with content "an automated post from WebWand by @NormalComputing! :)" If I\'m not logged in, fail the task and wait for me to log in.',
"Find a book about AI and add a physical copy to cart on Amazon.com. Pick the cheaper one from papaerback and hardcover.",
];

Expand Down Expand Up @@ -39,7 +39,7 @@ const RecommendedTasks = ({
</Text>
<Text fontWeight={400} noOfLines={1} color="gray">
with content &quot;an automated post from WebWand by
@NormalComputing&quot;
@NormalComputing!&quot;
</Text>
</Button>
<Button
Expand Down
48 changes: 40 additions & 8 deletions src/helpers/aiSdkUtils.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
import Anthropic from "@anthropic-ai/sdk";
import OpenAI from "openai";
import { useAppState } from "../state/store";
import { enumValues } from "./utils";

export enum SupportedModels {
Gpt35Turbo16k = "gpt-3.5-turbo-16k",
Gpt4 = "gpt-4",
Gpt4TurboPreview = "gpt-4-turbo-preview",
Gpt4VisionPreview = "gpt-4-vision-preview",
Gpt4Turbo = "gpt-4-turbo",
Claude3Sonnet = "claude-3-sonnet-20240229",
}

function isSupportedModel(value: string): value is SupportedModels {
return enumValues(SupportedModels).includes(value as SupportedModels);
}

export const DEFAULT_MODEL = SupportedModels.Gpt4Turbo;

export const DisplayName = {
[SupportedModels.Gpt35Turbo16k]: "GPT-3.5 Turbo (16k)",
[SupportedModels.Gpt4]: "GPT-4",
[SupportedModels.Gpt4TurboPreview]: "GPT-4 Turbo (Preview)",
[SupportedModels.Gpt4VisionPreview]: "GPT-4 Vision (Preview)",
[SupportedModels.Gpt4Turbo]: "GPT-4 Turbo",
[SupportedModels.Claude3Sonnet]: "Claude 3 Sonnet",
};

export function hasVisionSupport(model: SupportedModels) {
return (
model === SupportedModels.Gpt4VisionPreview ||
model === SupportedModels.Gpt4Turbo ||
model === SupportedModels.Claude3Sonnet
);
}
Expand All @@ -41,6 +51,26 @@ export function isAnthropicModel(model: SupportedModels) {
return chooseSDK(model) === "Anthropic";
}

export function findBestMatchingModel(
selectedModel: string,
openAIKey: string | undefined,
anthropicKey: string | undefined,
): SupportedModels {
let result: SupportedModels = DEFAULT_MODEL;
// verify the string value is a supported model
// this is to handle the case when we drop support for a model
if (isSupportedModel(selectedModel)) {
result = selectedModel;
}
// ensure the provider's API key is available
if (!openAIKey && anthropicKey && !isAnthropicModel(result)) {
result = SupportedModels.Claude3Sonnet;
} else if (openAIKey && !anthropicKey && !isOpenAIModel(result)) {
result = SupportedModels.Gpt4Turbo;
}
return result;
}

export type CommonMessageCreateParams = {
prompt: string;
imageData?: string;
Expand Down Expand Up @@ -92,20 +122,22 @@ export async function fetchResponseFromModelOpenAI(
role: "user",
content,
});
// this trick does not work for GPT-4
// if (params.jsonMode) {
// messages.push({
// role: "assistant",
// content: "{",
// });
// }
if (params.jsonMode) {
messages.push({
role: "assistant",
content: "{",
});
}
const completion = await openai.chat.completions.create({
model: model,
messages,
max_tokens: 1000,
temperature: 0,
});
const rawResponse = completion.choices[0].message?.content?.trim() ?? "";
let rawResponse = completion.choices[0].message?.content?.trim() ?? "";
if (params.jsonMode && !rawResponse.startsWith("{")) {
rawResponse = "{" + rawResponse;
}
return {
usage: completion.usage,
rawResponse,
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/errorChecker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export default function errorChecker(
// other API errors are not recoverable
return false;
}
log("Error:", err);
log("Error: " + err.message, err);
// retry everything else (e.g. network errors, syntax error, timeout)
return true;
}
27 changes: 7 additions & 20 deletions src/state/settings.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { type Data } from "../helpers/knowledge/index";
import { MyStateCreator } from "./store";
import {
SupportedModels,
isAnthropicModel,
isOpenAIModel,
} from "../helpers/aiSdkUtils";
import { SupportedModels, findBestMatchingModel } from "../helpers/aiSdkUtils";

export type SettingsSlice = {
openAIKey: string | undefined;
Expand All @@ -23,27 +19,18 @@ export const createSettingsSlice: MyStateCreator<SettingsSlice> = (set) => ({
anthropicKey: undefined,
openAIBaseUrl: undefined,
anthropicBaseUrl: undefined,
selectedModel: SupportedModels.Gpt4VisionPreview,
selectedModel: SupportedModels.Gpt4Turbo,
voiceMode: false,
customKnowledgeBase: {},
actions: {
update: (values) => {
set((state) => {
const newSettings: SettingsSlice = { ...state.settings, ...values };
// set default model based on the API key
if (
!newSettings.openAIKey &&
newSettings.anthropicKey &&
!isAnthropicModel(newSettings.selectedModel)
) {
newSettings.selectedModel = SupportedModels.Claude3Sonnet;
} else if (
newSettings.openAIKey &&
!newSettings.anthropicKey &&
!isOpenAIModel(newSettings.selectedModel)
) {
newSettings.selectedModel = SupportedModels.Gpt4VisionPreview;
}
newSettings.selectedModel = findBestMatchingModel(
newSettings.selectedModel,
newSettings.openAIKey,
newSettings.anthropicKey,
);
// voice model current relies on OpenAI API key
if (!newSettings.openAIKey) {
newSettings.voiceMode = false;
Expand Down
12 changes: 10 additions & 2 deletions src/state/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { createJSONStorage, devtools, persist } from "zustand/middleware";
import { createCurrentTaskSlice, CurrentTaskSlice } from "./currentTask";
import { createUiSlice, UiSlice } from "./ui";
import { createSettingsSlice, SettingsSlice } from "./settings";
import { findBestMatchingModel } from "../helpers/aiSdkUtils";

export type StoreType = {
currentTask: CurrentTaskSlice;
Expand Down Expand Up @@ -46,8 +47,15 @@ export const useAppState = create<StoreType>()(
customKnowledgeBase: state.settings.customKnowledgeBase,
},
}),
merge: (persistedState, currentState) =>
merge(currentState, persistedState),
merge: (persistedState, currentState) => {
const result = merge(currentState, persistedState);
result.settings.selectedModel = findBestMatchingModel(
result.settings.selectedModel,
result.settings.openAIKey,
result.settings.anthropicKey,
);
return result;
},
},
),
);
Expand Down
Loading