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?

【AWS】【Bedrock】同じ依頼を API・モデル・リージョンを変えて投げ比べた(AIP 基礎編)

0
Posted at

変更履歴

  • 2026-11-03:公開

初めに

年内に AWS Certified Generative AI Developer – Professional(AIP-C01)を受ける。基盤モデル(FM)をアプリや業務に組み込み、本番の生成 AI を実装・運用できるかを測る Professional 級の試験で、出題は 75 問・180 分の選択式。範囲は Amazon Bedrock を中心に、データの扱い・安全とガバナンス・運用・評価まで広い(試験ガイド。2026-10-20 確認)。

出題分野は 5 つで、配点は以下の通り。

    1. 基盤モデルの統合・データ管理・コンプライアンス:31%
    1. 実装と統合:26%
    1. AI の安全・セキュリティ・ガバナンス:20%
    1. 運用の効率と最適化:12%
    1. テスト・検証・トラブルシュート:11%

その対策として、出題範囲のサービスを実際に動かす「AIP 基礎編」を全 7 本で書いていく。この記事はその 1 本目。2 本目以降の題は仮。

# テーマ 型 主な分野
1 基盤モデルの呼び出し方と選び方(本記事) 横断比較 1・2
2 Knowledge Bases で作る RAG 深掘り 1・5
3 Guardrails で防げること・防げないこと 深掘り 3・5
4 エージェントの組み方(Strands Agents・MCP・Step Functions・Flows) 横断比較 2・3
5 生成 AI に渡すデータの前処理と抽出 横断比較 1・2・3
6 推論のコスト・レイテンシ・ログ 横断比較 2・4・5
7 RAG と基盤モデルの評価 横断比較 3・5

この構成にしたのは、試験の中心が「要件に合うサービスや構成を選ぶ」問題で、しかも範囲が広いため。1 つのサービスを深く知るより、似た手段の違いを言えるほうが点になる。そこで、似た手段を 1〜3 回ずつ動かして使い分けをまとめる横断比較を中心にした。設定の違いで結果が大きく変わる RAG と Guardrails の 2 本だけは、条件をそろえて深く比べる。動かしている間ずっと料金がかかる SageMaker AI のエンドポイントや OpenSearch Serverless は動かさず、公式の文書で押さえる。各記事の最後には、要件ごとの選び方(試験の論点)を置く。

Amazon Bedrock で FM を呼ぶ方法は、API・モデル・処理するリージョンの組み合わせで何通りもある。試験で問われるのは「この要件ならどれを選ぶか」なので、同じ依頼を条件を変えて送り比べた。検証は 2026-11-03、東京リージョン(ap-northeast-1)、Python の boto3 1.43.114 で行った。

結論

API・答えの返し方・出力の形の縛り方・呼び先を変えて、同じ依頼を送り比べた。使い分けは次の 4 点にまとまった。

  • 複数のモデルを比べる・替えるなら Converse。InvokeModel は依頼と応答の JSON の形がモデルごとに違い、Converse は同じコードで Nova と Mistral の両方を呼べた(3 章)。
  • 答えを早く見せるならストリーミング。最初の文字は 0.3〜0.4 秒で届いた(4 章)。
  • 決まった形で取り出すなら、API で形を縛る。プロンプトで頼むだけでは 5 回中 1 回崩れた。ただし Nova は構造化出力に対応していない(5 章)。
  • 呼び先と機能は、先にモデルの対応を見る。東京では、Nova Micro をモデルの ID だけでは呼べなかった(7 章)。

構造化出力・推論プロファイル・プロンプトルーターは、モデルによって使えるかどうかが決まった。

Prompt Management・プロンプトルーター・AppConfig・サービスティアの結果は 6 章と 8〜10 章に、要件ごとの選び方は 11 章にまとめた。

本記事の構成

1 章で Bedrock で FM を呼ぶ方法の全体、2 章で検証環境を説明する。3〜10 章が手段ごとの結果で、11 章に要件ごとの選び方(試験の論点)をまとめた。

検証の全体像を図1に示す。呼び出しはすべて東京リージョンから行い、API・modelId に入れる呼び先・処理するリージョンの組み合わせを変えて比べた。箱の中の番号は、その結果を書いた章を指す。

