N番煎じでRinna社のQwen2.5 Bakeneko 32B Instruct V2を試す

ローカルモデル、触りたいものが多すぎる。

# 導入

Rinna社がQwen2.5 Bakeneko 32B InstructをベースにQwQ Bakeneko 32Bのマージと追加学習を行ったモデル、Qwen2.5 Bakeneko 32B Instruct V2を公開しました。
詳細は以下のポストを参照ください。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Qwen2.5 Bakeneko 32B Instructに、思考モデルQwQ Bakeneko 32Bのマージと追加学習をした、非思考モデル「Qwen2.5 Bakeneko 32B Instruct V2」を公開しました。<br>JMT-Benchにおける、Instruct V2のスコアは思考モデルと同等です。<br>ベンチマーク：<a href="https://t.co/Ggs9wHCHRM">https://t.co/Ggs9wHCHRM</a><br>HF：<a href="https://t.co/UEYS0AkbwF">https://t.co/UEYS0AkbwF</a> <a href="https://t.co/Two7Oc3NZW">https://t.co/Two7Oc3NZW</a></p>&mdash; rinna Research (@rinna_research) <a href="https://twitter.com/rinna_research/status/1902287002000867551?ref_src=twsrc%5Etfw">March 19, 2025</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

非Reasoningモデルながら、JMTベンチの結果がReasoningモデルであるQwQ Bakeneko 32Bに匹敵する性能となっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168727/6d41bd8c-ba00-476c-8707-b8961b4c8cdd.png)


高速かつReasoningモデルに匹敵する性能の日本語モデルというと相当な万能性能だと思いましたのでいつものようにDatabricks上で試してみます。
内容は[こちらの記事]とほぼ同じ内容を実施しました。利用モデルは以下のAWQ量子化モデルを利用しています。

https://huggingface.co/rinna/qwen2.5-bakeneko-32b-instruct-v2-awq

:::note warn
量子化モデルのため、オリジナルとは異なる出力を示している可能性があります。
:::


準備編は基本的には以下の記事で解説している内容と同じです。準備編の詳細は割愛して試験用コードと結果を記載します。

https://qiita.com/isanakamishiro2/items/dc1c8185db77f5d7aae8


# 使ってみる

ノートブックを作成し、必要なパッケージをインストール。
（クラスタはサーバレスを利用しました）

```python
%pip install databricks-langchain mlflow-skinny[databricks]

%restart_python
```

MLflow Tracingを有効化します。

```python
import mlflow

mlflow.langchain.autolog()
```

使用するDatabricks Mosaic AI Model Serving名を設定。

```python
endpoint = "sglang_qwen_bakeneko_32b_v2_awq_endpoint"
```


