OpenAI互換APIでLangChainを動かす
ChatOpenAIにbase_urlとapi_keyの二つを渡すだけでこのエンドポイントにつながります。invokeもstreamもツール呼び出しも構造化出力もそのまま通る理由、四つのエイリアスの選び方、そしてキャッシュ読み取りが入力の十分の一で課金されエージェントのループを安く保つ仕組みまで。
LangChainは、相手が私たちであることを知る必要がありません。OpenAIのchat modelは自分のコンストラクタからbase URLを読み取りますし、このゲートウェイはOpenAIのワイヤ形式で応答します。ですからOpenAI向けに書かれたチェーンは、引数を二つ変えるだけでここで動きます。以下に出てくるものは、langchain-openaiのフォークでも、その上に被せたラッパーでも、パッチを当てた複製でもありません。すでにお使いのパッケージそのものを、設定しただけです。
変えるべき二つのフィールド
ChatOpenAIの引数は数が多いのですが、ほとんどはそのままで構いません。そのままにできないのは二つ — 送り先のアドレスと、一緒に送るキーです。
import os
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="terra",
base_url="https://aifromu.com/v1",
api_key=os.environ["AIFROMU_API_KEY"],
)
print(llm.invoke("Name three uses for a cache read.").content)
上のbase URLは、このページが印字したものであって、ここに打ち込まれたものではありません。このデプロイが実際に応答しているアドレスそのものなので、いま読んでいるホストに対してそのまま動きます。誰かが昔サンプルの中に埋め込んだホスト名に対して、ではありません。キーをenvironmentから読むのは、キーがいつもそうされる理由と同じです。コンストラクタに書いたキーはリポジトリに入ったキーであり、あとから回すには変数を変えるのではなくコードを直すことになります。
オブジェクトのそれ以外はすべてライブラリのものです。invokeは完成したmessageを返し、.contentがその本文です。文字列一つの代わりにmessageのリストを渡しても同じように動きますし、ChatPromptTemplate | llm | StrOutputParser()の組み立て方も以前のままです。そのどれも私たちが変えてよいものではありません — それが、自前の形式を発明せずに他社のワイヤ形式をそのまま提供する理由のすべてです。
ストリーミング、ツール呼び出し、構造化出力
llm.stream("...")は、モデルが書いているそばから答えを少しずつ渡します。server-sent eventsはこのゲートウェイを通り抜けてあなたのプロセスまで届くので、その周りに書くループはライブラリ自身のループのままです。
for chunk in llm.stream("Explain a cache read in two sentences."):
print(chunk.content, end="", flush=True)
長いものには、これを使ってください。何分もかかる生成は、あなたのプロセスとモデルのあいだのどこかで待たされている接続が切られる前に、何かを回線に送り出さなければなりません。それを送り出すのがストリーミングです — その境目がどこにあり、どう対処するかはストリーミングのガイドにまとめてあります。
ツール呼び出しと構造化出力には、ここで独立した節を割いていません。それこそが要点です。bind_toolsもwith_structured_outputも、これまでと同じリクエストボディを組み立てます。ゲートウェイが提供するのはOpenAIのワイヤ形式で、返ってくる答えもLangChainがすでに解釈できる形のままです。移植するものも、回避するものもありません。
どのモデル名を書くか
model=には四つのエイリアスのどれかを渡します。選ぶ基準は、誰のモデルかではなく、その仕事に何が要るかです。astraは100万トークンの窓での最前線の推論に、solは深い推論と難しいコーディングに、terraは日々のアシスタント仕事 — RAG、エージェント、ツール、コーディング — に。上のコードでterraを使っているのはそのためです。そしてlunaは、速い下書き、分類、大量のチャットに向いています。
すでにOpenAI風のモデル名を送っているチェーンなら、呼び出し箇所を一つずつ直す必要はありません。それらの名前はそのまま受け取られ、属する重量級として読まれます。miniやnanoという名前は誰かの安い大量呼び出しなのでlunaで、gpt-6という名前は今年の最前線なのでastraで、ふつうの4系はその中間のterraで提供されます。応答はあなたが送った文字列をそのまま返すので、クライアントは自分のリクエストだと認識できます。そしてどの規則にも当てはまらない名前は、推測されるのではなく拒否されます — 推測は二つの方向へ同時に誤請求することであり、そのどちらも、誰かが台帳を読むまで表に出てきません。
ループするチェーンと、キャッシュ読み取り
エージェントはループであり、ループは自分のプレフィックスを送り直します。agent executorは一周ごとに、システムプロンプトとツールの定義と、そこまでのやり取りをまとめて回線へ送り上げます。取得した各パッセージの前に同じ指示を置くRAGのチェーンも、規模は小さいながら同じことをしています。長く走るチェーンの費用の大半を占めるのは、答えではなく、このプレフィックスです。
そしてそこは、送り直すぶんには安い部分でもあります。キャッシュされたプレフィックスから読み戻したトークンは、そのモデル自身の入力単価の10分の1で課金されます。ですから、一周ごとにバイト単位で同じままのシステムプロンプトこそが、エージェントのループを安く保つものです。メッセージの順序もそれに合わせてください。決して変わらない部分を先に、取得した文書とユーザーの発話をそのあとに置きます。
どれも信用で受け取っていただく必要はありません。ポータルはリクエストごとに明細を出しますから、節約は目に見えるところに現れます — 一周ごとに、それを使った実行そのものと突き合わせて。
キャッシュされたプレフィックスから読み戻したトークンは、そのモデル自身の入力単価の10分の1で課金されます。ですから、一周ごとにバイト単位で同じままのシステムプロンプトこそが、エージェントのループを安く保つものです。
本記事は機械翻訳です。ネイティブによる確認は未了で、英語版が正本です。