JA ▾
API キーを取得

LLM APIからのチャットボット構築:Pythonチュートリアル

Abliteratedモデルは、安全性の拒否を強制するメカニズムが除去された大規模言語モデルです。これにより、モデルはコンテンツフィルタに引っかかることなく、論争の的となる質問、成人向けの内容、またはニッチな質問に回答できます。このアプローチにより、開発者はモデルのトーンと知識のカットオフを完全に制御でき、標準的なガードレールが有用な出力をブロックする可能性があるコーディング、ロールプレイ、クリエイティブライティングにおいて特に価値があります。

チャットボットの会話ループを示す、ループ内で移動するチャットメッセージブロックと中央のキューブ

主要ポイント

  • APIはステートレスなので、会話履歴はコード側で管理し、各リクエストで送信する必要があります。
  • ストリーミング、関数呼び出し、JSONモードはすべて標準的なOpenAIパラメータを使用します。
  • 100,000トークンのコンテキストウィンドウに近づいたら、履歴をトリミングまたは要約してください。
  • 2,000入力トークンと400出力トークンのチャット1,000回のコストは約$0.50です。

核となる概念:チャットボットはループとリストである

言語モデルはリクエスト間で何も記憶しません。POST /v1/chat/completionsへの呼び出しはすべて独立しています。記憶のように見えるのは、プログラムが以前のメッセージを再度送信しているためであり、各メッセージには役割がタグ付けされています。systemは指示用、userはユーザーの入力用、assistantはモデルの以前の返信用です。

したがって、アーキテクチャは非常にシンプルです。リストを保持し、ユーザーメッセージを追加し、リストを送信し、返信を追加し、繰り返します。このチュートリアルのストリーミングからツールに至るまで、すべてはこのループのバリエーションです。

ここで使用するAPIはOpenAI互換なので、通常のopenaiパッケージを使用します。ベースURLをhttps://api.abliterated.cc/v1に設定し、あなたのAPIキーを使用して、モデルをuncensoredに設定してください。APIキーページからキーを取得してください。$0.50のトライアルクレジットは、数百件の短いテストメッセージに十分な量です。

ステップ1:動作するコンソールチャットボット

pip install openaiでSDKをインストールし、これを実行します。

from openai import OpenAI

client = OpenAI(
    base_url="https://api.abliterated.cc/v1",
    api_key="YOUR_KEY",
)

messages = [
    {"role": "system", "content": "You are a concise, friendly support assistant for a bike shop."}
]

while True:
    text = input("you: ")
    if text.strip() in {"quit", "exit"}:
        break
    messages.append({"role": "user", "content": text})
    resp = client.chat.completions.create(
        model="uncensored",
        messages=messages,
        temperature=0.7,
    )
    answer = resp.choices[0].message.content
    messages.append({"role": "assistant", "content": answer})
    print("bot:", answer)

これは完全なチャットボットです。システムメッセージがその役割を定義し、リストが記憶を保持し、temperatureが返信の多様性を制御します。事実に基づくサポートボットの場合は0.2に近づけるように下げ、クリエイティブなボットの場合は1.0に近づけるように上げます。

役割を設定するシステムプロンプトの作成

システムメッセージはチャットボットを改善するための最も安価な場所です。短く、具体的に保ってください。役割、対象ユーザー、返信の長さとトーン、およびボットが知らない場合にどうするかを明記します。「あなたは自転車屋の質問に答えるアシスタントです。返信は最大3文に収めてください。修理の価格に関する質問には、整備士が点検する必要があると答えてください」といったプロンプトは、一般的な美徳を並べただけの1ページ分以上の効果を発揮します。

効果的な習慣が2つあります。第一に、態度ではなく行動の形でルールを記述します。「役に立つ」ではなく、「曖昧なリクエストには1つの確認質問を投げる」とします。第二に、空のメッセージ、非常に長い貼り付け、製品と無関係なリクエストなど、敵対的な入力でテストします。結果に基づいてプロンプトを修正し、それらの入力を回帰テストリストとして保持します。