図1 検証の全体像

1. Bedrock で FM を呼ぶ方法

本記事で比べるのは、テキストの会話に使う API の 4 つ。

  • InvokeModel と InvokeModelWithResponseStream:モデルごとの形の JSON を body に入れる。前者はまとめて返し、後者は少しずつ返す(ストリーミング)。
  • Converse と ConverseStream:どのモデルでも同じ形(messages)で書く。前者はまとめて返し、後者は少しずつ返す。

Converse は、メッセージを扱うすべてのモデルに同じ形で書ける API で、ツール呼び出しやガードレールもここで指定する(公式)。モデル固有の値は additionalModelRequestFields で渡す。

API を決めたら、次に呼び先(modelId)を決める。構造化出力やツールは、呼び出しに付ける設定になる。今回使った呼び先の指定は 2 通り。

  • 基盤モデルの ID を直接指定する(例:mistral.ministral-3-8b-instruct)。呼んだリージョンで処理する。以下、単一リージョンの呼び出しと書く。
  • クロスリージョン推論プロファイルの ID を指定する(例:jp.amazon.nova-2-lite-v1:0)。決まった複数のリージョンのどこかで処理する。この振り分けを Cross-Region Inference と呼ぶ。

推論プロファイルには、利用料を用途ごとに分けるために自分で作るアプリケーション推論プロファイルもあるが、今回は使っていない。modelId には、ほかにプロンプトルーターや Prompt Management のプロンプトの ARN も入る(6 章・8 章)。

2. 検証環境

使ったモデルは以下の通り。

  • Amazon Nova 2 Lite(jp.amazon.nova-2-lite-v1:0):主に使ったモデル。推論プロファイル経由で呼ぶ。
  • Mistral AI の Ministral 3 8B(mistral.ministral-3-8b-instruct):API の形の比較・構造化出力・サービスティアで使った。東京の単一リージョンで呼ぶ。
  • Qwen3 32B(qwen.qwen3-32b-v1:0):構造化出力で使った。東京の単一リージョンで呼ぶ。
  • Amazon Nova Micro(apac.amazon.nova-micro-v1:0):推論プロファイルの比較と、モデルの切り替え先に使った。
  • Amazon Nova Lite と Nova Pro:既定の Nova Prompt Router が振り分ける先。

依頼には、架空の会社への問い合わせ文を 3 つ使った(ノートPC の故障・プリンタのインクの配送先の変更・クラウド会計の取り込みエラー)。どれも 150 字ほどの日本語。

3. InvokeModel と Converse

まず気になったのは、モデルを替えたときにコードのどこが変わるか。「次の問い合わせを、要件・期限・対応の希望の 3 行で要約してください」という同じ依頼を、Nova 2 Lite と Ministral 3 8B に 2 つの API で送った。boto3 では、どちらの API も bedrock-runtime のクライアントから呼ぶ。

InvokeModel の body は、モデルごとに形が違う。

invoke_vs_converse.py
rt = boto3.client("bedrock-runtime", region_name="ap-northeast-1")

BODIES = {
    "jp.amazon.nova-2-lite-v1:0": {
        "schemaVersion": "messages-v1",
        "messages": [{"role": "user", "content": [{"text": PROMPT}]}],
        "inferenceConfig": {"maxTokens": 300},
    },
    "mistral.ministral-3-8b-instruct": {
        "messages": [{"role": "user", "content": PROMPT}],
        "max_tokens": 300,
    },
}

応答の形も違った。Nova は output.message.content[0].text に答えが入り、Ministral は choices[0].message.content に入る(OpenAI の API と同じ形)。

Converse は、modelId を替えるだけで両方を呼べた。

invoke_vs_converse.py
resp = rt.converse(
    modelId=model_id,  # Nova でも Ministral でも同じ
    messages=[{"role": "user", "content": [{"text": PROMPT}]}],
    inferenceConfig={"maxTokens": 300},
)
text = resp["output"]["message"]["content"][0]["text"]

応答の usage(入力と出力のトークン数)・metrics.latencyMs(Bedrock 側が返す処理時間)・stopReason(止まった理由)も、どちらのモデルでも同じ場所に返る。モデルを替える前提の用途なら、自分は Converse を使う。

4. 同期とストリーミング

