0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

GolangとGPT APIを利用して、入力したトピックから自動的にwordpressにブログを投稿できるようにしてみる

Last updated at Posted at 2023-06-04

はじめに

OpenAIからchatGPTという対話できるAIが世間を賑わせています。
これを利用して自動的にWordPressで記事を投稿してみようと思い、今勉強中のGolangを使って作ってみようと思いました。
GPT APIのドキュメントなどは、Pythonでのドキュメントがほとんどなので基本的にはPythonを利用するのが一般的かなと思います
ちなみにGPT APIの利用方法などは私が個人で書いてるブログで紹介してます

また、全量のコードについてはこちらに置いています。

参照

GPT APIリファレンス
Wordpress APIリファレンス

まずはプロンプトを考える

質問した回答を想定して、回答は日本語で質問は英語にする必要があります。これは、APIの料金に関わってくるトークン数が日本語の方が多くなってしまうことから、質問に関しては英語で作成した方が良さそうです

まずは、結果を日本語で返してもらうために

You are a helpful assistant that speaks Japanese

とします。これをRoleのsystemにすることで会話が全て日本語で返されるようになりました

次に何をしてもらうかと条件をどうするか?ですが下記のようにすることでブログのタイトルと本文を考えてもらいます

#Operation.
Write a blog post for me. Generate a blog title and content for the topic: {topic}.
#conditions
 {conditions}

上記では{topic}にはブログのトピック、{conditions}にはブログの文字数などの条件を入力できるようにします

これで一旦プロンプトについては完了です

実装する

1. まずGPT APIの処理を作る

データ型を定義

type GptRequest struct {
	Model    string `json:"model"`
	Messages []struct {
		Role    string `json:"role"`
		Content string `json:"content"`
	} `json:"messages"`
}

type GptResponse struct {
	Usage struct {
		PromptTokens     int `json:"prompt_tokens"`
		CompletionTokens int `json:"completion_tokens"`
		TotalTokens      int `json:"total_tokens"`
	} `json:"usage"`
	Choices []struct {
		Message struct {
			Content string `json:"content"`
		} `json:"message"`
	} `json:"choices"`
}

上記では、APIにアクセスするためのデータ型を定義しています、文字列でもできると思いますがjsonをデータ型に変換したほうがわかりやすいのとgolangではjsonを変換する方法が簡単なので変換します
GptRequestのmodelにはgpt-4などの使いたいモデル、Messagesは質問する内容になっています。Roleについてはsystem, user, assistantとなっていてsystemとuserは同じ役割でAIへの質問内容になってますがGPT-4だと優先的にこちらに設定した質問が適応されるようです。
assistantについては質問する内容に補足する内容を設定するようです

APIへのアクセス

func GetContent(topic string, apiKey string) (string, string, error) {
	url := "https://api.openai.com/v1/chat/completions"
    prompt := "#Operation.\n Write a blog post for me. Generate a blog title and content for the topic: {topic}.\n#conditions \n {conditions}"

	fmt.Println("---------- this prompt create ----------")
	
	conditions := strings.Join(["At least 100 words of text"], "\n")
	prompt = strings.ReplaceAll(prompt, "{topic}", topic)
	prompt = strings.ReplaceAll(prompt, "{conditions}", conditions)
	fmt.Println(prompt)
	fmt.Println("---------- prompt end  ----------")
	payload := &GptRequest{
		Model: "gpt-4",
		Messages: []struct {
			Role    string `json:"role"`
			Content string `json:"content"`
		}{
			{
				Role:    "system",
				Content: "You are a helpful assistant that speaks Japanese",
			},
			{
				Role:    "user",
				Content: prompt,
			},
		},
	}

	jsonPayload, err := json.Marshal(payload)
	if err != nil {
		return "", "", err
	}

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonPayload))
	if err != nil {
		return "", "", err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey))

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", "", err
	}
	fmt.Println("http status " + resp.Status)

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", "", err
	}

	var gpt4Response GptResponse
	err = json.Unmarshal(body, &gpt4Response)
	if err != nil {
		return "", "", err
	}
	text := gpt4Response.Choices[0].Message.Content
	split := strings.SplitN(text, "\n", 2)
	title := split[0]
	content := ""
	if len(split) > 1 {
		content = split[1]
	}
	fmt.Println("usage PromptTokens =", gpt4Response.Usage.PromptTokens)
	fmt.Println("usage CompletionTokens =", gpt4Response.Usage.CompletionTokens)
	fmt.Println("usage TotalTokens =", gpt4Response.Usage.TotalTokens)
	return title, content, nil
}

