本教程将指导您在本地计算机上使用 LM Studio 搭配开源 IBM Granite 3.3-8b instruct 模型。您将首先测试基础模型性能,随后编写可供模型调用的 Python 函数实现自动工具调用。最后,您将开发更多功能,实现与 AI 智能体国际象棋对弈。本教程同步收录于 IBM Granite 社区的 Granite Snack Cookbook GitHub,以 Jupyter Notebook 形式提供。
LM Studio 是一个用于处理本地 [大型语言模型] (LLM) (https://www.ibm.com/cn-zh/think/topics/large-language-models) 的应用程序。您可以在 LM Studio 中使用任何 [开源模型] (https://www.ibm.com/cn-zh/think/topics/open-source-llms),如 [Mistral AI] (https://www.ibm.com/cn-zh/think/topics/mistral-ai) 模型、 [Google's Gemma] (https://www.ibm.com/cn-zh/think/topics/google-gemma)、Meta's Llama 或 [DeepSeek's R1] (https://www.ibm.com/cn-zh/think/topics/deepseek) 系列。
借助 LM Studio,初学者和高级用户都可以使用计算机的 CPU 甚至 GPU 来运行 LLM (https://www.ibm.com/cn-zh/think/topics/gpu)。LM Studio 提供了一个类似聊天的界面,可与本地 LLM 进行交互,类似于 ChatGPT 的聊天功能。
虽然 LLM 擅长理解和生成类似人类文本的文本,但当任务需要精确计算、访问实时外部数据或执行特定的明确定义程序时,它们往往会面临限制。通过实施 工具调用,我们为 LLM 配备了一套“工具”(外部函数)他们可以选择调用这些函数,从而显著扩展功能。本教程将演示如何定义并集成这些工具,使 LLM 能够以更高的可靠性执行更广泛的任务。
在安装 LM Studio 之前,请检查本地计算机是否满足最低系统 要求。
接下来, 下载适合您计算机操作系统(Windows、macOS 或 Linux)的安装程序。然后,按照以下 说明将模型下载到本地计算机。
本示例将采用 Granite 3.3-8b instruct 模型,您也可自由选择任何 LLM。如果您使用的是 Granite 模型,则可以
接下来通过点击
我们首先需要安装必要的库,包括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 instruct 模型。我们还将通过调用 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 函数。模型可以选择调用其中一个或多个工具来回答用户查询。
请查阅 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 几乎都回答“strawberry”中只有 2 个 R。另外,它甚至可能会产生字母位置不正确的幻觉。
如今,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 的数量。
自动工具调用工作流最具价值的应用场景之一,是赋予模型与外部环境进行交互的能力。我们现在构建一个能够运用工具进行国际象棋对弈的智能体!
虽然语言模型可以拥有很强的国际象棋概念知识,但它们本质上并不是为理解国际象棋而设计的。如果您尝试与在线聊天机器人下棋,它往往会在几个回合后失控,下出不合规则或不合理的棋步。
我们为模型提供了多种工具,帮助它理解棋盘并与之交互。
这些工具虽不多,但足以让模型在避免幻觉的前提下完成整局对弈,并运用智能推理进行决策。
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()' 调用,我们将向为执白或执黑的国际象棋智能体提供对弈指令。
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()。
通过使用 “model.act()”如果使用之前的工具调用,我们将为代理提供我们定义的指令(“聊天”)、可供其使用的工具并建立 “max_prediction_rounds”。此函数显示代理为执行特定棋步可以进行独立工具调用的最大数量。
运行下一个单元后,系统将显示一个空白输入框,供您输入棋步走法。如果不确定当前可用的合法棋步,请输入“help”指令,系统将显示可用棋步的代数记谱说明:其中首字母代表棋子名称的缩写(例如 “B” 代表象,“Q” 代表后,以此类推;但 “N” 代表马,因为 “K” 已用于表示王,而兵则不标注首字母)。随后列出的字母与数字组合,表示该棋子应移动至的目标行与列坐标。关于王车易位或歧义棋步等特殊情形的记谱规则,请参阅维基百科的国际象棋代数记谱法相关页面。
祝您对弈顺利!
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 能够参与更复杂的交互式工作流,有效地将其转变为更通用、更强大的助手。
构建、部署和管理强大的 AI 助手和智能体,运用生成式 AI 实现工作流和流程自动化。
借助值得信赖的 AI 解决方案,您可以勾勒未来业务发展蓝图。
IBM Consulting AI 服务有助于重塑企业利用 AI 实现转型的方式。