今天要來實作一下現在話題最高的科技項目-ChatGPT

我記得這股AI潮好像是從大概今年1月多開始的,但其實從2021年左右的時候,OpenAI這家公司就發表了這款AI軟件

而其實呢,我自己也都在思考。老實說我認識到這個ChatGPT也只是在今年1月的時候知道的,並沒有比社會大眾早多少

而也在這時,我的YouTube不知道是因為演算法的緣故還是真的太多人出片

我的YouTube也瞬間被OpenAI還有各種AI模型應用洗版....

 

然後人類繼續討論AI會不會取代人類,這屁話我在大學期間都不知道聽過幾次了= =

好啦,回歸正題。由於AI很夯的緣故,我也去搜尋了一下個人使用者能不能去串接他的API...答案很意外地是肯定的

OpenAI他們公司有開放所有個人使用者去有限度地使用他們的引擎,多虧他們佛心我們這種小小的開發者也可以秀一手AI串接技術(笑)

好,那廢話就別再繼續說了,讓我們開始吧

來看看功能

然後Github

->點我

 


 

1. 前言

首先,雖然說是串連ChatGPT的API,但其實說穿了這只是一篇很普通的okHttp3應用實作(笑)

也就是說,本質上我只是去串接OpenAI提供的API而已,其實並沒有什麼大不了的,跟「人工智慧」甚至沒啥屁關係(大笑

而這次的實作,基本上我不想做什麼聊天室、甚至上下文對話等等

我這次想做的就是很簡單的一問一答,就這樣

 

總之,廢話到此為止。開始實作囉!

 


 

2. 取得ChatGPT金鑰&使用Postman測試

 

首先,我們要先去ChatGPT官網取得金鑰

網址在這

->https://platform.openai.com/

截圖 2023-03-11 下午10.38.11

首先,這是官網。進去後,沒有註冊的先註冊註冊

有註冊的應該就會是跟我一樣的登入狀態

再來請點選View API keys(補充,這個View API keys手機版沒有)

截圖 2023-03-11 下午10.39.23

再來點選「API keys」,然後點「+Create new secrent key

截圖 2023-03-11 下午10.41.04

最後,這段碼就是你的金鑰囉!

截圖 2023-03-11 下午10.44.58

而基本上這串金鑰只會出現這一次,之後就再也無法用任何方法在看到這串金鑰。請特別注意囉!

 

OK,取得金鑰後,接著要來在Postman上面做測試

Postman的部分我就先預設大家都有,就不再提如何安裝

不會的話問ChatGPT

 

首先,ChatGPT的基本URL為

->https://api.openai.com/v1/completions

總之先在URL欄裡將發送方式設為POST,然後填入

 

再來點選Headers,在key的地方打上Authorization,value的部分請這樣寫

Bearer key

截圖 2023-03-11 下午10.52.55

假設你的金鑰是oooo08570857的話,那你就要這樣打

Bearer oooo08570857」,Bearer跟oooo08570857之間記得要空白

 

最後在點選Body,輸入以下的JSON字串

{
    "model": "text-davinci-003",
    "prompt": "如何使用okHttp3",
    "temperature": 0.5,
    "max_tokens": 1000,
    "top_p": 1,
    "frequency_penalty": 0,
    "presence_penalty": 0
}

截圖 2023-03-11 下午10.58.11

最後點Send就可以成功發送囉!

另外,他回傳的時間並不是很即時,也需要大概2秒的時間去算。因此按下送出後也要稍微等一下哦!

回傳長這樣,供參考↓

截圖 2023-03-11 下午11.00.07

最後,附上ChatGPT告訴我的每個參數的意義(笑)

  • "model": 指定使用的模型,這裡是 "text-davinci-003",它是 OpenAI 最先進和最強大的模型之一。

  • "prompt": 是模型要生成的文字提示,即模型需要回答的問題或開始的話題。

  • "temperature": 控制模型生成的多樣性和隨機性。值越高,生成的結果越隨機。這裡的溫度為 0.3,意味著生成的結果相對保守,但仍可能有一些意想不到的答案。

  • "max_tokens": 生成的最大文本數量,即模型生成的回答或文章的最大長度。這裡設置為 1000 個標記(tokens)。

  • "top_p": 用於過濾生成的詞彙,僅保留最有可能出現在答案中的詞。這裡設置為 1,即不進行過濾。

  • "frequency_penalty": 設置生成詞彙頻率的懲罰程度,以避免生成的內容重複或不夠多樣化。這裡設置為 0,即不進行懲罰。

  • "presence_penalty": 設置生成詞彙存在的懲罰程度,以避免生成的內容出現太多相似的詞彙。這裡設置為 0,即不進行懲罰。

 


 

3. 寫入權限與載入需要的庫

 

OK,如果你成功送出並取得回傳的話,恭喜你,你已經完成一半了

接下來就是將這個成果套進去Android中,僅此而已

 

首先,我們要先在Android權限中寫入網路權限

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:usesCleartextTraffic="true"
        android:theme="@style/Theme.ChatGPTExample"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <meta-data
                android:name="android.app.lib_name"
                android:value="" />
        </activity>
    </application>

</manifest>

再來打開build.gradle(Moudle)

build.gradle(Moudle)

截圖 2023-03-11 下午11.29.48

加入

plugins {
    id 'com.android.application'
}

android {
    namespace 'com.noahliu.chatgptexample'
    compileSdk 33

    defaultConfig {
        applicationId "com.noahliu.chatgptexample"
        minSdk 28
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.8.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    implementation 'com.squareup.okhttp3:okhttp:4.7.2'
    implementation 'com.squareup.okhttp3:okhttp-urlconnection:4.7.2'
    implementation 'com.squareup.okhttp3:logging-interceptor:4.7.2'
    implementation 'com.google.code.gson:gson:2.8.6'
}

大致上,以上的函式庫就是okHttp3跟GSON的函式庫而已,我也沒有在加其他的新東西

到這邊就完成基礎設置了,接下來是寫介面跟做實體類

 


 

4. 畫介面與撰寫實體類

 

首先我展示一下我創建好的所有檔案

截圖 2023-03-11 下午11.36.59

首先是實體類,由於本人真的很懶,因此我介紹一個線上工具

->https://json2csharp.com/code-converters/json-to-pojo

這個線上工具很方便,他可以自動幫你把Response的json格式自動轉換成Java的程式

在這邊我就不示範操作,我只貼上我轉出來的結果

ChatGPTRespond.java

public class ChatGPTRespond{
    public String id;
    public String object;
    public int created;
    public String model;
    public ArrayList<Choice> choices;
    public Usage usage;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getObject() {
        return object;
    }

    public void setObject(String object) {
        this.object = object;
    }

    public int getCreated() {
        return created;
    }

    public void setCreated(int created) {
        this.created = created;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public ArrayList<Choice> getChoices() {
        return choices;
    }

    public void setChoices(ArrayList<Choice> choices) {
        this.choices = choices;
    }

    public Usage getUsage() {
        return usage;
    }

    public void setUsage(Usage usage) {
        this.usage = usage;
    }

    public class Choice{
        public String text;
        public int index;
        public Object logprobs;
        public String finish_reason;

        public String getText() {
            return text;
        }

        public void setText(String text) {
            this.text = text;
        }

        public int getIndex() {
            return index;
        }

        public void setIndex(int index) {
            this.index = index;
        }

        public Object getLogprobs() {
            return logprobs;
        }

        public void setLogprobs(Object logprobs) {
            this.logprobs = logprobs;
        }

        public String getFinish_reason() {
            return finish_reason;
        }

        public void setFinish_reason(String finish_reason) {
            this.finish_reason = finish_reason;
        }
    }
    public class Usage{
        public int prompt_tokens;
        public int completion_tokens;
        public int total_tokens;

        public int getPrompt_tokens() {
            return prompt_tokens;
        }

        public void setPrompt_tokens(int prompt_tokens) {
            this.prompt_tokens = prompt_tokens;
        }

        public int getCompletion_tokens() {
            return completion_tokens;
        }

        public void setCompletion_tokens(int completion_tokens) {
            this.completion_tokens = completion_tokens;
        }

        public int getTotal_tokens() {
            return total_tokens;
        }

        public void setTotal_tokens(int total_tokens) {
            this.total_tokens = total_tokens;
        }
    }
}

 

再來是介面

activity_main.xml

截圖 2023-03-11 下午11.45.31

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.88" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline2"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.75" />

    <EditText
        android:id="@+id/edittext_Input"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_margin="4dp"
        android:ems="10"
        android:text="如何安裝Android studio"
        android:hint="如何交到女朋友"
        android:inputType="textPersonName"
        android:minHeight="48dp"
        app:layout_constraintEnd_toStartOf="@+id/guideline2"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline" />

    <Button
        android:id="@+id/button_Send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Send"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline2"
        app:layout_constraintTop_toTopOf="@+id/guideline" />

    <TextView
        android:id="@+id/textView_Title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:text="您的問題:"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        android:textStyle="bold"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.2" />

    <ScrollView
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:padding="8dp"
        app:layout_constraintBottom_toTopOf="@+id/guideline"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline3">


        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <TextView
                android:id="@+id/textView_Title2"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="ChatGPT回答:"
                android:textAppearance="@style/TextAppearance.AppCompat.Large"
                android:textStyle="bold" />

            <TextView
                android:id="@+id/textView_Answer"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:text="TextView" />
        </LinearLayout>
    </ScrollView>

    <TextView
        android:id="@+id/textView_Question"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:text="TextView"
        app:layout_constraintBottom_toTopOf="@+id/guideline3"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/textView_Title"
        app:layout_constraintTop_toBottomOf="@+id/textView_Title" />

</androidx.constraintlayout.widget.ConstraintLayout>

 


 

5. 撰寫功能  

 

最後終於來到重點了(累...

首先我先來架一下Http請求的主幹程式

HttpRequest.java

public class HttpRequest {


    public void sendPOST(String url, RequestBody requestBody,OnCallback callback){
        /**建立連線*/
        OkHttpClient client = new OkHttpClient()
                .newBuilder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(20, TimeUnit.SECONDS)
                .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC))
                .build();

        /**設置傳送需求*/
        Request request = new Request.Builder()
                .url(url)
                .header("Authorization", "Bearer "+MainActivity.YOUR_KEY)
                .post(requestBody)
                .build();
        /**設置回傳*/
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NonNull Call call, @NonNull IOException e) {
               callback.onFailCall(e.getMessage());
            }

            @Override
            public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                String res = response.body().string();
                callback.onOKCall(res);
            }
        });
    }
    interface OnCallback{
        void onOKCall(String respond);
        void onFailCall(String error);
    }
}

 

