Skip to content
This page has been auto-translated and may contain errors.View in English

コードからモデルを呼び出す

docs.scrimba.com

前の章ではメッセージのリストを構築しました。この章では、Pythonから実際のモデルにそのリストを送信して、答えを取得します。そのやり取りは、これらのモデルで構築する最小限の完全なループであり、あなたが書くほぼすべての機能はその上に乗っています。

プロンプトを入力すると、コードはそれをどこかに送信して、テキストが返ってきます。モデルがプログラムの中に住んでいるように感じます。そうではなく、実際にどこで実行されるかを理解することで、コスト、速度、そして出会うことになる癖が説明されます。

では、物理的に何が起きているかを想像してください。モデルは膨大なパラメータの集合で、プロバイダーのサーバー上に、あなたが決して見ることのないハードウェア上に置かれています。呼び出すと、コードはインターネット経由で普通のウェブリクエストを送信し、メッセージを運びます。

プロバイダーはマシン上で予測ループを実行し、生成されたトークンをあなたに返します。モデルの呼び出しは他人のコンピュータを数瞬間レンタルすることです。このフレーミングは多くのことを説明します:レイテンシーはモデルが彼らの端でトークンを1つずつ生成することであり、コストはそれらがそのコンピュートのために請求することであり、全体がネットワーク呼び出しであり、それが含むすべてのことです。

例では公式なOpenAIライブラリを使用しています。他のプロバイダーは詳細が異なりますが、形状はほぼすべての場所で同じです:メッセージのリストを送信し、メッセージを返します。その同じことはロックイン対する保険です。同じコードは、ホストするか他の場所でレンタルするオープンモデルをポイントできるため、ベースURLの変更だけで多くの場合動作します(オープンおよびクローズドモデルはその選択をカバーしています)。

ライブラリ関数を呼び出すと、数秒が経過し、テキストが返ってきます。ライブラリはそれをローカルに感じさせますが、モデルについて何もあなたのマシン上にありません。

理解する動きはその関数がラップしているものです。コードはリクエストをJSONとしてパッケージ化し、プロバイダーへのHTTPS接続を開き、彼らのハードウェアが予測ループを実行し、トークンをストリーミングバックするの間待ちます。SDK(プロバイダーのソフトウェアライブラリ、例えばopenai)は1つのPOSTリクエストの上の薄い利便性層です。

下の配線フォーマットがプレーンJSONであることを知ることは実践で報酬を得ます。呼び出しが奇妙に動作する場合、正確なリクエストとレスポンスを検査できます。SDKはまだ公開されていない機能が必要な場合、フィールドを手動で送信できます。

全体がネットワーク呼び出しであることは、この章の残りのコスト、レイテンシー、エラーハンドリングを駆動します。例はOpenAIを使用していますが、リクエストの形状、入力のメッセージのリスト、出力のメッセージは、プロバイダー間でほぼ同じです。

モデルの呼び出しは関数呼び出しのように見えて、不安定なインフラストラクチャ上のリモートプロシージャのように動作します。そのミスマッチは本番問題の発生源であるため、最初から実画像を保持する価値があります。

SDKの下で、すべての呼び出しは1つのHTTPS POST:JSONボディが上がり、プロバイダーは彼らのハードウェア上で推論を実行し、トークンは、オプションでオープン接続上でストリーミングされた戻ってくる。SDK(プロバイダークライアントライブラリ)はあなたに認証、再試行、入力、ストリーミング解析を購入し、あなたが制御しない変動上の抽象化レイヤーをあなたに費用がかかります。それを黒いボックスではなく、見透すことができるラッパーとして扱います。

2つの結果がこの章の残りを設定します。第1に、これはトークンで請求されるネットワーク呼び出しであるため、コスト、レイテンシー、べき等性、失敗処理は設計上の懸念事項であり、後で設定するアフターソートではありません。第2に、SDKとそのフィールド名は、あなたが依存する最もボラティルな表面であるため、耐久的な動きはプロバイダー固有の部分をあなた自身の境界の後ろに分離することです。例はOpenAIを使用しています;リクエストの形状はほぼユニバーサルですが、その周りのラッパーはまさにあなたがコードベース全体に広げたくないものです。

リクエスト

pip install openaiでライブラリをインストールし、クライアントを作成して呼び出します。クライアントはAPIキーを環境変数から読み込むため、キーはコードに表示されることはありません。

python
from openai import OpenAI

client = OpenAI()  # 環境からOPENAI_API_KEYを読み込む

MODEL = "gpt-4o-mini"  # モデルを切り替えるための1行

response = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": "You are a concise assistant. Answer in one sentence."},
        {"role": "user", "content": "Why is the sky blue?"},
    ],
)

2つの部分が必要です:model、実行したいモデルの名前、およびmessages、前の章のロールタグ付きメッセージのリスト。その他すべてはデフォルトを持っています。

