DeepSeek Harness(略称 dsh)をブラウザ上のページから自分のプログラムへ移そうとすると、Pure の Web UI では対応できません——人間が眺めるためのインタラクション方式は、バッチスクリプトや CI パイプライン、自社製品で再利用できないからです。このときに本当に使うべき入口は Python SDK です。これは dsh を、人手でクリックする必要のある画面ではなく、あなたのコードにおける一行の呼び出しに変えてくれます。本記事は「DeepSeek Harness の Python SDK と多形態呼び出し:Web UI からヘッドレス実行まで」の前半として、最も実用的な一つの道筋に焦点を当てます——deepseek-harness-sdk をインストールし、リポジトリ内蔵のサンプル minimal.py を動かし、DeepSeekHarness コンテキストマネージャのライフサイクルを理解し、ついでに資格情報、エンドポイント、セッションログといった、見落とされがちでありながら成否を直接左右するエンジニアリングの細部を整理します。この段落を読み終えれば、隔離された workspace で Agent をヘッドレスに駆動できるようになり、次段のマルチモーダルおよび多形態呼び出しのための土台を固められるはずです。

Python SDK で何をインストールするのか:deepseek-harness-sdk と内蔵ランタイム

多くの人が SDK に初めて触れるときこう尋ねます。すでに dsh コマンドラインツールをグローバルにインストールしているのに、なぜ Python パッケージまでインストールする必要があるのか、と。答えは SDK の設計に隠されています。SDK の役割は Agent ランタイムを再実装することではなく、軽量な適配層になることです——ランタイムの起動、設定の組み立て、タスクの送出、結果の回収を Python 側の API にします。それが本当に解決する問題は、一言でまとめられます:dsh を、ブラウザの一ページではなく、あなたのプログラムにおける一回の関数呼び出しにすること

この適配層の要点はバージョン結合にあります。python -m pip install deepseek-harness-sdk を実行すると、pip は Python パッケージ本体だけでなく、それと同一バージョンの内蔵ランタイムも取得します。つまり、SDK のバージョン番号とランタイムのバージョン番号は対になっており、公式はこの方法でインターフェースの取り決めが一致することを保証し、「SDK は A フィールドを渡したのに、ランタイムは B フィールドしか認識しない」といったミスマッチを避けています。これは上級読者にとって重要です。片方だけを手動でアップグレードし、もう片方に手を付けなければ、互換性の落とし穴を踏む可能性があります。堅実な方法は常に pip で SDK をインストールまたはアップグレードすることです。ランタイムを単独でいじるのではなく、依存関係リゾルバに対になるバージョンを固定してもらいましょう。

もう一つ誤解されやすい点は Node.js 依存です。dsh 自体は多言語呼び出しに向けたツールチェーンであり、コマンドライン形態では確かに Node エコシステムと交差します。しかし SDK のランタイムは自前で持っている一份です。SDK をインストールした後、ランタイムはシステムが Node.js を提供することを必要とせず、Python プロセスが自ら使用可能なランタイムを携えています。これはつまり、Python だけがインストールされたクリーンなコンテナで Agent を動かせ、Node 環境や npm バージョン、グローバルパッケージの競合を追加で維持する必要がないということです。CI/CD や本番デプロイにとって、これはイメージサイズと環境ドリフトのリスクを大幅に低減できます。

示意图
この図は Python SDK と内蔵ランタイムの対になる関係を示しています。pip で SDK をインストールすると同一バージョンのランタイムも一緒に取得され、Python プロセスはシステムの Node.js を必要とせず自前でランタイムを携えます。

では、SDK は結局誰に向いているのか。公式が示す典型的なシナリオは三つある:

  • バッチ処理タスク:同じ Agent タスクを数百から数千の入力に対して繰り返し実行する必要があり、手作業で UI をクリックするのは明らかに現実的でない;
  • 自社製品への統合:Agent の機能を製品内の一機能モジュールとして扱い、外部の Web ページではなくプロセス内で呼び出す必要がある;
  • テストでの Agent 駆動:自動テストの中でタスクを構築し、出力をアサートして Agent の挙動がリグレッションしていないかを検証する。

これら三つのシナリオには共通する特徴が一つある:呼び出し元がプログラムであり、人間ではないということだ。SDK はまさにプログラムからの呼び出しのために生まれた抽象化であり、ライフサイクル、資格情報、セッションといった状態を制御可能なオブジェクトにまとめ上げ、上位のコードは「どんなタスクを送り、どんな結果を受け取るか」だけを気にすればよくなる。この位置づけを理解すれば、後続のインストールと呼び出しはすべて自然に筋道が通る。

前提条件の対照表:Python 3.10、Git、Linux/macOS 14+ arm64

実際にインストールを始める前に、まず環境を照合して確認しよう。SDK はシステム要件を比較的明確に記載しており、このステップを飛ばすと実行時に不可解なエラーに遭遇しやすい。以下の表は五つの前提条件を一つずつ列挙したもので、項目ごとに確認することを勧める:

依存項目要件確認ポイント
Python3.10 以上3.10 未満ではインストールまたはインポートの段階で失敗する。まず python --version を実行して確認する
Gitインストール済みインストール手順の最初のステップがリポジトリのクローンであり、Git がなければ出発点で詰まる
オペレーティングシステムLinux x64、Linux arm64、または macOS 14+ の arm64macOS のバージョンとアーキテクチャの制限に注意。古すぎるシステムや Intel アーキテクチャはサポート対象外
API エンドポイントDeepSeek 互換の API エンドポイントと資格情報API Key を用意し、必要に応じて互換プロキシの base URL も準備する
ワークスペースagent が変更可能な隔離された workspaceAgent が読み書きする権限を持つディレクトリである必要がある。隔離はホストマシンのファイルを誤って変更するのを避けるためだ

一つずつ分解してみよう。まず Python 3.10+ は、推奨値ではなく厳格なハードルである。理由は、SDK 内部で比較的新しい型アノテーションや構文機能が使われている可能性があり、3.9 以下ではインポートの成功を保証できないからだ。次に Git。公式が推奨する導入方法はリポジトリをクローンして同梱のサンプルを実行することであり、クローンしなければ自分で設定を手書きすることになり、コストが高くミスも起きやすい。

三つ目のオペレーティングシステムについてはもう少し補足したい:サポート範囲は Linux x64、Linux arm64、そして macOS 14 以上の arm64 である。macOS には二つの制約があることに注意——バージョン 14+ とアーキテクチャ arm64 だ。より古い macOS や Intel チップの Mac の場合、公式サポートリストに含まれず、問題が起きたときの受け皿が欠ける。四つ目は API エンドポイントと資格情報で、デフォルトの DeepSeek 公式エンドポイントを使う場合は API Key が一つあればよい。OpenAI 互換プロキシを経由する場合は、追加で base URL を提供する必要がある(後の環境変数の節で説明する)。