[前回](https://qiita.com/isanakamishiro2/items/dc1c8185db77f5d7aae8)同様、```databricks-langchain```の```ChatDatabricks```クラスをオーバーライドしてcustom_inputsやcustom_outputs等のパラメータ入力/出力結果を扱えるようにしたクラス```ChatDatabricksExt```を用意します。

```python
from typing import List, Optional, Dict, Any, Mapping
from langchain_core.messages import BaseMessage
from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult
from databricks_langchain import ChatDatabricks

class ChatDatabricksExt(ChatDatabricks):
    def _prepare_inputs(
        self,
        messages: List[BaseMessage],
        stop: Optional[List[str]] = None,
        **kwargs: Any,
    ) -> Dict[str, Any]:
        """Prepare the inputs for the model."""

        data = super()._prepare_inputs(messages, stop, **kwargs)

        if "response_format" in data:
            response_format = data.pop("response_format")
            if response_format.get("type") == "json_schema":
                if "name" not in response_format["json_schema"]:
                    response_format["json_schema"]["name"] = "foo"

        return data

    def _convert_response_to_chat_result(
        self, response: Mapping[str, Any]
    ) -> ChatResult:
        """ Convert the response to a ChatResult. """
        resp = super()._convert_response_to_chat_result(response)

        if "custom_outputs" in response:
            resp.llm_output.update({"custom_outputs": response["custom_outputs"]})

        return resp
```

では、試してみましょう。

## 単純推論

シンプルな質問をしてみます。

```python

llm = ChatDatabricksExt(model=endpoint)

messages = [
    {"role": "system", "content": "あなたは日本語を話すAIアシスタントです。"},
    {"role": "user", "content": "Databricksとは何？"},
]

# 推論実行!
result = llm.invoke(messages, max_tokens=2000, temperature=0.6, top_p=0.95)

# 結果出力
print(" ==== Thinking ====")
print(result.response_metadata["custom_outputs"]["reasoning_content"])
print(" ==== Answer ====")
print(result.content)
```

````:出力結果
 ==== Thinking ====
None
 ==== Answer ====
**Databricks**は、**Apache Spark**を基にした**クラウドネイティブのデータプラットフォーム**であり、データエンジニアリングやデータサイエンスの作業を効率化するためのサービスです。以下に特徴をまとめます：

### **主な特徴**
1. **統合されたデータ処理と分析**  
   - Sparkを基にした**分散処理**と**機械学習**を統合。データの収集、変換、分析、可視化、モデル作成を一元化できます。
   - **Delta Lake**：高性能なデータレイク技術で、トランザクション対応のデータストレージを提供。

2. **クラウドネイティブ設計**  
   - AWS/Azure/GCPで動作し、クラウドリソースを動的に管理。コスト最適化や自動スケーリングが可能です。

3. **開発者向けの環境**  
   - **Databricks Notebook**：Python/Scala/R/SQLでコードを書け、データ可視化やモデル訓練が可能です。
   - **ワークフロー自動化**：パイプラインの構築やジョブスケジューリングを容易に実現。

4. **機械学習の強化**  
   - **MLflow**統合：モデルの実験管理、デプロイ、モニタリングを支援。  
   - 様々なMLフレームワーク（PyTorch、TensorFlowなど）をサポート。

5. **データエンジニアリング向け機能**  
   - データ統合（ETL/ELT）、データクレンジング、データ品質管理を効率化。

### **主な使用例**
- **データ分析**：BIツール（Power BI、Tableau）との連携でレポート作成。
- **機械学習**：モデル訓練からデプロイまでの一気通貫開発。
- **リアルタイム処理**：ストリーミングデータの分析（Kafka連携）。

### **競合製品との違い**
- **他のクラウドデータサービス**（AWS Glue、Azure Databricks）と比較して、**開発者向けの柔軟性**と**統合されたワークフロー**が特徴です。
- **Sparkのみの環境**より、**GUIと自動化機能**が充実しています。

### **適用企業**
- **データドリブンな企業**（金融、小売、医療など）で、データ活用を加速したい場合に利用されます。

Databricksは、**データ処理と分析のパイプラインを効率化するためのプラットフォーム**として、エンタープライズ向けに広く採用されています。
````

非ReasoningモデルなのでThinkingプロセスはありません。
```temperature=0.6```なので結果は実行都度大きく変わる可能性がありますが、ストレートな結果を得られています。
Reasoningモデルである[QwQ-Bakeneko-32Bの結果](https://qiita.com/isanakamishiro2/items/ec5e9cdd1391445fb1ca)と比べると良くも悪くも非Reasoningモデルとしての素直な結果ですね。

別の内容も出力してみます。

```python
messages = [
    {"role": "system", "content": "あなたは日本語を話すAIアシスタントです。"},
    {"role": "user", "content": "日本の製造業における生成AI活用の重要性について論じてください。"},
]

# 推論実行!
result = llm.invoke(messages, max_tokens=2000, temperature=0.6, top_p=0.95)

# 結果出力
print(" ==== Thinking ====")
print(result.response_metadata["custom_outputs"]["reasoning_content"])
print(" ==== Answer ====")
print(result.content)
```

```:出力結果
 ==== Thinking ====
None
 ==== Answer ====
**生成AIが日本製造業にもたらす革新とその重要性**

日本の製造業は、高度な技術力と精密な生産プロセスで世界をリードしてきました。しかし、グローバル競争の激化や人材不足、環境規制の強化など、新たな課題に直面しています。その解決に「生成AI」が持つ可能性は、従来の自動化技術とは異なる次元で、製造業の競争力を強化する重要な要素と言えます。

### 1. **設計・開発の革新：製品創造の加速**
生成AIは、製品設計プロセスを根本から変える技術です。従来のCADシステムでは、設計者は膨大な時間と経験を積み重ねて最適な形状や構造を検討していました。しかし、生成AIは以下のような効果をもたらします：

- **多様な設計案の自動生成**：材料力学や製造プロセスを考慮した最適設計を瞬時に生成。従来では試行錯誤が必要だったプロセスが短縮されます。
- **省資源化・軽量化の実現**：AIが「軽量で強度のある形状」を生成し、環境規制への対応やコスト削減につながります。
- **カスタマイズ製品の効率化**：BtoB向けの製品カスタマイズにおいて、顧客要望に応じた設計を自動生成し、納期短縮が可能に。

**例**：自動車メーカーがAIで車体設計を最適化し、燃料効率向上とコスト削減を両立させた事例。

### 2. **生産現場の効率化：人間とAIの協調作業**
製造業の現場では、作業手順の最適化や異常検知が課題です。生成AIは以下で貢献します：

- **作業マニュアルの自動生成**：未経験者向けの作業手順書や安全チェックリストを生成し、教育コストを削減。
- **予測メンテナンス**：機械学習で故障パターンを学習し、予防保全の計画を生成。設備ダウン時間の削減に寄与。
- **品質管理の強化**：AIが検査基準を生成し、人間の目で見落としがちな微細な欠陥を検出。

**例**：半導体製造装置メーカーがAIで工程管理を最適化し、不良率を0.1%削減。

### 3. **サプライチェーンの最適化：需要予測とリスク管理**
生成AIは、サプライチェーンの脆弱性を軽減する手段として活用されます：

- **需要予測モデルの生成**：市場データから需要パターンを予測し、在庫最適化を実現。
- **代替調達ルートの提案**：サプライチェーンリスクに備え、AIが代替調達先を自動生成。
- **環境規制への対応**：CO2排出量削減策を生成し、持続可能な生産体制を構築。

**例**：電子機器メーカーがAIでサプライチェーンリスクを分析し、代替調達先を自動提案。

### 4. **人材不足対策：知識継承と作業支援**
高齢化する技術者のノウハウをAIが補完します：

- **経験知識の可視化**：ベテランの作業記録からAIが標準作業手順を生成し、新人教育に活用。
- **ARを活用した作業支援**：AIが生成した手順をAR表示し、現場作業の効率化を支援。
- **技術者の創造的業務へのシフト**：ルーティン作業をAIに任せ、技術者は新製品開発に注力可能。

### 5. **競争優位性の構築：イノベーション加速**
生成AIは、製造業の「イノベーション速度」を加速させます：

- **新市場開拓**：AIが未開拓の製品コンセプトを生成し、新市場への参入を支援。
- **持続可能な生産技術**：AIが環境負荷を最小化する生産プロセスを生成し、規制対応を強化。
- **グローバル競争力**：AIを活用した効率化により、コスト競争力と品質を両立。

**課題と対策**  
- **データ不足への対応**：IoTセンサーや画像認識技術で現場データを収集。
- **人材育成**：AIエンジニアと現場技術者の協働体制を構築。
- **倫理的な課題**：AIの意思決定プロセスの透明性確保と人間中心の運用。

### 結論  
生成AIは、日本の製造業が「高品質・効率性・持続可能性」を追求する上で不可欠な技術です。設計から生産、サプライチェーンまで、全工程でのAI活用が進むことで、従来の「ものづくり」が「AIを駆動するスマート製造」へと進化するでしょう。この変革は、日本の製造業がグローバル競争で勝ち残るための「次世代の基盤」となることでしょう。
````

レポート的な出力結果という印象。
なんとなくRAGとの組み合わせ相性が良さそうです。

## Tool Calling

Tool Callingを実行してみます。

```python
from langchain_core.tools import tool

@tool
def add(a: int, b: int) -> int:
    """Adds a and b."""
    return a + b

@tool
def multiply(a: int, b: int) -> int:
    """Multiplies a and b."""
    return a * b

tools = [add, multiply]

llm_with_tools = llm.bind_tools(tools)
```

```python
query = "What is 3 * 12? Also, what is 11 + 49?"
result = llm_with_tools.invoke(query)

print("=== Tool Calls ===")
print(result.tool_calls)
print("=== Thinking ===")
print(result.response_metadata["custom_outputs"]["reasoning_content"])
```

```:出力結果
=== Thinking ===
None
=== Tool Calls ===
[{'name': 'multiply', 'args': {'a': 3, 'b': 12}, 'id': '1', 'type': 'tool_call'}]
```

これは[QwQ Bakeneko](https://qiita.com/isanakamishiro2/items/ec5e9cdd1391445fb1ca#tool-calling)とは差がでましたね。
期待する動作としてはかけ算と足し算の2種をリストアップして欲しいのですが、上記ではかけ算のみという結果でした。

とはいえ、以下のようにシステムプロンプトを設定すると問題なく列挙されたので量子化影響かハイパーパラメータの問題な気もします。

```python
query = "What is 3 * 12? Also, what is 11 + 49?"
messages = [
    ("system", "ツール実行が必要な場合、必要なツール呼び出しを全て列挙してください"),
    ("user", query),
]
result = llm_with_tools.invoke(
    messages,
    temperature=0.0,
)

print("=== Thinking ===")
print(result.response_metadata["custom_outputs"]["reasoning_content"])
print("=== Tool Calls ===")
print(result.tool_calls)
```

```:出力結果
=== Thinking ===
None
=== Tool Calls ===
[{'name': 'multiply', 'args': {'a': 3, 'b': 12}, 'id': '1', 'type': 'tool_call'}, {'name': 'add', 'args': {'a': 11, 'b': 49}, 'id': '0', 'type': 'tool_call'}]
```

ちなみにMLflow Tracingの画面だと以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168727/b1bae51f-25ff-4f52-8ecc-e8fc43acecaf.png)


## Structured Output

続けて構造化出力。
まずはLangchainの単純な```with_structured_output```を使ったやり方。

```python
from pydantic import BaseModel, Field

class ResponseFormatter(BaseModel):
    """Always use this tool to structure your response to the user."""

    answer: str = Field(description="The answer to the user's question")
    followup_question: str = Field(description="A followup question the user could ask")

model_with_structure = llm.with_structured_output(ResponseFormatter) # method="function_calling"が適用される
structured_output = model_with_structure.invoke("What is the powerhouse of the cell?")

structured_output
```

```:出力結果
ResponseFormatter(answer="The powerhouse of the cell is the **mitochondrion**. Mitochondria are organelles responsible for producing most of the cell's energy through a process called cellular respiration, which converts nutrients and oxygen into adenosine triphosphate (ATP), the energy currency of the cell.", followup_question='How does cellular respiration work inside mitochondria?')
```

問題なく出力されます。
内部的にはTool Callingの仕組でJSON形式に変換されるのですが、ツール呼び出しの性能は問題ないように見えます。

パラメータ```method="json_schema"```を設定した構造化出力も試してみます。

```python
import json

model_with_structure = llm.with_structured_output(
    ResponseFormatter, method="json_schema"
)
structured_output = model_with_structure.invoke(
    f"What is the powerhouse of the cell? Please answer with the following JSON format:{json.dumps(ResponseFormatter.model_json_schema())}",
    temperature=0.6,
    top_p=0.95,
    max_tokens=1000,
)

structured_output
```

```:出力結果
ResponseFormatter(answer="The powerhouse of the cell is the mitochondrion. Mitochondria are organelles responsible for producing most of the cell's energy through the process of oxidative phosphorylation, which generates ATP (adenosine triphosphate), the energy currency of the cell.", followup_question='How does the mitochondrion produce ATP, and what is the role of the electron transport chain in this process?')
```

こちらでも問題なく構造化出力できます。

# まとめ

Rinna社のQwen2.5 Bakeneko 32B Instruct V2をDatabricks Mosaic AI Model Servingにデプロイして試してみました。
非Reasoningモデルの日本語ローカルモデルの中でも非常に高い性能を持っていると思います。
感覚的にはReasoningモデルのQwQ Bakenekoの方が創造的な回答を得られやすいと感じますので、うまく使い分けすることでレイテンシを抑えたエージェントシステムが作れそうです。

ローカルLLMを追いかけ続けて結構時間が経ちますが、数年でここまで実用的な状態になってきているのは本当に面白く感じますし、さらなる発展が楽しみです。