モデル名は単一の定数に入っていることに注意してください。より良い、より安いモデルが数ヶ月ごとに着陸する分野では、それは意図的です。**モデル名**を1つの場所に保つため、モデルのスワップは1行の変更です。これは実践中のチャーンルールです:ボラティル固有のものを見つけることができる場所に保ち、コード全体に散らばさないでください。

Junoリクエスト 呼び出しは2つのことだけが必要です:modelmessages。モデル名が1つの定数に属します。なぜなら、より安いか良いモデルが数ヶ月ごとに現れ、9つのファイルに貼り付けられた後に検索に行きたくないからです。

2つのフィールドが必要です、modelmessages、その他すべてがデフォルトを持っています。モデル名は単一の定数であることはスタイルポイントではありません:それはより安いか良いモデルが十分に頻繁に到着するため、1つの揮発性の詳細であり、ファイル全体にハードコーディングすることはスワップを検索に変えます。

今1つのレベル下を見てください。そのcreate呼び出しはJSONボディに連続化され、POSTします。つづり出すと、**配線フォーマット**はプロバイダーが受け取るリテラルリクエストです:

python
import httpx, os

# SDKが送信するのと同じリクエスト、手動で
resp = httpx.post(
    "https://api.openai.com/v1/chat/completions",
    headers={"Authorization": f"Bearer {os.environ['OPENAI_API_KEY']}"},
    json={
        "model": "gpt-4o-mini",
        "messages": [
            {"role": "system", "content": "You are a concise assistant."},
            {"role": "user", "content": "Why is the sky blue?"},
        ],
    },
    timeout=20,
)
data = resp.json()  # SDKが解析するのと同じ形状

実際のコードではSDKに到達します:認証、再試行、入力を処理しています。しかし、ボディがプレーンdictであることを見ることで、残りは具体的になります。URLとフィールド名はOpenAIのもので、正確なエンドポイントとキーはプロバイダーによって異なりますが、動きはJSONイン、JSONアウトが、すべての場所で同じです。

Junoリクエスト 必須フィールドはmodelmessagesです。SDKのcreate呼び出しは1つのJSONポストの下にあるため、何か見間違いがあると思われる場合はワイアーにドロップして、リテラルボディを検査できます。モデル名を定数に保つ:それはチャーンされる部分であり、1つの場所に欲しい。

必須フィールドはmodelmessagesです。興味深い質問は彼らが何であるかではなく、彼らがコードのどこに住んでいるかです。なぜならmodelはリクエスト内で最もボラティルな値であり、SDKはその周りで最もボラティルな依存関係だからです。

create呼び出しはJSONポストに連続化されます。それは重要です。なぜなら、リクエストはSDKが実行する魔法ではなく、あなたが所有するデータだからです。ボディを構築し、ログに記録し、実行全体でdiffを実行するか、SDKが新しいプロバイダー機能に遅れているときに手動で送信できます。**配線フォーマット**を知ることは、抽象化があなたから隠している呼び出しをデバッグすることができます。

本番習慣は、すべての呼び出しを1つの狭い関数を通してルーティングしています:

python
def complete(messages, *, model=DEFAULT_MODEL, **params):
    # 1つのチョークポイント:プロバイダー、ログ、再試行をここで交換する
    return client.chat.completions.create(model=model, messages=messages, **params)

そのチョークポイントは、モデルスワップが1行の変更になり、プロバイダー固有のチャーンが漏れずに含まれている場所です。エンドポイントパスとフィールド名は表示されているOpenAIのもので、別のプロバイダーはルートと一部のキーの名前を変更します。そのため、境界の値は、名前の変更が1つのファイルに触れ、コードベース全体ではないということです。モデルバージョンをピンイングします:「最新」が下の下にあるあなたを静かに移動することは、LLMの仕組みで取り扱われているのと同じ回帰リスクであり、今はあなたのリクエストボディに。

Junoリクエスト 必須であるmodelmessagesだけで、両方とも制御する境界の後ろに住む価値があります。すべての呼び出しを1つの関数を通してルーティングしている:それはモデルスワップが1行である場所であり、ログが行く場所であり、プロバイダーのチャーンが呼び出し元に到達する前に停止する場所です。同時にモデルバージョンをピンイングしている:「最新」が下の下にあるあなたは、デプロイしなかった回帰であり、見ることができません。

レスポンスの読み取り

返信は何らかの構造でラップされて返ってきます。必要なテキストは中に1つのパスですが、構造の残りは価値があります。何が起きたかを言うためです。

python
choice = response.choices[0]

print(choice.message.content)   # 返信テキスト
print(choice.finish_reason)     # "stop"  -> モデルが自分で終了した
print(response.usage)           # Usage(prompt_tokens=24, completion_tokens=18, total_tokens=42)

3つのことがそこで重要です。message.contentはテキストで、APIは複数のオプションを返すことができるため、choices[0]の内側に座っています。ただし、ほぼ常に1つを要求して最初を読みます。

finish_reasonはモデルがなぜ停止したかを言います。"stop"はそれが思考を終了したことを意味し、"length"はそれがトークンキャップに当たり、半分の答えで切り取られたことを意味します。そのフィールドを確認することは、目の代わりにコードで切り詰められた返信を検出する方法です。