最後の項目であるworkspace の隔離は最も軽視されやすいですが、実はセキュリティの最低ラインです。Agent はタスクを実行する際にファイルを読み書きし、コマンドを実行します。もしそれをあなたのホームディレクトリやプロジェクトルートに直接向けてしまうと、一度の誤操作で触れてはいけないファイルを変更してしまう可能性があります。正しいやり方は、専用の空ディレクトリを workspace として確保することです。Agent にはその中で自由に作業させ、タスク終了後は必要に応じて破棄またはアーカイブします。この workspace は絶対パスでなければならない理由は、コマンドライン引数の節で詳しく説明します。

これら5項目を揃えれば、「作業を開始できる」チェックリストが完成します。いずれかが満たされていない場合は、先に補ってから続行することをおすすめします。そうしないと、後で問題を調査するときに、環境の問題なのか使い方の問題なのかを判断しにくくなります。

リポジトリを仮想環境にクローンする:4つのコマンドでインストール完了

環境チェックが通れば、インストール自体は実はとても短いです。公式には仮想環境の使用が推奨されており、SDK をシステム上の他の Python パッケージから分離し、バージョン競合を避けることができます。完全な手順は以下のコマンド群に圧縮できます。順番に実行することをおすすめします:

# ステップ1:リポジトリをクローンし、実行可能なサンプルを取得
$ git clone https://github.com/deepseek-ai/deepseek-harness.git

# ステップ2:リポジトリのディレクトリに移動
$ cd deepseek-harness

# ステップ3:仮想環境を作成
$ python -m venv .venv

# ステップ4:仮想環境を有効化(Linux / macOS)
$ . .venv/bin/activate

# ステップ5:SDK と同じバージョンの組み込みランタイムをインストール
$ python -m pip install deepseek-harness-sdk

各コマンドの役割とよくある落とし穴を順に説明します:

  • git clone:リポジトリをローカルに取得します。これは必ずしもソースから実行するためではなく、examples/ 配下の実行可能なサンプルと付属の設定ファイルを取得するためです。サンプル内の minimal.cordis.yml はどのプラグインを起動するかを記述しており、一から自分で書くのは時間がかかり、抜け漏れも起きやすいです。
  • cd deepseek-harness:リポジトリのルートディレクトリに移動します。以降のコマンドはこれを基準にします。サンプルのパスはリポジトリルートからの相対パスなので、移動しないとパスが見つからなくなります。
  • python -m venv .venv.venv という名前の仮想環境を作成します。venv を直接叩くのではなく python -m venv を使うのは良い習慣です。これにより現在の python インタプリタが確実に使われ、PATH が別のインタプリタを指していることを避けられます。
  • . .venv/bin/activate:仮想環境を有効化します。有効化後はコマンドラインプロンプトに通常環境名が表示され、このとき pythonpip はどちらも仮想環境内のバージョンを指します。Windows を使う場合、有効化スクリプトのパスは異なりますが、公式のサポートマトリクスには Windows が含まれていないため、ここでは詳しく触れません。
  • pip install deepseek-harness-sdk:SDK をインストールします。前述のとおり、このステップでは付属の組み込みランタイムも一緒にインストールされます。インストール後はランタイムが自己完結しており、システムが Node.js を提供する必要はもうありません

なぜ仮想環境を繰り返し強調するのか。それは、Agent の実行時に一連の依存関係が導入され、それをシステムの Python に直接インストールすると既存のパッケージと衝突しやすいからです。特に、同じマシン上で複数のプロジェクトを同時に維持している場合にはなおさらです。仮想環境は依存関係をサンドボックスに閉じ込め、.venv を削除すればクリーンなアンインストールと同等になります。このようなロールバック可能性は、エンジニアリング上非常に重視される性質です。

インストール完了後は、簡単なセルフチェックを行うことをおすすめします。アクティブ化された仮想環境内でインポートを実行し、パッケージが見つかること、バージョンが想定どおりであることを確認します。インポートでエラーが出た場合は、まず仮想環境が本当にアクティブ化されているか、Python のバージョンが 3.10 に達しているかを確認してください。この2点を切り分ければ、インストール時の問題のほとんどを特定できます。

minimal.py を動かす:SDK チェーン全体を検証する

インストールが完了したからといって、チェーンが使えるとは限りません。リポジトリには親切にも組み込みのサンプル minimal.py が用意されています。その位置づけは「最小限の実行可能な検証」です。これを動かせれば、Python からの呼び出し、ランタイムの起動、資格情報の読み取り、タスクの送信、モデルの応答、結果の回収までのSDK チェーン全体を検証したことになります。機能の網羅性は追求せず、エンドツーエンドを通すことだけを追求しているため、自作プロジェクトに組み込む前の最初の試金石として最適です。

示意图
この図は、リポジトリ組み込みの minimal.py サンプルを実行する全過程を示しています。スクリプトは workspace とセッションのパラメータを受け取り、Agent にタスクを実行させ、最終的な返信を出力します。

minimal.py の場所は examples/jsonrpc-agent/minimal.py です。これはコマンドラインで実行可能なスクリプトで、いくつかのパラメータを受け取ってタスクを一度発行し、assistant の最終的な返信を出力します。その判断基準は非常に直感的です。スクリプトがモデルの最終的な返信を正常に出力し、例外終了しなければ、資格情報、エンドポイント、ランタイム、設定の各環が通っていることを意味します。逆に、どこかの環で詰まると、エラーメッセージは通常、具体的な環を指し示します。資格情報が誤っていれば認証失敗が表示され、パスが誤っていればディレクトリが存在しないと表示され、設定が誤っていればプラグインの読み込み例外が表示されます。

特筆すべきは、このサンプルが「最小限」であり得るのは、プラグインの組み立ての詳細をスクリプトにハードコードするのではなく、リポジトリが提供する複合設定ファイルを再利用しているからです。いわゆる複合設定とは、「どのプラグインを起動するか」を記述したリストであり、SDK はそれに基づいてランタイムを組み立てます。この点を理解することは、後で自作プログラムから呼び出す際に重要です。サンプルの中核ロジックを再現するときにも、同様に設定ファイルを指定する必要があります。

サンプルを動かすメリットは「使えることの検証」にとどまりません。それは参考にできる骨組みでもあります。サンプルを自分のタスクに改造するとき、パラメータの渡し方、パスの指定の仕方、セッションの標示の仕方に、既成のテンプレートが手に入ります。多くの初学者はサンプルを飛ばして自分の呼び出しコードを直接書いてしまい、その結果、設定とパスで試行錯誤を繰り返し、かえって遅くなります。まず動かし、それから改造する。それがより時間を節約できる道筋です。

資格情報とエンドポイントの環境変数:DEEPSEEK_API_KEY、DEEPSEEK_BASE_URL、DSH_MODEL

minimal.py は、資格情報と実行パラメータを取得するためにいくつかの環境変数に依存しています。公式に示されている設定方法は次のとおりです。

# 必填:你的 DeepSeek API 密钥
$ export DEEPSEEK_API_KEY=sk-your-key-here

# 可选:仅当模型不是由默认 DeepSeek 端点提供时才需要
# export DEEPSEEK_BASE_URL=http://127.0.0.1:8000/v1

# 可选:指定模型名
# export DSH_MODEL=deepseek-v4-flash

# 可选:自定义系统提示词
# export DSH_SYSTEM_PROMPT='You are a helpful software engineer assistant.'

