Головна / Статті / Ручно створений цикл інструментів LangChain для порівняння показників активних запасів

Ручно створений цикл інструментів LangChain для порівняння показників активних запасів

Створіть поступово невеликого асистента для порівняння акцій LangChain та ознайомтесь із циклом створення, прив’язки, виклику та виконання, який дозволяє ШІ отримувати актуальні дані замість того, щоб вгадувати.

1667 слів

Модель мови знає лише те, що було в її навчальних даних, тому запит у неї про сьогоднішню ціну акцій призводить до впевнених припущень, а не до фактів. Це серйозна проблема для будь-яких фінансових питань. Інструмент, який використовує виклики моделі, вирішує цю проблему, дозволяючи моделі запитати ваш код про отримання актуальних даних, а потім проаналізувати результат. У цьому посібнику створюється компактний асистент, який порівнює дві акції за ціною та коефіцієнтом P/E, при цьому чітко показуючи кожен етап робочого процесу виклику інструментів LangChain: створення, прив’язка, виклик, виконання.

Чому модель без інструментів робить припущення

Дайте співробітнику сканер штрих-кодів, і він буде перевіряти інформацію замість того, щоб здогадуватися. У LangChain сканер є інструментом: звичайною функцією, яку модель може запитати про виконання, наприклад для пошуку ціни, запиту до бази даних чи виклику API. Механізм виклику інструментів дозволяє моделі вирішувати, коли скористатися сканером, який елемент сканувати та як використовувати отримані дані.

Чотирьохетапний цикл