システムメッセージは各ターンで再送信されるため、その中のすべての単語がすべてのリクエストに対して課金対象となります。400トークンのプロンプトを5ターンずつの1,000回のチャットで実行すると、2,000,000の入力トークンが発生し、2 x $0.15 = $0.30になります。これは小さな金額ですが、冗長なプロンプトが大量使用時にどのように累積するかを示しています。

ステップ2:ストリーミングで返信を取得

チャットウィンドウで完全な回答を待つのは遅く感じられます。stream: trueを設定し、到着した順に出力します。ストリームはサーバー送信イベントを使用し、usageブロックで終了するため、トークン数をログに記録できます。

from openai import OpenAI

client = OpenAI(base_url="https://api.abliterated.cc/v1", api_key="YOUR_KEY")
messages = [{"role": "user", "content": "Explain tire pressure in two sentences."}]

stream = client.chat.completions.create(
    model="uncensored",
    messages=messages,
    stream=True,
)
reply = ""
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        part = chunk.choices[0].delta.content
        reply += part
        print(part, end="", flush=True)
    if getattr(chunk, "usage", None):
        print("\ntokens:", chunk.usage.prompt_tokens, chunk.usage.completion_tokens)

組み立てたreplyを履歴に追加することを忘れないでください。そうしないと、次のターンでボットが何を言ったのかわからなくなります。

ステップ3:コンテキストが肥大化する前に管理する

ウィンドウは100,000トークンで、プロンプトと補完の両方で共有されます。ほとんどのチャットボットはこれに近づきませんが、履歴が増えるほどコストが増加します。各ターンでプロンプト全体に対して課金するためです。単純なポリシーで両方を制御できます:

  • システムメッセージは常に保持してください。
  • 最後の10〜20メッセージをそのまま保持します。
  • リストが選択したサイズ制限(例えば推定8,000トークン)を超えた場合、最も古いブロックを「この会話を150語以内に要約してください」という指示とともにモデルに送信し、そのブロックをシステムメッセージとして要約文で置き換えます。

トリガーには「英字4文字でトークン1つ」の概算で十分です。レスポンスの使用量で正確な数値が得られるため、制限を調整できます。

最も古いメッセージを削除して切り捨てる場合は、ユーザーとアシスタントのペアとして削除してください。質問のない返信や、返信のない質問は、予想以上にモデルを混乱させます。

ステップ4:ボットにツールを付与

関数呼び出しにより、モデルは注文の検索など、コードに何かを実行させることができます。toolsで関数を記述し、モデルはtool_callsエントリで返信します。関数を実行し、結果をrole: "tool"メッセージとして送信します。

import json
from openai import OpenAI

client = OpenAI(base_url="https://api.abliterated.cc/v1", api_key="YOUR_KEY")

tools = [{
    "type": "function",
    "function": {
        "name": "get_order_status",
        "description": "Look up the status of an order by its id",
        "parameters": {
            "type": "object",
            "properties": {"order_id": {"type": "string"}},
            "required": ["order_id"],
        },
    },
}]

def get_order_status(order_id):
    return {"order_id": order_id, "status": "shipped"}

messages = [{"role": "user", "content": "Where is order A1042?"}]
resp = client.chat.completions.create(
    model="uncensored", messages=messages, tools=tools, tool_choice="auto"
)
msg = resp.choices[0].message

if msg.tool_calls:
    messages.append(msg)
    for call in msg.tool_calls:
        args = json.loads(call.function.arguments)
        result = get_order_status(**args)
        messages.append({
            "role": "tool",
            "tool_call_id": call.id,
            "content": json.dumps(result),
        })
    final = client.chat.completions.create(
        model="uncensored", messages=messages, tools=tools
    )
    print(final.choices[0].message.content)

副作用を伴う処理を実行する前に、常に引数を検証してください。モデルは呼び出しを提案しますが、実行するかどうかはコード側で判断します。

ステップ5:JSONモードで構造化された出力を得る

