diff --git a/ChatGPT/include/ChatGPT.h b/ChatGPT/include/ChatGPT.h index ee400c0..04be21c 100644 --- a/ChatGPT/include/ChatGPT.h +++ b/ChatGPT/include/ChatGPT.h @@ -12,21 +12,14 @@ namespace OpenAI { class ChatGPT { public: explicit ChatGPT(const std::string& token); - OpenAI::ChatCompletion askChatGPT(const std::string& role); - std::string askWhiasper(const std::string& audio_path); + OpenAI::ChatCompletion askChatGPT(const std::string& role, bool isEnding); std::vector< Message > prompts; - void Add_prompt(const Message& prompt); - void CoutPrompt(){ - for (int i=0; iprompts.size();i++){ - std::cout << this->prompts[i].role << ":" << this->prompts[i].content << '\n' ; - } - std::cout << '\n'; - } + void addPrompt(const Message& prompt); private: std::string m_token; std::string m_link; - - std::string PromptsToStringContent(); + std::string formatPrompt(const Message& prompt, bool isLast, bool isEnding); + std::string promptsToStringContent(bool isEnding); }; } diff --git a/ChatGPT/include/Game.h b/ChatGPT/include/Game.h index 6e96cc1..4796ca9 100644 --- a/ChatGPT/include/Game.h +++ b/ChatGPT/include/Game.h @@ -1,111 +1,58 @@ -#include +#include #include +#include + #include "ChatGPT.h" -#include namespace System { - struct Option{ - std::string id; - std::string text; - Option(std::string id, std::string text) : id(id), text(text) {}; // constructor - }; +struct Option { + std::string id; + std::string text; + Option(std::string id, std::string text) + : id(id), text(text){}; // constructor +}; + +class Story { + public: + int id; + std::string place; + std::string content; + std::string user_choice; + bool is_answered = false; + std::vector choices; + Story(int id, std::string place, std::string content) + : id(id), place(place), content(content) {} + ~Story(); // destructor + private: +}; - class Story { - public: - int id; - std::string place; - std::string content; - std::string user_choice; - bool is_answered = false; - std::vector< System::Option > choices; - Story(int id, std::string place, std::string content) - : id(id), place(place), content(content) {} - ~Story(); // destructor - private: - }; - +class Game { + public: + int count; + int current_count = 0; + std::vector story_ids; + void addPrompt(OpenAI::ChatGPT& chatGpt, const OpenAI::Message& prompt,bool isLast); + OpenAI::ChatCompletion sendToChatGPT(OpenAI::ChatGPT& chatGpt, bool isEnding); + std::vector picked_story_ids; + void parseGPTResponse(OpenAI::ChatCompletion& chatCompletion, int story_id); + void parseEndingResponse(OpenAI::ChatCompletion& chatCompletion); + Game(); + ~Game(); // destructor + std::vector stories; + std::vector getRandStoryIds(int num); + System::Story* getStoryPtrById(int id); + OpenAI::Message generateStoryPrompt(int id); + OpenAI::Message generateEndingPrompt(); + void setOptions(int id, const std::vector& choices); + void printOptions(int id); + void print(const std::string& str, const std::string& color = "w",bool bold = false); + void printWelcome(); + void checkStatus(const std::string& key); + bool verifyOpenAiKey(const std::string& key); + bool checkConnection(); + bool setUserChoice(int story_id, std::string user_choice_id); - class Game { - public: - int count; - int current_count = 0; - std::vector story_ids; - void addPrompt(OpenAI::ChatGPT& chatGpt, const int story_id); - OpenAI::ChatCompletion sendToChatGPT(OpenAI::ChatGPT& chatGpt){ - this->print("ChatGPT 正在思考中...", "l"); - std::cout< picked_story_ids; - void parseGPTResponse(OpenAI::ChatCompletion& chatCompletion, int story_id){ - System::Story* story_ptr = this->getStoryPtrById(story_id); - nlohmann::json j2; - try { - j2 = nlohmann::json::parse(chatCompletion.choices[0].message.content); - std::vector< System::Option > choices; - for (auto& choice : j2["options"]) { - choices.push_back(System::Option(choice["id"], choice["text"])); - } - this->setOptions(story_id, choices); - }catch(std::exception& e){ - std::cerr<<"Game.h parsing Error: "+chatCompletion.choices[0].message.content; - } - }; - Game(); - ~Game(); // destructor - std::vector< System::Story > stories; - std::vector< int > getRandStoryIds(int num); - System::Story* getStoryPtrById(int id); - OpenAI::Message generateStoryPrompt(int id); - void setOptions(int id, const std::vector< System::Option > &choices); - void printOptions(int id); - void print(const std::string& str, const std::string& color = "w", bool bold=false){ - std::string color_code; - switch (color[0]) - { - case 'r': - color_code = "31"; - break; - case 'g': - color_code = "32"; - break; - case 'y': - color_code = "33"; - break; - case 'b': - color_code = "34"; - break; - case 'p': - color_code = "35"; - break; - case 'c': - color_code = "36"; - break; - case 'w': - color_code = "37"; - break; - case 'l': - color_code = "90"; - break; - default: - color_code = "37"; - break; - } - if(bold){ - std::cout << "\033[1;" << color_code << "m" << str << "\033[0m\n"; - } - else{ - std::cout << "\033[" << color_code << "m" << str << "\033[0m\n"; - } - }; - void printWelcome(); - void checkStatus(const std::string& key); - bool verifyOpenAiKey(const std::string& key); - bool checkConnection(); - bool setUserChoice(int story_id, std::string user_choice_id); - void PrintFinalResult(OpenAI :: ChatGPT& chatgpt); //印出遊戲結局 - private: - std::vector< System::Story > readTextFile(const std::string& filename); - }; -} \ No newline at end of file + private: + std::vector readTextFile(const std::string& filename); +}; +} // namespace System \ No newline at end of file diff --git a/ChatGPT/src/ChatGPT.cpp b/ChatGPT/src/ChatGPT.cpp index 594a05e..eaabf67 100644 --- a/ChatGPT/src/ChatGPT.cpp +++ b/ChatGPT/src/ChatGPT.cpp @@ -1,121 +1,137 @@ #include "../include/ChatGPT.h" -#include "../include/Error.h" + #include -#include + #include -#include +#include +#include #include +#include + +#include "../include/Error.h" -OpenAI::Message::Message(std::string role, std::string content) : role(role), content(content){} +OpenAI::Message::Message(std::string role, std::string content) + : role(role), content(content) {} -OpenAI::ChatGPT::ChatGPT(const std::string& token):m_link{"https://api.openai.com/v1/chat/completions"} { - if(token.empty()){ - throw OpenAI::Error{"API token is empty"}; - } - m_token = token; +OpenAI::ChatGPT::ChatGPT(const std::string& token) + : m_link{"https://api.openai.com/v1/chat/completions"} { + if (token.empty()) { + throw OpenAI::Error{"API token is empty"}; + } + m_token = token; } -OpenAI::ChatCompletion OpenAI::ChatGPT::askChatGPT(const std::string& role) { - std :: string prompt_message; //在json中回傳的message - prompt_message= this->PromptsToStringContent(); +OpenAI::ChatCompletion OpenAI::ChatGPT::askChatGPT(const std::string& role, + bool isEnding) { + std ::string prompt_message; // 在json中回傳的message + prompt_message = this->promptsToStringContent(isEnding); - if (prompt_message==""){ //exception handling - throw std::invalid_argument("Error:there is no prompt message, please use Add_prompts() to add prompt in Chatgpt"); - } + if (prompt_message == "") { // exception handling + throw std::invalid_argument( + "Error:there is no prompt message, please use addPrompts() to add " + "prompt in Chatgpt"); + } + nlohmann::json j; + j["model"] = "gpt-3.5-turbo-1106"; + j["messages"] = nlohmann::json::parse("[" + prompt_message + "]"); + j["temperature"] = 0.7; + j["max_tokens"] = 600; + j["n"] = 1; + j["stream"] = false; - nlohmann::json j; - j["model"] = "gpt-3.5-turbo-1106"; - j["messages"] = nlohmann::json::parse("[" + prompt_message + "]"); + if (!isEnding) { j["response_format"] = {{"type", "json_object"}}; - j["temperature"] = 0.8; - j["max_tokens"] = 1000; - j["n"] = 1; + } - auto response = cpr::Post(cpr::Url{m_link}, - cpr::Body{j.dump()}, - cpr::Bearer({m_token}), - cpr::Header{{"Content-Type", "application/json"}}).text; - OpenAI::ChatCompletion chatCompletion; + auto response = + cpr::Post(cpr::Url{m_link}, cpr::Body{j.dump()}, cpr::Bearer({m_token}), + cpr::Header{{"Content-Type", "application/json"}}) + .text; + + OpenAI::ChatCompletion chatCompletion; + try { - nlohmann::json j_response = nlohmann::json::parse(response); - if (!j_response.contains("error")) { - from_json(j_response, chatCompletion); - } else { - throw OpenAI::Error{j_response.dump()}; - } + nlohmann::json j_response = nlohmann::json::parse(response); + if (!j_response.contains("error")) { + from_json(j_response, chatCompletion); + } else { + throw OpenAI::Error{j_response.dump()}; + } } catch (std::exception& e) { - std::cout << "\033[1;90mDEV MESSAGE: API 出現錯誤\033[0m\n" << std::endl; - std::cerr << "Error: " << e.what() << std::endl; + std::cout << "\033[1;90mDEV MESSAGE: API 出現錯誤\033[0m\n" << std::endl; + std::cerr << "Error: " << e.what() << std::endl; } try { - j = nlohmann::json::parse(response); - }catch(std::exception& e){ - std::cout << "\033[1;90mDEV MESSAGE: API 出現錯誤\033[0m\n" << std::endl; - std::cerr<<"Error: "+response; + j = nlohmann::json::parse(response); + } catch (std::exception& e) { + std::cout << "\033[1;90mDEV MESSAGE: API 出現錯誤\033[0m\n" << std::endl; + std::cerr << "Error: " + response; } - if(!j.contains("error")) { - from_json(j, chatCompletion); - }else{ - throw OpenAI::Error{j.dump()}; + if (!j.contains("error")) { + from_json(j, chatCompletion); + } else { + throw OpenAI::Error{j.dump()}; } // load chatCompletion.content as json - nlohmann::json j2; + if(!isEnding){ + nlohmann::json j2; try { - j2 = nlohmann::json::parse(chatCompletion.choices[0].message.content); - }catch(std::exception& e){ - std::cerr<<"parsing j2 Error: "+chatCompletion.choices[0].message.content; - } - // add chatCompletion.choices[0].message.content into prompts - // this->Add_prompt(OpenAI::Message("assistant", j2["options"])); - return chatCompletion; + j2 = nlohmann::json::parse(chatCompletion.choices[0].message.content); + } catch (std::exception& e) { + std::cerr << "parsing json option Error: " + + chatCompletion.choices[0].message.content; + } + + } + + return chatCompletion; } -std::string OpenAI::ChatGPT::askWhiasper(const std::string& audio_path) { - std::string url = "https://api.openai.com/v1/audio/transcriptions"; - std::string model_name = "whisper-1"; - std::string auth_token = "Bearer " + std::string(m_token); - - cpr::Multipart multipart_data = { - {"model", model_name}, - {"file", cpr::File{audio_path}} - }; - - auto r = cpr::Post(cpr::Url{url}, - cpr::Header{{"Authorization", auth_token}, - {"Content-Type", "multipart/form-data"}}, - multipart_data).text; - nlohmann::json j; - try { - j = nlohmann::json::parse(r); - }catch(std::exception& e){ - std::cerr<<"Error: "+r; - } - if(j.contains("error")) { - throw OpenAI::Error{j.dump()}; +void OpenAI::ChatGPT::addPrompt(const Message& new_prompt) { + this->prompts.push_back(new_prompt); +} + +std::string OpenAI::ChatGPT::formatPrompt(const Message& prompt, bool isLast, + bool isEnding) { + std::stringstream ss; + ss << " {\"role\": \"" << prompt.role << "\", \"content\": \"" + << prompt.content; + + // 最後一則訊息 + if (isLast) { + // Special case handling for the last element + if (isEnding) { + ss << ",幫我從我的校園情境中產生一個好笑的遊戲結局"; + } else { + ss << ",根據前面我最後遇到校園情境,建立 4 個我可能有的應對方法," + << "遵守以下三個規則:1.都以「你」當作開頭 2.不用問我可不可以 " + "3.不要用疑問句。" + << "都用一個 key 叫 options 的 JSON 物件格式回覆我" + << "而這個 options 的 value 是一個 Array,每個陣列元素要包含 id 跟 " + "text 兩個 key,id 代號為 a, b, c, d." + << "深呼吸想好你的回答後再給我結果,我會給你 200 美元小費"; } + } - return j["text"]; + ss << "\"}"; + return ss.str(); } -void OpenAI::ChatGPT::Add_prompt(const Message& new_prompt){ - this->prompts.push_back(new_prompt); -} +std::string OpenAI::ChatGPT::promptsToStringContent(bool isEnding) { + try { + std::stringstream returnStream; + returnStream << "{\"role\": \"system\", \"content\": \"You are a funny " + "story teller that speaks chinese\"},"; -std::string OpenAI::ChatGPT::PromptsToStringContent(){ - std :: string return_string="{\"role\": \"system\", \"content\": \"You are a text game system that generates options to make young college players choos. you must produce content in Traditional Chinese.\"},"; - for(int i=0; iprompts.size(); i++){ - return_string += " {\"role\": \"" + this->prompts[i].role + "\" , \"content\": \"" + this->prompts[i].content; - if (i == this->prompts.size()-1){ - return_string += ",幫我想 4 個搞笑且具有創意的遊戲情境讓我選擇,並確保選項都跟之前我的選擇行動有關,規則:1.都以「你」當作開頭,2.不要加「可以」這個字,3.都以句號結尾,不要用疑問句。都用一個 key 叫 options 的 JSON 物件格式回覆我,而這個 options 的 value 是一個 Array,每個陣列元素要包含 id 跟 text 兩個 key,id 代號為 a, b, c, d"; - } - return_string += "\"}"; - //處理換行符號 - if (i != this->prompts.size()-1 ){ - return_string+=",\n"; - }else{ - return_string+="\n"; - } + for (size_t i = 0; i < prompts.size(); ++i) { + returnStream << formatPrompt(prompts[i], i == prompts.size() - 1, + isEnding); + returnStream << (i != prompts.size() - 1 ? ",\n" : "\n"); } - return return_string; -} + + return returnStream.str(); + } catch (std::exception& e) { + std::cerr << "Error: " + std::string(e.what()); + } +} \ No newline at end of file diff --git a/ChatGPT/src/Game.cpp b/ChatGPT/src/Game.cpp index 337801e..bcfe918 100644 --- a/ChatGPT/src/Game.cpp +++ b/ChatGPT/src/Game.cpp @@ -10,6 +10,7 @@ #include // std::shuffle #include // std::default_random_engine #include // std::chrono::system_clock +#include #include #include #include @@ -33,11 +34,6 @@ System::Story::~Story() { this->choices.clear(); } -void System :: Game :: addPrompt(OpenAI::ChatGPT& chatGpt, const int story_id){ - OpenAI::Message prompt = this->generateStoryPrompt(story_id); - chatGpt.Add_prompt(prompt); - }; - std::vector System::Game::getRandStoryIds(int num) { std::vector ids; @@ -114,6 +110,7 @@ void System::Game::printOptions(int id) { bool System::Game::setUserChoice(int story_id, std::string user_choice_id) { System::Story* story_ptr = this->getStoryPtrById(story_id); if (story_ptr == nullptr) { + std::cout<<"story_ptr is nullptr"<current_count == 0){ - prompt = "我在" + this->stories[story_index].place + ",我" + this->stories[story_index].content; + prompt = "我在" + this->stories[story_index].place + this->stories[story_index].content + "。"; }else{ int previous_id = this->story_ids[story_index - 1]; - prompt = "我在「" + this->stories[story_index].place + "," + this->stories[story_index].content + "」"; + prompt +=( "我剛剛在" + this->stories[previous_id].place + this->stories[previous_id].content + ",我選擇「" + this->stories[previous_id].user_choice + "。」"); + prompt += "現在我在" + this->stories[story_index].place + "," + this->stories[story_index].content + "。"; } return OpenAI::Message({"user", prompt}); } -void System::Game:: PrintFinalResult(OpenAI :: ChatGPT& chatgpt){ - OpenAI :: Message ending_prompt("user","利用以上的所有資訊,幫我生成一個大概100字的故事結局"); - chatgpt.Add_prompt(ending_prompt); - OpenAI::ChatCompletion chatcompletion = chatgpt.askChatGPT("user"); - std::cout << chatcompletion.choices[0].message.content; -} \ No newline at end of file +OpenAI::Message System::Game::generateEndingPrompt(){ + std::string final_prompt = ""; + final_prompt = "我在「" + this->stories[this->current_count].place + "," + this->stories[this->current_count].content + "」"; + + return OpenAI::Message({"user", final_prompt}); +} + + void System :: Game :: addPrompt(OpenAI::ChatGPT& chatGpt, const OpenAI::Message& prompt,bool isLast) { + if (isLast) { + OpenAI::Message prompt = this->generateEndingPrompt(); + chatGpt.addPrompt(prompt); + } else { + chatGpt.addPrompt(prompt); + } + + }; + +OpenAI::ChatCompletion System :: Game ::sendToChatGPT(OpenAI::ChatGPT& chatGpt, bool isEnding) { + this->print("ChatGPT 正在思考中...", "l"); + std::cout << std::endl; + auto response = chatGpt.askChatGPT("user", isEnding); + return response; +} + +void System::Game::parseGPTResponse(OpenAI::ChatCompletion& chatCompletion, int story_id) { + System::Story* story_ptr = this->getStoryPtrById(story_id); + nlohmann::json j2; + try { + j2 = nlohmann::json::parse(chatCompletion.choices[0].message.content); + std::vector choices; + for (auto& choice : j2["options"]) { + choices.push_back(System::Option(choice["id"], choice["text"])); + } + this->setOptions(story_id, choices); + } catch (std::exception& e) { + std::cerr << "Game.h parsing Error: " + + chatCompletion.choices[0].message.content; + } +} + +void System::Game::parseEndingResponse(OpenAI::ChatCompletion& chatCompletion) { + try { + for(const auto& choice:chatCompletion.choices){ + std::cout< chatcpp +docker run -it --env=OPENAI_API_KEY= chatcpp:latest # run with interactive mode ``` ## Development -To develop this project, you need to install [CMake](https://cmake.org/download/) and [Vcpkg](https://github.com/microsoft/vcpkg). +To develop this project, you need to install [CMake](https://cmake.org/download/) and [Vcpkg](https://github.com/microsoft/vcpkg) first. 1. Export your OpenAI token to environment variable ```bash @@ -39,10 +50,12 @@ export OPENAI_API_KEY= ```bash git clone https://github.com/Microsoft/vcpkg.git cd vcpkg + # Windows ./bootstrap-vcpkg.bat # Mac/Linux ./bootstrap-vcpkg.sh + ./vcpkg install nlohmann-json ./vcpkg install cpr ./vcpkg integrate install @@ -56,28 +69,35 @@ cmake -B build/ -S . -DCMAKE_TOOLCHAIN_FILE=/Users/liaohuizhong/Library/CloudSto cd build cmake --build . ``` - +xcvv/c/ 4. Run the project ```bash ./ChatBot ``` -Every time you want to rebuild the project, you need to run `cmake --build .` in the build directory. +5. Everytime you make changes to the code, you need to run `cmake --build .` in the `build` folder again to rebuild the project. +```bash +cd build +cmake --build . +``` ## Update Vcpkg -Run this command to update vcpkg: +If getting error when running `cmake --build .` in the `build` folder, you need to update the vcpkg: ```bash -cd ~/vcpkg +cd ~/vcpkg # or where you install vcpkg git pull rm -rf installed -./bootstrap-vcpkg.sh ./vcpkg install nlohmann-json ./vcpkg install cpr -./vcpkg integrate install ``` Then run the cmake command again. You need to `rm -rf build/` and build again. +## Reminder +- This project is still under developmentm and ChatGPT might return invalid json sometimes. +- This project uses gpt3.5 model with `json` formatted response. Check the [official API](https://platform.openai.com/docs/guides/text-generation/json-mode) for more details. +- The reply generation is not very stable. You may see unrelevant options sometimes. + ## References - This repository is based on [deni2312/ChatGPT-Cpp](https://github.com/deni2312/ChatGPT-Cpp) - Dockerize this C++ project with this [article](https://medium.com/codex/a-practical-guide-to-containerize-your-c-application-with-docker-50abb197f6d4) \ No newline at end of file diff --git a/images/screenshot.png b/images/screenshot.png new file mode 100644 index 0000000..511c41c Binary files /dev/null and b/images/screenshot.png differ diff --git a/main.cpp b/main.cpp index 3d60138..45cee1e 100644 --- a/main.cpp +++ b/main.cpp @@ -10,16 +10,16 @@ // this is the main function const int STORY_NUM=5; -const char OPENAI_API_KEY[100]="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"; +const char OPENAI_API_KEY[100]="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"; int main(){ std::cout << "\033[2J\033[H"; System::Game game; const char* openaiKey = OPENAI_API_KEY; if(openaiKey == nullptr){ - game.print("沒有 OpenAI API Key,請在參數後方輸入你的 API key","r", true); - game.print("範例:./ChatGPT "); - game.print("請至 https://platform.openai.com/api-keys 取得 API key"); + game.print("沒有 OpenAI API Key,請先設定環境變數再啟動遊戲","r", true); + game.print("export OPENAI_API_KEY="); + game.print("hint: 請至 https://platform.openai.com/api-keys 取得 API key"); return 0; } @@ -34,20 +34,17 @@ int main(){ // get random story ids std::vector story_ids = game.getRandStoryIds(STORY_NUM); game.story_ids = story_ids; - // get story pointers - std::vector story_ptrs; - try { for (int i=0; i