Utilice LM Studio para crear llamadas automáticas a herramientas con Granite

Autores

Caleb Palappillil

Open-source AI Software Developer Intern

Erika Russi

Data Scientist

IBM

En este tutorial paso a paso, utilizará LM Studio con el modelo de instrucciones de código abierto IBM® Granite 3.3-8b en su máquina local. Inicialmente, probará el modelo local tal cual y, a continuación, escribirá funciones de Python que el modelo pueda utilizar para la llamada automática de herramientas. Por último, desarrollará más funciones para jugar una partida de ajedrez con un agente de IA. Este tutorial también puede encontrarse en el GitHub de Granite Snack Cookbook de la comunidad de IBM® Granite en forma de Jupyter Notebook.

LM Studio

LM Studio es una aplicación para trabajar con [modelos de lenguaje de gran tamaño] (https://www.ibm.com/es-es/think/topics/large-language-models) (LLM) locales. Puede utilizar cualquier modelo de código abierto con LM Studio, como los modelos Mistral AI , Gemma de Google, Llama de Meta o la serie R1 de DeepSeek .

Con LM Studio, los usuarios principiantes y más avanzados pueden ejecutar LLM con la CPU de su ordenador o incluso con una [GPU] (https://www.ibm.com/es-es/think/topics/gpu). LM Studio ofrece una interfaz similar a un chat para interactuar con LLM locales similar al chat de ChatGPT .

Con un modelo de IA local, puede afinar, inferir y mucho más sin tener que preocuparse de llamadas externas a API externa (como las interfaces de programación de aplicaciones OpenAI o IBM watsonx.ai, o API) o del uso de tokens. LM Studio también permite a los usuarios "chatear con documentos" de forma local y privada. Un usuario puede adjuntar un documento a una sesión de chat y hacer preguntas sobre el documento. En los casos en que el documento sea largo, LM Studio configurará un sistema de generación aumentada por recuperación (RAG) para consultas.

Llamada a herramientas

Aunque los LLM sobresalen en la comprensión y generación de texto humano, a menudo se enfrentan a limitaciones cuando las tareas requieren un cálculo preciso, el acceso a datos externos en tiempo real o la ejecución de procedimientos específicos y bien definidos. Al implementar llamadas a herramientas, equipamos a los LLM con un conjunto de "herramientas" (funciones externas) a las que pueden optar por llamar y que pueden ampliar significativamente sus capacidades. Este tutorial demostrará cómo definir estas herramientas e integrarlas, lo que permitirá al LLM realizar una gama más amplia de tareas con mayor fiabilidad.

Pasos

Paso 1. Instalar LM Studio

Antes de instalar LM Studio, compruebe que su máquina local cumple los requisitos mínimos del sistema.

A continuación, descargue el instalador adecuado para el sistema operativo de su ordenador (Windows, macOS o Linux). Después, siga estas instrucciones para descargar los modelos en su máquina local.

Utilizaremos el modelo de instrucciones Granite 3.3-8b para esta receta, pero no dude en utilizar cualquier LLM de su elección. Si utiliza el modelo Granite, puede buscar la cadena de usuario/modelo específicaibm-granite/granite-3.3-8b-instruct-GGUF en elSelect a model to load en LM Studio.

A continuación, inicie el servidor local de LM Studio navegando hasta el iconoDeveloper verde en la parte superior izquierda de LM Studio. Active laStatus en la parte superior izquierda aRunning .

Paso 2. Instalar dependencias

Primero tenemos que instalar las bibliotecas necesarias, incluido el SDK de LM Studio y la biblioteca de ajedrez.

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

Paso 3. Cargar el modelo

A continuación especificaremos el modelo que queremos utilizar en esta receta. En nuestro caso, será el modelo que descargamos en LM Studio, instrucción Granite 3.3-8b.

También comenzaremos a chatear con él llamando a "model.respond()" con un mensaje inicial.

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

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

Paso 4. Realizar un cálculo sin herramientas

Empecemos pidiendo al modelo que haga un cálculo sencillo.

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

Aunque el modelo podría proporcionar una aproximación cercana, no devolverá la respuesta exacta porque no puede calcular el cociente por sí solo.

Paso 5. Crear herramientas de cálculo con funciones de Python

Para resolver este problema, proporcionaremos herramientas al modelo. Las herramientas son funciones de Python que proporcionamos al modelo en la inferencia. El modelo puede optar por llamar a una o más de estas herramientas para responder a la consulta del usuario.

Consulte la documentación de LM Studio para obtener más información sobre cómo escribir herramientas. En general, debe asegurarse de que sus funciones de herramientas tengan un nombre apropiado, tipos de entrada y resultado definidos y una descripción que explique el propósito de la herramienta. Toda esta información se pasa al modelo y puede ayudarle a seleccionar la herramienta correcta para responder a su consulta.

Escribiremos varias funciones matemáticas sencillas para que el modelo las utilice como herramientas:

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

Ahora, podemos volver a ejecutar la misma consulta pero proporcionar al modelo algunas herramientas para ayudarle a responder. Usaremos la llamada "model.act()" a la herramienta automática e indicar al modelo que puede utilizar las funciones que hemos creado.

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

Podemos ver que el modelo pudo seleccionar la herramienta correcta en "ToolCallRequest", "name", utilizó las entradas apropiadas en "arguments" (los argumentos para pasar a la función) y evitó utilizar las herramientas irrelevantes. Por último, la respuesta en "AssistantResponse", "content", "text" muestra la respuesta del modelo, una respuesta exacta a la pregunta.

¿Cuántas R hay en fresa?

Una pregunta muy sencilla que deja perplejos incluso a los modelos lingüísticos más inteligentes. Casi todos los LLM con un corte de formación antes de 2024 responden que solo hay 2 R en la palabra "fresa". Como beneficio adicional, incluso podría alucinar posiciones incorrectas para las letras.

Hoy en día, los LLM tienden a responder correctamente a esta pregunta específica, simplemente porque su viralidad la ha llevado a la mayoría de los conjuntos de datos de entrenamiento. Sin embargo, los LLM suelen fallar en tareas similares de recuento de letras.

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

Vamos a escribir una herramienta para ayudar al modelo a hacer un mejor trabajo.

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

Ahora podemos pasar la herramienta al modelo y volver a ejecutar la instrucción.

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

Usando la herramienta "get_letter_frequency()", el modelo pudo contar con precisión el número de b en la palabra "blackberry".

Paso 6. Implementar una herramienta automática que llame a un agente

Uno de los mejores casos de uso de este flujo de trabajo automático de llamada de herramientas es dar a su modelo la capacidad de interactuar con su entorno externo. ¡Creemos un agente que utilice herramientas para jugar al ajedrez!

Aunque los modelos de lenguaje pueden tener un sólido conocimiento conceptual del ajedrez, no están diseñados de forma inherente para entender un tablero. Si intenta jugar una partida de ajedrez con un chatbot en línea, a menudo se desviará después de varios turnos, haciendo movimientos ilegales o irracionales.

Estamos proporcionando al modelo varias herramientas que le ayudan a comprender e interactuar con el tablero.

  • legal_moves(): proporciona una lista de todos los movimientos legales en la posición actual
  • possible_captures(): proporciona una lista de todas las posibles capturas en la posición actual
  • possible_checks(): proporciona una lista de todas las comprobaciones posibles en la posición actual
  • get_move_history(): proporciona una lista de todos los movimientos jugados hasta el momento
  • get_book_moves(): proporciona una lista de todos los movimientos del libro
  • make_ai_move(): una interfaz para que el modelo haga su mover

No es mucho, pero es suficiente para que el modelo juegue una partida completa de ajedrez sin alucinaciones y utilice un razonamiento inteligente para basar sus decisiones.

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)

A continuación, prepararemos la partida de ajedrez con un agente de IA. Mediante el uso de la llamada "lms.Chat()", proporcionaremos instrucciones a nuestro agente de IA de ajedrez para saber cuándo el agente está jugando con blancas o negras.

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

Por último, configuraremos dos funciones para rastrear la coincidencia: "update_board()" y "get_end_state()".

Mediante el uso de "model.act()" que utilizamos para llamar a la herramienta anteriormente, alimentaremos las instrucciones del agente ("chat") que definimos, las herramientas disponibles para su uso y estableceremos un "max_prediction_rounds". Esta función muestra el número máximo de llamadas de herramientas independientes que el agente puede realizar para ejecutar un mover específico.

Después de ejecutar la siguiente celda, debería aparecer un campo de entrada vacío para que escriba sus movimientos. Si no está seguro de los movimientos disponibles, escriba "ayuda" y se mostrarán las anotaciones de los movimientos disponibles, donde la primera inicial es el nombre inicial de la pieza (\"B\" es alfil, \"Q\" es reina , etc. Pero \"N\" es caballo, ya que \"K\" representa al rey y ninguna inicial es para un peón). La siguiente letra y número enumerados son la fila y la columna a las que mover esa pieza. Para la notación de casos especiales como el enroque o los movimientos ambiguos de las piezas, consulte la página de Wikipedia sobre notación algebraica (ajedrez).

¡Buena suerte!

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

Resumen

En este notebook, demostramos cómo la integración de herramientas puede mejorar los servicios y las capacidades de los LLM. Mostramos que al proporcionar a un LLM acceso a funciones externas predefinidas, puede trascender sus capacidades de procesamiento del lenguaje para realizar tareas como cálculos precisos o interactuar con sistemas externos. No puede hacerlo de forma fiable por sí solo. La conclusión clave es que el uso de herramientas permite a los LLM delegar subproblemas específicos a rutinas especializadas, lo que les permite basar sus respuestas en datos fácticos u operaciones. Este enfoque no solo mejora la precisión, sino que también permite a los LLM participar en flujos de trabajo más complejos e interactivos, transformándolos eficazmente en asistentes más versátiles y potentes.

Soluciones relacionadas
Agentes de IA para empresas

Cree, implemente y gestione potentes asistentes y agentes de IA que automaticen flujos de trabajo y procesos con IA generativa.

    Explore watsonx Orchestrate
    Soluciones de agente de IA de IBM

    Construya el futuro de su empresa con soluciones de IA en las que puede confiar.

    Explore las soluciones de los agentes de IA
    Servicios de IA de IBM Consulting

    Los servicios de IA de IBM Consulting ayudan a reinventar la forma de trabajar de las empresas usando IA para la transformación.

    Explore los servicios de inteligencia artificial
    Dé el siguiente paso

    Tanto si opta por personalizar las aplicaciones y habilidades prediseñadas como si prefiere crear e implementar servicios agentivos personalizados mediante un estudio de IA, la plataforma IBM watsonx le ofrece todo lo que necesita.

    Explore watsonx Orchestrate Explore watsonx.ai