これらの変数の値の決定ロジックは明確に区別する必要があります。特に DEEPSEEK_BASE_URL の発動条件に注意してください:

  • DEEPSEEK_API_KEY:必須です。これは認証情報であり、欠落または誤りがあるとリクエストが直接拒否されます。キーがコードとともに漏洩するのを避けるため、コードにハードコードするのではなく環境変数で設定することを推奨します。
  • DEEPSEEK_BASE_URLあなたのモデルがデフォルトの DeepSeek エンドポイントではなく、OpenAI 互換プロキシ経由で提供される場合にのみ設定が必要です。公式エンドポイントを使う場合はこの項目を省略でき、サンプルでもコメントアウトされています。設定する際は URL にバージョンパスを含める必要があり、サンプルでは http://127.0.0.1:8000/v1 のような形式で、ローカルプロキシを指しています。
  • DSH_MODEL:モデル名を指定します。サンプルのコメントで示されている値は deepseek-v4-flash です。複数の利用可能なモデルがあり、特定の一つを固定したい場合、デフォルト値に頼るよりも明示的に設定する方が確実です。
  • DSH_SYSTEM_PROMPT:カスタムのシステムプロンプトです。サンプルではソフトウェアエンジニアアシスタントのペルソナを表す一文が与えられています。バッチ処理タスクではシステムプロンプトを固定することで、出力スタイルが安定し、ドリフトを減らすのに役立ちます。

ここにエンジニアリング上非常に実用的な判断ルールがあります:自前構築またはサードパーティのプロキシエンドポイントに切り替える際は、DEEPSEEK_BASE_URL がそのプロキシを指していること、そして DEEPSEEK_API_KEY がそのプロキシに認められた認証情報であることを同時に確保しなければなりません。両者が一致しないことが、最もよくある「キーは明らかに正しいのに認証 401」の根本原因です——キーは正しいのですが、それが別のエンドポイントに属しているだけなのです。逆に、ローカルで一時的に互換プロキシを立てた場合は、必ず BASE_URL を明示的に export してください。そうしないとリクエストは公式エンドポイントに飛び、予期しない課金や権限の問題が発生します。

もう一つの細かい点は変数のスコープです。export で設定した環境変数は現在のシェルセッションでのみ有効で、新しいターミナルを開くと消えてしまいます。永続化したい場合は、シェルの設定ファイルに書き込むか、起動スクリプトで一括して注入する必要があります。CI 環境では、キーをリポジトリのファイルに書き込むのではなく、プラットフォームが提供するシークレット管理機構を使って注入することが推奨されます。

minimal.py のコマンドライン引数:--workspace、--session-root、--session-id

環境変数を設定したら、サンプルを実行できます。公式に示されているコマンドの形式は次のとおりです:

$ python examples/jsonrpc-agent/minimal.py \
    --workspace /absolute/path/to/workspace \
    --session-root /absolute/path/to/sessions \
    --session-id example-001 \
    "Inspect the repository and fix the failing tests."

3 つの引数にはそれぞれ明確な役割分担があります。一つずつ説明します:

  • --workspace:Agent がアクセスできる作業ディレクトリです。絶対パスでなければなりません。このディレクトリは Agent が自由に読み書きできる「サンドボックス」であり、タスク内の「リポジトリを検査し、失敗したテストを修正する」といった動作はすべてその中で行われます。相対パスを使うと実行時の解決基準が不確定になり、予期しない場所を指す可能性があるため、公式は絶対パスを要求しています。
  • --session-root:セッションログと状態の保存ディレクトリです。同様に絶対パスでなければなりません。すべてのセッションのディスク上のファイルはこのルートディレクトリの下に集約され、一元的な管理とクリーンアップが容易になります。
  • --session-id:この永続化された対話の識別子です。サンプルでは example-001 が与えられています。これは一回のタスクと一つのセッションログを結び付け、事後の検索を容易にします。同じ session-id は、同じ対話コンテキストの継続を意味します。

コマンドの最後にある引用符で囲まれた文字列はタスク記述であり、つまりあなたが Agent にやってほしいことです。例では「リポジトリを確認して失敗したテストを修正する」となっており、非常に典型的なソフトウェアエンジニアリングのタスクです。タスクの表現が Agent の行動経路に影響することに注意してください。具体的に書くほど、使える結果を得やすくなります。

なぜパスは絶対パスでなければならないのか。それは、実行時のカレントワーキングディレクトリ、SDK プロセスのワーキングディレクトリ、そして Agent が内部でコマンドを実行するときのワーキングディレクトリが一致しない可能性があるからです。相対パスはこの三者間で曖昧さを生みますが、絶対パスは曖昧さを排除します。これは多くの「ファイルは確かにあるのに見つからない」問題の根源です。エンジニアリング上は、コード内で Path("/absolute/path/to/workspace").resolve() のような方法で絶対パスを生成し、SDK に渡す前に正規化して手書きミスを減らすことをおすすめします。

もう一点注意してください。workspace とセッションディレクトリは分けることをおすすめします。workspace は Agent の作業領域であり、頻繁に変更され、ファイルが削除されることさえあります。セッションディレクトリはログ領域であり、監査とデバッグのために保持する必要があります。混ぜてしまうと、ログが Agent の書き込み操作によって汚染され、作業領域がログファイルで埋め尽くされてしまいます。

セッションディレクトリ内の JSONL ログ:モデルリクエストとツール呼び出しに何が記録されるか

例を実行すると、画面に表示される最終応答のほかに、上級読者にとってより注目に値するものがあります。それはセッションディレクトリにディスクへ書き出される JSONL ログです。スクリプトの実行中、セッションディレクトリには JSONL 形式の記録が届きます。そこには組み立て後のモデルリクエストツール呼び出しという二種類の内容が含まれます。この二種類の記録の価値はまったく異なるので、分けて見てみましょう。

  • 組み立て後のモデルリクエスト:これは SDK とランタイムがユーザータスク、システムプロンプト、履歴コンテキスト、利用可能なツール一覧などを組み立て終えた後、実際にモデルへ送信するリクエストボディです。「組み立て後」という言葉に注意してください。それはあなたがコードに書いたタスク記述ではなく、ランタイムによって加工され、完全なコンテキストを含む最終形態です。Agent の目に「見えている」ものを知りたいなら、この記録を見るのが最も直接的です。
  • ツール呼び出し:Agent がタスクを実行する過程でツールを呼び出した記録であり、何を呼び出し、どんな引数を渡し、何が返されたかを含みます。失敗したテストの修正のようなタスクでは、ファイルの読み取り、コードの変更、コマンドの実行といった一連のツール呼び出しが含まれることが多く、それらはすべて痕跡を残します。

JSONL(JSON Lines)形式自体についても一言触れる価値があります。各行が独立した JSON オブジェクトであり、ストリーミングでの追記書き込みと行単位の解析に自然に向いています。ログは実行しながら書き出されるため、タスクの終了を待たずに読むことができ、長いタスクのデバッグに非常に適しています。Agent がまだ実行中のときにログを tail して、それが何をしているかをリアルタイムで観察できます。