そしてusageは、呼び出しが費やしたトークンを報告し、送信したプロンプトと書き込んだ完了に分割されます。その**使用法**オブジェクトはあなたの請求書です、品目分け。すべての呼び出しはそれがトークンでコストするもの正確に言うため、構築するときに見る数字です。

Junoレスポンスの読み取り 返信テキストはresponse.choices[0].message.contentにありますが、2つの隣人も重要です。finish_reasonはなぜそれが停止したか言う:"stop"はできたことを意味し、"length"はそれがトークンキャップに当たり、切られたことを意味します。そしてresponse.usageはあなたの請求書であり、プロンプトと完了に分割されたトークンで、あなたが呼び出しコストを推測する必要はありません。

3つのフィールドが信号を運びます。choices[0].message.contentはテキストです。finish_reasonはモデルが停止した理由です:"stop"はクリーン終了です。"length"はトークンキャップに当たり、切り詰められたことを意味します。そのためそれを確認することは、切り詰められた返信を半分の答えを出荷する代わりにコードで捕まえる方法です。そしてusageはトークン数です。プロンプトと完了が分割されています。

ここで報酬を得る動きはusageを本物のお金に変えることです。**使用法**オブジェクトはコマンドごとの請求データであるため、リアルタイムでコストを計算できます:

python
# プロバイダーの価格はトークンごと;現在のレート、彼らが変わる確認
PROMPT_RATE = 0.15 / 1_000_000      # プロンプトトークンあたりドル
COMPLETION_RATE = 0.60 / 1_000_000  # 完了トークンあたりドル

u = response.usage
cost = u.prompt_tokens * PROMPT_RATE + u.completion_tokens * COMPLETION_RATE
print(f"{u.total_tokens} tokens, ${cost:.6f}")

プロンプトと完了は通常異なる価格で、完了トークンはしばしばより多くの費用がかかります。そのため、分割は最適化するときに重要です。コマンドごとのコストをログに記録すると、月間の請求書の前に高い機能を見ることができます。ここのフィールド名はOpenAIのもの;他のプロバイダーはやや異なるキーの下に同じ数を公開しているため、正確な属性ではなく形状を読んでください。

Junoレスポンスの読み取り 3つのフィールドを読む:テキスト用content、コード内で"length"切り詰めを捕まえるためのfinish_reason、トークン用usageusageをその場でドルに変えます。プロンプトと完了が別に価格設定され、コマンドごとにログに記録します。請求書の前に高い機能が見えます。

レスポンスは3つのフィールドを運びます。あなたは行動:contentfinish_reasonusage。ビルダーがスキップし、悔やむのはfinish_reasonです。"length"値はモデルがmax_tokensにぶつかり、出力が切り詰められたことを意味します。多くの場合、JSONの形成が不十分または半終了した文として、下流のパーサーがそれでも窒息することになります。"stop"以外の値をすべて失敗した呼び出しとして扱い、contentが完全であると仮定しないでください。

**使用法**オブジェクトはあなたのユニット経済テレメトリであり、印刷されて忘れられるのではなく、観測可能性に配線される価値があります。プロンプトと完了トークンは別に価格設定され、完了は通常より高いため、機能でタグ付けされたコマンドごとに両方を発行します:

python
u = response.usage
log.info("llm_call", feature="invoice_extract", model=MODEL,
         prompt_tokens=u.prompt_tokens, completion_tokens=u.completion_tokens,
         finish_reason=response.choices[0].finish_reason)

その場所では、機能あたりのコストとユーザーあたりのコストは推測ではなく、クエリになり、静かに3倍に成長したプロンプトが請求書の代わりにメトリックとして現れます。非対称性を見る:入力トークンは安いと並列処理です。出力トークンは高価で、LLMの仕組みでカバーされた連続部分です。したがって、長い返信を発行する機能はプロンプトサイズが示唆するよりもより多くコストと遅れます。ログがカウントを後ろに属する属性名はOpenAIのもので、プロバイダーによってシフトします。そのため、アプリ全体に散らばることなく、独自の境界の背後にあるカウントをログに記録してください。

Junoレスポンスの読み取り"stop"ではないfinish_reasonをすべて失敗した呼び出しとして扱う:"length"切り詰めはパーサーに壊れたJSONを渡し、下流でバグを追いかけます。機能でタグ付けされたログにusageを配線し、プロンプトと完了を分割し、機能あたりのコストはクエリになり、バルーニングプロンプトはメトリックであり、請求書の驚きではありません。出力トークンは高価で、連続部分であるため、会話機能はプロンプトより多くコストします。

知る価値のあるパラメータ

modelmessagesを超えて、2つのオプション設定は常に出ます。

temperatureLLMの仕組みからのランダムネスダイヤルです、具体化されました。低い値(0近く)はモデルをトップピックの近くに保つ:焦点が絞られ、繰り返し可能、抽出と分類に欲しいもの。より高い値(0.7から1周り)はそれに到達させます。可能性の低いトークン:より多様と創造的、そして放浪する傾向があります。あなたはモデルがどの程度太胆にサンプリングするかをチューニングしているだけで、他の何もありません。

