はじめに
前回の記事「watsonxのAPIを呼び出せるようになるまで」では、 curl
コマンドを利用してwatsonxのAPIを呼び出すことを紹介しましたが、今回の記事では Node.js
から呼び出す方法を紹介します。
本当はNode.js用のSDKが公開されていれば良いのですが、2023/8/3時点ではPythonしか無いので、Node使いとしてはSDK無しでの実装が必要です。
実行環境
- node: v18.17.0
- npm: 9.6.7
- os: windows 11
コード紹介
とりあえずコード全文。
Node.jsのv18から fetch API
が素で使えるようになったので、特にpackageにモジュールを追加していません。
ただ、v17以前の環境で動かす場合、 node-fetch
のモジュールで置き換えても動作することは確認済です。
(async() => {
const WML_APIKEY = `前回の記事で作成したAPIキー`;
const PROJECT_ID = 'watsonxのProject ID';
const iamEndpointUrl = 'https://iam.cloud.ibm.com/identity/token';
const iamRequestUrl = `${iamEndpointUrl}?grant_type=urn:ibm:params:oauth:grant-type:apikey&apikey=${WML_APIKEY}`
const options = {
method: 'POST',
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json",
},
}
try {
const iamResponse = await fetch(iamRequestUrl, options);
const iamJson = await iamResponse.json();
if(iamJson.access_token){
const accessToken = iamJson.access_token;
const watsonxEndpointUrl = 'https://us-south.ml.cloud.ibm.com/ml/v1-beta/generation/text?version=2023-05-29';
const watsonxBody = {
model_id: "ibm/mpt-7b-instruct2",
input: "入力:\\n日本の首都は?",
parameters: {
decoding_method: "greedy",
max_new_tokens: 20,
min_new_tokens: 0,
stop_sequences: [],
repetition_penalty: 1
},
project_id: PROJECT_ID
}
const watsonxOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${accessToken}`
},
body: JSON.stringify(watsonxBody),
}
const watsonxResponse = await fetch(watsonxEndpointUrl, watsonxOptions);
const watsonxJson = await watsonxResponse.json();
console.log(watsonxJson);
}
} catch (error) {
console.log(error);
}
})();
実行結果は、以下のような内容です。
C:\node-apps\watsonx-test> node index.js
{
model_id: 'ibm/mpt-7b-instruct2',
created_at: '2023-08-03T01:28:10.364Z',
results: [
{
generated_text: '\\n東京です。\\n東京は日本の首都',
generated_token_count: 20,
input_token_count: 11,
stop_reason: 'MAX_TOKENS'
}
]
}
何か、、、くどい回答ですね。
詰まったところ
当初は何度やっても以下のようなエラーが返ってきて、APIに渡してるJSONの先頭文字に o
なんて無いのにと悩んでいました。
{
errors: [
{
code: 'json_unmarshal_failed',
message: "Failed to deserialize json: invalid character 'o' looking for beginning of value"
}
],
trace: '90726e661564ae478594ef92f64b5d88',
status_code: 400
}
普段はSDKを用いてサービスのAPIを利用することに慣れているので、下記のようにAPIに渡したい内容を記述しがちです。
const watsonxBody = {
model_id: "ibm/mpt-7b-instruct2",
input: "入力:\\n日本の首都は?",
parameters: {
decoding_method: "greedy",
max_new_tokens: 20,
min_new_tokens: 0,
stop_sequences: [],
repetition_penalty: 1
},
project_id: PROJECT_ID
}
でも、これってよく考えるとJSONじゃなくてObjectですよね。
なのでfetchに何も考えずに渡すと [object object]
になってしまってるので、先頭文字のoは認識できないよってエラーになってたようです。気づくのに半日以上かけてしまいました。。。orz
なので、fetchにbodyを渡す際には JSON.stringify()
を利用して文字列にすることで、エラーが解消しました。
body: JSON.stringify(watsonxBody),
注意点
今回紹介したコードでは下記のように、都度IAMからアクセストークンを取得しています。
const iamResponse = await fetch(iamRequestUrl, options);
const iamJson = await iamResponse.json();
しかし、アクセストークンはデフォルトでは1時間再利用可能となっているので、都度発行するのは本来正しくありません。
常駐アプリケーションに組み込む際は、アクセストークンを発行してから1時間以内に再利用する場合は、既存のアクセストークンを流用するようにコードを書くのが良いです。
やり方はいくつかあると思いますが、例えば、アクセストークンを取得する関数を別で定義して、アクセストークンを取得した時に、そのアクセストークンとそのプログラムが稼働するプラットフォームの時刻情報を記録して、再度その関数が呼び出された時にプラットフォームの時刻を取得して、記録された時刻と1時間以内の差異なら記録されたアクセストークンを取得する、1時間以上あるならば再発行といったコードを書くのも1つの手かと思います。
さいごに
いかがでしたでしょうか?
Python使いの人も多いと思いますが、私のようにNodeばかり使ってる人でも、watsonxをプログラムから呼び出せることが証明できました。
ちょっとアプリから呼び出せるようにしてみようかな、とか、少しでもwatonsxの興味が広がれば幸いです。