Voltaとnodenvを切り替える

Voltaの案件とnodenvの案件を行き来することになったので切り替え方法のメモ。

環境

  • macOS M1
  • volta 1.0.5
  • nodenv 1.4.1

両方有効な場合の挙動

~/.zshrc 設定を見る:

# Volta設定
export VOLTA_HOME="$HOME/.volta"
export PATH="$VOLTA_HOME/bin:$PATH"

# nodenv設定
eval "$(nodenv init -)"

この場合、nodenvが優先され、voltaが機能しない(package.json"volta" フィールドを見てくれない)。

# nodenv設定
eval "$(nodenv init -)"

# Volta設定
export VOLTA_HOME="$HOME/.volta"
export PATH="$VOLTA_HOME/bin:$PATH"

ひっくり返してこうするとVoltaが優先になり、nodenvが機能しない(.node-version を見てくれない)。

切り替え方法

Voltaを使う場合

# Volta設定
export VOLTA_HOME="$HOME/.volta"
export PATH="$VOLTA_HOME/bin:$PATH"

# nodenv設定
#eval "$(nodenv init -)"

nodenv設定をコメントアウトする。

nodenvを使う場合

# Volta設定
#export VOLTA_HOME="$HOME/.volta"
#export PATH="$VOLTA_HOME/bin:$PATH"

# nodenv設定
eval "$(nodenv init -)"

Volta設定をコメントアウトする。

いちいちコメントアウトしなくても共存できればベストだが、いい方法が見つからなかったので一旦上記のようにしている。

CDKのデプロイ前にセキュリティの静的解析ができるcdk-nagを試す

cdk-nagを触ってみた。

Security HubやAWS Configを使った設定チェックはデプロイ後のチェックになるのに比べ、cdk-nagはデプロイ前に静的解析できるのが特長。違反しているとそもそもデプロイできないので開発メンバーに確実にルールを守ってもらえる。CIにも組み込みやすい。

環境

  • node 16.15.0
  • typescript 5.0.2
  • aws-cdk-lib 2.70.0
  • constructs 10.1.289
  • cdk-nag 2.23.5

サンプルコードを用意

こんな感じのAPIGW+Lambda定義を用意する。

// lib/apigateway-stack.ts
import { Construct } from "constructs";
import * as cdk from "aws-cdk-lib";

export class ApigatewayStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // APIGW Lambda関数
    const apiFn = new cdk.aws_lambda_nodejs.NodejsFunction(this, "apiFn", {
      runtime: cdk.aws_lambda.Runtime.NODEJS_18_X,
      entry: "src/lambda/api-handler.ts",
      bundling: {
        sourceMap: true,
      },
      timeout: cdk.Duration.seconds(29),
    });

    // APIGW
    const api = new cdk.aws_apigateway.RestApi(this, "api", {
      deployOptions: {
        tracingEnabled: true,
        stageName: "api",
      },
    });
    api.root.addProxy({
      defaultIntegration: new cdk.aws_apigateway.LambdaIntegration(apiFn),
    });
  }
}
// bin/aws-playground.ts
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { ApigatewayStack } from "../lib/apigateway-stack";

const region = "ap-northeast-1";
const app = new cdk.App();

new ApigatewayStack(app, "ApigatewayStack", {
  env: { region },
});

以下コマンドを実行:

npx cdk synth 

特にエラー発生せずCFn定義が出力される。

cdk-nag設定を加える

cdk-nagによるチェックをかぶせてみる。ルールパックは特に何も考えず AwsSolutionsChecks を適用。

// bin/aws-playground.ts
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { ApigatewayStack } from "../lib/apigateway-stack";
+ import { AwsSolutionsChecks } from "cdk-nag";

const region = "ap-northeast-1";
const app = new cdk.App();

new ApigatewayStack(app, "ApigatewayStack", {
  env: { region },
});

+ cdk.Aspects.of(app).add(new AwsSolutionsChecks());

コマンド実行:

npx cdk synth 

そうすると以下のエラーがずらずらと出てくる:

