Verwenden Sie LM Studio, um einen automatischen Toolaufruf mit Granite zu erstellen

Autoren

Caleb Palappillil

Open-source AI Software Developer Intern

Erika Russi

Data Scientist

IBM

In dieser Schritt-für-Schritt-Anleitung verwenden Sie LM Studio mit dem Open-Source-Modell IBM® Granite 3.3-8b instruct auf Ihrem lokalen Rechner. Zunächst testen Sie das lokale Modell unverändert und schreiben dann Python-Funktionen, die das Modell für den automatischen Toolaufruf verwenden kann. Schließlich entwickeln Sie weitere Funktionen, um eine Partie Schach mit einem KI-Agenten zu spielen. Dieses Tutorial finden Sie auch im Granite Snack Cookbook GitHub der IBM Granite Community in Form eines Jupyter Notebook.

LM Studio

LM Studio ist eine Anwendung für die Arbeit mit lokalen großen Sprachmodellen (LLMs). Sie können jedes Open-Source-Modell mit LM Studio verwenden, z. B. Mistral KI Modelle, Gemma von Google, Meta's Llama oder DeepSeek's R1 Serie.

Mit LM Studio können Anfänger bis fortgeschrittene Benutzer LLMs entweder mit der CPU ihres Computers oder sogar mit einer GPU ausführen. LM Studio bietet eine chatähnliche Oberfläche zur Interaktion mit lokalen LLMs, ähnlich wie bei ChatGPT. Chat.

Mit einem lokalen KI-Modell können Sie Feinabstimmung, Inferenz und mehr, ohne sich um externe API kümmern zu müssen Aufrufe (wie OpenAI oder IBM watsonx.ai Programmierschnittstellen oder APIs) oder Token-Nutzung. LM Studio ermöglicht es Benutzern auch, lokal und privat „mit Dokumenten zu chatten“. Ein Benutzer kann ein Dokument an eine Chatsitzung anhängen und Fragen zu dem Dokument stellen. In Fällen, in denen das Dokument lang ist, richtet LM Studio eine Retrieval Augmented Generation ein, (RAG)-Systems für die Abfrage von Daten.

Tool Calling

LLMs sind zwar hervorragend darin, menschenähnlichen Text zu verstehen und zu generieren, stoßen aber oft an ihre Grenzen, wenn Aufgaben präzise Berechnungen, den Zugriff auf externe Echtzeitdaten oder die Ausführung spezifischer, klar definierter Verfahren erfordern. Durch die Implementierung von Tool Calling statten wir LLMs mit einer Reihe von „Tools“ – externen Funktionen – aus, die sie aufrufen können und die ihre Fähigkeiten erheblich erweitern können. Dieses Tutorial zeigt, wie Sie diese Tools definieren und integrieren, damit das LLM ein breiteres Spektrum an Aufgaben mit größerer Zuverlässigkeit ausführen kann.

Schritte

Schritt 1. Installieren von LM Studio

Bevor Sie LM Studio installieren, prüfen Sie, ob Ihr Rechner die Mindestsystemanforderungen erfüllt.

Laden Sie anschließend das entsprechende Installationsprogramm für das Betriebssystem Ihres Computers (Windows, macOS oder Linux) herunter. Folgen Sie anschließend diesen Anweisungen, um die Modelle auf Ihren lokalen Rechner herunterzuladen.

Wir werden für dieses Rezept das Granite 3.3-8b Instruct Model verwenden, aber Sie können auch ein beliebiges LLM Ihrer Wahl verwenden. Wenn Sie das Granite-Modell verwenden, können Sie in LM Studio nach dem spezifischen Benutzer-/Modell-String-ibm-granite/granite-3.3-8b-instruct-GGUF imSelect a model to load Bereich suchen.

Starten Sie als Nächstes den lokalen Server von LM Studio, indem Sie zum grünenDeveloper Symbol oben links in LM Studio zu navigieren. Bitte aktivieren Sie dieStatus Leiste oben links, umRunning .

Schritt 2. Abhängigkeiten installieren

Zuerst müssen wir die notwendigen Bibliotheken installieren, einschließlich des LM Studio SDK und der Schachbibliothek.

%pip install git+https://github.com/ibm-granite-community/utils \
    lmstudio \
    chess
import lmstudio as lms

Schritt 3. Laden des Modells

Als Nächstes geben wir das Modell an, das wir in diesem Rezept verwenden wollen. In unserem Fall handelt es sich um das Modell, das wir in LM Studio heruntergeladen haben, Granite 3.3-8b Instruct.

Wir beginnen auch mit dem Chatten, indem wir „model.respond()“ aufrufen dank einer ersten Nachricht.