這裡就是一個很經典的okHttp3的寫法

而由於ChatGPT他的回傳時間通常比較久,因此兩個timeout的時間我拉得比較長

最後在先前的header的部分,則在粉底白字的部分加入

另外,我的程式並沒有提供key讓你用。因此key的部分請照我上面的步驟自己去申請哦!

 

最後就是主程式的部分

MainActivity.java

public class MainActivity extends AppCompatActivity {
    public static final String YOUR_KEY = "";
    public static final String URL = "https://api.openai.com/v1/completions";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tvAnswer = findViewById(R.id.textView_Answer);

        ((Button)findViewById(R.id.button_Send)).setOnClickListener(view -> {
            String question = ((EditText)findViewById(R.id.edittext_Input)).getText().toString();
            if (question.isEmpty()) return;
            ((TextView)findViewById(R.id.textView_Question)).setText(question);
            tvAnswer.setText("請稍候..");
            //設置Header中的Content-Type
            MediaType mediaType = MediaType.parse("application/json");
            //寫入body
            String content = new Gson().toJson(makeRequest(question));
            RequestBody requestBody = RequestBody.create(mediaType, content);
            //發送請求
            new HttpRequest().sendPOST(URL, requestBody, new HttpRequest.OnCallback() {
                @Override
                public void onOKCall(String respond) {
                    Log.d("TAG", "onOKCall: "+respond);
                    ChatGPTRespond chatGPTRespond = new Gson().fromJson(respond,ChatGPTRespond.class);
                    runOnUiThread(()->{
                        tvAnswer.setText(chatGPTRespond.getChoices().get(0).getText());
                    });

                }
                @Override
                public void onFailCall(String error) {
                    Log.e("TAG", "onFailCall: "+error);
                    tvAnswer.setText(error);
                }
            });
        });
    }
    //寫入body
    private WeakHashMap<String,Object> makeRequest(String input){
        WeakHashMap<String,Object> weakHashMap = new WeakHashMap<>();
        weakHashMap.put("model","text-davinci-003");
        weakHashMap.put("prompt",input);
        weakHashMap.put("temperature",0.5);
        weakHashMap.put("max_tokens",1000);
        weakHashMap.put("top_p",1);
        weakHashMap.put("frequency_penalty",0);
        weakHashMap.put("presence_penalty",0);
        return weakHashMap;
    }
}

比較需要注意的點只有這個MediaType的部分。這個MediaType呢他相當於就是取代掉原本必須在header裡面加的content-Type

再來就是由於直接用字串寫body太不直觀,因此我在紫底的部分寫Json body,讓request更易於辨識

 

做到這邊按下執行應該可以順利跑出來囉!希望大家跟著我做都有成功!

最後..

TK

arrow
arrow

    碼農日常 發表在 痞客邦 留言(3) 人氣()