[Error at /ApigatewayStack/apiFn/ServiceRole/Resource] AwsSolutions-IAM4[Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole]: The IAM user, role, or group uses AWS managed policies.

[Error at /ApigatewayStack/api/Resource] AwsSolutions-APIG2: The REST API does not have request validation enabled.

[Error at /ApigatewayStack/api/CloudWatchRole/Resource] AwsSolutions-IAM4[Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs]: The IAM user, role, or group uses AWS managed policies.

[Error at /ApigatewayStack/api/DeploymentStage.api/Resource] AwsSolutions-APIG1: The API does not have access logging enabled.

[Warning at /ApigatewayStack/api/DeploymentStage.api/Resource] AwsSolutions-APIG3: The REST API stage is not associated with AWS WAFv2 web ACL.

[Error at /ApigatewayStack/api/DeploymentStage.api/Resource] AwsSolutions-APIG6: The REST API Stage does not have CloudWatch logging enabled for all methods.

[Error at /ApigatewayStack/api/Default/{proxy+}/ANY/Resource] AwsSolutions-APIG4: The API does not implement authorization.

[Error at /ApigatewayStack/api/Default/{proxy+}/ANY/Resource] AwsSolutions-COG4: The API GW method does not use a Cognito user pool authorizer.

[Error at /ApigatewayStack/api/Default/ANY/Resource] AwsSolutions-APIG4: The API does not implement authorization.

[Error at /ApigatewayStack/api/Default/ANY/Resource] AwsSolutions-COG4: The API GW method does not use a Cognito user pool authorizer.

Found errors

Errorに抑制もしくは対応していく(今回はWarningはスルー)。

エラーに対応する場合

[Error at /ApigatewayStack/api/DeploymentStage.api/Resource] AwsSolutions-APIG1: The API does not have access logging enabled.

[Error at /ApigatewayStack/api/DeploymentStage.api/Resource] AwsSolutions-APIG6: The REST API Stage does not have CloudWatch logging enabled for all methods.

まず、上記ルールに対してリソース定義を追加することで対応してみる(正攻法)。

    // 追加
    const apiLogAccessLogGroup = new cdk.aws_logs.LogGroup(
      this,
      "apiAccessLogGroup",
      {
        logGroupName: `/aws/apigateway/apiAccessLogGroup`,
        retention: 365,
      }
    );
    // APIGW
    const api = new cdk.aws_apigateway.RestApi(this, "api", {
      deployOptions: {
        tracingEnabled: true,
        stageName: "api",
        // 追加
        loggingLevel: cdk.aws_apigateway.MethodLoggingLevel.INFO,
        accessLogDestination: new cdk.aws_apigateway.LogGroupLogDestination(
          apiLogAccessLogGroup
        ),
        accessLogFormat: cdk.aws_apigateway.AccessLogFormat.clf(),
      },
    });

スタックレベルでエラー抑制する場合

続いて抑制する場合。

[Error at /ApigatewayStack/api/Default/{proxy+}/ANY/Resource] AwsSolutions-COG4: The API GW method does not use a Cognito user pool authorizer.

[Error at /ApigatewayStack/apiFn/ServiceRole/Resource] AwsSolutions-IAM4[Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole]: The IAM user, role, or group uses AWS managed policies.

[Error at /ApigatewayStack/api/Resource] AwsSolutions-APIG2: The REST API does not have request validation enabled.

上記ルールに対してスタックレベルで抑制をかける。

const apigatewayStack = new ApigatewayStack(app, "ApigatewayStack", {
  env: { region },
});
// 追加
NagSuppressions.addStackSuppressions(apigatewayStack, [
  {
    id: "AwsSolutions-COG4",
    reason: "本サービスではCognitoを使用しない",
  },
  {
    id: "AwsSolutions-IAM4",
    reason: "本サービスではAWS管理ポリシーをアタッチしても良い",
  },
  {
    id: "AwsSolutions-APIG2",
    reason:
      "本サービスではリクエストバリデーションはLambda関数のロジックで行う",
  },
]);

エラー内容を厳密に確認したわけではないので、上記理由が要領を得ていないかもしれない可能性がある(その場合すみませんがコメントください)。

