このステップバイステップのチュートリアルでは、ローカル・マシン上のオープンソースIBM® Granite 3.3-8b命令モデルでLM Studioを使用します。まず、ローカル・モデルをそのままテストし、モデルが自動ツール呼び出しに使用できるPython関数を記述します。最後に、AIエージェントとチェスのゲームをプレイするための機能をさらに開発します。このチュートリアルは、IBM® GraniteコミュニティのGranite Snack Cookbook GitHubにもJupyter Notebook形式で掲載されています。
LM Studioは、ローカルの[大規模言語モデル](https://www.ibm.com/jp-ja/think/topics/large-language-models)で作業するためのアプリケーションです。(LLM)。LM Studioでは、あらゆる[オープンソース・モデル](https://www.ibm.com/jp-ja/think/topics/open-source-llms)や、[Mistral AI](https://www.ibm.com/jp-ja/think/topics/mistral-ai)モデル、[GoogleのGemma](https://www.ibm.com/jp-ja/think/topics/Google-gemma)、Meta's Llamaまたは[DeepSeek's R1](https://www.ibm.com/jp-ja/think/topics/deepseek)シリーズを使用できます。
LM Studioを使用すると、初心者から上級ユーザーまで、コンピューターのCPUまたは[GPU](https://www.ibm.com/jp-ja/think/topics/gpu)のいずれかでLLMを実行できます。LM Studioは、[ChatGPT](https://www.ibm.com/jp-ja/think/topics/chatgpt)のチャットと同様に、ローカルのLLMと対話するためのチャットのようなインターフェースを提供しています。
ローカルAIモデルを使用すると、[ファイン・チューニング](https://www.ibm.com/jp-ja/think/topics/Fine-tuneing)や、推論などを行うことが可能です。これにより、外部API(OpenAIやIBM® watsonx.aiなどのアプリケーション・プログラミング・インターフェース)やトークン使用量を気にする必要がなくなります。LM Studioを使用すると、ユーザーはローカルおよびプライベートに「ドキュメントを使用したチャット」を行うこともできます。ユーザーはチャット・セッションに文書を添付し、その文書について質問できます。文書が長い場合、LM Studioは[検索拡張生成](https://www.ibm.com/jp-ja/think/topics/retrieval-augmented-Generation)(RAG)システムを使用してクエリを実行します。
LLMは人間のようなテキストの理解と生成には優れていますが、タスクで正確な計算、リアルタイムの外部データへのアクセス、または特定の明確に定義された手順の実行が必要な場合、しばしば制限に直面します。[ツール呼び出し](https://www.ibm.com/jp-ja/think/topics/tool-calling)を実装することで、LLMに一連の「ツール」(外部機能)を装備しており、LLMの機能を大幅に拡張するための呼び出しを選択できます。このチュートリアルでは、LLMがより信頼性の高い幅広いタスクを実行できるように、これらのツールを定義して統合する方法について説明します。
LM Studioをインストールする前に、ローカル マシンが最小システム要件を満たしていることを確認してください。
次に、お使いのコンピューターのオペレーティング・システム(Windows、macOS、またはLinux®)に適したインストーラーをダウンロードします。次に、次の手順に従ってモデルをローカル・マシンにダウンロードします。
このレシピではGranite 3.3-8b命令モデルを使用しますが、お好みのLLMをご使用ください。Graniteモデルを使用している場合は、特定のユーザー/モデルの文字列を
次に、LM Studioの左上にある緑色のアイコンをクリックして、
まず、LM Studio SDKやチェス・ライブラリなどの必要なライブラリをインストールする必要があります。
%pip install git+https://github.com/ibm-granite-community/utils \
lmstudio \
chess
import lmstudio as lms
次に、このレシピで使用するモデルを指定します。私たちの場合は、LM Studioでダウンロードしたモデル、Granite 3.3-8b命令になります。
また、「model.respond()」で、最初のメッセージを呼び出してチャットを開始します。
model = lms.llm("ibm-granite/granite-3.3-8b-instruct-GGUF")
print(model.respond("Hello Granite!"))
まずは、モデルに簡単な計算をしてもらうことから始めましょう。
print(model.respond("What is 26.97 divided by 6.28? Don't round."))
その間このモデルは近似値を提供できる可能性がありますが、答を独自に計算することができないため、正確な答えを返すことはできません。
この問題を解決するために、モデルにツールを提供します。ツールは、推論時にモデルに提供するPython関数です。モデルは、ユーザーの質問に答えるために、これらのツールのうち1つ以上を呼び出すことを選択できます。
ツールの作成方法の詳細については、LM Studioドキュメントを参照してください。一般に、ツール関数には適切な名前、定義されたインプットとアウトプットのタイプ、およびツールの目的を説明する説明があることを確認する必要があります。これらすべての情報がモデルに渡され、モデルが質問に答えるための適切なツールを選択するのに役立ちます。
モデルがツールとして使用するためのいくつかの簡単な数学関数を記述します。
def add(a: float, b:float):
"""Given two numbers a and b, return a + b."""
return a + b
def subtract(a: float, b:float):
"""Given two numbers a and b, return a - b."""
return a - b
def multiply(a: float, b: float):
"""Given two numbers a and b, return a * b."""
return a * b
def divide(a: float, b: float):
"""Given two numbers a and b, return a / b."""
return a / b
def exp(a: float, b:float):
"""Given two numbers a and b, return a^b"""
return a ** b
ここで、同じクエリを再実行しますが、モデルが回答するのに役立ついくつかのツールをモデルに提供します。「model.act()」呼び出しを自動ツール呼び出しとして使用し、モデルに作成した関数を使用できることを示します。
model.act(
"What is 26.97 divided by 6.28? Don't round.",
[add, subtract, multiply, divide, exp],
on_message=print,
)
モデルが「ToolCallRequest」と「name」から正しいツールを選択し、「arguments」から適切なインプット(関数に渡す議論)を使用し、無関係なツールの使用を回避できたことを確認できます。最後に、「AssistantResponse」、「content」、「text」の回答には、モデルからの回答、つまり質問に対する正確な回答が表示されます。
フレームワークには何個のRがありますか。
最も賢い言語モデルでさえも困惑させる非常に単純な質問です。2024年以前にトレーニングを中断したLLMのほぼすべてのLLMは、「strawberry」という言葉にRが2つしかないと回答しています。さらに、文字の位置を間違って認識することもあります。
現在、LLMはこの具体的な質問を正しく理解する傾向にありますが、それは純粋に、そのウイルス性がほとんどのトレーニング・データ・セットに組み込まれているためです。しかし、LLMは依然として、同様の文字カウントタスクで多くの場合失敗します。
print(model.respond("How many Bs are in the word 'blackberry'?"))
モデルをより良く機能させるためのツールを作成しましょう。
def get_letter_frequency(word: str) -> dict:
"""Takes in a word (string) and returns a dictionary containing the counts of each letter that appears in the word. """
letter_frequencies = {}
for letter in word:
if letter in letter_frequencies:
letter_frequencies[letter] += 1
else:
letter_frequencies[letter] = 1
return letter_frequencies
これでツールをモデルに渡してプロンプトを再実行することができます。
model.act(
"How many Bs are in the word 'blackberry'?",
[get_letter_frequency],
on_message=print,
)
「get_letter_frequency()」ツールを使用して、モデルは単語「Blackberry」に含まれるbの数を正確に数えることができました。
この自動ツール呼び出しワークフローの最良のユースケースの1つは、モデルに外部環境と対話する機能を提供することです。ツールを使用してチェスをプレイするエージェントを構築しましょう。
言語モデルはチェスに関する強力な概念知識を持つことができますが、本質的にはチェス盤を理解するように設計されていません。オンライン・チャットボットを使ってチェスのゲームをプレイしようとすると、数ターン後に脱却し、違法または非合理な動きになることがよくあります。
私たちはモデルがボードの理解と対話を行うのに役立ついくつかのツールを提供しています。- legal_moves():現在の位置で有効なすべての動きのリストを提供します
多くはありませんが、モデルが幻覚に陥ることなくチェスのゲームをプレイし、インテリジェントな推論に基づいて決定を下すには十分です。
import chess
import chess.polyglot
from IPython.display import display, SVG, clear_output
import random
import os, requests, shutil, pathlib
board = chess.Board()
ai_pos = 0
# Download book moves
RAW_URL = ("https://raw.githubusercontent.com/"
"niklasf/python-chess/master/data/polyglot/performance.bin")
DEST_FILE = "performance.bin"
if not os.path.exists(DEST_FILE):
print("Downloading performance.bin …")
try:
with requests.get(RAW_URL, stream=True, timeout=15) as r:
r.raise_for_status()
with open(DEST_FILE, "wb") as out:
shutil.copyfileobj(r.raw, out, 1 << 16) # 64 KB chunks
except requests.exceptions.RequestException as e:
raise RuntimeError(f"Download failed: {e}")
def legal_moves() -> list[str]:
"""
Returns a list of legal moves in standard algebraic notation.
"""
return [board.san(move) for move in board.legal_moves]
def possible_captures() -> list[dict]:
"""
Returns all legal captures with metadata:
- san: SAN notation of the capture move.
- captured_piece: The piece type being captured ('P','N','B','R','Q','K').
- is_hanging: True if the captured piece was undefended before the capture.
"""
result = []
for move in board.generate_legal_captures():
piece = board.piece_at(move.to_square)
piece_type = piece.symbol().upper() if piece else "?"
# Check defenders of the target square
defenders = board.attackers(not board.turn, move.to_square)
is_hanging = len(defenders) == 0 # no defenders => hanging
result.append({
"san": board.san(move),
"captured_piece": piece_type,
"is_hanging": is_hanging
})
return result
def possible_checks() -> list[dict]:
"""
Returns all legal checking moves with metadata:
- san: SAN notation of the checking move.
- can_be_captured: True if after the move, the checking piece can be captured.
- can_be_blocked: True if the check can be legally blocked.
- can_escape_by_moving_king: True if the king can move out of check.
"""
result = []
for move in board.legal_moves:
if not board.gives_check(move):
continue
temp = board.copy()
temp.push(move)
can_capture = any(
temp.is_capture(reply) and reply.to_square == move.to_square
for reply in temp.legal_moves
)
# King escapes by moving
king_sq = temp.king(not board.turn)
can_escape = any(
reply.from_square == king_sq for reply in temp.legal_moves
)
# Blocking: legal non-capture, non-king move that resolves check
can_block = any(
not temp.is_capture(reply)
and reply.from_square != king_sq
and not temp.gives_check(reply)
for reply in temp.legal_moves
)
result.append({
"san": board.san(move),
"can_be_captured": can_capture,
"can_be_blocked": can_block,
"can_escape_by_moving_king": can_escape
})
return result
def get_move_history() -> list[str]:
"""
Returns a list of moves made in the game so far in standard algebraic notation.
"""
return [board.san(move) for move in board.move_stack]
def get_book_moves() -> list[str]:
"""
Returns a list of book moves in standard algebraic notation from performance.bin
for the current board position. If no book moves exist, returns an empty list.
"""
moves = []
with chess.polyglot.open_reader("performance.bin") as reader:
for entry in reader.find_all(board):
san_move = board.san(entry.move)
moves.append(san_move)
return moves
def is_ai_turn() -> bool:
return bool(board.turn) == (ai_pos == 0)
def make_ai_move(move: str) -> None:
"""
Given a string representing a valid move in chess notation, pushes move onto chess board.
If non-valid move, raises a ValueError with message "Illegal move.
If called when it is not the AI's turn, raises a ValueError with message "Not AI's turn."
THIS FUNCTION DIRECTLY ENABLES THE AI TO MAKE A MOVE ON THE CHESS BOARD.
"""
if is_ai_turn():
try:
board.push_san(move)
except ValueError as e:
raise ValueError(e)
else:
raise ValueError("Not AI's turn.")
def make_user_move(move: str) -> None:
"""
Given a string representing a valid move in chess notation, pushes move onto chess board.
If non-valid move, raises a ValueError with message "Illegal move.
If called when it is not the player's turn, raises a ValueError with message "Not player's turn."
If valid-move, updates the board and displays the current state of the board.
"""
if not is_ai_turn():
try:
board.push_san(move)
except ValueError as e:
raise ValueError(e)
else:
raise ValueError("Not player's turn.")
def print_fragment(fragment, round_index=0):
print(fragment.content, end="", flush=True)
次に、AIエージェントとのチェスの試合をセットアップします。「lms.Chat()」呼び出しを使用して、チェスAIエージェントに白または黒でプレイしているときの指示を提供します。
chat_white = lms.Chat("""You are a chess AI, playing for white. Your task is to make the best move in the current position, using the provided tools.
You should use your overall chess knowledge, including openings, tactics, and strategies, as your primary method to determine good moves.
Use the provided tools as an assistant to improve your understanding of the board state and to make your moves. Always use the book moves
if they are available. Be prudicious with your checks and captures. Understand whether the capturable piece is hanging, and its value in
comparison to the piece you are using to capture. Consider the different ways the opponent can defend a check, to pick the best option.""")
chat_black = lms.Chat("""You are a chess AI, playing for black. Your task is to make the best move in the current position, using the provided tools.
You should use your overall chess knowledge, including openings, tactics, and strategies, as your primary method to determine good moves.
Use the provided tools as an assistant to improve your understanding of the board state and to make your moves. Always use the book moves
if they are available. Be prudicious with your checks and captures. Understand whether the capturable piece is hanging, and its value in
comparison to the piece you are using to capture. Consider the different ways the opponent can defend a check, to pick the best option.""")
最後に、一致を追跡するために、「update_board()」と「get_end_state()」という2つの関数を設定します。
以前にツール呼び出しに使用した「model.act()」呼び出しを使用することで、定義したエージェント指示(「chat」)とその使用に利用できるツールを入力し「max_prediction_rounds」を確立します。この関数は、エージェントが特定の動きを実行するために実行できる独立ツール呼び出しの最大数を示します。
次のセルを実行すると、動きを書き込むための空のインプット・フィールドが表示されます。利用可能な動きがわからない場合は、「ヘルプ」と入力すると、利用可能な動きの表記が表示されます。ここで、最初のイニシャルはそのピースのイニシャル名です(「\"B\"」はビショップ、「"Q\"」はクイーン)のようになります。しかし、「\"K\"」は王を表し、「\"N\"」はナイトであり、ポーンを表す頭文字は存在しません。次の文字と数字は、そのピースを動きさせる行と列です。キャスリングや曖昧な駒の動きなどの特殊なケースの表記については、代数記法(チェス)のWikipediaページを参照してください。
頑張ってください。
move = 0
import chess.svg
board.reset()
ai_pos = round(random.random())
def update_board(move = move, ai_pos = ai_pos):
"""
Updates the chess board display in the notebook.
"""
clear_output(wait=True) # Clear previous output
print(f"Board after move {move+1}")
if (ai_pos == 1):
display(SVG(chess.svg.board(board, size=400)))
else:
display(SVG(chess.svg.board(board, size=400, orientation = chess.BLACK)))
def get_end_state():
"""
Returns the end state of the chess game.
"""
if board.is_checkmate():
return "Checkmate!"
elif board.is_stalemate():
return "Stalemate!"
elif board.is_insufficient_material():
return "Draw by insufficient material!"
elif board.is_seventyfive_moves():
return "Draw by 75-move rule!"
elif board.is_fivefold_repetition():
return "Draw by fivefold repetition!"
else:
return None
clear_output(wait=True) # Clear any previous output from the cell
if (ai_pos == 1):
display(SVG(chess.svg.board(board, size=400)))
else:
display(SVG(chess.svg.board(board, size=400, orientation = chess.BLACK)))
# 2. Loop through moves, apply each move, clear previous output, and display new board
userEndGame = False
while True:
if ai_pos == 0:
# AI's turn
model.act(
chat_white,
[get_move_history, legal_moves, possible_captures, possible_checks, get_book_moves, make_ai_move],
on_message=print,
max_prediction_rounds = 8,
)
if is_ai_turn(): # failsafe in case AI does not make a move
make_ai_move(legal_moves()[0]) # Default to the first legal move if AI does not respond
update_board(move)
move += 1
game_over_message = get_end_state()
if game_over_message:
print(game_over_message)
break
# User's turn
while True:
user_move = input("User (Playing Black): Input your move. Input 'help' to see the list of possible moves. Input 'quit' to end the game ->")
if user_move.lower() == 'quit':
print("Game ended by user.")
userEndGame = True
break
if user_move.lower() == 'help':
print("Possible moves:", legal_moves())
continue
try:
make_user_move(user_move)
break
except ValueError as e:
print(e)
if userEndGame:
break
update_board(move)
move += 1
game_over_message = get_end_state()
if game_over_message:
print(game_over_message)
break
else:
# User's turn
while True:
user_move = input("User (Playing White): Input your move. Input 'help' to see the list of possible moves. Input 'quit' to end the game ->")
if user_move.lower() == 'quit':
print("Game ended by user.")
userEndGame = True
break
if user_move.lower() == 'help':
print("Possible moves:", legal_moves())
continue
try:
make_user_move(user_move)
break
except ValueError as e:
print(e)
if userEndGame:
break
update_board(move)
move += 1
game_over_message = get_end_state()
if game_over_message:
print(game_over_message)
break
model.act(
chat_black,
[get_move_history, legal_moves, possible_captures, possible_checks, get_book_moves, make_ai_move],
max_prediction_rounds = 8,
on_message=print,
)
if is_ai_turn(): # failsafe in case AI does not make a move
make_ai_move(legal_moves()[0]) # Default to the first legal move if AI does not respond
update_board(move)
move += 1
game_over_message = get_end_state()
if game_over_message:
print(game_over_message)
break
このノートブックでは、統合ツールによってLLMのユーティリティとエージェント機能をどのように強化できるかを示しました。事前定義された外部関数へのアクセスをLLMに提供することで、LLMがコアとなる言語処理機能を超えて、正確な計算などのタスクを実行したり、外部システムとのインターフェースを作成したりできることを示しました。それだけでは確実に行うことはできません。重要なポイントは、ツールの使用により、LLMが特定の下位問題を特殊なルーチンに委任できるようになり、事実に基づいたデータや正確なオペレーションに基づいて対応できるようになるということです。このアプローチにより、精度が向上するだけでなく、LLMはより複雑でインタラクティブなワークフローに取り組むことができ、LLMをより多用途で強力なアシスタントに効果的に変えることができます。
生成AIを使用してワークフローとプロセスを自動化する強力なAIアシスタントとエージェントを構築、デプロイ、管理しましょう。
信頼できるAIソリューションでビジネスの未来を構築します。
IBMコンサルティングAIサービスは、企業がAIをトランスフォーメーションに活用する方法を再考するのに役立ちます。