Головна / Статті / Реакція з нуля: думка, дія, пауза, спостереження без жодної структури

Реакція з нуля: думка, дія, пауза, спостереження без жодної структури

Створіть власноруч мініатюрний цикл Groq ReAct — спочатку ручні спостереження, потім виклики інструментів на основі регулярних виразів — щоб зрозуміти, чому сучасні фреймворки агентів мають саме таку структуру.

1373 слів

Вступ

Запитайте модель мови простою мовою про поточну ціну акцій Nvidia та про кількість акцій, які можна купити за 100 000 доларів. Без спеціальних інструментів вона вигадує застарілі дані з матеріалів навчання, а потім робить правильні обчислення з неправильними вхідними даними. У неї немає вбудованої функції, яка б означала «Я не знаю — потрібно знайти інформацію», оскільки пошук інформації — це не один процес обробки даних.

У міру покращення якості відповідей моделі виявилися корисними для одноразового міркування. Однак саме планування ще не забезпечує автономії. Питання, які визначають агентів, полягають у наступному: що знаходиться поза межами знань моделі, яка дія дозволяє їх отримати, як досягти результату, як скоригувати план та коли припинити. Ланцюг „рішення → дія → перевірка → перепланування“ є циклом, а не прямою лінією. Саме цей цикл є основою сучасних штучних інтелектуальних агентів. Підхід ReAct (Міркування + Дія) встановив цю модель. Початкові демонстрації не були додатками LangChain — це був ШІ, структуроване запитання та людина (або згодом скрипт), які виконували дії та повертали результати назад.

Представляємо ReAct: Міркування + Дія

Приклад роботи, крок за кроком:

from groq import Groq
import re
from dotenv import load_dotenv

_ = load_dotenv()
client = Groq()
message = client.chat.completions.create(
    model="openai/gpt-oss-120b",  # free, fast open-weight model hosted on Groq
    max_tokens=1000,
    messages=[
        {"role": "user", "content": "Hello, GPT!"}
    ],
)
print(message.choices[0].message.content) # Check the client
class Agent:
    def __init__(self, system=""):
        self.system = system
        self.messages = []
        if self.system:
            self.messages.append({"role": "system", "content": system})

    def __call__(self, message):
        self.messages.append({"role": "user", "content": message})
        result = self.execute()
        self.messages.append({"role": "assistant", "content": result})
        return result

    def execute(self):
        response = client.chat.completions.create(
            model="openai/gpt-oss-120b",
            max_tokens=1000,
            messages=self.messages,
        ).choices[0].message.content
        return response
prompt = """
You run in a loop of Thought, Action, PAUSE, Observation.
At the end of the loop you output an Answer
Use Thought to describe your thoughts about the question you have been asked.
Use Action to run one of the actions available to you - then return PAUSE.
Observation will be the result of running those actions.

Your available actions are:

calculate:
e.g. calculate: 4 * 7 / 3
Runs a calculation and returns the number - uses Python so be sure to use floating point syntax if necessary

fish_weight:
e.g. fish_weight: Shark
returns weight of a fish when given the breed

Example session:

Question: How much does a shark weigh?
Thought: I should look the fish weight using fish_weight
Action: fish_weight: Shark
PAUSE

You will be called again with this:

Observation: A Great white shark weights 41000 lbs

You then output:

Answer: A Great white shark weights 41000 lbs
""".strip()
def calculate(expression):
    return eval(expression)

def fish_weight(name):
    if "Shark" in name:
        return("Great white shark weighs 41000 lbs")
    elif "Puffer" in name:
        return("A puffer fish weighs 20 lbs")
    else:
        return("A fish can weight upto 47000 lbs")

known_actions = {
    "calculate": calculate,
    "fish_weight": fish_weight
}
abot = Agent(prompt)
result = abot("How much does a Shark weigh?")
print(result)
result = fish_weight("Shark")
result
next_prompt = "Observation: {}".format(result)
abot(next_prompt)
abot.messages # View the list of messages containing the conversation
# Present a compund query
abot = Agent(prompt)
# New query
question = """I have 2 fishes, a Shark and a puffer fish. \
What is their combined weight"""
abot(question)
next_prompt = "Observation: {}".format(fish_weight("Shark"))
print(next_prompt)
abot(next_prompt)
next_prompt = "Observation: {}".format(fish_weight("Puffer fish"))
print(next_prompt)
abot(next_prompt)
next_prompt = "Observation: {}".format(eval("41000 + 20"))
print(next_prompt)
abot(next_prompt)
## Add loop (automate the reasoning + act process)
action_re = re.compile('^Action: (\w+): (.*)
) # python regular expression to selection action
def query(question, max_turns=5):
    i = 0
    bot = Agent(prompt)
    next_prompt = question
    while i < max_turns:
        i += 1
        result = bot(next_prompt)
        print(result)
        actions = [
            action_re.match(a)
            for a in result.split('\n')
            if action_re.match(a)
        ]
        if actions:
            # There is an action to run
            action, action_input = actions[0].groups()
            if action not in known_actions:
                raise Exception("Unknown action: {}: {}".format(action, action_input))
            print(" -- running {} {}".format(action, action_input))
            observation = known_actions[action](action_input)
            print("Observation:", observation)
            next_prompt = "Observation: {}".format(observation)
        else:
            return