プログラムで文章ではなくデータの処理が必要な場合(例:メッセージをカテゴリに分類するなど)、response_formatを使用してJSONモードを使用します。プロンプトにも「JSON」という単語と期待されるキーを含めてください。

curl https://api.abliterated.cc/v1/chat/completions \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "uncensored",
    "response_format": {"type": "json_object"},
    "messages": [
      {"role": "system", "content": "Reply with JSON: {\"intent\": string, \"urgent\": boolean}"},
      {"role": "user", "content": "My brakes squeal and I ride tomorrow morning."}
    ]
  }'

それでもjson.loadsはtryブロックで囲んでください。解析に失敗した場合は、エラーテキストを付加して1回だけ再試行してください。失敗したリクエストは課金されません。

エラー、コスト、デプロイメントの注意点

エラー、リトライ、レート制限の処理

実際のユーザーはタイミングの悪いリクエストを送信し、ネットワークは失敗します。呼び出しをラップして、1つの不良応答で会話がクラッシュしないようにしてください。

import time
from openai import OpenAI, APIError, RateLimitError

client = OpenAI(base_url="https://api.abliterated.cc/v1", api_key="YOUR_KEY")

def chat(messages, tries=3):
    for attempt in range(tries):
        try:
            resp = client.chat.completions.create(
                model="uncensored", messages=messages, temperature=0.7
            )
            return resp.choices[0].message.content
        except RateLimitError:
            time.sleep(2 ** attempt)
        except APIError as exc:
            if attempt == tries - 1:
                raise
            time.sleep(1)
    return "Sorry, the assistant is busy. Please try again in a moment."

フルキーは8つの同時リクエストと1分あたり300リクエストを許可し、トライアルキーは2と60を許可します。ボットが同時に多くのユーザーに対応する場合は、リトライに頼るのではなく、ワーカー制限を8未満にしてリクエストをキューに入れます。失敗したリクエストには課金されないため、再試行にはトークンコストがかかりません。実際の障害時に速やかに失敗させるために、再試行回数を小さく保ってください。

また、会話の各ターンでX-Request-Idヘッダーをログに記録してください。返信が正しくない場合、このIDにより後で正確なリクエストを検索できます。

コストとデプロイメント

料金は入力100万トークンあたり$0.15、出力100万トークンあたり$0.50です。サポートチャットで合計2,000入力トークンと400出力トークンを使用すると仮定します。

  • 入力:0.002M x $0.15 = $0.0003
  • 出力:0.0004M x $0.50 = $0.0002
  • チャット1回:$0.0005、つまり1,000回のチャットで$0.50

現在のレートとチャージボーナスは料金ページでご確認ください。本番環境では、APIキーをブラウザのコードではなくサーバーに保持し、クレジットが尽きる前に通知されるようにX-Balance-USDレスポンスヘッダーを監視してください。フルキーでは同時8リクエスト、1分あたり300リクエストまで許可されるため、一度にすべて送信するのではなくバーストをキューに蓄積してください。ドキュメントにはサポートされているすべてのパラメータが記載されています。

質問と回答

チャットボットを構築するためにフレームワークは必要ですか?

いいえ。OpenAI SDKとメッセージリストがあれば、動作するボットが作れます。フレームワークの追加機能が必要になった場合にのみ、後で追加してください。

APIは過去のメッセージを記憶しますか?

いいえ。各リクエストは独立しているため、コードで履歴を毎回再送信する必要があります。

履歴が長くなりすぎないようにするにはどうすればよいですか?

システムメッセージと直近のやり取りを保持し、リストのサイズが設定した閾値を超えた場合に、古い会話を短い要約に置き換えます。

ボットは自分の関数を呼び出せますか?

はい。toolsとtool_choiceを渡して、返信にtool_callsが含まれている場合に関数を実行し、結果をtoolメッセージとして返します。

キーはフォーム 1 つで手に入ります

アカウントを作成し、キーをコピーし、ベースURLを変更します。これでセットアップは完了です。