主要ポイント
- ChatOpenAIは変更なしで動作します:base_url、api_key、およびモデルを
uncensoredに設定します。 - APIはツール呼び出しをサポートしているため、bind_toolsと組み込みエージェントの両方が機能します。
- ループの反復回数を制限し、ツールの引数を検証します。オープンウェイトモデルは場合によっては誤ることがあります。
- エージェントを並列実行する際は、フルキーで8件の同時リクエスト制限を守ってください。
開始前に必要なもの
Python 3.10以降、APIキー、およびいくつかのパッケージが必要です。キーはAPIキーページから取得します:Googleまたはメールアドレスとパスワードでサインアップし、ワンクリックで$0.50の無料トライアルクレジットを受け取ります。トライアルキーは同時2リクエスト、1分あたり60リクエストまで許可され、エージェントを1つずつ実行する限り、この記事のすべてに対応できます。
pip install langchain-openai langchain-core langgraph
export API_KEY="paste-your-key-here"APIはPOST /v1/chat/completionsとGET /v1/modelsを公開しています。LangChainには最初のものだけで十分です。このモデルは、成人による合法的な利用においてコンテンツ拒否なしで回答するように調整された単一のオープンウェイトモデルであり、独立したプロバイダによって実行されており、いかなるモデルベンダーの公式APIではありません。ドキュメントには、エディタの横に確認したいヘッダーと制限が記載されています。
コンポーネントのAPIへのマッピング
LangChainのネットワーク上の動作を理解すると役立ちます。HumanMessageはrole userのメッセージになります。AIMessageはassistantになり、ツール呼び出しがある場合はtool_calls配列として表示されます。ToolMessageはrole toolになり、一致する呼び出しIDを持ちます。システムプロンプトはrole systemとして最初に来ます。
このマッピングはデバッグに重要です。エージェントが奇妙な動作をする場合は、各呼び出しの前にメッセージリストを出力し、トランスクリプトのように読み取ってください。通常、1分以内に問題が特定できます:空の結果、一致しない呼び出しID、または予想より大きく膨らんだ履歴など。「エージェント型」という言葉はこれを魔法のように聞こえさせますが、すべてのステップは通常のチャット補完(chat completion)リクエストです。
ステップ1:ChatOpenAIの接続
統合全体は3つの引数です。LangChainは標準的なOpenAIスタイルのリクエストを構築するため、サーバーは違いを区別できません。
import os
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="uncensored",
base_url="https://api.uncensoredvideo.cc/v1",
api_key=os.environ["API_KEY"],
temperature=0.7,
max_retries=2,
)
print(llm.invoke("Name three beats of a heist scene, one line each.").content)3行出力されれば、連携は正常です。401の場合はキーを確認してください。404の場合は、URLが/v1で終わり、追加のパスがないことを確認してください。max_retriesはクライアントオプションであり、Retry-After: 5を含む一時的な429応答に対処するのに役立ちます。
ストリーミングは、任意のOpenAIモデルで使用する場合と同じ方法で機能します:llm.stream(...)を呼び出して反復します。最後のチャンクには使用状況が含まれるため、呼び出しごとのトークン数をログに記録できます。
ステップ2:ツールの定義
エージェントはループです:モデルは会話を読み取り、回答するかツールを要求し、あなたのコードがツールを実行し、結果がモデルに戻ります。以下のツールは単純なPython関数です。メカニズムを確認することが目的であるため、意図的に退屈にしています。
from langchain_core.tools import tool
@tool
def word_count(text: str) -> int:
"""Count the words in a piece of text."""
return len(text.split())
@tool
def spoken_minutes(words: int, words_per_minute: int = 150) -> float:
"""Estimate how many minutes a script takes to read aloud."""
return round(words / words_per_minute, 2)
tools = [word_count, spoken_minutes]docstringと型ヒントは、モデルが参照するツール説明とスキーマになります。同僚に指示を出すつもりで記述してください。あいまいなdocstringは、エージェントが間違ったツールを選択する最も一般的な理由です。動画のスクリプトを書く場合、同じパターンはショットリストを検索したり、長さの予算を確認したりするツールに拡張できます。このトピックはSora 2スクリプトジェネレーターページでカバーされています。
ステップ3:エージェントループを手動で実行する
組み込みヘルパーはループを隠蔽します。自分で一度記述することで、後続のバグの発見が容易になります。
from langchain_core.messages import HumanMessage, ToolMessage
by_name = {t.name: t for t in tools}
llm_tools = llm.bind_tools(tools)
messages = [HumanMessage(
"Here is a draft: 'The vault door hums. Nina counts to three, "
"then cuts the power.' How many words is it, and how long is it read aloud?"
)]
for step in range(6):
ai = llm_tools.invoke(messages)
messages.append(ai)
if not ai.tool_calls:
break
for call in ai.tool_calls:
result = by_name[call["name"]].invoke(call["args"])
messages.append(ToolMessage(content=str(result), tool_call_id=call["id"]))
print(messages[-1].content)3つの詳細が重要です。range(6)の制限は、混乱したモデルが永遠にループするのを防ぎ、支出も制限します。tool_call_idは、回答する呼び出しと一致する必要があります。また、モデルは1回の返信で複数のツールを要求できるため、内側のループはリストを処理します。
内部では、リクエストはtools配列を含み、返信はtool_callsを含み、結果はrole: "tool"のメッセージとして返されます。tool_choiceで特定のツールを強制でき、これはLangChainでbind_toolsへの引数として公開されます。
ステップ4:ビルド済みLangGraphエージェントを使用する
手動ループが理解できれば、組み込みエージェントは行数を節約します。同じモデルとツールを受け取ります。
import os
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
llm = ChatOpenAI(
model="uncensored",
base_url="https://api.uncensoredvideo.cc/v1",
api_key=os.environ["API_KEY"],
)
agent = create_react_agent(llm, tools)
out = agent.invoke({"messages": [("user", "How long is a 450 word script read aloud?")]})
print(out["messages"][-1].content)LangChainとLangGraphはリリース間でAPIを変更し、一部のヘルパーは名前が変更または移動されています。インポートに失敗した場合は、同等のエージェントコンストラクタの現在のLangChainドキュメントを確認してください。接続部分、ベースURL付きのChatOpenAIは変更されません。
ループとグラフの選択
手書きのループは、いくつかのツールから選択して停止するエージェントには十分です。読みやすく、ログに記録しやすく、フレームワークの予期しない動作がありません。状態に依存する分岐が必要な場合にLangGraphに移行しましょう:もう一度処理するために作業を送り返すレビューステップ、危険なツールが実行される前に人間の承認、または結果を互いに渡す複数の専門的なサブエージェントなど。
選択したものに関係なく、各ノードを小さく保ち、1つの仕事だけを行ってください。JSON形式で短い番号付きリストを出力する計画ノード、ツールを実行する実行ノード、最終テキストを生成する作成ノードは、1つの巨大なプロンプトよりもテストが容易です。また、異なる温度を使用することもできます:計画とツール使用には低く、作成ステップでは高くします。各ノードは通常のチャット補完呼び出しであるため、同じキー、URL、モデル名がそれらすべてに共通して使用でき、コストは呼び出しの合計になります。
最後の習慣:実行中のトークン合計を出力してください。デモでは安価に見えるエージェントでも、実際のタスクでは10倍のコストがかかることがあり、すべての応答の使用状況データにより、最初の実行でそれが明確になり、残高での予期せぬ支出を防げます。
メモリ、テスト、構造化出力
上記のループはスクリプトが終了するとすべての情報を忘れます。最も単純なメモリはメッセージリストそのものです:ユーザーのターン間でこれを保持し、新しい質問をそれぞれに追加し、長くなったら最も古いメッセージをトリミングします。長時間のセッションでは、モデルによって書かれた短い要約で古いメッセージを置き換えてください。入力はトークンごとに課金されるため、500トークンの要約は、コストと焦点の両面で20,000トークンの生履歴に勝ります。
エージェントのテストは、主に観察可能な動作を確認することです。温度を0に設定するか、APIがそのまま渡す固定のseedを渡して、実行をより再現可能にします。次に、どのツールを呼び出すべきか知っている質問をいくつか書き、回答の正確な言葉遣いではなく、メッセージリスト内のツール名と引数についてアサートします。プロンプトやツール説明を変更するたびにこれらを実行してください。docstringを変更するとモデルが選択するツールが変わる可能性があり、テストはそれを見つける最も安価な方法です。
実行ごとに次の3つをログに記録します:ステップ数、引数付きのツール呼び出し、およびトークン使用状況。実行が失敗した場合、これらの3行は、モデルが誤った選択をしたのか、ツールが失敗したのか、ループがステップを使い果たしたのかを示します。
構造化出力とJSONモード
エージェントはしばしば文章ではなくデータを返す必要があります。APIはresponse_format: {"type":"json_object"}を通じてJSONモードをサポートします。LangChainではllm.bind(response_format={"type": "json_object"})でそれを要求できます。プロンプトで、回答がJSONオブジェクトでなければならないことと期待するキーの名前をモデルに伝え、文字列を解析してPydanticで検証します。解析ステップが常に成功するとは限りません。エラーテキストをモデルに1回送信するリトライでラップしてください。
陥りやすい罠とその解決策
- ツール引数の誤り。 オープンウェイトモデルは、整数を宣言した場所に文字列を送信することがあります。引数を検証し、ツールから短いエラーメッセージを返し、次のステップでモデルが自己修正できるようにします。
- 暴走ループ。 常に反復回数を制限してください。開発中は各ステップをログに記録します。
- 並列エージェント。 完全なキーは8つの同時リクエストを許可します。バッチを実行する場合は、
llm.batchにconfig={"max_concurrency": 4}を設定して余裕を持たせてください。制限を超えると429が返され、コードはconcurrencyになります。1分あたり300リクエストを超えるとrate_limitedが返されます。 - ストリーミングとツールの併用。 ストリーミングリクエストはストリームが終了するまで同時実行スロットを保持するため、トライアルキーのストリームハンドラ内で新しい呼び出しを開始しないでください。
- 履歴の肥大化。 各ステップで完全なメッセージリストが再送信されます。コンテキストウィンドウは100,000トークンですが、各呼び出しの入力に対して課金されます。
エージェント実行のコスト
タスクを完了するために4回のモデル呼び出しを行う控えめなエージェントを考えます。平均プロンプトは3,000トークン、呼び出しあたりの出力トークンは200です。入力:4 x 3,000 = 12,000トークン、これは12,000 x $0.15 / 1,000,000 = $0.0018です。出力:4 x 200 = 800トークン、これは$0.0004です。1回の実行コストは$0.0022なので、1,000回の実行で$2.20かかります。$0.50の無料トライアルクレジットは、約227回の実行をカバーできます。失敗または拒否されたリクエストは課金されず、クレジットは期限切れになりません。チャージ額とボーナスについては価格表をご覧ください。このサイトは暗号資産のみを受け付けます。TRC20上のUSDTまたはBase上のUSDCです。