この二種類の記録を組み合わせて見ることで、あるタスクの完全なタイムラインを再構築できます。モデルが何を受け取り、どのツールを呼び出すと決め、ツールが何を返し、それに基づいてモデルがさらにどんな決定をしたのか。この一連の流れは、Agent の異常な挙動を調査するうえで特に重要です。たとえば「Agent がいつも間違ったファイルを変更する」という場合、モデルリクエストを確認して workspace の記述が正確かを見たり、ツール呼び出しを確認して最初に読んだファイルが何かを見たりするほうが、最終応答を眺めて推測するよりずっと速いことがよくあります。

強調しておく必要があるのは、セッションログにはモデルリクエストやツール呼び出しなど、機密情報を含む可能性のある記録が含まれることです。あなたのタスクがプライベートなコードや内部データに関わる場合、必ずセッションディレクトリをコードと同等レベルのアクセス制御に含め、うっかり公開リポジトリにコミットしないようにしてください。期限切れのセッションを定期的に清理するのも良い習慣であり、ディスクがログで埋め尽くされるのを防げます。

DeepSeekHarness コンテキストマネージャ:遅延起動と自動解放

リポジトリに組み込まれているサンプル minimal.py は、実は SDK 呼び出しの軽量なラッパーであり、コマンドライン引数の解析を取り除くと、コアロジックはわずか二段階です。コンテキストを構築し、タスクを送る。以下の等価なコードは、自作プログラムに統合する際の出発点として使えます。

# ファイルパス:examples/jsonrpc-agent/minimal.py の等価な書き方
from pathlib import Path
from deepseek_harness import DeepSeekHarness

# サンプルのコンポジション設定ファイルの絶対パス(.cordis.yml はどの Plugin を起動するかを記述する)
config = Path("examples/jsonrpc-agent/minimal.cordis.yml").resolve()

# agent がアクセスできる workspace。絶対パスである必要がある
workspace = Path("/absolute/path/to/workspace").resolve()

# セッションログと状態の保存ディレクトリ。絶対パスである必要がある
sessions = Path("/absolute/path/to/sessions").resolve()

# コンテキストマネージャ:enter 時に組み込みランタイムを遅延起動し、exit 時に自動で解放する
with DeepSeekHarness(
    provider="deepseek-official",   # DeepSeek 公式プロバイダを使用
    model="deepseek-v4-flash",      # モデル名。SDK のデフォルトもこれ
    max_tokens=49_152,              # 1 回の返信の最大 token 数
    cwd=str(workspace),             # workspace を agent の作業ディレクトリに設定
    session_root=str(sessions),     # セッションログの書き込み先
    cordis=str(config),             # どのコンポジション設定で起動するか
) as harness:
    # タスクを送信する。session_id はこの永続化された対話を識別するために使う
    result = harness.run(
        "Inspect the runoob-demo repository and fix the failing tests.",
        session_id="example-001",
    )

# assistant の最終応答を出力
print(result.final_response)

このコードの重心は DeepSeekHarness コンテキストマネージャ のライフサイクルセマンティクスにあります。これは SDK の中核クラスであり、Python の with プロトコルでランタイムを管理します:

  1. with ブロックに入る際に組み込みランタイムを遅延起動する。「遅延」という言葉に注目してください。オブジェクトを構築した瞬間にランタイムを立ち上げるのではなく、ブロックに入る時になって初めて起動します。この遅延戦略により、まず設定オブジェクトを構築してパラメータ検証を行い、時間のかかるランタイム起動を本当に必要な時まで先送りできます。失敗点がより手前になり、特定しやすくなります。
  2. exit 時に自動で解放する。ブロック内が正常終了でも例外発生でも、with は解放ロジックをトリガーし、ランタイムを閉じてリソースを回収します。これにより、手動クリーンアップの漏れによるプロセス残留のリスクがなくなり、コンテキストマネージャの最も実用的な価値となっています。
  3. ブロック内で run を繰り返し呼び出せる。ランタイムは一度起動すれば、同じブロック内で複数のタスクを実行でき、タスクごとにランタイムを再起動する必要はありません。バッチ処理のシナリオでは、この再利用特性が効率を直接左右します。起動コストは一度だけ払えばよいのです。

コンストラクタ引数にもかなりの情報が埋め込まれています。項目ごとに見ていきましょう:

  • provider:値 deepseek-official は DeepSeek 公式プロバイダーを使用することを示します。プロバイダーを切り替えるときはここを変更し、SDK はそれに基づいてエンドポイントと認証戦略を選択します。
  • model:例では deepseek-v4-flash を取っており、コメントにはこれが SDK のデフォルトモデルでもあることが明記されています。モデルを変更する必要があるときはこの項目を変更するだけです。
  • max_tokens:1回の返信の最大 token 数で、例では 49_152 を取ります。Python のアンダースコア付き数値リテラルは大きな数を読みやすくします。この値は1回の返信の上限を決め、大きすぎると無駄にクォータを消費し、小さすぎると切り捨てられる可能性があります。
  • cwd:workspace を Agent の作業ディレクトリに設定します。ここでは先に resolve した絶対パス文字列を渡します。
  • session_root:セッションログの書き込み先で、前節で説明した JSONL ログディレクトリに対応します。
  • cordis:どの組み合わせ設定で起動するかを指定します。例では minimal.cordis.yml を指しており、このファイルはどのプラグインを起動するかを記述しています。

run メソッドの呼び出し形態も注目に値します。最初の引数はタスク説明文字列で、session_id はこの永続化された会話を識別するために使われます。戻り値 resultfinal_response プロパティが assistant の最終返信であり、そのまま出力すればよいだけです。

バッチ処理を行う場合は、with ブロックを外側に置き、ループをブロック内に置くことをおすすめします。そうすれば実行時は一度だけ起動されます。逆に with をループ内に入れると毎回ランタイムを再構築することになり、起動オーバーヘッドが何倍にも膨らみます。これは両方の書き方が意味的には正しいものの、性能が天と地ほど違う箇所であり、上級の読者は特にこの落とし穴を避けるべきです。もう一つの実務的な提案は、異なるタスクには異なる session_id を使うことです。そうすれば各会話がセッションディレクトリ内で独立した一篇となり、後から id で検索しても互いに干渉しません。同じ id を再利用すると、複数タスクのコンテキストが予期しない形で絡み合う可能性があります。

ここまでで、SDK のインストール、検証、資格情報、引数、ログ、ライフサイクルが一本の完全なチェーンとしてつながりました。あなたはすでに Agent を隔離された workspace でヘッドレスにタスク実行させ、監査可能な JSONL 記録を残せるようになりました。次にまだ重要なピースがあります。入力がもはや文字だけではなく、画像とテキストの混合になったとき、Harness はどの形態で呼び出すべきか——まさに次の段落で展開するマルチモーダルおよび多形態呼び出しです。

前の段落では、Web UI の可視化デバッグからヘッドレス実行の基本形態まで一気に進み、dsh が人間が画面を見ながら使うことも、プログラムによって非対話的に駆動することもできることを確認しました。この段落では視点を徹底的にコードレベルまで押し進めます。まず Python SDK の各コンストラクタフィールドを分解し、次に Agent 能力の質的変化の入口であるマルチモーダルへと広げます。これにより、Harness を自動化パイプラインに組み込むことも、Agent に画像やデザインカンプを本当に「見せる」こともできるようになります。