Виклик інструментів (часто називаний викликом функції) завжди слідує одному й тому ж циклу:

  1. Створіть інструмент. Напишіть функцію на Python із описовою назвою, параметрами з типизацією та документацією, яка пояснює її функціонал.
  2. Приєднайте його до моделі. Викличте .bind_tools() у чат-моделі, щоб вона знала, які інструменти існують та які аргументи вони приймають.
  • Викликати модель. Надіслати запит користувача. Якщо потрібні зовнішні дані, модель повертає структурований запит на кшталт "викликати get_stock_price з параметром ticker='MSFT'" замість остаточної відповіді.
  • Виконувати та повертати результати. Ваш код запускає звернений функцію та передає отриманий результат назад до моделі, щоб вона могла сформувати відповідь.
  • Важлива особливість полягає у тому, що сам модель, а не ваш код, вирішує, коли потрібен інструмент. На запитання на кшталт „Скільки дорівнює 2+2?“ відповідається безпосередньо, тоді як запит „Порівняйте MSFT та AAPL“ може призвести до двох викликів інструментів у одній відповіді. Модель лише запитує про ці виклики; їх виконання залишається вашою роботою. Щодо того, як фреймворки автоматизують цей цикл, дивіться що саме автоматизує LangChain після створення циклу агента.

    Крок 1: Встановіть залежності під час першого запуску

    Скрипт спочатку перевіряє наявність необхідних пакетів та встановлює ті, яких бракує, щоб його можна було запустити без окремого кроку pip install. Він перетворює назву кожного пакета на його ім’я для імпорту, замінюючи дефіси на підкрески (langchain-groq стає langchain_groq), а якщо це не допомагає, звертається до pip через поточний інтерпретатор:

    import sys
    import subprocess
    
    required_packages = ["langchain-groq", "langchain-core", "yfinance"]
    
    for package in required_packages:
        try:
            __import__(package.replace("-", "_"))
        except ImportError:
            print(f"📦 Package '{package}' not found. Installing now...")
            subprocess.check_call([sys.executable, "-m", "pip", "install", package])
    

    Це підходить для демонстрацій, але у справжніх проектах залежності слід вказувати у файлі requirements.txt або pyproject.toml; встановлення під час виконання робить процес будування непередбачуваним.

    Крок 2: Надати ключ API

    Модель працює на Groq, тому клієнту Groq потрібен ключ API у середовищі. У демонстрації він присвоюється як замінник безпосередньо у коді:

    import os
    os.environ["GROQ_API_KEY"] = "Your-API"
    

    Справжні ключі мають знаходитися у файлі .env, який не включається до контролю версій, або у менеджері секретів, ніколи не у коді, який зберігається у версіях.

    Крок 3: Визначення інструменту для роботи з даними акцій

    Інструмент використовує yfinance для пошуку ідентифікатора акції, отримання останньої ціни закриття за певний день з історичних даних, а також отримання інформації про ринкову капіталізацію та співвідношення ціни до прибутку з опису акції:

    from langchain_core.tools import tool
    import yfinance as yf
    
    @tool
    def get_stock_price(ticker: str) -> str:
        """Fetches the current stock price and key statistics for a given ticker symbol."""
        try:
            stock = yf.Ticker(ticker)
            todays_data = stock.history(period='1d')
            if todays_data.empty:
                return f"Could not find data for ticker {ticker}."
    
            price = todays_data['Close'].iloc[-1]
            info = stock.info
            market_cap = info.get('marketCap', 'N/A')
            pe_ratio = info.get('trailingPE', 'N/A')
    
            return f"{ticker} Current Price: ${price:.2f}, Market Cap: {market_cap}, P/E Ratio: {pe_ratio}"
        except Exception as e:
            return f"Error fetching data for {ticker}: {str(e)}"
    

    Три аспекти роблять цю функцію хорошим інструментом:

    • Декоратор @tool перетворює функцію на інструмент LangChain та генерує схему вхідних даних на основі її типових підказок.
    • Документація — це не просто декорація. Модель читає її разом із назвою, щоб визначити, коли слід застосувати інструмент; тому в ній має бути чітко описано, що повертає інструмент.
    • Помилки повертаються у вигляді описових рядків, а не генеруються як винятки. Невідомий ідентифікатор акції чи невдала заявка призводять до повідомлення, яке модель може проаналізувати („дані не знайдені“), замість винятку, який призупиняє всю роботу.

    Зверніть увагу, що yfinance — це неофіційний обгорток для Yahoo Finance: котирування можуть бути затриманими, а деякі поля відсутніми, тому використовуються значення 'N/A' як замінники.

    Крок 4: Створення моделі та прив’язка інструментів

    Далі створюється модель чату та до неї додається список інструментів:

    from langchain_groq import ChatGroq
    
    llm = ChatGroq(
        model="openai/gpt-oss-120b",
        temperature=0
    )
    
    tools = [get_stock_price]
    llm_with_tools = llm.bind_tools(tools)
    

    bind_tools() з кожним запитом надсилає моделі назви інструментів, їхні описи та схеми аргументів, щоб вона знала, про що може запитувати. Зауважте, що неприв’язаний llm також зберігається; його буде використано пізніше для остаточного узагальнення. Назва моделі відображає те, що пропонував Groq на момент написання коду, тому перед запуском перевірте поточний список моделей постачальника.

    Встановлення temperature=0 забезпечує більш консистентний та фокусований вихід, хоча це зменшує випадковість, але не гарантує ідентичних відповідей.

    Крок 5: Створення ланцюга запитів

    Шаблон запиту містить інструкції для системи та вставляє запит користувача у повідомлення для людини. Оператор „|“ потім об’єднує цей запит та модель, яка розуміє інструменти, у один виконуваний формат, схожий на конвеєр Unix:

    from langchain_core.prompts import ChatPromptTemplate
    from IPython.display import display, Markdown
    
    prompt = ChatPromptTemplate.from_messages([
        ("system", "You are an expert financial analyst. Use the tools provided to pull real-time data before comparing or concluding."),
        ("human", "{input}")
    ])
    
    chain = prompt | llm_with_tools
    

    Саме системне повідомлення виконує основну роботу: воно просить модель отримати дані за допомогою інструментів перед тим, як проводити будь-яке порівняння. Без нього модель може відповідати на основі застарілих даних навчання.

    Крок 6: Запустити цикл та обробити кілька викликів інструментів

    Основний блок поєднує все разом. Він ініціює послідовність дій, перевіряє, чи містить відповідь елементи tool_calls, виконує кожен запит на пошук, збирає результати та нарешті просить звичайну модель написати порівняння на основі зібраних даних. Якщо інструменти не були заявлені, виводиться пряма відповідь моделі:

    if __name__ == "__main__":
        query = "Compare the current stock price and P/E ratio of Microsoft (MSFT) AND Apple (AAPL). Which one looks cheaper based on P/E?"
        print(f"🚀 Invoking Financial Pipeline with query: '{query}'\n")
    
        # 1. Ask the model what tools it wants to use
        ai_message = chain.invoke({"input": query})
    
        # 2. Check if the model requested tool use
        if ai_message.tool_calls:
            print(f"🛠️ Model requesting {len(ai_message.tool_calls)} real-time tool lookups...\n")
            tool_outputs = []
    
            # 3. Execute ALL generated tool calls
            for tool_call in ai_message.tool_calls:
                if tool_call["name"] == "get_stock_price":
                    ticker_symbol = tool_call["args"]["ticker"]
                    print(f"   -> Executing tool lookup for: {ticker_symbol}")
    
                    result = get_stock_price.invoke(tool_call["args"])
                    print(f"      [Tool Output] {result}")
                    tool_outputs.append(result)
    
            # 4. Supply the full collective data back to the LLM
            summary_prompt = f"""
            User Query: {query}
            Real-time Data Harvested: {'; '.join(tool_outputs)}
    
            Synthesize a final response evaluating which asset looks cheaper.
            """
            final_answer = llm.invoke(summary_prompt)
    
            print("\n--- Final Analysis Output ---")
            display(Markdown(final_answer.content))
        else:
            print("\n--- Final Analysis Output ---")
            print(ai_message.content)
    

    Щодо запитання, пов’язаного як з Microsoft, так і з Apple, модель зазвичай повертає два виклики інструментів у одній відповіді — по одному на кожен тикер. Люп виконує їх усі перед тим, як переходити далі, тому на останньому кроці одночасно отримуються обидві серії чисел. Модель запитує виклики паралельно, але цей код виконує їх по черзі; для повільних API їх можна виконувати одночасно.

    Є два важливі моменти, які варто знати, коли цей підхід почне працювати. По-перше, перевірка назви всередині циклу дозволяє розподіляти завдання між кількома інструментами; словник, який пов’язує назви інструментів з їх об’єктами, працює ефективніше, ніж ланцюг операторів if. По-друге, ця версія передає результати шляхом створення нового текстового запиту. Більш типовий підхід LangChain полягає у додаванні кожного результату як об’єкта ToolMessage, який містить відповідний tool_call_id, до розмови, та у повторному запуску моделі, прив’язаної до інструменту — це зберігає повний обмін даними та дозволяє моделі запитати про подальші виклики, якщо перших результатів недостатньо.

    Розширення асистента відбувається поступово: інструменти на кшталт get_financial_news чи calculate_valuation підключаються через той самий виклик bind_tools(), а модель обирає інструменти за їхніми описами.

    Основні висновки

    • Інструмент — це функція з чітко визначеним ім’ям, підказками щодо типу та документацією; декоратор @tool виконує решту завдань.
    • .bind_tools() під’єднує ваші функції до моделі, описуючи їх у кожному запиті.
    • Модель просить лише про виклики інструментів. Ваш код їх виконує та повертає результати, і цей контроль є корисним, а не обмеженням.
    • Повертайте помилки від інструментів у вигляді читабельних рядків, щоб одна невдала спроба не призводила до завершення роботи.
    • Почніть з одного інструменту та одного циклу, потім додавайте інструменти, передачу результатів у вигляді повідомлень та конкурентність за потреби.

    Той самий патерн застосовується там, де моделі потрібна актуальна інформація — від погоди та запасів до записів CRM чи вашої власної бази даних, щоб асистенти могли працювати з живими системами, а не з застиглим знімком.