max_tokensは返信が実行するトークンの数をキャップします。それはランウェイ答えと走りアウェイ請求書からあなたを保護します。**max_tokens**を低すぎると設定すると、モデルは切り取られます。それは最後の部分からの"length"終了理由とまさに同じです。そのため、本物のヘッドルームを残してください。

python
response = client.chat.completions.create(
    model=MODEL,
    messages=messages,
    temperature=0.2,  # 焦点が絞られ、一貫性のある
    max_tokens=300,   # 返信の長さを上限
)
Juno知る価値のあるパラメータtemperatureはモデルがどの程度太胆にサンプリングするかをチューニングしている:焦点を絞って繰り返し可能な答えのための低い、多様で創造的なもののための高い。max_tokensは請求書を保護するために返信の長さを上限にしますが、あまりに低く設定するとそれは"length"カットオフをトリガーします。そのため、ヘッドルームを残してください。抽出と分類については、毎回低い気温に到達してください。

2つのパラメータがほとんどの重みを運びます:temperaturemax_tokens。メカニクスはLLMの仕組みにあります;ここでの動きは、出荷されるデフォルトを信頼する代わりに、タスクに値を一致させることです。

**温度**をグローバル一度ではなく、タスクごとの設定として考えてください。タスクが必要とするものから選んでください:

python
TEMPERATURE_BY_TASK = {
    "extraction": 0.0,      # 1つの正しい答え、毎回それを欲しい
    "classification": 0.0,  # ラベルを選び、創造性は望まない
    "summary": 0.3,         # ほぼ忠実で、少し言い換えの自由
    "brainstorm": 0.9,      # 多様性が全体的なポイント
}

後で解析するものについて、構造化されたデータ、カテゴリ、ドキュメントから引き出された数字について、0に行き、ランダムネスについての推論を停止してください。ドラフト作成とアイデア出しのため、多様性は値である場所、高く行きます。

max_tokensは対象ではなく上限です:それは費用とランウェイ返信をキャップしますが、それをタスクが必要とするものの下に設定すると、最後の部分からの"length"切り詰めが得られ、それは少し大きな請求書よりも悪いです。タスク必要とするもの長い正当な答えと余裕にサイズを変更してください。これらの2つのフィールド名はプロバイダー全体で安定しています;少しmax_tokensを名前変更します。そのため、交換するときキーを確認してください。

Juno知る価値のあるパラメータ グローバル一度ではなく、タスクごとにtemperatureを設定してください:後で解析するもののための0、多様性がポイントである場所のドラフト作成のための高い。max_tokensは安全な上限です。そのため、本物の長い答えと余裕にサイズを変更してください;これを長引きすぎるとセット、あなたはわずかな請求書節約のための切り詰められた返信を取引します。SDKが配布されたデフォルトを信頼を停止してください。

temperaturemax_tokensはダイヤルのように見えて、コストと信頼性を添付したポリシー決定として行動します。ここの深さはあなたが出荷する他のすべてとそれがどのように相互作用するかにあります。

**温度**を信頼性契約の一部として扱ってください。あなたが解析または保存する任意の出力について、サンプリングが駆動され、LLMの仕組みからの分散がプロバイダーが許可するぐらい小さくなるように0で実行してください。これはプロバイダーが許可する限り完全に決定的ではないため、正確な一致キャッシングまたはバイト同一テストを上に構築しないでください。しかし、それはあなたが構造化された仕事のために欲しい床です。多様性が製品である表面のための高い温度を予約し、実行全体でevals測定する分散、単一のパスであなたが目視でない何かとして扱います。

max_tokensは安全ネットだけでなく、レイテンシーとコストレバーです。出力トークンは生成の連続的、高価な部分であるため、長さをキャップすることはあなたの最悪のケースの請求書と最悪のケースの待つをバウンドしています。

罠は目で大きさを決めることです:あまりに長くタイトに切り詰め、有効な答えは"length"失敗に変わり、それはパーサーに壊れた出力を渡します。あまりに緩い、縮退ループが何かがそれを停止する前に費用と遅れを実行させることができます。タスクごとに正当な長い答えと余裕にサイズを変更し、上限に当たる場合をログに記録し、機能が切り詰め続けるため、目に見えるバグを隠すパラメータとして見えます。両方のパラメータ名はOpenAIのもので、プロバイダーの少しはそれらを名前変更します。そのため、すべての呼び出しサイトではなく、境界関数の背後にそれらを設定します。

Juno知る価値のあるパラメータtemperatureは信頼性契約です:あなたが解析するもののための0。高い、多様性がポイントである場合のみ。そして決して正確な一致キャッシュが0の上に、それはバイト決定的ではないため。max_tokensは最悪のケースの費用と遅れをバウンドし、本物の長い答えと余裕にサイズを変更し、当たるログ、機能が切り詰め続けるため、パラメータを隠すバグ。両方を設定後ろに、すべての呼び出しサイトで、独自の境界、ない。