2つの必須パスと1つの設定ファイル:workspace、sessions、minimal.cordis.yml

SDK の世界では、すべては3つの Path オブジェクトから始まります。それらはオプションの装飾的な引数ではなく、Agent が起動できるかどうか、そして実行後に追跡できるかどうかの土台です。公式サンプル minimal.py の核心は極めてミニマルですが、そのミニマルさの裏には必ず押さえるべき3つのパスがあります。workspace は agent が読み書きできるワークスペースで、すべてのファイルレベル操作はこのルートディレクトリ配下に制限されます。それは隔離されたサンドボックスであり、ディスク全体ではありません。SDK は Path("/absolute/path/to/workspace").resolve() でそれを絶対パスに変換します。これは、異なる作業ディレクトリで相対パスが生む曖昧さを排除するためです。相対パスだけを書くと、プロセスのカレントディレクトリが変わったときに、Agent がリポジトリルートの外でファイルを操作する可能性があります。sessions はセッションログと状態の保存ディレクトリで、同様に .resolve() で絶対パスにする必要があります。harness.run を呼び出すたびに、このディレクトリには JSONL ログが増え、そこには組み立てられたモデルリクエストと各ツール呼び出しの経緯が記録されます。それがなければ、ターミナルに出力された最終返信だけが残り、問題が起きたときに振り返る手段がありません。あれば、推論チェーン全体が監査可能になります。minimal.cordis.yml は、初心者がしばしば見落とすものの、「Agent にどのような能力があるか」を決める設定ファイルです。これは起動時にどのプラグインをロードするかの組み合わせ方案、つまり dsh のプラグインアセンブリ一覧を記述します。Agent の「スキル設定シート」と理解してよいでしょう。どのツールを有効にし、どのプロバイダーに接続し、どのランタイム動作を注入するかはすべてここで宣言されます。SDK の例では Path("examples/jsonrpc-agent/minimal.cordis.yml").resolve() でリポジトリ内蔵の組み合わせ設定を指しています。実際のプロジェクトでは通常、自分で管理できるディレクトリに置き、製品と一緒にバージョン管理する必要があります。設定が変われば Agent の能力境界も変わるからです。

この三者は安定した三角形を構成します。cordis は「何ができるか」を決め、workspace は「どこで行うか」を決め、sessions は「行った痕跡がどこに残るか」を決めます。 エンジニアリングで最もよくある落とし穴は、パスが絶対化されていない、あるいは workspace が読み取り専用ディレクトリを指しているため、Agent が実際にファイルを変更しようとしたときに権限やパス解決の失敗で途中エラーになることです。もう一つの見えにくい落とし穴は、sessions ディレクトリが複数の並行タスクで共有され分離されていない場合で、ログが互いに上書きされ、調査時に二本のテープが重なって見えるような状態になります。推奨される方法は、タスク単位またはユーザー単位でもう一段サブディレクトリを分け、各セッションに独自の落点を持たせることです。

コンストラクタ引数を項目ごとに分解:provider、model、max_tokens、cwd、cordis

with DeepSeekHarness(...) as harness: というコンテキストマネージャに入ると、SDK は内蔵ランタイムの起動を遅延させ、with ブロックを抜けるときに自動的に解放します。この一文には二つのエンジニアリング上の意味があります。第一に、with ブロックに入った瞬間に実際にプロセスが立ち上がるのではなく、リソースは最初に必要になったときに準備されるため、起動オーバーヘッドが後ろにずれます。第二に、途中で例外が発生しても正常終了しても、ランタイムは後処理され、孤児プロセスを残しません。以下ではコンストラクタ引数を一つずつ分解します。

  • provider:どの提供元を使うか。例では deepseek-official で、DeepSeek 公式提供元を経由することを表します。公式のデフォルトエンドポイントではなく、OpenAI 互換プロキシ経由でモデルを提供する場合は、別途 DEEPSEEK_BASE_URL 環境変数を設定してプロキシアドレスを指定する必要があります。provider と base_url は直交する二つの情報で、一方は「誰を探すか」、もう一方は「どこへ探しに行くか」を表します。
  • model:モデル名。SDK のデフォルトは deepseek-v4-flash です。つまり、明示的に値を渡さなくてもこのモデルを使います。例で明示的に書いているのは、引数を一目で分かるようにするためだけです。モデルを切り替えたいときに変更するのはこのフィールドです。
  • max_tokens:1回の返信の最大 token 数。例では 49_152 が指定されています。Python のアンダースコア付き数値リテラルにより読みやすくなっており、49152 と等価です。この値は、提供したいタスクの複雑さに直接関係します。小さすぎると長い返信が切り詰められ、大きすぎると高並行時に VRAM と遅延の負荷が増えます。49_152 はかなり余裕のある設定で、Agent が1ターンで比較的長いコードや分析を生成するのに適しています。
  • cwdstr(workspace) を渡して、agent の作業ディレクトリに設定します。ここで一度文字列変換を行っていることに注意してください。cwd は文字列を受け取るのに対し、前述の workspace は Path オブジェクトなので、この変換は省略できません。
  • cordis:どの組み合わせ設定で起動するか。渡すのは設定ファイルパスの文字列形式、つまり前節の minimal.cordis.yml です。これは今回の実行でどのプラグインを組み込むかを決めます。

これらのフィールドをまとめて見ると、二つのグループに分かれることが分かります。provider、model、max_tokens は「どの脳を使い、どれだけの枠を与えるか」を表し、cwd、cordis は「どのような環境で、どの装備を伴うか」を表します。 この区分は問題調査に非常に役立ちます。モデルの答えが正しくない場合は前のグループを見て、Agent がツールを使えない、あるいは特定のディレクトリに到達できない場合は後ろのグループを見ます。SDK をインストールした後にもう一つ見落とされがちな事実があります。ランタイムはシステムが Node.js を提供する必要がありません。Python プロセス自身が内蔵ランタイムを携えています。これは、Python だけがあるクリーンなコンテナにデプロイでき、Node ツールチェーンやバージョン互換性の問題を追加で維持する必要がないことを意味し、Agent を CI やバックエンドサービスに組み込むうえで非常に現実的な負担軽減になります。

harness.run の呼び出し形態:タスク文字列 + session_id による永続化された対話

構築が完了した後、実際に Agent を駆動するのは harness.run です。with ブロックに入った後は run を繰り返し呼び出せますが、これは「1 つの with では 1 回しか実行できない」とよく誤解されます。呼び出し形態は非常にシンプルです:

# タスクを送信する。session_id はこの永続化された対話を識別する
result = harness.run(
    "Inspect the runoob-demo repository and fix the failing tests.",
    session_id="example-001",
)

# assistant の最終応答を出力する
print(result.final_response)