次は、画面に答えを出す速さ。問い合わせ(クラウド会計の取り込みエラー)への返信の下書き(300 字ほど)を Nova 2 Lite に書かせ、4 つの API でそれぞれ 3 回続けて送った。出力の上限は 500 トークン。

時間は手元のスクリプトで測った。呼び出しの直前から、空でない最初の文字を受け取るまでと、応答を全部受け取るまで。同期の API は全部そろってから返すので、2 つの時間は同じになる。

API 最初の文字まで [s] 全部が届くまで [s] 出力トークン数
Converse 3.19・3.27・3.56 3.19・3.27・3.56 500・500・500
ConverseStream 0.38・0.42・0.32 2.09・3.07・2.70 292・500・387
InvokeModel 2.28・2.63・2.39 2.28・2.63・2.39 307・392・386
InvokeModelWithResponseStream 0.34・0.33・0.36 2.41・3.19・2.24 記録していない

最初の文字は、ストリーミングなら 0.3〜0.4 秒で届いた。チャットの画面なら、書き始めをすぐ見せられる。全部が届くまでの時間は、出力の長さが回ごとに違う(292〜500 トークン)ので、この表から API ごとの速さの差は言えない。

5. ツール呼び出しと構造化出力

モデルの答えをプログラムで扱うには、外の処理を呼ばせる手段と、出力の形を縛る手段がある。

ツール呼び出し

Nova 2 Lite に、Converse の toolConfig で「型番を受け取って在庫と出荷日を返す」ツールを 1 つ渡し、「ノートPC『LX-500』の代替機をすぐ出せるか」と聞いた。ツールの中身は、自分のスクリプトの中で固定の在庫(3 台・11/7 出荷)を返す作り物にした。

1 往復目は、答えの代わりに stopReason: tool_use が返り、ツールの名前と入力({"model_number": "LX-500"})が入っていた。モデルは自分ではツールを動かさない。呼び出す側のコードが在庫を調べ、結果を toolResult として返すと、2 往復目で「在庫は 3 台あり、出荷予定日は 2026 年 11 月 7 日」と答えた。

ツールは外の処理を呼ぶための仕組みだが、ツールに渡す引数の形を決められる。そのため、出力の形を縛る用途にも使える(下の (b))。

出力の形を縛る

問い合わせから名字・製品名・緊急度を JSON で取り出させる依頼を、次のやり方で送り比べた。

  • (a) プロンプトで「JSON だけを返して」と頼む
  • (b) ツールの入力の形(inputSchema)で縛り、そのツールを必ず使わせる(toolChoice)
  • (c) 構造化出力(outputConfig の JSON Schema1)で縛る

使ったスキーマは以下の通り。キーは 3 つで、緊急度は「高・中・低」のどれかに限り、ほかのキーは許さない。

structured_output.py
EXTRACT_SCHEMA = {
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "product": {"type": "string"},
        "urgency": {"type": "string", "enum": ["高", "中", "低"]},
    },
    "required": ["name", "product", "urgency"],
    "additionalProperties": False,
}

構造化出力の指定は以下の通り。スキーマは JSON の文字列にして渡す。

structured_output.py
resp = rt.converse(
    modelId=model_id,
    messages=[{"role": "user", "content": [{"text": prompt}]}],
    outputConfig={"textFormat": {"type": "json_schema", "structure": {"jsonSchema": {
        "name": "inquiry_extract",
        "schema": json.dumps(EXTRACT_SCHEMA),
    }}}},
)

やり方とモデルの組ごとに 5 回呼んだ。入力は 3 つの問い合わせを順に回し、5 回の内訳はノートPC・インク・会計・ノートPC・インク。判定は、答えから { から } までを取り出して JSON として読み、キーが 3 つちょうどで、緊急度が高・中・低のどれかなら形どおりとした。Qwen3 32B は、構造化出力を試す 2 つ目のモデルとして (c) だけで使った。

形どおりにならなかった回数(5 回のうち)は以下の通り。

やり方 Nova 2 Lite Ministral 3 8B Qwen3 32B
(a) プロンプトで頼む 0 1 —
(b) ツールの形で縛る 0 0 —
(c) 構造化出力 対応していない 0 0