ストリーミング

デフォルトでは、全体の答えが完成するまで待機し、その後一度にそれが到着します。モデルはトークンを1つずつ生成するため、長い返信は何も見つめながらの長い待機を意味します。

**ストリーミング**は生成された瞬間、各トークンをあなたに送り、テキストが正しく表示され、チャットアプリが返信を入力しているままを見せる方法で、ライブに満たされます。

python
stream = client.chat.completions.create(
    model=MODEL,
    messages=messages,
    stream=True,
)

for chunk in stream:
    piece = chunk.choices[0].delta.content
    if piece:
        print(piece, end="", flush=True)

総生成時間はどちらの方法でも同じです;モデルはより速くありません。ストリーミングはトークンのみが表示される場合を変更し、最後まで保持する代わりに生成されるときにそれらをサーフィンします。

チャンクを反復処理し、テキストの各deltaを印刷することでそれを処理します。トレードオフは少し多くのコード、はるかに良い待機のためのコード。ストリーミングはいつトークンを見るか変更し、どの程度速くそれらが到着するかではない。

Junoストリーミング モデルがトークンを1つずつ生成し、デフォルトでは長い返信は長い静かな待機です。ストリーミングは、モデルが生成した瞬間、各トークンを転送し、ユーザーはテキストをすぐに見るため、全体的な返信を待ちません。生成時間は同じ、あなたはチャンクを反復処理し、各`delta`を印刷することで、トークンをより速く見るだけです。

ストリーミングは、モデルが生成するたびに各トークンをオンにしているため、ユーザーはテキストをすぐに見る代わりに完全な返信を待ちます。総生成時間は変わりません。降りる最初のトークンに時間です。これは最も「速く感じる」意味です。

クリーン版は、おもちゃのループがスキップする2つのことを処理します。テキストのないチャンク、完全な返信の累積します。

python
stream = client.chat.completions.create(model=MODEL, messages=messages, stream=True)

full = []
for chunk in stream:
    delta = chunk.choices[0].delta
    piece = delta.content
    if piece is None:          # ロール専用と最後のチャンクはテキストを運ぶことをしない
        continue
    full.append(piece)
    print(piece, end="", flush=True)

reply = "".join(full)          # 履歴または保存のための完全なメッセージ

空のdeltaチェックは重要です:最初のチャンクはしばしばロール専用で、テキストなし、**最後のチャンク**は空の内容のfinish_reasonを運ぶことができます。Noneをスキップし、あなたは出力に"None"を印刷することを避けます。

fullに累積することは、ストリーミングがテキストを表示しますが、あなたのために保存しないため、履歴に追加するために全体のメッセージを与えます。知ることのための1つのトレードオフ:ストリーム化された呼び出しはデフォルトでusageブロックを返しません。トークン数が必要な場合、明示的に要求するか、自分でカウントしてください。ストリーミングはプロバイダー全体で存在します;チャンクの形状、deltacontentfinish_reasonが最初を見えます。これはプロバイダー固有です。

Junoストリーミング ストリーミングは最初のトークンの時間を切ります、総時間ではない。空の`delta`のためのガード:最初のチャンクはしばしば、ロール専用であり、最後は内容のない`finish_reason`を運びます。そのため、`None`をスキップするか、返信に"None"を印刷します。自分のために累積し、履歴のための部分を覚えているし、ストリーム化された呼び出しはあなたが要求する限り`usage`をスキップしています。

ストリーミングはシンプルな応答をより優れたレイテンシープロファイルと取引します:あなたは最初のトークンの時間を切ります。しかし、あなたはシンプルな応答オブジェクトをあきらめ、チャンクが到着したときに状態を組み立てることをとります。到着する価値。誰かが見ている何かのために、決してバックエンド呼び出しのための価値。あなたは全体を解析します。

本番でバイト詳細はチャンク境界にあります。最初のチャンクはロール専用です。**最後のチャンク**は空の内容のfinish_reasonを運びます。そして、あなた自身をテキストを組み立てる必要があります。何も組み立てられたメッセージをあなたに与えません:

python
full, finish = [], None
for chunk in stream:
    choice = chunk.choices[0]
    if choice.delta.content:
        full.append(choice.delta.content)
    if choice.finish_reason:        # ストリームの最後に到着
        finish = choice.finish_reason
reply = "".join(full)
if finish == "length":              # 切り詰めは依然として中ストリーム起こる
    handle_truncated(reply)

3つの本番現実:

  • finish_reasonは依然としてストリームの終わりに到着します。"length"の切り詰めはいつストリーム内に消えず、あなたはそれを後で注意し、組み立てたテキストを信頼する前にそれを確認します。
  • ストリームは飛行の中空に壊すことができます:接続は40のうち10トークンの後にドロップし、あなたは部分的な返信を残します。そのため、ストリーム化された呼び出しは独自の部分失敗処理が必要であり、きれいに再試行することは厄介です。
  • ストリーム化された応答は通常usageをスキップします。そのため、あなたはそれを含む旗を渡すか、コスト追跡のため、自分で組み立てられたテキストをトークン化してください。