上記のコードでは、引数に指定されたトピックとAPI keyからプロンプトを作成して、GPT APIへのPOST送信を行い、帰ってきたレスポンスのjsonデータをGptResponse型に変換した後に、タイトルと本文を切り出し、戻り値にしています。
リクエストとして、jsonデータへの変換が jsonData, _ := json.Marshal(payload)、レスポンスをデータ型に変換するのがjson.Unmarshal(body, &gpt4Response)のように1発でできるのでとても簡単です

2. wordpress側の処理を作る

データ型の定義

GPTと同様にリクエスト、レスポンスのデータ型を作ります

type WPRequest struct {
	Title   string `json:"title"`
	Content string `json:"content"`
	Status  string `json:"status"`
}

type WPResponse struct {
	ID int `json:"id"`
}

今回必要になってくるのはタイトル、本文、公開するか否か?なのでリクエストにはTitle,Content,Statusの3つを定義して、レスポンスには作成した記事のIDを取得できるようにしました

APIへのアクセス

func PostBlog(title string, content string, siteName string, apiKey string, postUserName string) error {
	url := siteName + "/wp-json/wp/v2/posts"
	payload := &WPRequest{
		Title:   title,
		Content: content,
		Status:  "publish",
	}
	jsonPayload, err := json.Marshal(payload)
	if err != nil {
		return err
	}

	req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonPayload))

	req.Header.Add("Content-Type", "application/json")

	req.SetBasicAuth(postUserName, apiKey)

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}

	fmt.Println("http status " + resp.Status)

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	var wpResponse WPResponse
	json.Unmarshal(body, &wpResponse)

	fmt.Printf("Posted new blog with ID: %d\n", wpResponse.ID)
	return nil
}

上記では引数に指定されたタイトル、本文、サイト名(ドメイン名)、APIkey、作成ユーザ名をもとに記事を投稿します
今回はStatusをpublishにすることで公開するようにします

3. main.goの作成

最後に作成した関数を使って実際に投稿するためにエントリーポイントとなるmain.goを作成します

func main() {
	openAIKey := os.Getenv("OPENAI_API_KEY")
	siteName := os.Getenv("WORDPRESS_SITE_NAME")
	postUserName := os.Getenv("WORDPRESS_USER_NAME")
	wordpressKey := os.Getenv("WORDPRESS_API_KEY")

	reader := bufio.NewReader(os.Stdin)
	fmt.Println("Enter the topic for the blog post:")
	topic, _ := reader.ReadString('\n')

	fmt.Println("Thank you input topic")
	topicLog := fmt.Sprintf("generating blog for gpt [topic = %s\n]", topic)
	fmt.Println(topicLog)
	title, content, err := openai.GetContent(topic, openAIKey)
	fmt.Println("completing generating blog for gpt")
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println("posting blog")
	wordpress.PostBlog(title, content, siteName, wordpressKey, postUserName)
	fmt.Println("complete posting blog")
}

上記コードでは、os.Getenvを用いて環境変数からAPIキーやサイト名など必要な情報を取得し、reader := bufio.NewReader(os.Stdin)を用いて、CLIから入力するトピック情報をもとに記事を作成していきます。

4. 実行

今回は環境変数を利用したいので.envファイルを作成して、makefileで実行してみたいと思います

OPENAI_API_KEY=your_openai_api_key_here
WORDPRESS_SITE_NAME=your_wordpress_site_name_here
WORDPRESS_USER_NAME=your_wordpress_user_name_here
WORDPRESS_API_KEY=your_wordpress_api_key_here
include .env
export

.PHONY: build run

build:
	go build -o build/auto-blog-wordpress

run: build
	./build/auto-blog-wordpress

上記でmake runコマンドを実行してみます

make run
go build -o build/auto-blog-wordpress
./build/auto-blog-wordpress
Enter the topic for the blog post:
sample
Thank you input topic
generating blog for gpt topic = sample

---------- the topics ----------
generating blog for gpt [topic = sample
]
---------- end  ----------
---------- this prompt create ----------
#Operation.
 Write a blog post for me. Generate a blog title and content for the topic: sample
.
#conditions 
 content is 'sample'
---------- prompt end  ----------
http status 200 OK
usage PromptTokens = 50
usage CompletionTokens = 882
usage TotalTokens = 932
completing generating blog for gpt
posting blog
http status 201 Created
Posted new blog with ID: 2152
complete posting blog

ちょっとログがおかしいような感じがしますが、成功しました。
wordpressを見ても、それらしい内容の記事が作成できていました。

ただ、帰ってくる内容がhtmlではないため、ソースコードや段落の指定などまだまだなのでプロンプトの調整や、wordpressの設定でなんとかして、いい感じに記事が投稿できるようにしてきたいと思います

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?