question = """I have 2 fishes, a Shark and a puffer fish. \
What is their combined weight"""
query(question)
  1. Імпортуйте клієнт Groq, модуль re для зіставлення шаблонів та функцію load_dotenv; завантажте параметри середовища, щоб ключ API не був закодований безпосередньо у коді.
  2. Створіть клієнт Groq, який читає значення GROQ_API_KEY з середовища.
  3. Надішліть просте повідомлення „Hello“ до налаштованої моделі для підтвердження з’єднання.
  4. Сконструюйте об’єкт Agent, який зберігає системні вказівки та історію повідомлень: при кожному запиті додається наступне повідомлення від користувача, надсилається повна історія, додається відповідь моделі та повертається текст.
  • Напишіть системний запит, який забезпечує послідовність „Мислення → Дія → ПАУЗА → Спостереження“.
  • Зареєструйте інструменти, такі як calculate() для арифметичних обчислень та fish_weight() для невеликої таблиці пошуку в known_actions.
  • Створіть екземпляр агента з використанням системного запиту ReAct.
  • Запитайте „Скільки важить акула?“ Очікуйте пропозицію у вигляді Action: fish_weight: Shark, а не просто числа.
  • Вручну виконайте fish_weight("Shark") та збережіть результат (спочатку у ручному режимі).
  • Надайте назад значення у форматі Observation: …, щоб агент міг дати відповідь.
  • Перегляньте abot.messages, щоб побачити повний текст, який бачить модель.
  • Створіть нового агента з комплексним запитанням (вага акули та фугу). 13–15. Вручну введіть кожне спостереження, включаючи сумарну суму за потреби.
  • Дозвольте агенту синтезувати кінцеву відповідь, як тільки з’являться спостереження.
  • Додайте регулярний вираз, який автоматично виявляє Action: name: input.
  • Обгорніть цикл у функцію query(): створіть нового агента, надішліть запитання, виведіть результат, розпарсьте дію, перевірьте її проти списку known_actions, виконайте дію, спостерігайте та повторюйте процес до отримання кінцевої відповіді або до досягнення ліміту кроків.
  • Запустіть query() на складному запитанні та спостерігайте за послідовністю «Мислення → Дія → Спостереження → Відповідь» без необхідності виконання ручних кроків.
  • Висновок

    Агенти починають свою роботу з циклу, а не з певної фреймворк-структури. Моделі, навчені методом ланцюга мислення, вчилися міркувати вголос; підхід ReAct структурує процес мислення–дія–спостереження так, як це роблять люди. Популярні фреймворки для агентів індустріалізують цю ідею. Створення циклу вручну назавжди змінює сприйняття фреймворків. Оригінальна стаття доступна за посиланням https://arxiv.org/abs/2210.03629.

    Промисловий урок полягає у тому, що фреймворки приховують цикл за елементами типу runners та tool nodes, але контракт залишається незмінним: модель мусить генерувати дію, яку можна проаналізувати, середовище виконання мусить виконувати лише дозволені інструменти, а спостереження мусять знову потрапляти до транскрипту як повноцінні повідомлення. Якщо проігнорувати будь-який з цих елементів, у результаті вийде чат-бот із побічними ефектами, а не агент.

    Промисловий урок полягає у тому, що фреймворки приховують цикл за елементами типу runners та tool nodes, але контракт залишається незмінним: модель мусить генерувати дію, яку можна проаналізувати, середовище виконання мусить виконувати лише дозволені інструменти, а спостереження мусять знову потрапляти до транскрипту як повноцінні повідомлення. Якщо проігнорувати будь-який з цих елементів, у результаті вийде чат-бот із побічними ефектами, а не агент.

    Промисловий урок полягає у тому, що фреймворки приховують цикл за елементами типу runners та tool nodes, але контракт залишається незмінним: модель мусить генерувати дію, яку можна проаналізувати, середовище виконання мусить виконувати лише дозволені інструменти, а спостереження мусять знову потрапляти до транскрипту як повноцінні повідомлення. Якщо проігнорувати будь-який з цих елементів, у вас вийде чат-бот із побічними ефектами, а не агент.

    Промисловий урок полягає у тому, що фреймворки приховують цикл за елементами типу runners та tool nodes, але контракт залишається незмінним: модель мусить генерувати дію, яку можна проаналізувати, середовище виконання мусить виконувати лише дозволені інструменти, а спостереження мусять знову потрапляти до транскрипту як повноцінні повідомлення. Якщо проігнорувати будь-який з цих елементів, у вас вийде чат-бот із побічними ефектами, а не агент.