SSEフレーミングとチャンクスキーマはプロバイダー固有です。そのため、残りの同じ境界の後ろでストリーム解析を保ち、呼び出し側に、生のチャンクではなく、組み立てられた結果を見させてください。

Junoストリーミング ストリーミングは、最初のトークンの時間を購入し、シンプルな応答オブジェクトをコストします。それを使用する場所、人間を見て、バックエンド解析をスキップしてください。最後のチャンクは依然として`finish_reason`を運びます。そのため、`"length"`の切り詰めは、中ストリーム実空である場合、テキストを信頼する前に確認します。そしてストリームは40の10トークンの後に死ぬことができます。そのため、部分的な返信を処理し、境界の後ろのプロバイダー固有のチャンク解析を保ちます。

すべての呼び出しは独立している

これはチャプターの最も重要な行動であり、コンテキストウィンドウレッスンから直接それに従います。APIは**ステートレス**:各リクエストは完全にそれ自身に立っています。プロバイダーのサーバーは、前の呼び出しを覚えていません。それはあなたが送信した正確にメッセージで予測を実行し、結果を返し、交換について何も保持しません。

したがって、会話はモデルまたはAPIによってどこにも保存されていません。それはあなたのコードの中に住んでいます。ターン2が何が起こったかに知りたいターン1で、あなたが走行中の運転者を保つ場合は、それ自身を複製し、毎回全体をすべて送信します。バックアンドフォー会話は、毎回ことを大きくなる履歴を再送信することで、あなたが作成し、幻想です。

これはまた、なぜ長い会話で、メッセージあたりのコストがより多くの時間にわたってコストなぜかを説明します:各呼び出しは前のターンをすべて入力トークンとして再送信し、ユーザーの新しい質問が短い場合さえ、プロンプトが成長し続けます。

python
history = [
    {"role": "system", "content": "You are a friendly travel assistant. Keep answers short."},
]

def chat(user_text):
    history.append({"role": "user", "content": user_text})

    # 全体の履歴が毎回上がる;サーバーは何も覚えていない
    response = client.chat.completions.create(model=MODEL, messages=history)
    reply = response.choices[0].message.content

    history.append({"role": "assistant", "content": reply})  # 次のターンのための返信を保つ
    return reply

chat("I have a weekend in Tokyo. What should I see?")
chat("Which of those is best for kids?")  # ターン1が履歴を運ぶため、いけい機能します

2番目の質問は、最初の質問とそれの答えが依然としてhistoryにあり、一緒に送信されるため意味があります。あなたはメモリです。この1つのアイデア。あなたがフルコンテキストを組み立て、毎回それを再送信すること。下層、会話を下層。そして、後にそれは、ツールユーザーの使用、検索、およびエージェント、あまりにの下層。それらはすべて、各独立した呼び出しの前に、そのメッセージリストに何が行くかを決定することの変動。

Junoすべての呼び出しは独立している APIはステートレスです:各要求は、あなたが送信するメッセージで実行し、サーバーは返信の瞬間、交換を忘れます。会話はあなたのコード、モデルには住んでいません。そのため、あなたは走行中の運転者を保つし、毎回全体を再送信します。それはなぜ長いチャットは時間にわたってメッセージあたりのコストをコストなぜです。各呼び出しはすべての前のターンをすべて入力トークンとして再送信します。

APIは**ステートレス**:サーバーはあなたが送信するメッセージで実行し、その後何も保持します。会話はあなたのコードに住んでいて、あなたは毎ターン全体の履歴を再送信して、継続を偽造します。そのため、長いチャットは新しい質問が短い場合さえ、メッセージあたり、より多くの時間にわたってコストします。

その成長は、あなたが管理し、無視しない問題です。なぜなら、履歴は最終的にコンテキストウィンドウを乗り越えるためです。最初の動きはスライディングウィンドウです:システムメッセージと最近のターンを保ちます。

python
def trim(history, keep_recent=10):
    system = history[:1]               # 常にシステムメッセージを保つ
    recent = history[1:][-keep_recent:]  # 最新のターンのみ
    return system + recent

これは安いであり、あなたのコストをバウンドします。しかし、それは静かに古いターンを落とし、モデルは、ユーザーが依然としてそれを知ることを期待する早い期間からの事実を忘れます。それが重要な場合、次のステップアップ落とされるターンを、それらを直立して削除せずに、1つの短いメッセージに要約すること。どのあなたが選んですか。機能によって異なります:オリジナルの問題を参照するサポートボット。概要が必要です。クイック質問と回答。スライディングウィンドウは、プロバイダー全体でステートレス自体があります;その周りのメッセージ形式のみが異なります。

