主要ポイント
- base_url を API に設定し、モデルを uncensored に設定して ChatOpenAI を使用してください。
- チェーン、ストリーミング、ツール呼び出し、JSON出力はすべて標準のチャット補完ルート経由で行われます。
- 429 エラーを避けるために、フルキーではバッチの同時実行数を 8 に、トライアルキーでは 2 に制限してください。
- これはルーターではなくホストされた単一モデルなので、ベンダーごとのモデル切り替えはありません。
簡単なセットアップ
一般的なlangchain + llmの質問は、サードパーティのエンドポイントに特別な統合が必要かどうかです。必要ありません。LangChainのOpenAIチャットラッパーはカスタムbase URLを受け付け、このAPIはOpenAIチャット完了形式に従っているため、標準クラスが機能します。パッケージをインストールし、3つの値を渡すだけで、任意のチェーンに⟦drop-in⟧そのまま置き換え可能⟦/drop-in⟧なチャットモデルオブジェクトが得られます。
pip install -U langchain-openai langchain-core
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="uncensored",
base_url="https://api.nsfwllmrouter.com/v1",
api_key="YOUR_API_KEY",
temperature=0.7,
)
print(llm.invoke("Give me one sentence about LangChain.").content)
APIキーはAPIキーページから取得してください。新規ユーザーはサインアップ後にワンクリックで$0.50の無料トライアルクレジットを取得でき、これはこの記事のすべての操作を何度も実行するのに十分な量です。モデルIDは常にuncensoredであり、リクエストはPOST /v1/chat/completionsに送信されます。
サイト名に関する注記:ドメインに「ルーター」という言葉が含まれていますが、これはベンダー間のルーターではなく、独立したプロバイダーが運営する単一のオープンウェイトモデルです。また、いかなるベンダーの公式APIでもありません。ここではモデルを選択しません。コードで1つのモデルを指定すると、そのモデルが回答します。
APIキーをソースから分離する
APIキーをハードコードすると最初のテストでは機能しますが、ファイルにコミットした瞬間に問題になります。環境変数から読み取ってください。LangChainがラップするOpenAIクライアントライブラリはデフォルトでOPENAI_API_KEYとOPENAI_BASE_URLを探します。シェルでこれらを設定し、引数を少なくしてモデルを構築できます。
export OPENAI_API_KEY="YOUR_API_KEY"
export OPENAI_BASE_URL="https://api.nsfwllmrouter.com/v1"
import os
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="uncensored",
base_url=os.environ.get("OPENAI_BASE_URL", "https://api.nsfwllmrouter.com/v1"),
api_key=os.environ["OPENAI_API_KEY"],
)
上記のようにベースURLを明示的に渡すことには1つの利点があります。同じマシン上の別のプロジェクトが実際のOpenAIサービスを使用し、同じ環境変数に依存している場合、明示的な引数が2つが衝突するのを防ぎます。
プロンプトテンプレート付きの最初のチェーン
実際の作業のほとんどは、パイプ演算子で結合されたプロンプトテンプレート、モデル、出力パーサーを使用します。これは LangChain Expression Language のスタイルであり、カスタムエンドポイントには特別な設定は必要ありません。
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="uncensored",
base_url="https://api.nsfwllmrouter.com/v1",
api_key="YOUR_API_KEY",
)
prompt = ChatPromptTemplate.from_messages([
("system", "You write short story openings in the style the user asks for."),
("human", "Genre: {genre}\nSetting: {setting}\nWrite three sentences."),
])
chain = prompt | llm | StrOutputParser()
print(chain.invoke({"genre": "noir mystery", "setting": "a rainy harbor town"}))
このモデルは合法な成人用途に対してコンテンツ拒否を行わないため、より慎重なモデルが拒否する可能性のあるフィクション、ロールプレイシーン、またはコンパニオン対話の生成において重要です。唯一の固定ルールは、未成年者に関連する性的コンテンツは決して生成されないことです。製品により厳しいルールがある場合は、その上に独自の入力検証を構築してください。
ストリーミングと使用状況
APIはserver-sent eventsを介したストリーミングをstream: trueでサポートし、使用状況はストリームの終了時に報告されます。LangChainでは.stream()を呼び出してチャンクをイテレートします。
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="uncensored",
base_url="https://api.nsfwllmrouter.com/v1",
api_key="YOUR_API_KEY",
)
for chunk in llm.stream("Write a four-line poem about debugging at night."):
print(chunk.content, end="", flush=True)
覚えておくべき1つの詳細:ストリーミングリクエストはストリームが終了するまで同時リクエストの1つを占有します。UIがストリームを開き、ユーザーが閉じずに離脱した場合、そのスロットはビジー状態のままになります。フルキーでは8つの同時リクエストが可能なので、放棄されたストリームがいくつかあると新しいリクエストがブロックされる可能性があります。クライアントが切断された場合はイテレータをキャンセルしてください。
関数呼び出しとエージェント
関数呼び出しがサポートされています:toolsを渡し、オプションでtool_choiceを指定し、モデルはtool_calls(これもストリーミング可能)で返信し、role: "tool"でメッセージとして結果を送り返します。LangChainでは、bind_toolsがワイヤーフォーマットを処理します。
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
@tool
def word_count(text: str) -> int:
'Count the words in a piece of text.'
return len(text.split())
llm = ChatOpenAI(
model="uncensored",
base_url="https://api.nsfwllmrouter.com/v1",
api_key="YOUR_API_KEY",
)
llm_with_tools = llm.bind_tools([word_count])
msg = llm_with_tools.invoke("How many words are in: the quick brown fox?")
for call in msg.tool_calls:
print(call["name"], call["args"], word_count.invoke(call["args"]))
完全なエージェントループの場合、LangChainのエージェントヘルパーまたはLangGraphはこのサイクルを処理します:モデルを呼び出し、要求されたツールを実行し、ツールメッセージを追加し、再度呼び出します。ツール使用は標準的なOpenAIの形状に従うため、互換性のあるエンドポイントのいずれかに記述するのと同じコードが機能します。オープンウェイトモデルと同様に、特定のツールスキーマをテストしてください。引数名は簡潔にし、説明文は短く、ツールのリストは小さく保ってください。長いリストは、能力の低いモデルが間違ったものを選択する余地を与えます。
JSON モードによる構造化出力
JSONモードはresponse_format: {"type": "json_object"}で利用可能です。LangChainではモデル引数として渡して結果を自分で解析できます。これにより、検証とリトライの制御が可能になります。
import json
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="uncensored",
base_url="https://api.nsfwllmrouter.com/v1",
api_key="YOUR_API_KEY",
model_kwargs={"response_format": {"type": "json_object"}},
)
msg = llm.invoke(
"Return a JSON object with keys title and tags (a list of three strings) "
"for an article about hiking."
)
data = json.loads(msg.content)
print(data["title"], data["tags"])
プロンプトで「JSON」と言い、必要なキーを説明してください。JSONモードは出力を有効なJSONにしますが、正確なスキーマを保証するわけではないため、解析後にキーを確認し、失敗した場合はリトライしてください。失敗したリクエストには課金されないため、形式が正しくない出力に対するリトライループのコストはわずかです。
同時実行数、リトライ、レート制限
LangChain は .batch() で作業を分散させやすくしますが、ここで多くの人が最初のレート制限にぶつかります。フルキーでは同時リクエストが 8、1 分あたり 300 まで可能です。トライアルキーでは同時リクエストが 2、1 分あたり 60 までです。これを超えると API は HTTP 429 を返します。1 分あたりのリクエスト数を超えた場合はエラーコードが rate_limited となり、同時に実行されるリクエストが多すぎる場合は concurrency となります。一時的な過負荷時には、Retry-After: 5 ヘッダー付きの 429 も返されます。
バッチの同時実行数を自分で設定し、バックオフでクライアントがリトライするようにします。
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="uncensored",
base_url="https://api.nsfwllmrouter.com/v1",
api_key="YOUR_API_KEY",
max_retries=4,
timeout=120,
)
inputs = [f"Summarize idea number {i} in one line." for i in range(40)]
results = llm.batch(inputs, config={"max_concurrency": 6})
print(len(results))
フルキーでは6に保つことで、同じアカウントからの他のリクエストの余裕を残します。トライアルキーでは2を使用してください。それでもconcurrencyエラーが表示される場合は、終了しなかったストリームがないか確認してください。各応答にはX-Request-Id(サポートに連絡する際に引用する値)と、残りのクレジットを表示するX-Balance-USDが含まれます。
Webアプリ用の非同期呼び出し
Web サーバー内ではブロッキング呼び出しを避けることがほとんどです。LangChain のチャットモデルの各メソッドには、ainvoke、astream、abatch という名前の非同期版が存在し、同期版と同様にこのエンドポイントに対して動作します。
import asyncio
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="uncensored",
base_url="https://api.nsfwllmrouter.com/v1",
api_key="YOUR_API_KEY",
)
async def main():
async for chunk in llm.astream("Describe a quiet harbor at dawn in two sentences."):
print(chunk.content, end="", flush=True)
asyncio.run(main())
非同期サービスでは、キーのサイズに合わせたセマフォを使用して進行中の呼び出しの数を制限してください。例えば、フルキーでは8です。これにより、アプリの1つのビジーエンドポイントがすべてのスロットを埋めて他のエンドポイントの処理を妨げないようにします。呼び出しの例外をキャッチし、X-Request-Idヘッダーをログに記録し、スタックトレースの代わりにユーザーに短いリトライメッセージを表示してください。
デバッグのヒント:チェーンが奇妙な動作をする場合、モデルが受信する正確なメッセージを出力してください。プロンプトテンプレートは余分な空白を挿入したり変数をドロップしたりすることがあり、モデルは送信されたものしか回答できません。LangChainのverboseおよびdebugスイッチ、またはLangSmithトレーシング(すでに使用している場合)は最終メッセージリストを表示します。
コンテキストウィンドウがチェーンに与える影響
コンテキストウィンドウはプロンプトと補完を合わせて100,000トークンであり、max_tokensはオプションです。これは、多くのドキュメントを1つのプロンプトに詰め込む検索チェーンや、長い履歴を保持する会話メモリには十分大きいです。また、トリミングせずに毎回完全な履歴を送信すると高価になるほど大きいです。
簡単な計算です。12,000 個の入力トークンを送信して 800 個を取得する検索ステップは、0.012 x $0.15 + 0.0008 x $0.50 = $0.0018 + $0.0004 = $0.0022 のコストがかかります。これを 1,000 回実行すると、料金は $2.20 になります。料金は入力トークン 1M あたり $0.15、出力トークン 1M あたり $0.50 で、サブスクリプションはないため、チェーンが実際に使用したものに対してのみ課金されます。料金ページ にチャージのオプションが、クイックスタートドキュメント にサポートされているすべてのパラメータが記載されています。プロンプトはトレーニングには使用されません。