リソースレベルでエラー抑制する場合

[Error at /ApigatewayStack/api/Default/ANY/Resource] AwsSolutions-APIG4: The API does not implement authorization.

上記ルールに対してリソースレベルで抑制をかける。

    api.root.addProxy({
      defaultIntegration: new cdk.aws_apigateway.LambdaIntegration(apiFn),
    });
    // 追加
    NagSuppressions.addResourceSuppressions(
      api,
      [
        {
          id: "AwsSolutions-APIG4",
          reason: "本APIには認証機能を実装しない",
        },
      ],
      // 子リソースにも適用する
      true
    );

以上で

npx cdk cynth

を実行するとエラー発生せずCFn定義出力できた。

参考

Node.jsでGPT-4 APIを使う

届いたメール:

You're invited to use the OpenAI GPT-4 API!
You can now access GPT-4 models with 8K context via the existing OpenAI API.

Get started
As an early customer we'd love to hear about your experience. Feel free to share feedback on our community forum or reach out directly to our team.

Best,
The OpenAI team

OpenAIにGPT-4 APIのウェイトリスト登録をして数日、 Your GPT-4 API invite is here メールが来たので早速試してみた。

  1. 3月15日(水) 11:07 APIウェイトリスト登録
  2. 3月18日(土) 2:37 API使用可能メールが来る

4日くらい放置してた。

環境

  • dotenv 16.0.3
  • esbuild 0.17.11
  • esbuild-register 3.4.2
  • typescript 4.9.5
  • openai 3.2.1

使い方

モデル指定を gpt-4 にするだけっぽい。

- model: "gpt-3.5-turbo",
+ model: "gpt-4",

コード

.env ファイルにAPIキーをセットしている:

OPEN_AI_API_KEY=sk-xxxxxxxxxxxxx

TypeScriptコード:

import { Configuration, OpenAIApi } from "openai";
import * as dotenv from "dotenv";

dotenv.config();

const openAiApi = new OpenAIApi(
  new Configuration({
    apiKey: process.env.OPEN_AI_API_KEY!,
  }),
);

// 実行
const main = async () => {
  // GPT-3.5
  const completion1 = await openAiApi.createChatCompletion({
    model: "gpt-3.5-turbo",
    messages: [
      {
        role: "user",
        content: "こんにちは。元気ですか?",
      },
    ],
  });
  const chatGptMessage1 = completion1.data.choices[0].message!.content;
  console.log(chatGptMessage1); // => こんにちは。私はAIですので、元気です。ありがとうございます。

  // GPT-4
  const completion2 = await openAiApi.createChatCompletion({
    model: "gpt-4",
    messages: [
      {
        role: "user",
        content: "こんにちは。元気ですか?",
      },
    ],
  });
  const chatGptMessage2 = completion2.data.choices[0].message!.content;
  console.log(chatGptMessage2); // => こんにちは!私はAIアシスタントなので、感情はありませんが、お手伝いする準備ができています。あなたは元気ですか?
};

main();

実行コマンド:

node -r esbuild-register main.ts

出力:

# GPT-3.5
こんにちは。私はAIですので、元気です。ありがとうございます。

# GPT-4
こんにちは!私はAIアシスタントなので、感情はありませんが、お手伝いする準備ができています。あなたは元気ですか?

参考

App RunnerにExpressアプリをデプロイする

ECR+App RunnerをCDKデプロイする - dyoshikawa’s blog

をさらに実戦に近づけるために、今度はNode.js Expressアプリをデプロイしてみる。

環境

  • M1 Mac Big Sur
  • node 16.15.0
  • typescript 5.0.2
  • aws-cdk-lib 2.69.0
  • constructs 10.1.281
  • @aws-cdk/aws-apprunner-alpha 2.69.0-alpha.0
  • cdk-docker-image-deployment 0.0.195
  • esbuild 0.17.12

コード

  1. esbuildでバンドルJSファイル生成
  2. 1のバンドルファイルを入れたDockerイメージをビルドする
  3. 2のDockerイメージをECRリポジトリに上げる
  4. 3のECRリポジトリを参照するApp Runnerサービスを起動

