Zenn
🎉

Azure上でDeepSeek-R1をデプロイして使ってみる

2025/02/18に公開
2

1. はじめに

初めまして、Givery AI Lab所属のSharma Saurabh(ソウ)です。
最近「DeepSeek」という名の新たなプレイヤーが世界のAI市場を席巻しているのを、皆さんもニュースやSNSなどで耳にしたのではないでしょうか。Apple App Storeの無料アプリランキングでChatGPTを抜き去り、現在最もダウンロードされているアプリとして話題をさらっています。特に驚きなのは、中国発のスタートアップがわずか半年あまりで、この実績を成し遂げたという事実です。 そんなDeepSeekが打ち出したオープンソースモデル「DeepSeek-R1」は、“AIのスプートニク・モーメント”を引き起こしたとも言われています。1957年にソ連が世界初の人工衛星スプートニクを打ち上げて以降、アメリカを含む各国が一気に宇宙開発レースへ突入していった歴史的転換点になぞらえた表現です。つまり、DeepSeekの登場によってAI開発がさらに加速する大きな転機を迎えたのではないか、という見方がされているわけです。

世界経済フォーラム(WEF)の「AI Transformation of Industries」でも指摘されているように、オープンソースのAIは産業変革における重要なドライバーになり得ます。DeepSeek-R1の導入を通じて、大規模言語モデルを必要としているさまざまな組織や個人が大きな恩恵を受けられる可能性があるでしょう。

本記事では、DeepSeek-R1を使った実用的なアプリケーションを作るための最初のステップとして、Azure環境にデプロイし、Chain-of-Thoughtを活かした高度な推論を実行する流れを紹介します。

具体的には、以下の内容にフォーカスします。

  • Azure環境の準備
  • DeepSeek R1のサンプルアプリケーションを動かしてみる方法
  • Chain-of-Thoughtを用いた推論結果の解釈や使ってみた所感

2. DeepSeek R1の概要

DeepSeek R1が注目される理由の一つに、Chain-of-Thought(思考の連鎖)の標準サポートがあります。これは、モデルが回答を導く過程を可視化することで、信頼性の確保やデバッグの効率化を大幅に向上させる手法です。従来のブラックボックス型AIと異なり、

  • モデルの根拠を明示できる
  • ユーザーが誤りを起こしやすい箇所を把握し、修正をフィードバックできる
  • 説明責任(Explainability)の要求が厳しい業界にも適用しやすい

といったメリットを生み出します。単なる“回答の品質”だけではなく、“どうやって回答に至ったか”を重視する姿勢は、今後より一層重要視されていくでしょう。 ここから先は、Azure環境で実際にデプロイしていく手順を解説していきます。

3. Azure環境の準備

アーキテクチャ例

3.1 ハブ作成

はじめに、Azureポータル(portal.azure.com)へアクセスし、AI Foundry Hubを作成します。
AI Foundryハブの作成例

*DeepSeek-R1をデプロイ可能なリージョンが限られているため、対応リージョン(例:East US 2)を指定する必要があります。

3.2 プロジェクト作成

作成したAI Foundry Hubを開き、ダッシュボード上で「New Project(新しいプロジェクト)」をクリック。Hubとしては先ほど作成したものを選択し、プロジェクトを作成。

プロジェクト作成後、Models + endpointsから新規モデルデプロイを行い、DeepSeek-R1を選択します。MistralやMetaなど、他にも多彩なモデルが用意されています。

デプロイ完了後、エンドポイントのTarget URIやKeyとmodelパラメータが表示されるのでメモします。

ポイント: DeepSeek-R1はファインチューニングやカスタムデータの適用が可能なので、独自チューニングを行いたい場合はこの段階でカスタマイズ設定を検討できます。

これで、DeepSeek R1モデルのデプロイが完了しましたので、早速使ってみましょう。

4. サンプルアプリケーションの動作確認

4.1 Pythonのセットアップと動作確認