Junoすべての呼び出しは独立している ステートレスはサーバーがすべての返信の後に忘れることを意味し、あなたは全体の履歴を再送信し、成長するときにコストを支払います。最近のターンのスライディングウィンドウと、コストをバウンドするシステムメッセージを調整します。しかし、知ること、それは静かに、早い期間からの事実を落とし、ユーザーが依然して知ることを期待する。それが咬むとき、代わりに落とされるターンを要約し、削除します。

APIは**ステートレス**:あなたは会話を所有し、毎回それを再送信します。素朴なバージョン。添付後、そしてロットを送信すること。スケールで2つの方法失敗します:それは最終的にコンテキストウィンドウをオーバーフローし、毎ターン費用と遅いことになり、入力なしでバウンドが大きくなるため。

そのため、履歴管理は、予算問題、3つの標準戦略です。各失敗に独自の方法で:

  • スライディングウィンドウ最近のNターンを保つ:安いことと境界、しかし、それは静かに早い事実を落とし、ユーザーはいまだに参照します。
  • 要約古いターン圧縮実行概要に:トークンあたりより多くの履歴を保つが、詳細を失い、独自のエラーをすべての後のターンに焼くことができます。
  • 検索ターンの外部ストアと、関連のみを引き戻す:長い履歴へのスケール。しかし、それはあなたの指標に依存します。正しい部分を返す。ルックアップを追加します。

使用の場合によって選ぶ。楽器。それは最終的に。に表面。誤ったコンテキスト。

python
def build_messages(system, history, user_msg, budget_tokens=6000):
    msgs = [system]
    running = count_tokens(system) + count_tokens(user_msg)
    for turn in reversed(history):           # 最新の最初
        t = count_tokens(turn)
        if running + t > budget_tokens:
            break                             # 古いターンが予算に下がる
        msgs.insert(1, turn)
        running += t
    msgs.append(user_msg)
    return msgs

ターン数ではなく、トークン数で予算を計算してください。ターンはサイズが大きく異なるため、ターン数ウィンドウは誰かがドキュメントを貼り付けた瞬間、コンテキスト予算を吹きます。戦略はプロバイダー独立;あなたはカウントするトークンは。これはモデルの自分自身でカウント。これは、プロンプトRAGが構築するのと同じコンテキスト構成作業です。今、会話その自体。

Junoすべての呼び出しは独立している ステートレスは履歴管理があなたの予算問題であることを意味します。「無限に添付」。ウィンドウをオーバーフローし、すべてのターンのコストを膨張させます。ユースケースによって戦略を選んでください:スライディングウィンドウは安いですが、早い事実を落とし、概要は、より多くを保つが、独自のエラーを焼き、検索。スケール。しかし、あなたの指標に依存します。ターン数ではなくトークン数で予算を編成してください。1つの貼り付けドキュメント、ターン数、ウィンドウ。を爆破。

何か誤った場合

モデル呼び出しは。他人のサーバーへのネットワーク呼び出し。そのため、ネットワーク呼び出しが失敗するすべての方法で失敗します。最初からそれのための設計。

  • **エラーとレート制限。プロバイダーは。ウィンドウで送信できるリクエストの数をキャップします。上限に当たり。呼び出しはレート制限エラーで失敗します。標準応答は待ち。再試行。各失敗の後に待機を延長します(これはバックオフ**と呼ばれます)。そのため、あなたはハンマーをハンマーしません。。の忙しい。
  • **タイムアウト。**呼び出しはハング。タイムアウトを設定してください。そのため。1つ遅い要求。アプリを凍結しません。
  • **キーはサーバーに留まる。**APIキーはあなたのお金を費やすパスワード。APIキーをブラウザコードに入れないでください。誰もがdevツールを開いて読むことができます。ブラウザはバックエンドを話し。バックエンド。キーを保有。モデルを話します。
  • **あらゆる答えが誤った可能性があると仮定してください。**均等に成功した呼び出し。返すことができます。幻想。奇形出力。そのため、次のチャプターはあなたのコードを信頼できる何かの結果を作ることについてです。
python
def ask_model(messages):
    try:
        response = client.chat.completions.create(
            model=MODEL,
            messages=messages,
            timeout=20,  # 20秒の後にあきらめ
        )
        return response.choices[0].message.content
    except Exception as err:
        print("モデル呼び出しが失敗しました:", err)
        return "申し訳ありません。何かがうまくいきませんでした。もう一度試してください。"
Juno何か誤った場合 モデル呼び出しは他人のサーバーへのネットワーク呼び出しであるため、レート制限、タイムアウト、プレーン障害が予想され、再試行とバックオフですべての呼び出しをラップしてください。APIキーをサーバーに保つ。決してブラウザではなく。それは本物のお金を費やし、デバッグツールが読みます。そして成功した返信をできる可能性のあるものとして扱い、次のチャプターが処理するのを助けてください。

モデル呼び出しはネットワーク呼び出しが失敗する方法で失敗:レート制限。タイムアウト。一時的なサーバーエラー。動きは、特定の障害をキャッチし、再試行する価値があるのに応じて応じて適用しています。代わりに、裸のexceptですべてをラップしてください。それは本物のバグを隠します。