という方針。

CDKコード:

// lib/app-runner-stack-express.ts
import { Construct } from "constructs";
import * as cdk from "aws-cdk-lib";
import * as apprunner from "@aws-cdk/aws-apprunner-alpha";
import * as imagedeploy from "cdk-docker-image-deployment";

export class AppRunnerExpressStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const repository = new cdk.aws_ecr.Repository(this, "expressRepository", {
      repositoryName: "express-repository2",
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    new imagedeploy.DockerImageDeployment(this, "imageDeploy", {
      source: imagedeploy.Source.directory("./src/app-runner-express"),
      destination: imagedeploy.Destination.ecr(repository, {
        tag: "latest",
      }),
    });

    const service = new apprunner.Service(this, "apprunnerService", {
      source: apprunner.Source.fromEcr({
        imageConfiguration: { port: 3000 },
        repository,
        tagOrDigest: "latest",
      }),
    });

    new cdk.CfnOutput(this, "serviceUrl", {
      exportName: "serviceUrl",
      value: service.serviceUrl,
    });
  }
}

Dockerfile:

# src/app-runner-express/Dockerfile
FROM --platform=linux/amd64 node:18-slim

WORKDIR /app

COPY main.js main.js

CMD [ "node", "main.js" ]

Expressアプリケーションコード:

import express, { Request, Response } from "express";

const app = express();
const port = 3000;

app.get("/", (req: Request, res: Response) => {
  res.send("Hello, World!");
});

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});

package.json:

  // package.json
  "scripts": {
    "build:app-runner": "esbuild src/app-runner-express/main.ts --platform=node --target=node18 --bundle --minify --outfile=./src/app-runner-express/main.js",
  },

esbuildでアプリケーションTSコードをJSファイルにバンドルするnpm scriptsを登録しておく。

あとはこんな感じでビルドとデプロイできる:

npm run build:app-runner && npx cdk deploy

ハマった点

最初、バンドルファイルを {PROJECT_ROOT}/dist/app-runner-express/main.js に吐くようにしていた:

  // package.json
  "scripts": {
    "build:app-runner": "esbuild src/app-runner-express/main.ts --platform=node --target=node18 --bundle --minify --outfile=./dist/app-runner-express/main.js",
  },

そしてDockerfileのCOPYは次のようにしていた:

# src/app-runner-express/Dockerfile
COPY ../../dist/app-runner-express/main.js main.js

これで docker build src/app-runner-express すると

 => ERROR [3/3] COPY ../../dist/app-runner-express/main.js main.js                                                                         0.0s
------
 > [3/3] COPY ../../dist/app-runner-express/main.js main.js:
------
failed to compute cache key: failed to walk /var/lib/docker/tmp/buildkit-mountxxxxxxx/dist/app-runner-express: lstat /var/lib/docker/tmp/buildkit-mountxxxxxxxx/dist/app-runner-express: no such file or directory

とエラー。

Dockerビルド時のContext設定を合わせることで解決できるようだったが、今回はシンプルにDockerfileの隣にバンドルファイルを吐くようにすることで対応した。

  // package.json
  "scripts": {
-   "build:app-runner": "esbuild src/app-runner-express/main.ts --platform=node --target=node18 --bundle --minify --outfile=./dist/app-runner-express/main.js",
+   "build:app-runner": "esbuild src/app-runner-express/main.ts --platform=node --target=node18 --bundle --minify --outfile=./src/app-runner-express/main.js",
  },
# src/app-runner-express/Dockerfile
- COPY ../../dist/app-runner-express/main.js main.js
+ COPY main.js main.js

参考

ECR+App RunnerをCDKデプロイする

[アップデート] AWS App Runner でついに AWS WAF がサポートされました | DevelopersIO

自分は圧倒的にLambdaマンだが、WAF対応もしたということで本番投入機会が増えていきそうなApp Runnerをキャッチアップしてみる。CDKで素振りする。