まず、Pythonがインストールされた環境(condaやpyenvなどでバージョン管理推奨)を用意します。
必要なパッケージを導入した上で、以下のようなサンプルコード(例:chat.py)を用意します。
pip install azure-ai-inference azure-core-credentials

# chat.py
# Install the following dependencies: azure.identity and azure-ai-inference
import os
from azure.ai.inference import ChatCompletionsClient
from azure.ai.inference.models import SystemMessage, UserMessage
from azure.identity import DefaultAzureCredential

endpoint = os.getenv("AZURE_ENDPOINT")
model_name = os.getenv("DEPLOYMENT_NAME")
key = os.getenv("AZURE_API")
# Set the AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, and AZURE_TENANT_ID environment variables
client = ChatCompletionsClient(endpoint=endpoint, credential=AzureKeyCredential(key))

response = client.complete(
  messages=[
    SystemMessage(content="You are a helpful assistant."),
    UserMessage(content="Hi!")
  ],
  model = model_name,
  max_tokens=1000
)

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

.envや環境変数にAZURE_ENDPOINT、AZURE_APIなどの情報を設定しておけば、上記のコードを実行するだけでDeepSeek R1に質問し、回答を取得できます。

このコードを実行すると、以下のレスポンスが得られました。

<think>
Okay, the user just said "Hi!" That's a friendly greeting. I should respond warmly. Maybe say hello back and ask how I can assist them today. Keep it open-ended to encourage them to ask for what they need. Let's make sure the tone is welcoming and approachable.
</think>

Hi! How can I assist you today? 😊

レスポンスからは、回答と同時に思考プロセス(CoT)がログの一部として返されることがあります。回答の根拠や推論プロセスを可視化できるため、ユーザーへの説明やデバッグ時に役立ちます。ただし、高い利用人気から応答が遅い場合がある点に留意しましょう。ビジネス用途で導入する際は、有料プランやスケーリングオプションを検討することをおすすめします。

4.2 Webアプリケーションの構築

Flaskなどのフレームワークを利用すれば、手軽にチャットUIを実装できます。

ディレクトリ構成例:

.project_directory
├── app.py
├── templates/
│   └── index.html
└── static/
    ├── styles.css
    └── script.js

先ほどのサンプルコードを用いて、Flaskアプリの雛形を作成します。

# --- app.py
import os
from flask import Flask, request, Response, jsonify, render_template
from azure.ai.inference import ChatCompletionsClient
from azure.ai.inference.models import SystemMessage, UserMessage
from azure.core.credentials import AzureKeyCredential
from dotenv import load_dotenv

load_dotenv()

app = Flask(__name__)

# --- Azure Configuration ---
endpoint = os.getenv("AZURE_INFERENCE_SDK_ENDPOINT")
model_name = os.getenv("DEPLOYMENT_NAME")
key = os.getenv("AZURE_INFERENCE_SDK_KEY")

client = ChatCompletionsClient(endpoint=endpoint, credential=AzureKeyCredential(key))

@app.route('/')
def index():
   return render_template('index.html')

@app.route('/chat', methods=['POST'])
def chat():
   data = request.get_json()
   user_text = data.get('message', '')

   messages = [
       SystemMessage(content="You are a helpful assistant."),
       UserMessage(content=user_text)
   ]

   # Request streaming response from Azure
   stream = client.complete(
       messages=messages,
       model=model_name,
       max_tokens=1000,
       stream=True
   )

   def generate():
       """
       Generator that yields tokens as they come in.
       """
       for chunk in stream:
           if chunk.choices and len(chunk.choices) > 0:
               delta = chunk.choices[0].delta
               if delta and delta.content:
                   # Send each partial chunk right away
                   yield delta.content

   # Return a streaming response with "text/plain"
   return Response(generate(), mimetype='text/plain')

if __name__ == '__main__':
   app.run(debug=True)

HTMLテンプレートを作成。