タイプ例外をキャッチし、**バックオフ**を適用します。より長く待つ。各失敗の後。そのため、あなたはハンマーを停止します。忙しい。サービス:

python
import time
from openai import RateLimitError, APITimeoutError, APIError

def ask_model(messages, retries=3):
    for attempt in range(retries):
        try:
            r = client.chat.completions.create(model=MODEL, messages=messages, timeout=20)
            return r.choices[0].message.content
        except (RateLimitError, APITimeoutError, APIError) as err:
            if attempt == retries - 1:
                raise
            wait = 2 ** attempt          # 1秒、2秒、4秒:指数バックオフ
            print(f"再試行 {attempt + 1} {err}の後")
            time.sleep(wait)

例外タイプは、あなたが何をすべきか言う:RateLimitErrorAPITimeoutErrorは一時的であり、再試行する価値がある。一方、悪いリクエスト(奇形メッセージ、不明なモデル)はあなたのバグであり、うるさく失敗する必要があります。再試行しません。各試みの後、待機を倍にすることで、既に苦労しているサービスにあなたが一積み上げるのを保ちます。SDKは、いくつかの障害を再試行するため、あなた自身を追加する前にデフォルトを確認してください。

キーをサーバーサイドに保つ。成功した返信を誤ったとして扱う。これは次のチャプターで対応するため。例外クラス名はここではOpenAIです;他のSDKはそれら自身を上げます。そのため、それらを境界にマッピングしてください。

Juno何か誤った場合 タイプ例外をキャッチ。裸の`except`ではなく:`RateLimitError`と`APITimeoutError`は一時的であり、再試行する価値がある。悪いリクエストはあなたのバグであり、うるさく失敗する必要があります。指数バックオフをして。待機を倍にして。そのため、苦労しているサービスをハンマーを停止します。SDKが再試行する前にすでに、独自のレイヤーを追加してください。キーをサーバーサイドに保つ。

障害処理はこれが関数呼び出しを停止することになる。分散システムの仕事。呼び出しは既知の方法で失敗:レート制限。タイムアウト。一時的な5xxエラー。そして危険な部分は。再試行を思わずに。再試行する価値があります。あるかも。費用。

罠は**べき等性**(操作を2回実行することは、1回実行するのと同じ効果があるかどうか)。タイムアウト後の素朴な再試行は、二重請求できます:最初の要求はプロバイダーの側面で完了し、トークンを生成することができます。応答があなたに到達しないため、再試行はすべての生成に2番目の費用が発生します。読み取りスタイルの呼び出しは、無駄なお金です。あらゆる副作用のある何かのため。複製されたアクション。プロバイダーがべき等キーをサポートする場合。同じ要求として認識される再試行。それ一度それを送信;それをサポートしていない場所。周囲の操作。サーフを繰り返し。

python
import random, time
from openai import RateLimitError, APITimeoutError, APIError

RETRYABLE = (RateLimitError, APITimeoutError, APIError)

def complete(messages, *, retries=3, **params):
    for attempt in range(retries):
        try:
            return client.chat.completions.create(messages=messages, model=MODEL, **params)
        except RETRYABLE as err:
            if attempt == retries - 1:
                raise
            time.sleep((2 ** attempt) + random.random())  # バックオフ + ジッタ

3つのより本番現実:

  • **ジッタ。**各待機に追加されたランダム分数。クライアントの艦隊をロックステップで再試行させ。レート制限を再スパイク(それを雷雨の群れ問題と呼ぶ)を停止します。
  • **接続再利用。**負荷の下で重要:1つのクライアントを作成し。それを共有し。そのため、接続は。プール。每呼び出しの実行ではなく、新しいTLSハンドシェイク。独立した呼び出しを並行して実行。遅い連続ループではなく。
  • 再試行する前に分類:aRateLimitErrorは一時的。奇形要求エラーはあなたのバグ。再試行時間と、決してありません。成功する呼び出しは無駄。。。

例外クラスとべき等性機構はプロバイダー固有。そのため、このすべてのポリシーは。境界関数の背後にある。あなたがマップします。各プロバイダーのエラー。あなた自身にして。一度再試行決定。キーはサーバーサイドを滞在するため。成功した返信。いまだに。信頼できない。。。。構造化出力安全チャプターハンドル。

Juno何か誤った場合 再試行は危険な部分:素朴な再試行。タイムアウト後。二重請求できます。。。最初の呼び出し。生成可能性。トークンあなたは受け取りません。そのため、べき等キーを使用。または操作をして。安全繰り返す。バックオフするジッタを追加。そのため艦隊。クライアント。ロックステップで再試行しない。レート制限を再スパイク。1つを再利用。プール接続。クライアント。独立した呼び出しを並行して実行。分類最初。再試行一時的エラー。失敗。うるさく悪いリクエスト。保つすべてのポリシー。後ろ。1つの境界。その場所。各プロバイダーのなじみ。マップ。あなたの再試行決定。1つの場所。