model = lms.llm("ibm-granite/granite-3.3-8b-instruct-GGUF")

print(model.respond("Hello Granite!"))

Schritt 4. Führen Sie eine Berechnung ohne Werkzeuge durch

Beginnen wir damit, dass das Modell eine einfache Berechnung durchführen soll.

print(model.respond("What is 26.97 divided by 6.28? Don't round."))

Während das Modell unter Umständen eine Näherung liefern kann, gibt es keine exakte Antwort zurück, da es den Quotienten nicht selbst berechnen kann.

Schritt 5. Erstellen Sie Berechnungstools mit Python-Funktionen

Zur Lösung dieses Problems werden wir dem Modell Tools zur Verfügung stellen. Tools sind Python-Funktionen, die wir dem Modell bei der Inferenz zur Verfügung stellen. Das Modell kann eines oder mehrere dieser Tools aufrufen, um die Anfrage des Benutzers zu beantworten.

Weitere Informationen zum Schreiben von Tools finden Sie in der LM Studio-Dokumentation. Allgemein sollten Sie darauf achten, dass Ihre Toolfunktionen über einen angemessenen Namen, definierte Eingabe- und Ausgabetypen sowie eine Beschreibung verfügen, die den Zweck des Tools erläutert. All diese Informationen werden an das Modell übergeben und können ihm helfen, das richtige Werkzeug für die Beantwortung Ihrer Abfrage auszuwählen.

Wir werden mehrere einfache mathematische Funktionen schreiben, die das Modell als Werkzeuge verwenden kann:

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

Jetzt können wir dieselbe Abfrage erneut ausführen, dem Modell aber einige Tools zur Verfügung stellen, die es bei der Beantwortung unterstützen. Wir verwenden den „model.act ()“- Aufruf für automatische Werkzeugaufrufe, und geben Sie dem Modell zu verstehen, dass es die von uns erstellten Funktionen verwenden kann.

model.act(
  "What is 26.97 divided by 6.28? Don't round.",
  [add, subtract, multiply, divide, exp],
  on_message=print,
)

Wir können sehen, dass das Modell in der Lage war, das richtige Werkzeug unter „ToolCallRequest“ und „Name“ auszuwählen, die entsprechenden Eingaben unter „Argumenten“ verwendet (die Argumente, die an die Funktion übergeben werden sollen) und die Verwendung der irrelevanten Werkzeuge vermeidet. Schließlich zeigt die Antwort unter „AssistantResponse“, „Inhalt“, „Text“ die Antwort des Modells, eine exakte Antwort auf die Frage.

Wie viele Rs in „Strawberry“?

Eine sehr einfache Frage, die selbst die intelligentesten Sprachmodelle an ihre Grenzen bringt. Fast jeder einzelne LLM mit einem Ausbildungsschnitt vor 2024 antwortet, dass es nur 2 Rs in dem Wort „Strawberry“ gibt. Als Bonus kann es sogar falsche Positionen für die Buchstaben halluzinieren.

Heutzutage beantworten LLMs diese spezielle Frage in der Regel richtig, weil sie aufgrund ihrer Viralität in den meisten Datensätzen enthalten ist. Allerdings scheitern LLMs bei ähnlichen Aufgaben zum Zählen von Buchstaben immer noch häufig.

print(model.respond("How many Bs are in the word 'blackberry'?"))

Lass uns ein Tool schreiben, das dem Modell hilft, bessere Arbeit zu leisten.

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

können wir das Tool an das Modell weitergeben und den Prompt erneut ausführen.

model.act(
  "How many Bs are in the word 'blackberry'?",
  [get_letter_frequency],
  on_message=print,
)

Mithilfe von Mit dem Tool „get_letter_frequenz()“ konnte das Modell die Anzahl der Buchstaben „B“ im Wort „Blackberry“ genau zählen.

Schritt 6. Implementieren Sie einen automatischen Toolaufruf für einen Agenten

Einer der besten Anwendungsfall dieses automatischen Tool-Call-Workflows ist es, Ihrem Modell die Möglichkeit zu geben, mit seiner externen Umgebung zu interagieren. Entwickeln wir einen Agenten, der Tools nutzt, um Schach zu spielen!

Sprachmodelle können zwar über ein ausgeprägtes konzeptionelles Wissen über Schach verfügen, aber sie sind nicht von Natur aus darauf ausgelegt, ein Schachbrett zu verstehen. Wenn Sie versuchen, eine Schachpartie mit einem Chatbot zu spielen, entgleist er häufig nach mehreren Zügen und macht illegale oder irrationale Verschiebungen.