<!-- templates/index.html -->
<!DOCTYPE html>
<html>
<head>
 <meta charset="UTF-8" />
 <title>Azure Chatbot (DeepSeek R1)</title>
 <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
 <div class="page-container">

   <header>
     <h1>Azure Chatbot (Streaming)</h1>
   </header>

   <div id="chat-container">
     <div id="chat-log"></div>
   </div>

   <div id="input-bar">
     <input type="text" id="user-input" placeholder="Type your message..." />
     <button id="send-btn">Send</button>
   </div>

 </div>

 <script src="{{url_for('static', filename='scripts.js')}}"></script>
</body>
</html>

CSSファイルの作成。

/* static/styles.css */
html, body {
   margin: 0;
   padding: 0;
   height: 100%;
   font-family: Arial, sans-serif;
}

.page-container {
   display: flex;
   flex-direction: column;
   height: 100%;
}

header {
   padding: 1rem;
   background-color: #f5f5f5;
   border-bottom: 1px solid #ccc;
}

#chat-container {
   flex: 1;
   overflow-y: auto;
   padding: 1rem;
}

.message {
   margin: 1rem 0;
   white-space: pre-wrap;
}
.user {
   text-align: right;
   color: #0056b3;
}
.assistant {
   text-align: left;
   color: #006400;
}

#input-bar {
   background-color: #fafafa;
   border-top: 1px solid #ccc;
   padding: 0.5rem;
   display: flex;
}

#user-input {
   flex: 1;
   padding: 0.5rem;
   font-size: 1rem;
   border-radius: 4px;
   border: 1px solid #ccc;
   margin-right: 0.5rem;
}

#send-btn {
   padding: 0.5rem 1rem;
   font-size: 1rem;
   border-radius: 4px;
   border: none;
   background-color: #007bff;
   color: #ffffff;
   cursor: pointer;
}

#send-btn:hover {
   background-color: #0056b3;
}

.hidden-think {
   margin-top: 0.5rem;
   background-color: #f1f1f1;
   padding: 0.5rem;
   border-radius: 4px;
   border: 1px dashed #ccc;
}

.hidden {
   display: none;
}

.toggle-think-btn {
   display: inline-block;
   margin-top: 0.5rem;
   padding: 0.3rem 0.6rem;
   font-size: 0.8rem;
   color: #333;
   background-color: #e0e0e0;
   border: 1px solid #bbb;
   border-radius: 3px;
   cursor: pointer;
}

.toggle-think-btn:hover {
   background-color: #ccc;
}

JavaScriptの作成。

const chatLog = document.getElementById('chat-log');
const userInput = document.getElementById('user-input');
const sendBtn = document.getElementById('send-btn');

function addMessage(text, sender, hiddenThink = '') {
 const messageDiv = document.createElement('div');
 messageDiv.classList.add('message', sender);

 messageDiv.textContent = text;

 if (hiddenThink) {
   const thinkDiv = document.createElement('div');
   thinkDiv.classList.add('hidden-think', 'hidden');
   thinkDiv.textContent = hiddenThink;
   messageDiv.appendChild(thinkDiv);

   const toggleBtn = document.createElement('button');
   toggleBtn.classList.add('toggle-think-btn');
   toggleBtn.textContent = 'Show <think>';
   toggleBtn.addEventListener('click', () => {
     if (thinkDiv.classList.contains('hidden')) {
       thinkDiv.classList.remove('hidden');
       toggleBtn.textContent = 'Hide <think>';
     } else {
       thinkDiv.classList.add('hidden');
       toggleBtn.textContent = 'Show <think>';
     }
   });

   messageDiv.appendChild(document.createElement('br'));
   messageDiv.appendChild(toggleBtn);
 }

 chatLog.appendChild(messageDiv);
 chatLog.scrollTop = chatLog.scrollHeight;
}