DockerのhttpdイメージをApp Runnerにデプロイしてみる。

環境

  • M1 Mac Big Sur
  • node 16.15.0
  • typescript 5.0.2
  • aws-cdk-lib 2.69.0
  • constructs 10.1.281
  • @aws-cdk/aws-apprunner-alpha 2.69.0-alpha.0
  • cdk-docker-image-deployment 0.0.195

コード

aws-cdk-lib 以外のところで以下を利用する:

両方とも公式もしくは準公式のパッケージなので採用で問題ないはず。

CDKコード:

// lib/app-runner-stack.ts
import { Construct } from "constructs";
import * as cdk from "aws-cdk-lib";
import * as apprunner from "@aws-cdk/aws-apprunner-alpha";
import * as imagedeploy from "cdk-docker-image-deployment";

export class AppRunnerStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const repository = new cdk.aws_ecr.Repository(this, "httpdRepository", {
      repositoryName: "httpd-repository",
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    new imagedeploy.DockerImageDeployment(
      this,
      "exampleImageDeploymentWithTag",
      {
        source: imagedeploy.Source.directory("./src/app-runner"),
        destination: imagedeploy.Destination.ecr(repository, {
          tag: "latest",
        }),
      }
    );

    const service = new apprunner.Service(this, "apprunnerService", {
      source: apprunner.Source.fromEcr({
        imageConfiguration: { port: 80 },
        repository,
        tagOrDigest: "latest",
      }),
    });

    new cdk.CfnOutput(this, "ServiceUrl", {
      exportName: "ServiceUrl",
      value: service1.serviceUrl,
    });
  }
}

Dockerfile:

# src/app-runner/Dockerfile
FROM --platform=linux/amd64 httpd

M1 Macなので --platform=linux/amd64 を付ける必要がある(ないと後述のエラーが発生)。

ハマった点

上記コードに行き着くまでに結構ハマっている。App Runnerのデプロイ毎に数分かかることもあり、高速で試行を回しづらく時間を溶かしてしまった。

まず、最初にCDKデプロイしようとすると下記エラーに遭遇:

# CDKデプロイ時エラー
Resource handler returned message: "null" (RequestToken: xxxxxxx-xxxxxxxxx-xxxxxxxx, HandlerErrorCode: null)

詳細な内容がなく、これだと原因がわからない。

こういうときはInfrastructure as Codeだけでなんとかしようとするのはやめてマネコンからリソースを作ってみるに限る、ということでマネコンからApp Runnerをデプロイ。

アプリケーションログにおいて以下のエラーが発生していることが判明:

# アプリケーションログ
exec /usr/local/bin/httpd-foreground: exec format error

調べたところ、M1 MacでDockerイメージをビルドした際に起こる問題の模様。

【GCP】「exec user process caused: exec format error」というエラーを解決!【GKE】|HikariBlog

ここ数年Lambda一辺倒過ぎて最近(M1なんてもう最近でもないけど)のDocker事情をキャッチアップできてなかった。

最初は --platform=linux/amd64 を付けていなかったため、付与してこの点は解決。

- FROM httpd
+ FROM --platform=linux/amd64 httpd

で、またデプロイしようとすると再度エラー。今度はイベントログ:

# イベントログ
[AppRunner] Deployment with ID : xxxxxxxxxxxxxxx failed. Failure reason : Health check failed.
[AppRunner] Health check failed on port '8080'. Check your configured port number. For more information, read the application logs.

エラーによると「指定してるポート違うんじゃない?」とのことなので、見直して気づく。 8080 にしてしまっていたので 80 に直した。

- imageConfiguration: { port: 8080 }
+ imageConfiguration: { port: 80 },

以上でAWSマネコン操作でもCDKデプロイでも正常に起動できるようになった。

参考

ChatGPT APIで入力値に暴力的な表現が含まれていないかどうかをバリデーションする。さらに条件分岐する

ChatGPT APIを使うことで、これまで難しかった「ふわっとした」バリデーションができることに気づいた。