最初の引数はタスク文字列で、Agent に何を達成してほしいかを自然言語で記述します。例では典型的なソフトウェアエンジニアリングのタスク、つまりリポジトリを検査して失敗しているテストを修正するものです。2 番目の引数は session_id で、この永続化された対話を識別するために使われます。同じ session_id を複数の run 間で再利用するということは、対話のコンテキストが連続している、つまり Agent が以前に何が起きたかを覚えていることを意味します。別の session_id に変えれば、まったく新しい対話を始めることになります。返される result オブジェクトでは、final_response が assistant の最終応答を保持します——「最終」であることに注意してください。途中のツール呼び出しの過程はこのフィールドには現れず、sessions ディレクトリ配下の JSONL ログに書き込まれます。したがって調査の際は 2 つの線で見ます:final_response で結論を、JSONL で過程を見るのです。

ここで実戦でぜひ身につけたい習慣があります:同じ session_id に対してループの中で無関係なタスクをやたらと流し込まないことです。コンテキストは累積するため、詰め込むほど後続の各リクエストが運ぶ履歴が長くなり、コストとレイテンシが同時に上昇します。合理的なやり方は、「境界のあるタスク単位」ごとに session_id を割り当てることです。たとえば 1 回の CI ビルド、1 つのユーザーセッション、1 回のバグ修正ラウンドなどです。タスク集合が互いに独立している場合、異なる session_id を使うほうがむしろクリーンで、より経済的です。

SDK の 3 つの典型的な着地点:バッチ処理、プロダクトへの組み込み、テスト駆動の Agent

SDK が存在する意義は、dsh をブラウザの中の 1 ページではなく、あなたのプログラムの中の 1 行の呼び出しにすることです。この位置づけの下で、3 つの最も典型的な着地点があります。

  1. バッチ処理タスク。検査すべきリポジトリが一批、生成すべきレポートが一批、修正すべき失敗テストが一批あるとして、Web UI で 1 つずつ手作業でクリックするのは明らかに現実的ではありません。SDK でループを書き、各タスクに独立した workspace と session_id を指定し、Agent にバッチ処理させ、結果を一括で収集します。バッチ処理の鍵は分離です:各タスクのワークスペースは分けて、あるタスクの中間生成物が別のタスクを汚染しないようにします。
  2. dsh を自社プロダクトに統合する。あなたのプロダクトがバックグラウンドでこっそり Agent 能力を呼び出したいとします。たとえばユーザーが「自動修正」をクリックすると、バックエンドが 1 回の Harness 実行を駆動します。このとき SDK がその組み込み点となり、provider と model はプロダクト設定として、cordis は出荷時に組み立てるスキルセットを決定します。プロダクトへの組み込みは特にリソース解放に敏感で、with ブロックの自動解放メカニズムがまさに例外パスを支えてくれます。
  3. テストの中で Agent を駆動する。これは過小評価されている用法です:Agent をテスト対象として自動テストに書き込み、制御された workspace を構築し、決定的なタスクを実行させ、その後 final_response をアサートしたり sessions ログを検査したりできます。ランタイムが同梱されておりシステムの Node.js に依存しないため、このようなテストは CI コンテナで非常に軽く動きます。

リポジトリに同梱されている minimal.py のサンプルを動かせれば、SDK の一連の経路——資格情報の読み込み、ランタイムの起動、タスクの実行、ログの永続化まで——を検証できたことになります。サンプルを実行する前に、環境に資格情報を設定してください。公式サンプルにはいくつかの任意変数が示されています:

$ export DEEPSEEK_API_KEY=sk-your-key-here
# export DEEPSEEK_BASE_URL=http://127.0.0.1:8000/v1
# export DSH_MODEL=deepseek-v4-flash
# export DSH_SYSTEM_PROMPT='You are a helpful software engineer assistant.'

このうち DEEPSEEK_API_KEY は必須です。DEEPSEEK_BASE_URL は、モデルがデフォルトの DeepSeek エンドポイントではなく OpenAI 互換プロキシによって提供される場合にのみ設定が必要です。DSH_MODELDSH_SYSTEM_PROMPT は、それぞれモデル名とシステムプロンプトを上書きします。サンプルの実行コマンドでは、隔離された workspace とセッションディレクトリを一緒に渡します:

$ python examples/jsonrpc-agent/minimal.py \
  --workspace /absolute/path/to/workspace \
  --session-root /absolute/path/to/sessions \
  --session-id example-001 \
  "Inspect the repository and fix the failing tests."

スクリプトは assistant の最終応答を出力し、同時にセッションディレクトリには、組み立てられたモデルリクエストとツール呼び出しを含む JSONL ログが書き出されます。SDK 自体のインストールは仮想環境を使う方法で、公式には venv を使って SDK をシステム上の他の Python パッケージから隔離することが推奨されています:

$ git clone https://github.com/deepseek-ai/deepseek-harness.git
$ cd deepseek-harness
$ python -m venv .venv
$ . .venv/bin/activate
$ python -m pip install deepseek-harness-sdk

前提条件はまず照合して確認してください。Python 3.10 以上、Git がインストール済みであること、オペレーティングシステムが Linux x64 または Linux arm64 または macOS 14+ の arm64 であること、利用可能な DeepSeek 互換 API エンドポイントと資格情報、そして agent が変更できる隔離された workspace です。以下の表では、これらの要件と SDK の動作特性を並べて比較しているので、デプロイ前に一つずつチェックできます。

項目要件 / 動作エンジニアリング上の意味
Python バージョン3.10 以上低いバージョンのインタプリタではインストール不可
Gitインストール済みサンプル取得のためのリポジトリのクローンに必要
オペレーティングシステムLinux x64 / Linux arm64 / macOS 14+ arm64その他のプラットフォームはサポート対象外
API エンドポイントDeepSeek 互換エンドポイントと資格情報DEEPSEEK_BASE_URL でプロキシを指定可能
workspaceagent が変更できる隔離ディレクトリ読み書きサンドボックス。タスクごとの隔離を推奨
Node.jsシステムに不要Python プロセスに組み込みランタイムが同梱
ランタイムのライフサイクルwith に入ると遅延起動、抜けると自動解放例外経路でもプロセスをリークしない
デフォルトモデルdeepseek-v4-flashmodel を渡さなくてもこれが使われる
1 回の応答上限サンプルでは max_tokens=49_15249152 と等価。切り捨てとコストを制御

マルチモーダルとは何か:テキスト・画像・音声・動画から統一 token シーケンスへ

Agent をテキストの世界から視覚の世界へと押し進めるには、まずモダリティ(modality)という言葉を理解しなければなりません。モダリティとは、情報が表現される異なる形式のことです。文字は一つのモダリティであり、画像・音声・動画もそれぞれ異なるモダリティです。従来の大規模言語モデルはテキストという単一のモダリティしか扱えず、送る内容はすべてまず文字に変換する必要がありました。マルチモーダルモデルは、画像とテキストを同時に受け取り、それらを統一的に token シーケンスへ変換してまとめて処理できます。以下の表は、四つのモダリティ、よくある形式、対応する能力を対応付けたもので、ある要件がどの種類のモデルを求めるべきかを判断する助けになります。

モダリティよくある形式対応する AI 能力
テキスト記事、コード、会話大規模言語モデル(LLM)
画像写真、スクリーンショット、デザインカンプ、グラフ視覚理解モデル
音声音声、音楽音声認識・生成モデル
動画短編、画面録画、監視映像動画理解モデル

