2026年1月17日土曜日

Pydanticで記述

 PythonのPydanticとC#を比較してみた + C++ DCOMの思い出 #pydantic - Qiita

OpenAI Structured OutputsのPydantic連携を実装してみました | 株式会社divx(ディブエックス)

勉強になりました。

これから、Pydanticを使用してC#クラスを変換して行きたいと思います。

同じShiftAggregateというC#内部クラスをコパイと、Google AIモードの両方で生成させてみました。

コパイ生成モデル


from pydantic import BaseModel, Field
from typing import List

class ShiftAggregate(BaseModel):
    use: bool = Field(
        description="A flag to indicate whether it is valid or invalid. 有効(true)か無効かを示すフラグ。"
    )
    name: str = Field(
        default="ShiftAggregateDef",
        description="Object Name オブジェクト名"
    )
    def_name: str = Field(
        description="Definition name 定義名。シフトオブジェクトのORまたはNOTの集合体。必須。"
    )
    color: str = Field(
        default="Grey",
        description="Background color ラベル背景色"
    )
    operator: str = Field(
        default="OR",
        description="Operator 演算子。OR または NOT のみ。必須。"
    )
    members: List[str] = Field(
        default_factory=list,
        description="List of shift object names shift_objectの名前集合。必須。"
    )
    label: str = Field(
        description="Label name as displayed name ラベル名(GUI表示名)。必須。"
    )

下は、GOOGLE AIモード生成です。

from typing import List, Literal, Optional from pydantic import BaseModel, Field from mcp.server.fastmcp import FastMCP # 1. C# のクラスを Pydantic モデルとして定義 class ShiftAggregate(BaseModel): use: bool = Field( default=False, description="有効(true)か無効か。無効ならソルバは参照しない" ) name: str = Field( default="ShiftAggregateDef", description="オブジェクト名" ) def_name: str = Field( ..., description="定義名。シフトオブジェクトのORまたはNOTの集合体" ) color: str = Field( default="Grey", description="ラベル背景色" ) # Operator は OR または NOT のみ operator: Literal["OR", "NOT"] = Field( default="OR", description="演算子。OR または NOT のみ" ) members: List[str] = Field( default_factory=list, description="シフトオブジェクトの名前集合" ) label: str = Field( ..., description="表示用ラベル名" ) # 2. MCP サーバーの作成 mcp = FastMCP("ShiftManager") # 3. ツールとして登録 @mcp.tool() async def register_shift_aggregate(data: ShiftAggregate) -> str: """ シフトの集合体定義(ShiftAggregate)を登録します。 """ # ここに WinForms API (call_winforms_api) への送信処理などを記述 # 例: payload = data.model_dump() return f"定義 '{data.label}' (Operator: {data.operator}) を受け付けました。" if __name__ == "__main__": mcp.run()




さらに、C#クラスを少し修正して再度AIモードにお願いしました。修正した部分が分かって生成していることが分かります。こうした基本クラスならば、手で書くよりもAIにお願いした方が圧倒的に速いです。C#クラスのコメント部をdescriptionに解釈して書いてくれるので、その点でも楽です。model_dump()もBaseModelを継承して自動生成してくれます。AIモードは、MCPをどのように実装したらよいかを、解説しながら実装例を示してくれます。私のようなMCP初心者には、有難いです。基本的クラスは、こうした手法により、Python側のコードは、ほぼコピペで完結できます。C#側は、かなり手を加える必要があります。

コパイでは、C#コメントは、そのままPythonコメントになりましたが、Google AIモードでは、徒手選択追加されています。必ずしもC#コメントと同じになる訳ではありません。概して分かり易い方向に作用しますが、余計なお世話だ、という場合もあります。

驚くべきは、定義していないクラスまで生成したことです。それが、正確にPythonに変換生成されていました。以前GUIのC#コードを公開していたことがありましたが、それを参照したとしか考えられない、と思っています。驚きました。


from typing import List, Literal
from pydantic import BaseModel, Field
from mcp.server.fastmcp import FastMCP

# 1. C#のクラス構造をPydanticモデルで定義
class ShiftAggregate(BaseModel):
    use: bool = Field(
        default=False, 
        description="有効(true)か無効かを示すフラグ。無効ならソルバは参照しない"
    )
    # const扱いの名前。LLMが変更しないようdefault固定にする
    name: str = Field(
        default="ShiftAggregateDef", 
        frozen=True,
        description="オブジェクト名(固定値: ShiftAggregateDef)"
    )
    def_name: str = Field(
        default="", 
        description="定義名。シフトオブジェクトのORまたはNOTの集合体を表す一意識別子"
    )
    color: str = Field(
        default="Grey", 
        description="ラベル背景色(例: Blue, #FF0000)"
    )
    # Operatorをリテラルで制限
    Operator: Literal["OR", "NOT"] = Field(
        default="OR", 
        description="演算子。OR(いずれか)またはNOT(以外)のみ選択可能"
    )
    members: List[str] = Field(
        default_factory=list, 
        description="シフトオブジェクト名のリスト(例: ['日勤', '夜勤'])"
    )
    label: str = Field(
        default="", 
        description="GUI(予定入力・解上)での表示名"
    )

# 2. MCPサーバの初期化
mcp = FastMCP("ShiftProjectManager")

# 3. ツールとしての登録
@mcp.tool()
async def add_shift_aggregate(aggregate_data: ShiftAggregate) -> str:
    """
    WinFormsアプリに新しいシフト集計定義(ShiftAggregate)を追加または更新します。
    """
    # 実際にはここで call_winforms_api("update_settings", payload, ...) 等を呼び出す
    payload = aggregate_data.model_dump()
    
    # デバッグ出力
    print(f"Sending to WinForms: {payload}")
    
    return f"シフト定義 '{aggregate_data.label}' を {aggregate_data.Operator} 条件で登録しました。"

if __name__ == "__main__":
    # サーバの起動
    mcp.run()

0 件のコメント:

コメントを投稿