استخدم LM Studio لإنشاء استدعاء تلقائي للأدوات باستخدام Granite

المؤلفون

Caleb Palappillil

Open-source AI Software Developer Intern

Erika Russi

Data Scientist

IBM

في هذا البرنامج التعليمي التفصيلي، ستستخدم LM Studio مع نموذج التعليمات مفتوح المصدر IBM® Granite® 3.3-8b instruct على جهازك المحلي. في البداية، ستختبر النموذج المحلي كما هو ثم تكتب وظائف Python التي يمكن للنموذج استخدامها لاستدعاء الأداة تلقائيًا. وأخيرًا، ستطور المزيد من الوظائف للعب لعبة شطرنج مع وكيل الذكاء الاصطناعي. يمكن أيضًا العثور على هذا البرنامج التعليمي في الدليل العملي السهل لنماذج Granite على GitHub التابع لمجتمع IBM Granite في شكل Jupyter Notebook.

LM Studio

LM Studio هو تطبيق للعمل مع [النماذج اللغوية الكبيرة] (https://www.ibm.com/qa-ar/think/topics/large-language-models) (LLMs) المحلية . يمكنك استخدام أي نموذج مفتوح المصدر مع LM Studio، مثل نماذج Mistral AI، أو Google’s Gemma، أو سلسلة Meta’s Llama أو DeepSeek’s R1 .

باستخدام LM Studio، يمكن للمستخدمين المبتدئين والمتقدمين تشغيل النماذج اللغوية الكبيرة باستخدام وحدة المعالجة المركزية في أجهزة الكمبيوتر الخاصة بهم أو حتى وحدة معالجة الرسومات. يوفر LM Studio واجهة تشبه المحادثة للتفاعل مع النماذج اللغوية الكبيرة المحلية بشكل مشابه لمحادثة ChatGPT .

باستخدام نموذج الذكاء الاصطناعي المحلي، يمكنك الضبط الدقيق والاستدلال وفعل المزيد من دون الحاجة إلى القلق بشأن استدعاءات واجهة برمجة التطبيقات الخارجية (مثل واجهات برمجة تطبيقات OpenAI أو IBM watsonx.ai، أو اختصارًا API) أو استخدام الرموز المميزة. يتيح LM Studio أيضًا للمستخدمين "التحدث مع المستندات" محليًا وبشكل خاص. يمكن للمستخدم إرفاق مستند بجلسة محادثة وطرح أسئلة حول المستند. وفي الحالات التي يكون فيها المستند طويلاً، سيُعد LM Studio نظام [توليد معزز بالاسترجاع] (https://www.ibm.com/qa-ar/think/topics/retrieval-augmented-generation) (RAG) للاستعلام.

استدعاء الأدوات

على الرغم من أن النماذج اللغوية الكبيرة تتفوق في فهم نصوص شبيهة بنصوص البشر وتوليدها، فإنها غالبًا ما تواجه قيودًا عندما تتطلب المهام حسابات دقيقة أو الوصول إلى بيانات خارجية في الوقت الفعلي أو تنفيذ إجراءات محددة جيدًا. من خلال تنفيذ استدعاء الأدوات، نزود النموذج اللغوي الكبير بمجموعة من "الأدوات" — وظائف خارجية — يمكنها اختيار استدعائها لتوسيع قدراتها بشكل كبير. سيوضح هذا البرنامج التعليمي كيفية تعريف هذه الأدوات ودمجها، ما يمكّن النموذج اللغوي الكبير من أداء مجموعة واسعة من المهام بموثوقية أكبر.

الخطوات

الخطوة 1. تثبيت LM Studio

قبل تثبيت LM Studio، تحقق من أن جهازك المحلي يفي بالحد الأدنى من متطلبات النظام.

بعد ذلك، نزل أداة التثبيت المناسبة لنظام تشغيل جهاز الكمبيوتر الخاص بك (Windows أو macOS أو ®Linux). ثم اتبع هذه الإرشادات لتنزيل النماذج على جهازك المحلي.

سنستخدم نموذج Granite 3.3-8b instruct لهذه الوصفة، ولكن يمكنك استخدام أي نموذج لغوي كبير من اختيارك. وإذا كنت تستخدم نموذج Granite، فيمكنك البحث عن سلسلة المستخدم/النموذج المحددةibm-granite/granite-3.3-8b-instruct-GGUF فيSelect a model to load في LM Studio.

بعد ذلك، ابدأ تشغيل الخادم المحلي LM Studio بالانتقال إلى الأيقونة الخضراءDeveloper في الجزء العلوي الأيسر من LM Studio. بدلStatus الشريط في الجزء العلوي الأيسر إلىRunning .

الخطوة 2. تثبيت التبعيات

نحتاج أولاً إلى تثبيت المكتبات الضرورية، بما في ذلك مجموعة تطوير برمجيات LM Studio ومكتبة الشطرنج.

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

الخطوة 3. تحميل النموذج

سنحدد النموذج الذي نريد استخدامه في هذه الوصفة بعد ذلك. في حالتنا، سيكون النموذج الذي نزّلناه في 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!"))

الخطوة 4. إجراء عملية حسابية من دون أدوات

لنبدأ بطلب من النموذج إجراء عملية حسابية بسيطة.

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

على الرغم من أن النموذج قد يكون قادرًا على تقديم تقدير تقريبي دقيق، فإنه لن يعطي الإجابة الدقيقة لأنه لا يستطيع حساب الناتج بنفسه.

الخطوة 5. إنشاء أدوات حساب باستخدام وظائف Python

لحل هذه المشكلة، سنزود النموذج بأدوات. الأدوات هي وظائف 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 في كلمة strawberry؟

سؤال بسيط للغاية يحير حتى أذكى النماذج اللغوية. تقريبًا كل نموذج لغوي كبير دُرب قبل عام 2024 يجيب بأن هناك حرفين R فقط في كلمة "strawberry". بالإضافة إلى ذلك، قد تهلوس حتى بمواضع غير صحيحة للحروف.

في الوقت الحاضر، تنجح النماذج اللغوية الكبيرة في الإجابة على هذا السؤال المحدد بشكل صحيح، وذلك ببساطة لأن انتشاره الواسع جعله موجودًا في معظم مجموعات بيانات التدريب. ومع ذلك، لا تزال النماذج اللغوية الكبيرة تفشل كثيرًا في مهام عد الحروف المماثلة.

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()، تمكن النموذج من حساب عدد حروف b في كلمة "blackberry" بدقة.

الخطوة 6. تنفيذ استدعاء الأداة التلقائي لوكيل

أحد أفضل حالات استخدام سير عمل استدعاء الأدوات التلقائي هذا هو منح النموذج الخاص بك القدرة على التفاعل مع بيئته الخارجية. لنبنِ وكيل يستخدم الأدوات للعب الشطرنج!

على الرغم من أن النماذج اللغوية يمكن أن تتمتع بمعرفة قوية لمفاهيم الشطرنج، فإنها ليست مصممة بطبيعتها لفهم رقعة الشطرنج. إذا حاولت لعب لعبة شطرنج مع روبوت محادثة عبر الإنترنت، فغالبًا ما سيخرج عن المسار الصحيح بعد عدة دورات، وسينفذ حركات غير قانونية أو غير منطقية.

نوفر للنموذج عدة أدوات تساعده على فهم اللوحة والتفاعل معها.

  • legal_moves(): توفر قائمة بكل الحركات القانونية في الوضع الحالي
  • possible_captures(): توفر قائمة بكل عمليات الأسر الممكنة في الوضع الحالي
  • possible_checks(): توفر قائمة بجميع عمليات الفحص الممكنة في الوضع الحالي
  • get_move_history(): توفر قائمة بكل الحركات التي لُعبت حتى الآن
  • get_book_moves(): توفر قائمة بكل الحركات الموجودة في الكتاب
  • make_ai_move(): واجهة تسمح للنموذج بإدخال حركته

ليس كثيرًا، ولكنه كافٍ للنموذج للعب مباراة شطرنج كاملة من دون هلوسة، والتفكير بذكاء لاتخاذ قراراته.

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)

بعد ذلك، سنُعد مباراة الشطرنج مع وكيل الذكاء الاصطناعي. باستخدام استدعاء 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() الذي استخدمناه سابقًا لاستدعاء الأداة، سنزود الوكيل بالإرشادات (chat) التي حددناها، والأدوات المتاحة لاستخدامه، وننشئ 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

ملخص

في دفتر الملاحظات هذا، أوضحنا كيف يمكن أن يؤدي دمج الأدوات إلى تعزيز فائدة النماذج اللغوية الكبيرة وقدرات وكلاؤها. وأوضحنا أنه من خلال تزويد النموذج اللغوي الكبير بإمكانية الوصول إلى وظائف خارجية محددة سابقًا، يمكنه تجاوز قدراته الأساسية في معالجة اللغة لأداء مهام مثل الحسابات الدقيقة أو التفاعل مع الأنظمة الخارجية. لا يمكنه فعل ذلك بشكل موثوق بمفرده. الاستنتاج الرئيسي هو أن استخدام الأدوات يمكّن النماذج اللغوية الكبيرة من تفويض مشكلات فرعية محددة إلى إجراءات روتينية متخصصة، ما يسمح لها بتأسيس ردودها على بيانات واقعية أو عمليات دقيقة. لا يؤدي هذا النهج إلى تحسين الدقة فحسب، بل يمكّن النماذج اللغوية الكبيرة أيضًا من الانخراط في سير عمل أكثر تعقيدًا وتفاعلية، ما يحولها بشكل فعال إلى مساعدين أكثر تنوعًا وقوة.

حلول ذات صلة
وكلاء الذكاء الاصطناعي للأعمال

يمكنك إنشاء مساعدين ووكلاء ذكاء اصطناعي ووكلاء أقوياء يعملون على أتمتة مهام سير العمل والعمليات باستخدام الذكاء الاصطناعي التوليدي ونشرها وإدارتها.

    استكشف watsonx Orchestrate
    حلول وكلاء الذكاء الاصطناعي من IBM

    يمكنك بناء مستقبل عملك باستخدام حلول الذكاء الاصطناعي الجديرة بالثقة.

    استكشف حلول وكلاء الذكاء الاصطناعي
    خدمات الذكاء الاصطناعي لدى IBM Consulting

    تساعد خدمات IBM Consulting AI في إعادة تصور طريقة عمل الشركات باستخدام حلول الذكاء الاصطناعي من أجل النهوض بأعمالها.

    استكشف خدمات الذكاء الاصطناعي
    اتخِذ الخطوة التالية

    سواء اخترت تخصيص التطبيقات والمهارات المُعدّة مسبقًا أو إنشاء خدمات مخصصة مستندة إلى وكلاء ونشرها باستخدام استوديو الذكاء الاصطناعي، فإن منصة IBM watsonx تُلبي احتياجاتك.

    استكشف watsonx Orchestrate استكشف watsonx.ai