ここに決定的な技術的詳細があります。画像はモデルに「直接見られている」わけではありません。まず視覚エンコーダによって小さなブロック(patch)に分割され、ベクトルに変換されたうえで、テキスト token と同じシーケンスに連結されます。つまり、モデルが向き合うのは常に一つの token シーケンスであり、ただそのシーケンスの中に画像由来の token と文字由来の token が混ざっているだけなのです。この層を理解すれば、なぜ画像がリクエストコストを押し上げるのか、なぜ画像の解像度やトリミングの仕方が理解の質に影響するのか、そしてなぜ「画像を同じシーケンスに変換する」ことがマルチモーダルの統一的処理を可能にする鍵なのかが分かります。

示意图
マルチモーダルモデルは画像を視覚エンコーダでブロックに分割してベクトルに変換し、テキスト token と同じシーケンスに連結して処理する

Agent にとって、マルチモーダルがもたらすのは質的な変化であり、決して付け足しの彩りではありません。以下の対比からその差は一目で分かります。テキスト専用モデルの入力は文字だけなので、コードのエラーを調査するときはユーザーがエラーを手で文字に書き写す必要があり、デザインカンプを再現するときは視覚的なカンプを参照できず、データグラフを分析するときは画像の中の図を読み取れません。一方、マルチモーダルモデルはテキストと画像を混在入力でき、エラーのスクリーンショットをそのまま送り、デザインカンプと照らし合わせてページを書き、画像を見て直接結論を出せます。

なぜ画像も課金されるのか:視覚エンコーダが patch を切り分け、1 枚あたり最大 384 tokens

画像はまず patch に分割されてからベクトルに変換される以上、必然的に token 予算を消費します。1 枚の画像は最大 384 tokens を占めます。これこそがマルチモーダルの課金ルールの由来です。この上限の情報はエンジニアリング上とても実用的で、これに基づいて容量計画を立てられます。たとえば 1 回のリクエストに高解像度のスクリーンショットを何枚も詰め込んだとしても、token 消費は無限に膨らみはしませんが、ゼロでもありません。384 が 1 枚あたりの天井です。これを理解すれば、いくつかの実践的な結論は自然に導かれます。

  • 「画像は最大 384 tokens」だからといって無闇に画像を積み重ねないこと。1 枚ごとに予算の一部を占め、複数枚を重ねれば 1 回のリクエストのコストと処理時間は依然として大きく押し上げられます。必要に応じて画像を渡しましょう。
  • スクリーンショットの解像度と情報密度は重要です。画像はブロックに分割されベクトルに変換されたうえで理解される以上、ブロック内の情報が判別可能かどうかが理解の質を直接左右します。ぼやけていたり、小さすぎたり、大きく余白が空いた画像は、多くの場合割に合いません。
  • 同じ画像を何度も使うときは渡し方をよく考えること。後で三つの渡し方のトレードオフを説明しますが、頻繁に再利用する画像には Files API が通常より経済的です。

「モダリティ分類」と「画像課金」を合わせて見ると、マルチモーダルのコストモデルが明確になる。テキストはテキスト token で課金され、画像は視覚 token で課金され、しかも1枚あたりの上限があり、両者は同じシーケンス内でモデルによってまとめて処理される。あなたの最適化の余地は、半分は無効なテキストの削減に、半分は画像の数と品質の制御にある。

DeepSeek-V4-Flash-Vision-Exp と3種類の API 呼び出し形式

マルチモーダルを実際に使い始めるには、まず最新バージョンに更新しよう。使用前に最新版の dsh コマンドラインツールをインストールする:

npm install -g @deepseek-ai/dsh@latest

最新版に更新すると、モデル一覧に DeepSeek-V4-Flash-Vision-Exp がすでに表示されているのがわかる。ビジョンモデルに切り替えれば、画像や ppt ファイルをそのままドキュメントに投入し、画像内の内容を見せることができる。このモデルは実験的なマルチモーダル視覚理解モデルであり、現在 DeepSeek API プラットフォームに公開されており、model='deepseek-v4-flash-vision-exp' を設定することでアクセスできる。その能力の位置づけは一言でまとめられる:テキスト能力は劣化せず、視覚能力は大幅に向上。純粋なテキスト能力(Agent、推論、世界知識など)の面では、DeepSeek-V4-Flash 正式版と同等であり、視覚理解を必要とする Agent Benchmark では、DeepSeek-V4-Flash と比べて大幅な飛躍を実現し、マルチモーダル Agent 能力は Opus-4.8 に迫っている

能力の次元DeepSeek-V4-FlashDeepSeek-V4-Flash-Vision-Exp
純テキスト Agent タスク正式版ベースライン正式版と同等
推論と世界知識正式版ベースライン正式版と同等
視覚理解 Agent タスク非対応、評価ではマルチモーダル要素を無視大幅な飛躍、Opus-4.8 に迫る
モデルの位置づけ正式版実験版

比較データを誤読しないよう、評価の前提を明確にしておく必要がある:公開ベンチマークテストセットの Code Agent テキストタスクについて、DeepSeek シリーズモデルは DeepSeek Harness ミニマルモードをフレームワークとしてテストし、max 設定temperature=1.0topp=0.95 を使用している。ApexBench と Agents' Last Exam の評価では、テキストモデル DeepSeek-V4-Flash はその中のマルチモーダル要素を無視する——これが、表でその視覚行を「非対応、評価ではマルチモーダル要素を無視」と記している理由でもある。

接続の面では、マルチモーダル API は Chat Completions、Messages、Responses の3種類の呼び出し形式をサポートしており、各種 Agent ツールに容易に組み込める。3つの形式の能力は同一なので、慣れているインターフェーススタイルを選べばよい。

呼び出し形式インターフェーススタイルどんな人に適しているか
Chat CompletionsOpenAI のクラシックな対話インターフェースすでに OpenAI SDK のコードがある開発者
MessagesAnthropic Messages インターフェース、base_url は https://api.deepseek.com/anthropicすでに Anthropic スタイルのコードやツールチェーンがある開発者
ResponsesOpenAI の新しい Responses インターフェース新しい SDK を使い、簡潔な入力構造を好む開発者

3 つの形式はいずれもテキストと画像の混合入力をサポートしており、画像自体の渡し方には 3 通りあります:base64 インライン、外部 URL、Files API。これらの使い分けは、リクエストボディのサイズ、画像ホスティングが必要かどうか、そして適したシナリオに集約されます。

示意图
base64 インライン、外部 URL、Files API という 3 つの画像入力方式を、リクエストボディのサイズと適用シナリオの観点から比較したもの
入力方式リクエストボディのサイズ画像ホスティングが必要か適用シナリオ
base64 インライン大きい不要ローカル画像、一度きりの小さな画像
外部 URL小さい必要画像がすでに公開アクセス可能なサーバーにデプロイされている場合
Files API小さい不要同じ画像を何度も使う場合、高頻度のバッチタスク