(a) の Ministral は、1 回だけ {"response": [{…}]} と配列で包んで返した。また (a) は両モデルとも、5 回とも答えを ```json の囲みに入れて返した。判定では囲みを外して読んだが、そのまま JSON として使うなら、取り出す処理が要る。

Nova 2 Lite で (c) を試すと、エラーになった。

ValidationException: This model doesn't support the outputConfig field. Remove outputConfig and try again.

構造化出力に対応するのは、Anthropic・Mistral AI・Qwen・OpenAI など 9 社のモデルで、Amazon の Nova は入っていない(AWS のブログ。2026-11-03 確認)。Nova で形を縛るなら、(b) のツールの形を使う。

ただし、通常のツール呼び出しは、引数がスキーマに合うことを保証するものではない。保証が要るなら、対応するモデルでツールの定義に strict: true を付けるか(公式)、受け取った側でも検証する。今回の 0 回は 5 回の範囲の結果。

形を縛っても、中身の判断はモデル次第。Qwen3 32B は、急ぎではないインクの配送先の変更を「高」と判定した(形は崩れていない)。

6. Prompt Management

プロンプトの文面をコードから外に出し、バージョンで管理する機能。変数を {{inquiry}} のように書いておき、呼ぶときに値を入れる。

fmlab-summary というプロンプトを作った。モデルには Nova 2 Lite の推論プロファイルの ID(jp.amazon.nova-2-lite-v1:0)をそのまま指定できた。バージョン 1 は「3 行で要約」、バージョン 2 は「要件・期限・次にやることの箇条書き」にしている(図2・図3)。

図2 プロンプトのドラフトとバージョンの一覧

図3 バージョン 1 と 2 の差分

呼ぶときは、Converse の modelId にバージョンつきの ARN(末尾が :1 や :2)を入れ、変数の値を promptVariables で渡す。messages は渡さなくてよかった。

prompt_management.py
resp = rt.converse(
    modelId="arn:aws:bedrock:ap-northeast-1:<アカウント ID>:prompt/<プロンプト ID>:2",
    promptVariables={"inquiry": {"text": INQUIRIES[0]}},
)

バージョン 1 は文章の要約、バージョン 2 は 3 項目の箇条書きが返った。

7. 推論プロファイルと Cross-Region Inference

推論プロファイルには、処理するリージョンの範囲が決まっている。

  • jp.amazon.nova-2-lite-v1:0:東京と大阪(図4)
  • global.amazon.nova-2-lite-v1:0:商用のすべてのリージョン
  • apac.amazon.nova-micro-v1:0:アジア太平洋の 6 リージョン

図4 JP の推論プロファイル(処理するリージョンは東京と大阪)

単一リージョンで呼べないモデル

東京から Nova Micro を、推論プロファイルを通さずにモデルの ID(amazon.nova-micro-v1:0)で呼ぶと、以下のエラーが返った。

ValidationException: Invocation of model ID amazon.nova-micro-v1:0 with on-demand throughput isn't supported. Retry your request with the ID or ARN of an inference profile that contains this model.

今回東京で試したモデルでは、Nova 2 Lite と Nova Micro は推論プロファイル経由でしか呼べず、Ministral 3 8B と Qwen3 32B は単一リージョンで呼べた。モデルとリージョンごとに違うので、使う前にモデルの説明のページで確かめる。コンソールのプレイグラウンドでモデルを選ぶと、この違いが「推論」の欄に出る(図5・図6)。

図5 Nova 2 Lite は推論プロファイル(GLOBAL と JP)しか選べない

図6 Ministral 3 8B はオンデマンド(単一リージョン)

jp. と global. の違い

3 章と同じ形の要約の依頼を、Nova 2 Lite の jp. と global. で 3 回ずつ呼んだ。出力は 54〜67 トークン。時間は手元のスクリプトで、呼び出しから応答を受け取るまでを測った。出力の上限など条件が違うので、4 章の時間とは比べない。

推論プロファイル 処理時間 [s] 入力の単価 [USD/1,000 トークン] 出力の単価 [USD/1,000 トークン]
jp. 0.72・0.59・0.61 0.000396 0.003311
global. 0.87・1.09・0.76 0.00036 0.00301

単価は、2026-11-03 に AWS Price List API で東京の値を確認した。global は 1 割ほど安い。この日の 3 回では jp. の方が速かったが、3 回だけなので速さの優劣は言えない。

どのリージョンで処理されたかは、応答の本文にもヘッダーにも出なかった。処理したリージョンは、呼び出し元のリージョンの CloudTrail の記録の additionalEventData.inferenceRegion で分かる(公式。今回は見ていない)。

Geographic の推論プロファイル(jp. など)を使うには、推論プロファイルと、呼び出し元と処理先のすべてのリージョンのモデルに、呼び出しの権限が要る。SCP でリージョンを絞っている組織では、処理先のリージョンを許すか、推論プロファイルの例外を足す。global では aws:RequestedRegion が unspecified として評価される(公式。2026-11-03 確認)。

8. Intelligent Prompt Routing(プロンプトルーター)

プロンプトルーターは、同じ系統の 2 つのモデルのうち、どちらに回すかを依頼ごとに決める機能。2026-11-03 に東京で ListPromptRouters とコンソールを見ると、既定のルーターが 2 つあった。Nova Lite と Nova Pro の間で振り分ける Nova Prompt Router と、Claude 3 Haiku と Claude 3.5 Sonnet の間で振り分ける Anthropic Prompt Router(図7)。今回は Nova Prompt Router を使った。

図7 東京の既定のプロンプトルーター

ルーターには、フォールバックのモデルと、ルーティングの基準(応答の質の差)がある。フォールバックのモデルを基準にして、もう一方のモデルの予測の質がこの差より上回るときだけ、もう一方に回す(公式)。Nova Prompt Router は、フォールバックが Nova Pro、基準が 0% だった(図8)。

図8 Nova Prompt Router の詳細

呼ぶときは、ルーターの ARN を modelId に入れるだけでよい。実際に使われたモデルは、応答の trace.promptRouter.invokedModelId に入る。コンソールのプレイグラウンドでも、答えの横に出る(図9)。

図9 プレイグラウンドのルーターメトリクス

自分は、易しい問いは Nova Lite、難しい問いは Nova Pro に回ると予想していた。ただし、ルーターが見るのは問いの難しさではなく、2 つのモデルの答えの質の差の予測なので、この分け方と一致するとは限らない。

易しい問い・難しい問い・中くらいの問いを、英語と日本語で 1 回ずつ送った。難しい問いは、RPO(障害のときに失ってよいデータの時間幅)が 1 分の決済システムの災害対策の設計。結果は以下の通り。

問い 英語 日本語
日本の首都はどこか Nova Pro Nova Lite
RPO 1 分の決済システムの複数リージョンの災害対策を設計し、案ごとのトレードオフを述べる Nova Lite Nova Pro
TCP と UDP の違いを 2 文で Nova Lite Nova Lite

日本語は予想どおりに分かれた。英語は、首都の問いが Nova Pro に回って長い解説を返し、設計の問いは Nova Lite に回った。2 つのモデルの答えの質は比べていないので、英語の振り分けが誤りとも言えない。公式は、ルーティングが英語のプロンプトに最適化されていると書いている。6 問だけの観察なので、振り分けの傾向は言えない。使うなら、手元の依頼で振り分けを確かめてから決める。

9. AppConfig でモデルを切り替える

試験ガイドには、モデルを動的に切り替える手段として AWS AppConfig が出てくる。AppConfig は、設定をコードの外に置き、配り方(デプロイ戦略)を決めて配るサービス。アプリケーション・環境・設定プロファイルの 3 つを作り、設定の中身をデプロイする。

アプリケーション fmlab、環境 dev、自由形式の設定プロファイル model を作り、{"modelId": "jp.amazon.nova-2-lite-v1:0"} をデプロイした。アプリ側は appconfigdata の API で設定を読み、その modelId で Converse を呼ぶ。最初に StartConfigurationSession でセッションを始めて初めのトークンを受け取り、以後は GetLatestConfiguration が返す次のトークンで読む。

appconfig_model.py
resp = data.get_latest_configuration(ConfigurationToken=self.token)
self.token = resp["NextPollConfigurationToken"]
body = resp["Configuration"].read()
if body:  # 空なら前回から変わっていない
    self.model_id = json.loads(body)["modelId"]

設定を apac.amazon.nova-micro-v1:0 に替えてデプロイし直すと、同じコードのまま Nova Micro に切り替わった(図10・図11)。次に読むまでの間隔は、応答の NextPollIntervalInSeconds に従う(下限は 15 秒)。今回は、2 回目のデプロイのあと 16 秒待ってから読み直した。

図10 設定のバージョン 1 と 2 の差分

図11 デプロイの履歴

既定のデプロイ戦略 AppConfig.AllAtOnce には、配り終えたあと 10 分の様子見(ベイク)がある(図12)。今回は切り替えを続けて試すため、待ち時間 0 分の戦略を作った。様子見を含むデプロイの間に CloudWatch のアラームが鳴ると、AppConfig は元の設定に戻す(公式。切り戻しの権限の設定が要る)。今回は切り替えだけを確かめ、自動の切り戻しは試していない。

図12 デプロイ戦略の一覧

今回の 1 回では、切り替えたあとの Nova Micro は、「AppConfig とは何かを 1 文で」という問いに、Python の Web フレームワーク Django の AppConfig の説明を返した。切り替えの仕組みと、切り替え先のモデルの出来は、別に見る必要がある。

10. サービスティア(flex)

Converse では、serviceTier を指定すると、料金と処理の優先度の組み合わせを選べる。標準の default のほかに、混んでいるときに後回しにされうる代わりに安い flex と、優先して処理される代わりに高い priority がある。Ministral 3 8B の単価は、標準が入出力とも 1,000 トークンあたり 0.00018 USD、flex は 0.00009 USD だった(2026-11-03 確認)。

同じ要約の依頼を、標準と flex で 3 回ずつ送った。手元で測った時間は、標準が 0.62・0.57・0.69 秒、flex が 0.54・1.07・0.73 秒。flex の 2 回目は、出力が 200 トークンとほかの回(90〜126 トークン)より長かった。この日は、flex でもほぼ遅れなかった。応答の serviceTier には、実際に使われたティアが返ってくる。急がない処理なら、引数 1 つで単価を半分にできる。

11. 試験の論点

ここまでの結果と公式の説明から、要件ごとの選び方をまとめた。「(公式の説明)」と付けたところは、今回は動かしていない。

こういう要件なら 選ぶもの 選ばないもの
複数社のモデルを比べたい・後で替えたい Converse InvokeModel(依頼と応答の形がモデルごとに違う)
チャットで答えを早く見せたい ConverseStream 同期の Converse(全部そろうまで返さない)
動画の生成など、時間のかかる処理を受け付けて後で結果を取りたい(公式の説明) 非同期の呼び出し(StartAsyncInvoke。対応するモデルに限られ、結果は S3 に置かれる) 同期の API(終わるまで呼び出しを待ち続ける)
大量の依頼をまとめて安く流したい(公式の説明) バッチ推論 1 件ずつの同期の呼び出し
急がない処理を安くしたい flex priority
モデルに社内の処理を呼ばせたい ツール呼び出し(toolConfig) 構造化出力(形を縛るだけで、処理は呼ばない)
決まった形で抽出したい 構造化出力(対応モデル)、ツールの入力の形(受け取った側でも検証する。厳密に縛るなら strict: true) プロンプトで頼むだけ(崩れることがある)
プロンプトをバージョンで管理して差し替えたい Prompt Management コードにプロンプトを直書き
処理を国内のリージョンに留めたい Geographic の推論プロファイル(jp.) global の推論プロファイル
処理するリージョンを限らず、単価を下げたい global の推論プロファイル Geographic の推論プロファイル
同じ系統の大小 2 モデルを自動で使い分けたい プロンプトルーター AppConfig(自動では選ばない)
再デプロイ無しでモデルを替え、デプロイ中に問題が出たら戻したい(戻す部分は公式の説明) AppConfig(デプロイ戦略とアラームで戻す) プロンプトルーター(決めた 2 つの間でしか動かない)

最後に

Nova 2 Lite で構造化出力を指定するとエラーになり、Nova Micro は東京からモデルの ID だけでは呼べなかった。どちらも、手段の選び方より手前の、モデルの対応で止まった。

参考(2026-11-03 確認)

  1. JSON の形(キーの名前・型・必須か・選べる値)を書く決まり。 ↩

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?