「${content}」は暴力的な表現が含まれますか?3文字以内で、「はい」か「いいえ」で答えて下さい。`

と聞くことで「はい」か「いいえ」が返ってくるので、「はい」の場合は true 「いいえ」の場合は false を返す関数を書ける(3文字以内縛りを命じないと、蛇足的に長文説明されることがある)。

環境

  • node 16.13.0
  • typescript 4.9.5
  • openai 3.2.1

入力値に暴力的な表現が含まれていないかどうかを判定するコード

ニュースサイト、PS4/PS5のファンメ、コメントやユーザ間のやり取りが発生するあらゆるWebサービス・・・ユーザによる暴言コメントを弾きたいニーズは多そう。これを実現するために、従来であれば膨大な「暴言単語」の辞書を作る必要があったはず。

ChatGPT APIを使えば一瞬で暴言のバリデーションができる。

import { Configuration, OpenAIApi } from "openai";
import * as dotenv from "dotenv";

dotenv.config();

// 暴力的な表現かどうかを判定する関数
const hasViolentContent = async (content: string) => {
  const openAiApi = new OpenAIApi(
    new Configuration({
      apiKey: process.env.OPEN_AI_API_KEY!,
    }),
  );

  const completion = await openAiApi.createChatCompletion({
    model: "gpt-3.5-turbo",
    messages: [
      {
        role: "user",
        content: `「${content}」は暴力的な表現が含まれますか?3文字以内で、「はい」か「いいえ」で答えて下さい。`,
      },
    ],
  });
  const chatGptMessage = completion.data.choices[0].message!.content;
  console.log(chatGptMessage);
  return chatGptMessage.includes("はい");
};

// 実行
const main = async () => {
  console.log(await hasViolentContent("こんにちは")); // => false
  console.log(await hasViolentContent("このゴミカス野郎!")); // => true
};

main();

走らせたところサンプル入力に対しては意図通り動作した。便利。

あらゆるサービスに実装されるとちょっとディストピア感はある。実装すべきかはサービスの性質にもよりそう。

入力値が現代の著名人かどうかを判定するコード

もう一個例を出してみる。あなたがAIのSNSボットを作れるサービスを開発しているとする。このサービスのユーザに、存命している有名人の名前のボットを作って遊ばれることは、ボットにされた本人に迷惑がかかるのはもちろん、荒れる原因となり事業リスク上好ましくない。一方、歴史上の人物や架空の人物は良しとしたい1

これも今までは、上記のようなバリデーションをしようと思ったら、この用途にかなう人名検索APIのようなサービスを探すか、なければ自前で膨大な人名リストを用意する必要があったと思う2

で、やはりChatGPT APIだと一瞬で実装できる。

import { Configuration, OpenAIApi } from "openai";
import * as dotenv from "dotenv";

dotenv.config();

// 現代の著名人かどうかを判定する関数
const isModernFamousPerson = async (personName: string) => {
  const openAiApi = new OpenAIApi(
    new Configuration({
      apiKey: process.env.OPEN_AI_API_KEY!,
    }),
  );

  const completion = await openAiApi.createChatCompletion({
    model: "gpt-3.5-turbo",
    messages: [
      {
        role: "user",
        content: `${personName}は現代の著名人ですか?3文字以内で、「はい」か「いいえ」で答えて下さい。`,
      },
    ],
  });
  const chatGptMessage = completion.data.choices[0].message!.content;
  console.log(chatGptMessage);
  return chatGptMessage.includes("はい");
};

// 実行
const main = async () => {
  console.log(await isModernFamousPerson("岸田総理")); // => true
  console.log(await isModernFamousPerson("岸田文雄")); // => true
  console.log(await isModernFamousPerson("織田信長")); // => false
  console.log(await isModernFamousPerson("涼宮ハルヒ")); // => false
};

main();

感想

以上、ChatGPT APIで抽象的な指示のバリデーションができるという話。どうしても100%の精度は出ないだろう3が、割り切れるサービスであれば実用可能なレベルと思う。

現代の著名人かどうかの判定について、「きしだ総理」の表記ゆれを試したところ現状 false を返してしまうようだった。人名によって表記ゆれに対応できる場合とない場合がありそう。ただ、ここは精度が上がってくると期待できる気がする。

ただ、プロンプトインジェクションでくぐり抜けられそうな気もするので、後日プロンプトインジェクション対策のブログも書こうと思う。


  1. 実際のところ、この場合も著作権や何らかの権利上問題になる可能性はあるが、あくまで仮定の話としてそういう意思決定をしたとする。
  2. あるいはwikipediaをクローリングする?そもそもクローリングがグレーなのと、wikipedia利用規約上大丈夫なのかも不明。
  3. そもそもの指示的に「どこから暴言か」「どこから現代か、どこから著名か」に明確な基準を引くことが難しい。

LlamaIndex(GPT Index)を試す

jerryjliu/gpt_index: LlamaIndex (GPT Index) is a project that provides a central interface to connect your LLM's with external data.

LlamaIndex(GPT Index)を使うことで、大量の独自知識を詰め込んだオリジナルChatGPTを作る的なことができるらしい(?)ので素振りしてみる。

環境

インストール

普段全くPython書かないマンなのでライブラリの入れ方からドキュメントを見る。パッケージマネージャにpoetryとか使ったほうが良い気がするが、とりあえずスピード重視でREADME通りpip installする。

pip3 install llama-index

しばらく待つと、

ERROR: Command errored out with exit status 1:

無事こける。Warningでpipが古いと出ていた(WARNING: You are using pip version 19.2.3, however version 23.0.1 is available.)ので上げてみる。

pip3 install --upgrade pip

再チャレンジすると別のエラー。

error: package `rayon-core v1.11.0` cannot be built because it requires rustc 1.59 or newer, while the currently active rustc version is 1.58.1

rustcのバージョンが古いようなのでこちらも上げる。

rustup update

rustc 1.67.1 になった。

さらに再チャレンジ。今度はいくつかWarningは出たが通った。

OpenAI APIキーとdirenv設定

OpenAI APIキーを払い出す。

APIキーを .py ファイルにハードコードしたくないので、direnvを使って環境変数セットする。

touch .envrc

ファイルを以下のように編集する。

export OPENAI_API_KEY='sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxx'

以下コマンドを実行。

direnv allow

LlamaIndexのコードを書いて動かす

つい最近自分が書いた記事を読み込ませて質問するコードを書いて実行する

from llama_index import GPTSimpleVectorIndex, SimpleWebPageReader

documents = SimpleWebPageReader().load_data(['https://dev.classmethod.jp/articles/chatgpt-api-line-bot-aws-serverless/'])
index = GPTSimpleVectorIndex(documents)
print(index.query("ChatGPT APIでLINEボットを作るにあたり、文脈を保持するにはどうすればいい?")) # 質問をここに書く
python3 main.py

するとエラー。

ValueError: `html2text` package not found, please run `pip install html2text`

html2textパッケージをインストールする:

pip3 install html2text

再実行し、下記のエラー。

INFO:openai:error_code=None error_message='You exceeded your current quota, please check your plan and billing details.' error_param=None error_type=insufficient_quota message='OpenAI API error received' stream_error=False

Error Code 429 - You exceeded your current quota, please check your plan and billing details. | OpenAI Help Center

これはOpenAIで支払い設定してないことが原因っぽい。

Billing Overviewから支払い方法の設定をする。使いすぎることがないよう、Usage limitsからHardLimitを20ドルにしておく(何これすごい。AWSにもこの機能つけてほしい)。

ところがまだ同じエラーが出る。OpenAI APIキーを作り直してみた。

それからまた再実行すると、

INFO:root:> [query] Total LLM token usage: 4363 tokens
INFO:root:> [query] Total embedding token usage: 44 tokens

ChatGPT APIでLINEボットを作るにあたり、文脈を保持するには、DynamoDBを使用して会話の履歴を保存し、OpenAIからリリースされたChatGPTとWhisperのAPIを使用して、会話履歴を把握しながら回答する必要があります。

おおおおお。言ってることは若干不自然だけどポイントは抑えられていそう。

今日はここまで。

参考