async function sendMessage() {
 const text = userInput.value.trim();
 if (!text) return;
 addMessage(text, 'user');
 userInput.value = '';
 userInput.focus();

 const partialDiv = document.createElement('div');
 partialDiv.classList.add('message', 'assistant');
 partialDiv.textContent = '...';
 chatLog.appendChild(partialDiv);

 try {
   const response = await fetch('/chat', {
     method: 'POST',
     headers: { 'Content-Type': 'application/json' },
     body: JSON.stringify({ message: text })
   });

   const reader = response.body.getReader();
   let decodedText = '';
   let done, value;

   while (true) {
     ({ value, done } = await reader.read());
     if (done) break;
     decodedText += new TextDecoder('utf-8').decode(value);

     partialDiv.textContent = decodedText;
     chatLog.scrollTop = chatLog.scrollHeight;
   }

   const { finalText, thinkText } = separateThinkBlock(decodedText);

   partialDiv.remove();
   addMessage(finalText, 'assistant', thinkText);

 } catch (error) {
   console.error('Error:', error);
   partialDiv.textContent = 'Error receiving response.';
 }
}

function separateThinkBlock(fullText) {
 const thinkRegex = /<think>([\s\S]*?)<\/think>/;
 const match = fullText.match(thinkRegex);

 if (!match) {
   return { finalText: fullText, thinkText: '' };
 }
 const hidden = match[1];
 const cleaned = fullText.replace(thinkRegex, '');

 return { finalText: cleaned, thinkText: hidden };
}

sendBtn.addEventListener('click', sendMessage);

userInput.addEventListener('keydown', (e) => {
 if (e.key === 'Enter') {
   sendMessage();
 }
});

Flaskアプリ (app.py) を起動し、ブラウザで http://localhost:5000/ にアクセスすれば、Web上でDeepSeek-R1との対話を楽しめます。
チャット画面

5. Chain-of-Thoughtを活用した高度な推論

Chain-of-Thought (CoT) は、モデルが回答に至る過程(思考プロセス)を可視化・解析する手法です。大規模言語モデルにおいてはブラックボックス化が問題視されがちですが、CoTを導入することで「なぜその答えを導いたのか」をロジカルに追跡できます。
研究事例としては、Googleが提唱した「Chain-of-Thought Prompting Elicits Reasoning
in Large Language Models
」(Wei et al. 2022) が有名で、論理的推論や複雑な計算手順などを中間プロンプトとしてモデルに与えることで回答の正確性が向上することが確認されています。

5.1 PoCやデバッグでの具体的メリット

  • 回答根拠の明示: サポートチャットや医療・金融分野などで、回答の論拠を示すことが必須なケースで重宝します。

  • 学習データの不備把握: CoTを眺めることで、モデルが特定の領域に対して偏った知識を参照している場合などを早期に発見できます。

  • UX向上: 回答が不十分だった場合、ユーザー自身がどのステップで不十分な判断が行われたかを理解できるため、対話型の改善が期待できます。

所感として、CoTの導入によってモデルの誤り発生理由を部分的に可視化できるため、継続的な改善サイクルの質とスピードが飛躍的に向上します。

6. まとめと今後の展望

DeepSeek-R1は、完全オープンソースかつChain-of-Thought (CoT) 標準搭載という強みを兼ね備えた、今注目の大規模言語モデルです。Azure上にデプロイすることで、スケーラビリティと使いやすさが高まると同時に、高度なCoT推論を手軽に実装できる環境が得られます。
今後は、より低コストでエネルギー効率の高い学習手法や、ユーザーフレンドリーな対話設計がさらに進化していくでしょう。特にCoTをフル活用した説明可能なAI(Explainable AI)は多くの産業で需要が急拡大しており、DeepSeek-R1の普及がその後押しとなる可能性が高いです。
最終的には運用とセキュリティの両面を考慮しながら、ユーザーとの対話設計を最適化していくことがプロジェクトの成功につながります。ぜひ、Chain-of-Thoughtを積極的に活用し、モデルの思考プロセスを可視化しながら継続的に改善を図ってみてください。

最後までお読みいただき、ありがとうございました。Givery AIラボには優秀なAI人材が数多く在籍しております。生成AIを活用したPoCや各種支援にご興味がある方は、ぜひ以下のリンクからお問い合わせください。

https://givery.co.jp/lp/ai-lab/

参考資料

https://learn.microsoft.com/ja-jp/azure/ai-studio/concepts/architecture

2
Givery AI Lab

Discussion

ログインするとコメントできます