Wir stellen dem Modell mehrere Tools zur Verfügung, die ihm helfen, das Board zu verstehen und mit ihm zu interagieren.

  • legal_moves(): Liefert eine Liste aller zulässigen Züge in der aktuellen Position . - possible_captures(): Liefert eine Liste aller möglichen Schläge in der aktuellen Position . - possible_checks(): Liefert eine Liste aller möglichen Schachzüge in der aktuellen Position . - get_move_history(): Liefert eine Liste aller bisher gespielten Züge . - get_book_moves(): Liefert eine Liste aller Buchzüge
  • make_ai_move(): Eine Schnittstelle, über die das Modell seinen Zug eingeben kann

Es ist nicht viel, aber es reicht aus, wenn das Modell eine ganze Partie Schach spielt, ohne zu halluzinieren, und einige intelligente Argumentationen verwendet, um seine Entscheidungen zu treffen.

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)

Als Nächstes bereiten wir uns auf das Schachspiel mit einem KI-Agenten vor. Durch die Verwendung des Aufrufs „lms. Chat()“ geben wir unserem Schach-KI-Agenten Anweisungen, ob der Agent für Weiß oder Schwarz spielt.

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.""")

Schließlich richten wir zwei Funktionen ein, um das Spiel zu verfolgen: „update_board()“ und „get_end_state()“.

Durch die Verwendung von „model.act()“, den wir zuvor für den Tool-Aufruf verwendet haben, fügen wir die Anweisungen des Agenten („Chat“) hinzu, die wir definiert haben, sowie die dafür verfügbaren Tools, und erstellen einen „max_prediction_rounds“. Diese Funktion zeigt die maximale Anzahl unabhängiger Toolaufrufe an, die der Agent durchführen kann, um einen bestimmten Zug auszuführen.

Nach dem Ausführen der nächsten Zelle sollte ein leeres Eingabefeld erscheinen, in das Sie Ihre Züge eintragen können. Wenn Sie sich nicht sicher sind, welche Verschieben verfügbar sind, geben Sie „help“ ein. Die Notationen der verfügbaren Verschieben werden dann angezeigt, wobei der erste Initiale der initialisierte Name der Figur ist („B“ ist Läufer, „Q“ ist Dame und so weiter. Aber \"N\" ist ein Springer, da \"K\" für den König steht und keine erste Initiale für einen Bauern steht). Der nächste Buchstabe und die nächste Zahl sind die Zeile und Spalte, in die das Teil verschoben werden soll. Für die Notation von Sonderfällen wie Rochade oder mehrdeutigen Figurenbewegungen siehe die Wikipedia-Seite „Algebraische Notation (Schach)“.

Viel Glück!

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

Zusammenfassung

In diesem Notebook haben wir gezeigt, wie die Integration von Tools den Dienstprogramm und die Funktionen von LLMs verbessern kann. Wir haben gezeigt, dass ein LLM, indem es ihm Zugriff auf vordefinierte externe Funktionen bietet, seine Kernsprachverarbeitungsfähigkeiten überwinden kann, um Aufgaben wie genaue Berechnungen oder Schnittstellen zu externen Systemen durchzuführen. Er selbst kann dies nicht zuverlässig leisten. Die wichtigste Erkenntnis ist, dass die Verwendung von Tools LLMs in die Lage versetzt, bestimmte Teilprobleme an spezialisierte Routinen zu delegieren und so ihre Antworten auf sachliche Daten oder präzise Operationen zu stützen. Dieser Ansatz verbessert nicht nur die Genauigkeit, sondern ermöglicht es LLMs auch, komplexere, interaktive Workflows auszuführen, wodurch sie zu vielseitigeren und leistungsfähigeren Assistenten werden.

Weiterführende Lösungen
KI-Agenten für Unternehmen

Entwickeln, implementieren und verwalten Sie leistungsstarke KI-Assistenten und -Agenten, die Workflows und Prozesse mit generativer KI automatisieren.

    Entdecken Sie watsonx Orchestrate
    IBM KI-Agenten-Lösungen

    Gestalten Sie die Zukunft Ihres Unternehmens mit KI-Lösungen, denen Sie vertrauen können.

    KI-Agenten-Lösungen erkunden
    IBM Consulting KI-Dienstleistungen

    Die KI-Services von IBM Consulting unterstützen Sie dabei, die Art und Weise, wie Unternehmen mit KI arbeiten, neu zu denken.

    Erkunden Sie die Services im Bereich der künstlichen Intelligenz
    Machen Sie den nächsten Schritt

    Ganz gleich, ob Sie vorgefertigte Apps und Skills anpassen oder mithilfe eines KI-Studios eigene Agentenservices erstellen und bereitstellen möchten, die IBM watsonx-Plattform bietet Ihnen alles, was Sie brauchen.

    Entdecken Sie watsonx Orchestrate watsonx.ai erkunden