前書き
まずはこちらの画像と映像を御覧ください。
Slack(画像) | Discord(映像) |
---|---|
※Discord(映像)はYouTubeショートに飛びます
これらは、いずれもPipedreamのワークフローで作った、「質問に対してOpenAIからの回答をレスポンスする」様子です。
今回のアドカレでは、「3部構成(仮)」と称してPipedreamをネタにずっと記事を書いていましたが、今回は自作アクションによる処理の再利用に焦点を当てていこうと思います。
作ったものについて
コード
このGitHubリポジトリにあるopenai/actions/chatgpt.mjs
です。
現時点だと、このコードを記事の手順に従って自分のアカウントでpd publish
すると、私と同じようにOpenAIと戯れる事ができます。1
OpenAI APIを使って、ChatGPT「風」リアクションを返すためのワークフロー
このような2個のワークフローを作っています。
それぞれ、次のような処理になっています。
- プラットフォーム上の新規メッセージを検知する。
- 処理対象化を判定する。
- 対象外ならここで処理を終了する。
- OpenAI APIへポストして、結果を受け取る。
- Slack版のほうがステップが多いが、これはSlack側ではprefix制2にしており、除去する必要があるため。
- 結果をプラットフォームに返信する。
Pipedreamにおけるアクションの自作処理おさらい
最初は「Nodeアクション」として内部の処理を直接実装する形式にしていました。
自作処理の中身
import fetch from "node-fetch";
// To use previous step data, pass the `steps` object to the run() function
export default defineComponent({
async run({ steps, $ }) {
// Return data to use it in future steps
const apiUrl = "https://api.openai.com/v1/completions";
const payload = {
model: "text-davinci-003",
prompt: steps.trigger.event.text.substring(9),
max_tokens: 3000,
temperature: 0,
};
const options = {
method: "post",
body: JSON.stringify(payload),
headers: {
"content-type": "application/json",
"authorization": `Bearer ${process.env.OPENAI_APIKEY}`
}
};
const result = await fetch(apiUrl, options).then(res => res.json());
console.log(result.choices[0].text.trim());
return {
raw: result,
text: result.choices[0].text.trim(),
}
},
})
基本的にはOpenAIが提供しているCompletions APIに所定の形式でPOSTリクエストを投げるだけで、処理の大半をここに費やしています。3 4 API Keyについてはアカウントの作成後に簡単に作成することができるので割愛します。
console.log
とreturn
内のtext
で、レスポンスのオブジェクトからピンポイントで値を取ってきているのは、ここが「APIによって生成されたテキスト」=「回答」となっているためです。
また、今回のアクションでは明確に回答に相当する部分のみを必要としているため、APIの結果全体をraw
キーにセットするのとは別にtext
キーに回答部分のみセットし、後続処理に引き渡しています。
なお、PipedreamのNodeランタイムの関係で、node-fetch
が必要となっています。import
で宣言しておくだけで、自動的にダウンロードとインポートを実施してくれます。5
Slackワークフローだけでなく、Discordワークフローでも使いたい
最初のSlack用ワークフローは上記の通り、自作処理で実装しました。
この後にDiscord用ワークフローを作るのですが、最初に考える方法が「Nodeアクションのコードをそのままコピペする」です。
しかし、色々と面倒なのもあって「コピペ以外で使い回せないか?」という発想が出てきます。
その場合、「一つのアクションとして登録する」という手段を取ることが出来ます。
自作処理から自作アクションへ
ガイドがきちんと用意されています。より詳細を追いたい場合は、ドキュメントを参照しましょう。
まずはCLIの入手と認証を
アクションを後ほど登録するために、PipedreamのCLIコマンドを入手します。
各種OS用のバイナリが用意されているので、下記URLのガイドに従ってインストールしましょう。
Linux向けもシングルバイナリで提供されているため、セットアップは非常に簡単です。
入手後はpd login
を実行することで、ブラウザの起動とブラウザ上での認証が実行されます。
すでにアカウントがあれば連携画面が出るので、そのまま進みましょう。問題なく進めば、以降はpd
コマンドでの各種操作が可能になります。
アクションとしての定義をする
Pipedreamのカスタムアクションは「必要な属性を持つESModule」となっています。アクションとしての要素のみを抜き出すと以下のようになります。
export default {
key: "openai-shortcut-chatgpt",
name: "Completions text by OpenAI",
description: "For creation ChatGPT like answer by OpenAI API",
version: "0.1.0",
type: "action",
props: {
// TODO: 定義しましょう
},
async run() {
// TODO: 実装しましょう
return {};
},};
簡単な説明
名前 | 型 | 役割 |
---|---|---|
key | string | 識別子 |
name | string | アクション選択時の名前 |
description | string | アクション選択時の説明 |
version | versioning-string | アクションのバージョン |
type | "action" | コンポーネントの種別 |
props | object | アクションの引数 |
run | async function | アクションの実処理 |
極端な話、これらが定義・実装されており、全てがexport
されていればアクションになります。
とはいえ、これだと独立して何かを実行するだけになってしまうので、実際の処理を移植していきましょう。
重要なのはprops
とrun
です。
props
props
はアクションの引数です。ここで定義した値のみを用いて実際の処理が動くと考えればよいでしょう。
例えば、今回の処理では「API Key」を使って「質問となる文」をPOSTするため、2項目を必要とします。
export default {
props: {
apiKey: {
type: "string",
label: "API Key",
secret: true,
},
text: {
type: "string",
label: "Target text",
},
},
// 略
}
Pipedreamの画面上ではlabel
のが表示され、type
に応じて入力の受付を制御してくれます。(後述)
また、run
内の内部処理上ではthis.text
のようなprops
のキーがそのままプロパティになります。
run
アクション内の自作と基本的に同じです。ただし、汎用的な処理にする必要があるため、steps
は使えずに前述の通りprops
のみを使用していきます。
細かい制御をさせたい場合は、props
の中身を充実させましょう。
export default {
async run({ $ }) {
const apiUrl = "https://api.openai.com/v1/completions";
const payload = {
model: "text-davinci-003",
- prompt: this.text,
+ prompt: steps.trigger.event.text.substring(9),
max_tokens: 4000,
temperature: 0,
};
const options = {
method: "post",
body: JSON.stringify(payload),
headers: {
"content-type": "application/json",
- "authorization": `Bearer ${process.env.OPENAI_APIKEY}`
+ "authorization": `Bearer ${this.apiKey}`,
},
};
const result = await fetch(apiUrl, options).then((res) => res.json());
+ $.export("$summary", "Successfully api");
return {
raw: result,
text: result.choices[0].text.trim(),
};
},
};
これが実装上の差分です。基本的にOpenAIのAPIに引き渡す情報の出どころが変わっただけとなっています。7
テストする?
このままアップロードしてワークフローでトライ&エラーでもいいのですが、本当はテストがあると良いかもしれません。
ただし、この手の処理は単機能故にシンプルなので、ローカルで動作させるドライバーアプリだけで事足りることも多いです。
今回は、リポジトリ内でopenai/demo.mjs
という簡易デモを用意して、実装を直すたびに正しく動くかを簡易的に確認する手法を取りました。
アップロードする
認証済みCLIでpublish
コマンドを実行するだけです。
pd publish action.mjs
注意点として、アクションのプロパティにあるkey
とversion
の組み合わせがユニークになっている必要があります。
そのため、再度アップロードする際は、かならずversion
も更新しましょう。
実際に組み込んでみる
アップロード完了後にワークフロービルダーでアクションを追加しようとすると、「My Actions」の中に、アップロード済みアクションがname
,description
を表示する形式で一覧化されます。
利用予定のアクションをクリックすると、先程props
で定義したパラメーターを渡せるようになっています。
これで、自作のアクションを使い回せるようになりました。
後書き
ここまでで、自分のアカウント内でちょっと複雑な自作アクションを使い回せるようになりました。
このフェーズの先には、Pipedreamの公開リポジトリに提供することでの「公式アクション化」というステージが存在します。
いつか「この(他人の)ワークフローはワシが育てた」的なことを言ってみたいものですね。
最後に
OpenAIの回答は、その性質から必ずしも正しい内容となっている保証は出来ません。
用法用量を守って、適切に試しましょう。
-
記事を書いている間に細かい調整がはいっており、実際は記事のコードと違っています。 ↩
-
最初にある画像の通り、
chatgpt: (質問文)
という投稿に対してのみ反応する実装です。 ↩ -
ChatGPTのCLI用Golang実装である、kkdai/chatgptの中身を参照しました。6 ↩
-
ので、厳密にはタグとして載せているChatGPTと全く同じ動きで回答を作っているかまでは分かりません。 ↩
-
忘れてたのですが、
@pipedream/axios
で十分でした。 ↩ -
Node系だとchatgpt-apiというより本来の動きに近い実装があります。ただしPipedream上だと動きません。 ↩
-
$.export
については別の自作アクションからの流用で、無くてもおそらく困りません。 ↩