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 como está y luego escribirá funciones de Python que el modelo puede usar para llamadas automáticas a herramientas. Finalmente, desarrollará más funciones para jugar una partida de ajedrez con un agente de IA. Este tutorial también se puede encontrar en Granite Snack Cookbook GitHub de IBM Granite Community en forma de Jupyter Notebook.

LM Studio

LM Studio es una aplicación para trabajar con [modelos de lenguaje grandes] locales (https://www.ibm.com/mx-es/think/topics/large-language-models) (LLM). Puede usar cualquier modelo de código abierto con LM Studio, como Mistral IA modelos, Gemma de Google, Llama de Meta o series DeepSeek's R1.

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

Con un modelo de IA local, puede ajustar, inference y más sin tener que preocuparse por las llamadas externas API externa (como interfaces de programación de aplicaciones OpenAI o IBM watsonx.ai, o API) o 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 generación aumentada por recuperación (RAG) para consultas.

Llamada de herramientas

Si bien los LLM se destacan en la comprensión y generación de texto similar al humano, a menudo enfrentan limitaciones cuando las tareas requieren cálculos precisos, 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 para 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 confiabilidad.

Pasos

Paso 1. Instalar LM Studio

Antes de instalar LM Studio, verifique que su máquina local cumpla con los requisitos mínimos del sistema.

A continuación, descargue el instalador adecuado para el sistema operativo de su computadora (Windows, macOS o Linux). Luego, siga estas instrucciones para descargar los modelos a su máquina local.

Usaremos el modelo de instrucción Granite 3.3-8b para esta receta, pero no dude en usar cualquier LLM de su elección. Si está utilizando el modelo Granite, puede buscar la cadena específica de usuario/modeloibm-granite/granite-3.3-8b-instruct-GGUF en elSelect a model to load espacio en LM Studio.

A continuación, inicie el servidor local de LM Studio navegando hasta elDeveloper ícono verde en la esquina superior izquierda de LM Studio. Alterne laStatus barra en la parte superior izquierda paraRunning .

Paso 2. Instalar dependencias

Primero debemos 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 usar en esta receta. En nuestro caso, será el modelo que descargamos en LM Studio, instrucciones 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

Comencemos pidiéndole al modelo que haga un cálculo sencillo.

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

Mientras 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.

Revise los documentos 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 salida 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 simples para que el modelo las use 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 se debe proporcionar al modelo algunas herramientas para ayudarlo a responder. Usaremos la llamada model.act() para la herramienta automática e indicar al modelo que puede usar las funciones que creamos.

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

Nosotros podemos ver que el modelo pudo seleccionar la herramienta correcta en ToolCallRequest, name, usó las entradas apropiadas en arguments (los argumentos para pasar a la función) y evitó usar las herramientas irrelevantes. Finalmente, la respuesta en AssistantResponse, content, text muestra la respuesta del modelo, una respuesta exacta a la pregunta.

¿Cuántas R en fresa?

Una pregunta muy simple que deja perplejos incluso a los modelos de lenguaje más inteligentes. Casi todos los LLM con un límite de entrenamiento 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 esta pregunta específica, simplemente porque su viralidad la llevó a la mayoría de los conjuntos de datos de entrenamiento. Sin embargo, los LLM suelen fallar en tareas similares de conteo de letras.

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

Vamos 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 a herramientas es dar a su modelo la capacidad de interactuar con su entorno externo. ¡Creemos un agente que use herramientas para jugar al ajedrez!

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

Estamos proporcionando al modelo varias herramientas que lo 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 todos los posibles controles en la posición actual
  • get_move_history(): proporciona una lista de todos los movimientos jugados hasta ahora
  • get_book_moves(): proporciona una lista de todos los movimientos del libro
  • make_ai_move(): una interfaz para permitir que el modelo haga su mover

No es mucho, pero es suficiente para que el modelo juegue un juego completo de ajedrez sin alucinaciones y use 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.""")

Finalmente, configuraremos dos funciones para rastrear la coincidencia: update_board() y get_end_state().

Mediante el uso de model.act() que utilizamos para la llamada de herramientas 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 a 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 de la pieza (\"B\" es alfil, \"Q\" es reina , y así sucesivamente. Pero \"N\" es caballo, ya que \"K\" representa 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 el movimiento ambiguo de piezas, consulte la página de Wikipedia de 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 cuaderno, demostramos cómo la integración de herramientas puede mejorar la empresa de servicios públicos y las capacidades de los LLM. Ilustramos que al proporcionar un LLM con 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 manera confiable 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 precisas. 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 poderosos asistentes y agentes de IA que automaticen flujos de trabajo y procesos con IA generativa.

    Explore watsonx Orchestrate
    Soluciones de agentes de IA de IBM

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

    Explorar las soluciones de agentes de IA
    Servicios de IA de IBM Consulting

    Los servicios de IA de IBM Consulting ayudan a reinventar la forma en que las empresas trabajan con IA para la transformación.

    Explorar los servicios de inteligencia artificial
    Dé el siguiente paso

    Ya sea que elija personalizar aplicaciones y habilidades predefinidas o crear y desplegar servicios agénticos personalizados utilizando un estudio de IA, la plataforma IBM watsonx responde a sus necesidades.

    Explore watsonx Orchestrate Explore watsonx.ai