Главная / Статьи / Ручно созданный цикл инструментов 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 для поиска идентификатора акции, получения самой последней цены закрытия за определённый день, а также извлечения данных о рыночной капитализации и коэффициенте P/E из информации об акции:

    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 или собственной базы данных, — чтобы ассистенты могли работать с динамическими системами, а не с зафиксированными данными.