最も直接的な入門方法は base64 インラインです:ローカル画像を base64 文字列にエンコードし、data URL の形式でそのままリクエストボディに書き込みます。DeepSeek エンドポイントは OpenAI SDK と互換性があり、base_url を変更するだけで切り替えられます。以下のコードはそのまま貼り付けて実行できます。

# ファイルパス:vision_base64_demo.py
# 依存:pip install openai
import base64
from openai import OpenAI

# DeepSeek エンドポイントは OpenAI SDK と互換性があり、base_url を変更するだけで切り替えられる
client = OpenAI(
    api_key="sk-你的密钥",                      # 必須:自分の DeepSeek API キーに置き換える
    base_url="https://api.deepseek.com"       # 必須:DeepSeek 公式エンドポイント
)

# ローカル画像を読み込み、base64 文字列にエンコードする
with open("runoob-logo.png", "rb") as f:
    b64 = base64.b64encode(f.read()).decode("utf-8")

response = client.chat.completions.create(
    model="deepseek-v4-flash-vision-exp",     # 必須:マルチモーダル視覚理解モデル
    messages=[
        {
            "role": "user",
            "content": [
                # テキストと画像は配列の順序で混在し、モデルはその順序で理解する
                {"type": "text", "text": "画像の中の文字は何ですか?"},
                {
                    "type": "image_url",
                    # base64 インライン:data:画像形式;base64,エンコード内容
                    "image_url": {"url": f"data:image/png;base64,{b64}"}
                }
            ]
        }
    ],
    stream=False                              # 任意:ストリーミング出力するかどうか、デフォルトは False
)

print(response.choices[0].message.content)

このコードには注目すべき細かい点がいくつかある。第一に、content は配列である。テキストブロックと画像ブロックが配列の順序で混在し、モデルはその順序で理解するため、「先にコンテキストを与えてから画像を渡す」のか「先に画像を渡してから質問する」のかで効果が変わる。タスクの意味に沿って並べよう。第二に、画像ブロックの type は image_url であり、base64 のインラインであってもこのフィールドを通す必要がある。url の値は data:image/png;base64, にエンコード内容を続けた形になり、フォーマット宣言を間違えるとパースに失敗する。第三に、stream はデフォルトで False であり、ストリーミング出力が必要なときに明示的にオンにする。第四に、base64 のインラインはリクエストボディを明らかに大きくする。これが「大」に分類される理由でもある。ローカルの小さな画像を一度だけ使うなら問題ないが、同じ画像が繰り返し登場する場合には割に合わない——まさにそこが Files API の出番であり、リクエストボディは小さく、画像ホスティングも不要だ。そして、画像がすでに公開アクセス可能なサーバーに配置されているなら、外部 URL が最も手軽な選択肢である。

まとめとベストプラクティス

SDK とマルチモーダルの二つを合わせて見ると、エンジニアリングに落とし込む際にはそのまま従えるチェックリストになる。

  1. パスはまず絶対化してから引数に渡す。workspace、sessions、cordis の三つの Path は一律で .resolve() し、相対パスの曖昧さをなくす。workspace は実際に書き込み可能な隔離ディレクトリを指し、sessions はタスクやユーザーごとにさらにサブディレクトリに分け、並行ログが互いを上書きしないようにする。
  2. コンストラクタ引数は責務ごとにグループ化して理解する。provider、model、max_tokens は「どの頭脳を使い、どれだけの枠を与えるか」を決め、cwd、cordis は「どの環境で、どんな装備を持つか」を決める。モデルの問題を調べるときは前者のグループを、ツールとパスの問題を調べるときは後者のグループを見る。
  3. session_id でタスクの境界を区切る。同じ session_id を再利用すれば会話は一貫するが、ループ内に無関係なタスクを流し込んでコンテキストを無限に膨張させてはいけない。独立したタスクには独立した session_id を与えるほうが安く、きれいだ。
  4. 結論は final_response、過程は JSONL を見る。中間のツール呼び出しは final_response には現れない。完全な経路を調べるには sessions ディレクトリで JSONL ログを確認する。
  5. with の遅延起動と自動解放を活用する。例外経路でもランタイムプロセスが漏れないため、プロダクトへの組み込みやテスト駆動の場面で特に恩恵がある。with に入った後は run を繰り返し呼べるので、一度に一つずつ行う必要はない。
  6. 依存の前提条件をデプロイチェックリストにする。Python 3.10+、Git、Linux x64 または arm64 または macOS 14+ arm64、利用可能な互換エンドポイントと資格情報、隔離された workspace。良い知らせは、ランタイムが同梱されており、システムに Node.js は不要で、CI コンテナをよりクリーンにできることだ。
  7. 資格情報は必要な変数だけを設定する。DEEPSEEK_API_KEY は必須。デフォルトエンドポイントではなく OpenAI 互換プロキシを経由する場合にのみ DEEPSEEK_BASE_URL を設定する。DSH_MODEL と DSH_SYSTEM_PROMPT は必要に応じて上書きする。
  8. マルチモーダルではまず「なぜ画像を見るのか」をはっきりさせる。純テキストモデルでは、ユーザーがエラーを文字に書き起こす必要があり、デザイン案を参照できず、グラフも読めない。一方、マルチモーダルはスクリーンショット、デザイン案、グラフを直接扱える。タスクの本質が視覚であるときにビジョンモデルを導入する。
  9. 画像の token コストモデルを覚えておく。画像はビジョンエンコーダによって patch に分割されてベクトル化され、テキスト token と連結される。1 枚あたり最大 384 tokens。必要に応じて画像を渡し、解像度と枚数を制御する。上限があるからといって画像を積み重ねないこと。
  10. モデルとフォーマットを正しく選ぶ。視覚理解が必要なときは deepseek-v4-flash-vision-exp を使う。その純テキスト能力は DeepSeek-V4-Flash 正式版と同等で、視覚 Agent 能力は Opus-4.8 に近いが、位置づけは実験版であるため、正式な業務では安定性を評価すべきだ。接続フォーマットは技術スタックに応じて Chat Completions、Messages、Responses の中から選び、三者は能力が一致している。
  11. 画像の渡し方は再利用頻度で選ぶ。ローカルの一度きりの小さな画像は base64 インライン、すでに公開デプロイ済みの画像は外部 URL、同じ画像を何度も使う場合や高頻度のバッチタスクは Files API を使う。
  12. 評価の口径に照らしてデータを解釈する。公開ベンチマークの Code Agent テキストタスクは DeepSeek Harness の極簡モード、max 档位、temperature=1.0、topp=0.95 を使用する。ApexBench と Agents' Last Exam ではテキストモデルはマルチモーダル要素を無視するため、テキストの成績を視覚の成績として見てはいけない。

ここまで来ると、あなたの手元にはすでに実践可能な2つの道筋があります。1つは Python SDK を使って dsh をプログラム内の1行の呼び出しに変え、バッチ処理、プロダクトへの組み込み、テスト駆動をカバーするもの。もう1つはマルチモーダル API を使って Agent にスクリーンショット、デザインカンプ、グラフを本当に「見せる」ようにし、コストとフォーマットについて根拠ある取捨選択を行うものです。この2つを組み合わせれば、量産運用でき、かつ視覚入力を理解できる Agent エンジニアリングの基盤が完成します。