mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-12-27 02:52:22 +03:00
Now we use GPT4, if available, for plan.
This commit is contained in:
parent
83099c3d99
commit
958807f25b
@ -11,6 +11,8 @@ module Wasp.AI.CodeAgent
|
||||
getAllFiles,
|
||||
queryChatGPT,
|
||||
getTotalTokensUsage,
|
||||
getOpenAIApiKey,
|
||||
checkIfGpt4IsAvailable,
|
||||
)
|
||||
where
|
||||
|
||||
@ -36,7 +38,12 @@ runCodeAgent :: CodeAgentConfig -> CodeAgent a -> IO a
|
||||
runCodeAgent config codeAgent =
|
||||
fst <$> (_unCodeAgent codeAgent `runReaderT` config) `runStateT` initialState
|
||||
where
|
||||
initialState = CodeAgentState {_files = H.empty, _usage = []}
|
||||
initialState =
|
||||
CodeAgentState
|
||||
{ _files = H.empty,
|
||||
_usage = [],
|
||||
_isGpt4Available = Nothing
|
||||
}
|
||||
|
||||
writeToLog :: Text -> CodeAgent ()
|
||||
writeToLog msg = asks _writeLog >>= \f -> liftIO $ f msg
|
||||
@ -64,6 +71,19 @@ queryChatGPT params messages = do
|
||||
modify $ \s -> s {_usage = _usage s <> [ChatGPT.usage chatResponse]}
|
||||
return $ ChatGPT.getChatResponseContent chatResponse
|
||||
|
||||
getOpenAIApiKey :: CodeAgent OpenAIApiKey
|
||||
getOpenAIApiKey = asks _openAIApiKey
|
||||
|
||||
checkIfGpt4IsAvailable :: CodeAgent Bool
|
||||
checkIfGpt4IsAvailable = do
|
||||
gets _isGpt4Available >>= \case
|
||||
Just isAvailable -> pure isAvailable
|
||||
Nothing -> do
|
||||
key <- asks _openAIApiKey
|
||||
isAvailable <- liftIO $ ChatGPT.checkIfGpt4IsAvailable key
|
||||
modify $ \s -> s {_isGpt4Available = Just isAvailable}
|
||||
return isAvailable
|
||||
|
||||
type NumTokens = Int
|
||||
|
||||
-- | Returns total tokens usage: (<num_prompt_tokens>, <num_completion_tokens>).
|
||||
@ -75,6 +95,7 @@ getTotalTokensUsage = do
|
||||
return (numPromptTokens, numCompletionTokens)
|
||||
|
||||
data CodeAgentState = CodeAgentState
|
||||
{ _files :: H.HashMap FilePath Text, -- TODO: Name this "cacheFiles" maybe?
|
||||
_usage :: [ChatGPT.ChatResponseUsage]
|
||||
{ _files :: !(H.HashMap FilePath Text), -- TODO: Name this "cacheFiles" maybe?
|
||||
_usage :: ![ChatGPT.ChatResponseUsage],
|
||||
_isGpt4Available :: !(Maybe Bool)
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ module Wasp.AI.GenerateNewProject.Common
|
||||
emptyNewProjectConfig,
|
||||
queryChatGPTForJSON,
|
||||
defaultChatGPTParams,
|
||||
defaultChatGPTParamsForFixing,
|
||||
writeToWaspFileEnd,
|
||||
)
|
||||
where
|
||||
@ -99,7 +100,10 @@ queryChatGPTForJSON chatGPTParams = doQueryForJSON 0
|
||||
|
||||
-- TODO: Test more for the optimal temperature (possibly higher).
|
||||
defaultChatGPTParams :: ChatGPTParams
|
||||
defaultChatGPTParams = GPT.ChatGPTParams {_model = GPT.GPT_3_5_turbo_16k, _temperature = Just 1.0}
|
||||
defaultChatGPTParams = GPT.ChatGPTParams {_model = GPT.GPT_3_5_turbo_16k, _temperature = Just 0.7}
|
||||
|
||||
defaultChatGPTParamsForFixing :: ChatGPTParams
|
||||
defaultChatGPTParamsForFixing = GPT.ChatGPTParams {_model = GPT.GPT_3_5_turbo_16k, _temperature = Just 0.5}
|
||||
|
||||
writeToWaspFileEnd :: FilePath -> Text -> CodeAgent ()
|
||||
writeToWaspFileEnd waspFilePath text = do
|
||||
|
@ -22,6 +22,7 @@ import Wasp.AI.CodeAgent (CodeAgent, writeToFile, writeToLog)
|
||||
import Wasp.AI.GenerateNewProject.Common
|
||||
( NewProjectDetails (..),
|
||||
defaultChatGPTParams,
|
||||
defaultChatGPTParamsForFixing,
|
||||
queryChatGPTForJSON,
|
||||
writeToWaspFileEnd,
|
||||
)
|
||||
@ -123,7 +124,7 @@ generateOperation operationType newProjectDetails entityPlans operationPlan = do
|
||||
then return operationImpl
|
||||
else do
|
||||
let issuesText = T.pack $ intercalate "\n" ((" - " <>) <$> issues)
|
||||
queryChatGPTForJSON defaultChatGPTParams $
|
||||
queryChatGPTForJSON defaultChatGPTParamsForFixing $
|
||||
chatMessages
|
||||
<> [ ChatMessage {role = Assistant, content = Util.Aeson.encodeToText operationImpl},
|
||||
ChatMessage
|
||||
@ -155,7 +156,7 @@ actionDocPrompt =
|
||||
```wasp
|
||||
action updateTask {
|
||||
fn: import { updateTask } from "@server/taskActions.js",
|
||||
entities: [Task] // Entities that action uses.
|
||||
entities: [Task] // Entities that action mutates.
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -15,7 +15,7 @@ import NeatInterpolation (trimming)
|
||||
import Wasp.AI.CodeAgent (CodeAgent, getFile, writeToFile)
|
||||
import Wasp.AI.GenerateNewProject.Common
|
||||
( NewProjectDetails,
|
||||
defaultChatGPTParams,
|
||||
defaultChatGPTParamsForFixing,
|
||||
queryChatGPTForJSON,
|
||||
)
|
||||
import Wasp.AI.GenerateNewProject.Common.Prompts (appDescriptionBlock)
|
||||
@ -32,7 +32,7 @@ fixOperationsJsFile newProjectDetails waspFilePath opJsFilePath = do
|
||||
-- with npm dependencies installed, so we skipped it for now.
|
||||
fixedOpJsFile <-
|
||||
queryChatGPTForJSON
|
||||
defaultChatGPTParams
|
||||
defaultChatGPTParamsForFixing
|
||||
[ ChatMessage {role = System, content = Prompts.systemPrompt},
|
||||
ChatMessage {role = User, content = fixOpJsFilePrompt currentWaspFileContent currentOpJsFileContent}
|
||||
]
|
||||
@ -83,6 +83,7 @@ fixOperationsJsFile newProjectDetails waspFilePath opJsFilePath = do
|
||||
|
||||
With this in mind, generate a new, fixed NodeJS file with operations (${opJsFilePathText}).
|
||||
Don't do too big changes, like deleting or adding whole functions, focus on smaller things and those listed above.
|
||||
DO NOT add new queries / actions to the file, or delete existing ones!
|
||||
Do actual fixes, don't leave comments with "TODO"!
|
||||
Please respond ONLY with a valid JSON of the format { opJsFileContent: string }.
|
||||
There should be no other text in your response. Don't wrap content with the "```" code delimiters.
|
||||
|
@ -65,20 +65,24 @@ generatePage newProjectDetails entityPlans actions queries pPlan = do
|
||||
[trimming|
|
||||
${basicWaspLangInfoPrompt}
|
||||
|
||||
===============
|
||||
|
||||
${pageDocPrompt}
|
||||
|
||||
===============
|
||||
|
||||
We are implementing a Wasp app (check bottom for description).
|
||||
|
||||
Entities:
|
||||
Entities in our app:
|
||||
${entityDecls}
|
||||
|
||||
Actions:
|
||||
Actions in our app:
|
||||
${actionsInfo}
|
||||
|
||||
Queries:
|
||||
Queries in our app:
|
||||
${queriesInfo}
|
||||
|
||||
You should use Tailwind to style your pages. Example of Tailwind usage:
|
||||
We use Tailwind to style pages. Example of Tailwind usage:
|
||||
|
||||
```jsx
|
||||
<div className="p-4 bg-slate-50 rounded-lg">
|
||||
@ -88,6 +92,8 @@ generatePage newProjectDetails entityPlans actions queries pPlan = do
|
||||
</div>
|
||||
```
|
||||
|
||||
===============
|
||||
|
||||
Let's now implement the following Wasp page:
|
||||
- name: ${pageName}
|
||||
- component: ${componentPath}
|
||||
@ -150,6 +156,14 @@ pageDocPrompt =
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
A bit about importing actions and queries.
|
||||
If query is named "myQuery", its import should look like `import myQuery from '@wasp/queries/myQuery';`.
|
||||
It has to be default import, and name of the file is the same as name of the query.
|
||||
Same rules apply for actions.
|
||||
One import statement per query/action.
|
||||
|
||||
Note: there is no `useMutation` or `useAction` hook to import in Wasp. Actions are to be called directly.
|
||||
|]
|
||||
|
||||
operationInfo :: Operation -> Text
|
||||
|
@ -14,7 +14,7 @@ import NeatInterpolation (trimming)
|
||||
import Wasp.AI.CodeAgent (CodeAgent, getFile, writeToFile)
|
||||
import Wasp.AI.GenerateNewProject.Common
|
||||
( NewProjectDetails (..),
|
||||
defaultChatGPTParams,
|
||||
defaultChatGPTParamsForFixing,
|
||||
queryChatGPTForJSON,
|
||||
)
|
||||
import Wasp.AI.GenerateNewProject.Common.Prompts (appDescriptionBlock)
|
||||
@ -28,7 +28,7 @@ fixPageComponent newProjectDetails waspFilePath pageComponentPath = do
|
||||
currentPageComponentContent <- fromMaybe (error "couldn't find page file to fix") <$> getFile pageComponentPath
|
||||
fixedPageComponent <-
|
||||
queryChatGPTForJSON
|
||||
defaultChatGPTParams
|
||||
defaultChatGPTParamsForFixing
|
||||
[ ChatMessage {role = System, content = Prompts.systemPrompt},
|
||||
ChatMessage {role = User, content = fixPageComponentPrompt currentWaspFileContent currentPageComponentContent}
|
||||
]
|
||||
@ -38,45 +38,39 @@ fixPageComponent newProjectDetails waspFilePath pageComponentPath = do
|
||||
[trimming|
|
||||
${basicWaspLangInfoPrompt}
|
||||
|
||||
===============
|
||||
|
||||
${pageDocPrompt}
|
||||
|
||||
===============
|
||||
|
||||
We are together building a new Wasp app (description at the end of prompt).
|
||||
|
||||
Here is our main.wasp file that we generated so far, to provide you with the additional context about the app:
|
||||
```wasp
|
||||
${currentWaspFileContent}
|
||||
```
|
||||
|
||||
Here is a React component (${pageComponentPathText}) containing some frontend code
|
||||
that we generated together earlier:
|
||||
```js
|
||||
${currentPageContent}
|
||||
```
|
||||
|
||||
===============
|
||||
|
||||
The React component probably has some mistakes: let's fix it!
|
||||
|
||||
Some common mistakes to look for:
|
||||
Strong guidelines for fixing:
|
||||
- Make sure to use only queries and actions that are defined in the Wasp file (listed below)!
|
||||
- You should import queries from "@wasp/queries/{queryName}" like
|
||||
```
|
||||
import getTask from '@wasp/queries/getTasks';
|
||||
```
|
||||
- You should import actions from "@wasp/actions/{actionName}" like
|
||||
```
|
||||
import createTask from '@wasp/actions/createTask';
|
||||
```
|
||||
- Ensure query and action imports are correct. One import per query / action, default imports,
|
||||
name of the file same as name of the query.
|
||||
- Don't use `useAction` or `useMutation`! Use actions directly.
|
||||
- Use Tailwind CSS to style the page if you didn't.
|
||||
- Use <Link /> component from "react-router-dom" to link to other pages where needed.
|
||||
- "TODO" comments or "..." that should be replaced with actual implementation.
|
||||
Fix these by replacing them with actual implementation.
|
||||
- Duplicate imports. If there are any, make sure to remove them.
|
||||
- If there are any duplicate imports, make sure to remove them.
|
||||
- There might be some invalid JS or JSX syntax -> fix it if there is any.
|
||||
|
||||
Here is our main.wasp file, to provide you with the additional context about the app:
|
||||
```wasp
|
||||
${currentWaspFileContent}
|
||||
```
|
||||
|
||||
With this in mind, generate a new, fixed React component (${pageComponentPathText}).
|
||||
Do actual fixes, don't leave comments with "TODO"!
|
||||
Please respond ONLY with a valid JSON of the format { pageComponentImpl: string }.
|
||||
|
@ -17,15 +17,16 @@ import qualified Data.Text as T
|
||||
import GHC.Generics (Generic)
|
||||
import NeatInterpolation (trimming)
|
||||
import qualified Text.Parsec as Parsec
|
||||
import Wasp.AI.CodeAgent (CodeAgent)
|
||||
import Wasp.AI.CodeAgent (CodeAgent, checkIfGpt4IsAvailable)
|
||||
import Wasp.AI.GenerateNewProject.Common
|
||||
( NewProjectDetails (..),
|
||||
defaultChatGPTParams,
|
||||
defaultChatGPTParamsForFixing,
|
||||
queryChatGPTForJSON,
|
||||
)
|
||||
import Wasp.AI.GenerateNewProject.Common.Prompts (appDescriptionBlock)
|
||||
import qualified Wasp.AI.GenerateNewProject.Common.Prompts as Prompts
|
||||
import Wasp.AI.OpenAI.ChatGPT (ChatMessage (..), ChatRole (..))
|
||||
import Wasp.AI.OpenAI.ChatGPT (ChatGPTParams (_model), ChatMessage (..), ChatRole (..), Model (GPT_4))
|
||||
import qualified Wasp.Psl.Parser.Model as Psl.Parser
|
||||
import qualified Wasp.Util.Aeson as Util.Aeson
|
||||
|
||||
@ -34,7 +35,8 @@ type PlanRule = String
|
||||
|
||||
generatePlan :: NewProjectDetails -> [PlanRule] -> CodeAgent Plan
|
||||
generatePlan newProjectDetails planRules = do
|
||||
queryChatGPTForJSON defaultChatGPTParams chatMessages
|
||||
isGpt4Available <- checkIfGpt4IsAvailable
|
||||
queryChatGPTForJSON (makeGptParams defaultChatGPTParams isGpt4Available) chatMessages
|
||||
>>= fixPlanIfNeeded
|
||||
where
|
||||
chatMessages =
|
||||
@ -86,12 +88,14 @@ generatePlan newProjectDetails planRules = do
|
||||
"componentPath": "@client/pages/Task.jsx",
|
||||
"routeName: "TaskRoute",
|
||||
"routePath": "/task/:taskId",
|
||||
"pageDesc": "Diplays a Task with the specified taskId. Allows editing of the Task.",
|
||||
"pageDesc": "Diplays a Task with the specified taskId. Allows editing of the Task. Uses getTask query and createTask action.",
|
||||
}]
|
||||
}
|
||||
|
||||
We will later use this plan to write main.wasp file and all the other parts of Wasp app,
|
||||
so make sure descriptions are detailed enough to guide implementing them.
|
||||
Also, mention in the descriptions of actions/queries which entities they work with,
|
||||
and in descriptions of pages mention which actions/queries they use.
|
||||
|
||||
Typically, plan will have AT LEAST one query, at least one action, at least one page, and at
|
||||
least two entities. It will very likely have more than one of each, though.
|
||||
@ -104,6 +108,7 @@ generatePlan newProjectDetails planRules = do
|
||||
|
||||
fixPlanIfNeeded :: Plan -> CodeAgent Plan
|
||||
fixPlanIfNeeded plan = do
|
||||
isGpt4Available <- checkIfGpt4IsAvailable
|
||||
let issues =
|
||||
checkPlanForEntityIssues plan
|
||||
<> checkPlanForOperationIssues Query plan
|
||||
@ -113,7 +118,7 @@ generatePlan newProjectDetails planRules = do
|
||||
then return plan
|
||||
else do
|
||||
let issuesText = T.pack $ intercalate "\n" ((" - " <>) <$> issues)
|
||||
queryChatGPTForJSON defaultChatGPTParams $
|
||||
queryChatGPTForJSON (makeGptParams defaultChatGPTParamsForFixing isGpt4Available) $
|
||||
chatMessages
|
||||
<> [ ChatMessage {role = Assistant, content = Util.Aeson.encodeToText plan},
|
||||
ChatMessage
|
||||
@ -132,6 +137,10 @@ generatePlan newProjectDetails planRules = do
|
||||
}
|
||||
]
|
||||
|
||||
makeGptParams :: ChatGPTParams -> Bool -> ChatGPTParams
|
||||
makeGptParams params isGpt4Available =
|
||||
if isGpt4Available then params {_model = GPT_4} else params
|
||||
|
||||
checkPlanForEntityIssues :: Plan -> [String]
|
||||
checkPlanForEntityIssues plan =
|
||||
checkNumEntities
|
||||
|
@ -18,7 +18,7 @@ import NeatInterpolation (trimming)
|
||||
import Wasp.AI.CodeAgent (CodeAgent, getFile, writeToFile)
|
||||
import Wasp.AI.GenerateNewProject.Common
|
||||
( NewProjectDetails,
|
||||
defaultChatGPTParams,
|
||||
defaultChatGPTParamsForFixing,
|
||||
queryChatGPTForJSON,
|
||||
)
|
||||
import Wasp.AI.GenerateNewProject.Common.Prompts (appDescriptionBlock)
|
||||
@ -51,7 +51,7 @@ fixWaspFile newProjectDetails waspFilePath plan = do
|
||||
OnlyIfCompileErrors | null compileErrors -> return $ WaspFile {waspFileContent = wfContent}
|
||||
_otherwise ->
|
||||
queryChatGPTForJSON
|
||||
defaultChatGPTParams
|
||||
defaultChatGPTParamsForFixing
|
||||
[ ChatMessage {role = System, content = Prompts.systemPrompt},
|
||||
ChatMessage {role = User, content = fixWaspFilePrompt wfContent compileErrors}
|
||||
]
|
||||
|
@ -1,3 +1,4 @@
|
||||
{-# LANGUAGE DeriveAnyClass #-}
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
|
||||
module Wasp.AI.OpenAI.ChatGPT
|
||||
@ -10,12 +11,13 @@ module Wasp.AI.OpenAI.ChatGPT
|
||||
ChatMessage (..),
|
||||
ChatRole (..),
|
||||
getChatResponseContent,
|
||||
checkIfGpt4IsAvailable,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Arrow ()
|
||||
import Control.Monad (when)
|
||||
import Data.Aeson ((.=))
|
||||
import Data.Aeson (FromJSON, ToJSON, (.=))
|
||||
import qualified Data.Aeson as Aeson
|
||||
import Data.ByteString.UTF8 as BSU
|
||||
import Data.Text (Text)
|
||||
@ -83,6 +85,13 @@ queryChatGPT apiKey params requestMessages = do
|
||||
getChatResponseContent :: ChatResponse -> Text
|
||||
getChatResponseContent = content . message . head . choices
|
||||
|
||||
checkIfGpt4IsAvailable :: OpenAIApiKey -> IO Bool
|
||||
checkIfGpt4IsAvailable apiKey = do
|
||||
let request =
|
||||
HTTP.setRequestHeader "Authorization" [BSU.fromString $ "Bearer " <> apiKey] $
|
||||
HTTP.parseRequest_ $ "GET https://api.openai.com/v1/models/" <> show GPT_4
|
||||
(200 ==) . HTTP.getResponseStatusCode <$> HTTP.httpNoBody request
|
||||
|
||||
data ChatGPTParams = ChatGPTParams
|
||||
{ _model :: !Model,
|
||||
_temperature :: !(Maybe Float)
|
||||
@ -107,47 +116,37 @@ data ChatResponse = ChatResponse
|
||||
choices :: ![ChatResponseChoice],
|
||||
usage :: !ChatResponseUsage
|
||||
}
|
||||
deriving (Generic, Show)
|
||||
|
||||
instance Aeson.FromJSON ChatResponse
|
||||
deriving (Generic, Show, FromJSON)
|
||||
|
||||
data ChatResponseUsage = ChatResponseUsage
|
||||
{ prompt_tokens :: !Int,
|
||||
completion_tokens :: !Int,
|
||||
total_tokens :: !Int
|
||||
}
|
||||
deriving (Generic, Show)
|
||||
|
||||
instance Aeson.FromJSON ChatResponseUsage
|
||||
deriving (Generic, Show, FromJSON)
|
||||
|
||||
data ChatResponseChoice = ChatResponseChoice
|
||||
{ index :: !Int,
|
||||
message :: !ChatMessage,
|
||||
finish_reason :: !String
|
||||
}
|
||||
deriving (Generic, Show)
|
||||
|
||||
instance Aeson.FromJSON ChatResponseChoice
|
||||
deriving (Generic, Show, FromJSON)
|
||||
|
||||
data ChatMessage = ChatMessage
|
||||
{ role :: !ChatRole,
|
||||
content :: !Text
|
||||
}
|
||||
deriving (Generic, Show)
|
||||
|
||||
instance Aeson.ToJSON ChatMessage
|
||||
|
||||
instance Aeson.FromJSON ChatMessage
|
||||
deriving (Generic, Show, ToJSON, FromJSON)
|
||||
|
||||
data ChatRole = User | System | Assistant
|
||||
deriving (Generic, Show)
|
||||
|
||||
instance Aeson.ToJSON ChatRole where
|
||||
instance ToJSON ChatRole where
|
||||
toJSON User = "user"
|
||||
toJSON System = "system"
|
||||
toJSON Assistant = "assistant"
|
||||
|
||||
instance Aeson.FromJSON ChatRole where
|
||||
instance FromJSON ChatRole where
|
||||
parseJSON = Aeson.withText "ChatRole" $ \case
|
||||
"user" -> return User
|
||||
"system" -> return System
|
||||
|
Loading…
Reference in New Issue
Block a user