Галоўная / Артыкулы

Стварэнне агента ReAct на чыстай Python: Думка, дзеянне, абазаванне

Напісайце цыкл агента ReAct з нуля на Python: контракт Thought, Action, Observation, цыкл while, кіраваны прызначэнням PAUSE, адказ на памылкі і два спосабу выключэння працы.

Выдавец: REACTAPP.TOP8 хв чытання1785 слоў

Крыніцы: Building a ReAct AI Agent from Scratch in Pure Python

Агент, які плануе і выконвае задачы, мае просты трук: мадэль спачатку стварае нумераваныя спіс крокаў, а звычны Python праходзіць па іх. Гэта работае, пакуль рэальнае становішча не суперасходзіцца з планам. Канець шляху зменшыцца, пошук вярнее код 404, або мадэль проста неправа падумала ўжо на другі крок. Агент усё равно продовжае праходзіць па кроках з трэцьяго да дзесятаг, таму што нічога ў яго прыметах не дазволяе яму пераглядаць план.

ReAct выправляе гэта, заменяючы цэлы план на цыкл. Ён ствараецца ў чыстам Python, без жадных фрэймворкаў, а пасля розглядаюцься два спосабы, якімі ён можа зламацца, калі да яго прайду рэальныя корыстнікі.

З фіксаванага плану до цыклу зворотнай связі

Уявіце два спосабы пераходу праз незнайомы лес. Адзін пацярох запісвае «1 км на паўнечы, потым 500 м на восток» і следуе за гэтым планам абоўсюды, што значыць, ён продовжае ідзе, калі на высоце 200 м з’яўляецца скала. Іншы зупіняецца кожныя калькі крокаў і гучна размышляе: шлях на паўнечы заблокаваны скалай, таму трэба спробаваць паўзаў на захад як обхід, і дзеўаль, там ёсць безпечны шлях.

Першы пацярох — гэта агент планавання і выканання. Ён эконамічны ў відношэніі токенав і добра організаваны, але кранкаваты: калі другі крок не выйдзе, крокі 3-10 становяць нічога. Другі — гэта агент ReAct (размышленне плюс дзеянне). Ён выконвае по аднаму кроку за раз і перакантролюе свае абстрактыя перад наступным. Гэта коштуе больш токенав, але ён набагато лепшая вытрымаець, калі ўсё пайшло не так.

Коментары, які ведае другі мандроўнік, ёсць внутршній маналог, і самэ гэта ёсць механізам, які дазволяе агенту выглядаць адаптаваным, а не проста віконваючым запісаныя інструкцыі. Аднак, каб гэты маналог быў корыстным, ён павінен магчымаць запуск рэальных дзеянняў.

Інструменты, паказваные ў адным параграфе

Мадэль мовы можа выдаваць толькі тэкст. Ёй трэба «рукі», таму вы вікорыстоўваеце звычны слоўнік, які супарабатуе з іменамі і функцыямі Python, на прыклад {"get_weather": get_weather_api}. Калі выход мадэлі містыць строку get_weather, ваш код яе знаходзіць і запускае адпаведную функцыю. Інструменты залішаюцца абоўсумна тымі ж у агенце ReAct. Зменяецца толькі спосаб, якім мадэль выбірае, калі іх вікорыстаць.

Гэта ўсё тое, што ёсць трохі дзівнае ў цыям дызайне. Ёжчы мадэль выглядала так, нібы яна сама думае, яе павінны сувора обмежыць.

Контракт, які стоіць за маналогам

У зямест на тое, каб запытаць адпаведны ўтвар, вы прыкладзіце модэль да строгага фармата выходных даных і не дазволяеце ёй вядомаць з жадным інструментам, пакуль яна не будзе сялідваць гэты фармат. Гэты цыкл складаецца з трох этапаў:

  1. Мысль. Модэль аналізуе запітанне, усё, што было засборавана да гэтага часу, а таксама пакутныя непаводарэння, пасля чаго запісвае, што трэба зрабіць далей і чаму.
  2. Дзеянне. На адной з гэтых мысляў яна выбірае адні з інструментаў з спісу і запісвае точны вхідны даны для яго.
  3. Абазаванне. Гэты ўражлівы этап. Модэль должна зупніцца. Ваш код на Python перэймае кераванне, чытае інструкцію дзеяння, запускае рэальную функцыю (запыт пра паветрак, запыт да базы дадзеных і т. д.) і вяртае неапрануты рэзультат модэлі.

Кожны раз, калі мадэль праходзіць праз цыкл, яна отрымае новыя дапамогі для выважвання. Цыкл гіпотэзавання, выкарыстоўвання і пераканання павтараецца, пакуль мысля нарэшце не дасягне такога выніку, які можа прыйняць корыстнік. Корача кажучы, мадэль выважвае і прыме рашэнне, тады як звычны код выступае ў ролі суддзі, перэхопляючы кожны вызов інструмента і павертаюча інфармацыю пра тое, што на самай працоўнай стадыі адбылася.

На паперы ўсё гэта выглядае проста. Але практычны вопыт — гэта як змусіць прогнозатор тэксту зупыніцца на паўпацёку, чакаць скрыпт і пасля знову продаважваць свою логіку.

Рапісаванне цыклу вручную

Фрэймворкі такія як LangChain, CrewAI і AutoGen хаваюць усё гэта за аднім вызам .run(). Гэта зручна, пакуль агент не начынае кружыць у колах чы не стварае інструмент, якога на самай працоўнай стадыі няма. Дэбаггін «чорнай скрыванкі» ёсць крыхотны, таму важліва знаты, што ў яе ёсць.

Пачніце з сістэмнага запрошэння, якое задае правілы взаімадзейства і візначае кантракт:

system_prompt = """
You are a dynamic AI Agent. You work in a continuous
loop of
- Thought,
- Action,
- Observation.
- PAUSE

Your available tools are:
- get_weather(location: str): Returns the current weather.
- calculate(expression: str): Evaluates a math expression.

Use the following format exactly:
Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [get_weather, calculate]
Action Input: the input to the action
PAUSE
(You will then receive an Observation from the environment)
Thought: I now know the answer
Final Answer: the final answer to the original input question
"""

Лінія PAUSE віконвае главную роль, а запіт таксама вказывае точныя назвы поль (Question, Thought, Action, Action Input, Final Answer), ўсёлякі рэгулярны выраз можа ўпевненна знайсці іх у выходным тэксце свабоднага фармата. Ён прыказвае модэлю перастаць генеравацію, чым вяртае кантроль да Python, ў рэзультаце чаго інструмент можа запрацаваць, а яго выходны тэкст можа быць запісаны ў кантэкст як Observation.

Мадэлі не завжды абсалютна паўнаслухнае выконваюць інструкцыі па фармату, і вы пабачыце гэта ніжэй. Проты ўпорак ёсць толькі сваёрнік правіл. Патрэбна якаясь сістэма, якая яе выканаўвае: яна ловіць сігнал PAUSE, запускае функцыю і дазваляе працэсу продавацца даўжэй. Гэтая сістэма — гэта цыкл while, які ўтварае „серцэбіце“ агента. З кожным разам ён задае запіт модэлі, аналізуе адпаведзь, запускае неабходныя інструменты і дадае весь абмян у історыю, зупініваючыся, калі модэлі дае заканчыцкую адпаведзь або выкараны ліміт па колькісці ходоў.

import re

def react_agent(self, user_question, max_turns=5):
    # Initialize our conversational memory with the system prompt
    messages = [
        {"role": "system", "content": self.system_prompt},
        {"role": "user", "content": f"Question: {user_question}"}
    ]

    turn_count = 0

    while turn_count < max_turns:
        print(f"\n--- Turn {turn_count + 1} ---")

        # 1. Get the LLM's response (The Thought + Action)
        response = my_react_agent.call_llm(messages)
        print(response)

        # Add the LLM's generation to the memory
        messages.append({"role": "assistant", "content": response})

        # 2. Check if the agent has reached a conclusion
        if "Final Answer:" in response:
            print("\n✅ Task Complete.")
            #print(json.dumps(messages, indent=2))
            return response.split("Final Answer:")[-1].strip()

        # 3. Parse the Action and Action Input using Regex
        action_match = re.search(r"Action: (.*)", response)
        input_match = re.search(r"Action Input: (.*)", response)

        if action_match and input_match:
            action = action_match.group(1).strip()
            action_input = input_match.group(1).strip()

            # 4. The Observation Phase (Python takes control)
            print(f"⚙️ System Executing: {action}({action_input})")
            try:
                action_input = json.loads(action_input)
                observation_result = my_react_agent.execute_tool(action, action_input)
            except (TypeError, ValueError, json.JSONDecodeError) as error_messgae:
                observation_result = error_messgae
            # Format the observation and feed it back to the agent
            observation_text = f"Observation: {observation_result}"
            messages.append({"role": "user", "content": observation_text})
            print(observation_text)

        else:
            # If the LLM breaks the API contract, gently correct it
            messages.append({"role": "user", "content": "Error: Invalid format. Please provide an Action and Action Input, or a Final Answer."})

        turn_count += 1
    return "❌ Agent timed out before reaching a final answer."

Што дае вам наявна петля

Паколькі петля — гэта ваш саўны код, вы самі адлучваеце, што будзе дэйстваваць у разы непрацясы. Лог-даны з запуску за дапамою інструмента у стылі прогназу парадкавае, чаму гэта важна.

У першым разе мадэль правільна вырахоўвае, што ёй патрэбен інструмент пад назвай get_planet_mass, але запісвае даны вхіду ў одынарныя лапкі, {'planet': 'Earth'}. Гэта не ўсунены JSON, таму парсер у Python выдае стандартную паўтарку: ён чакаў назвы атрыбутаў у двойных лапках.

Звычайны скрыпт зупініўся б працэс тут. Што ж, гэтая петля не выклікае адмовы. Яна берае неапрануты тэкст адмовы і дадае яго ў розмову як наступную Observation.

У другій паўфазе мадэль чытае тую паведамлення і у своіх думках усвядомляе, што фармат вхідных дадзеных быў некарэктны і трэба дапэўніць ся правіламі JSON. Яна падае новы запіт з {"planet": "Earth"}, маршрутызатор запускае апрант, і задача завершваецца.

Заўважыце, што для такога случая не было напісана нічога спецыяльнага. Не існуе механізма павторных спроб для некорэктнага JSON, ёсць толькі звычка вяртаць памылкі як адзначэння, а рэшта выкаана самай мадэллю за дапамогою ўсвядомлення прачытанага. Саме так выглядае внутршняя мантрыя на практыцы. Статычны планавальнік бы зламаўся ў першай паўфазе і патрабаваў бы, каб чалавек яго перазапусціў. Цыкл ReAct дазволіў модэлі дыяганастыкаваць і вылечваць свою сінтаксіс пад час працы. Яна не проста следавала плану, а рэагавала на тое, што насправды выйшла.

Дзе ReAct не працуе ў практычных умовах

Код вышэй падобаецца гэтым чароўнам у першы раз, калі ён запускаецца. Перш чым вы яго аправіце, вам трэба ведаць два моменты, дзе ён можа зламацца.

Спіраля галюцынацый. Інодзе мадэль упертаям чынам пераканана, што несправны інструмент скора запрацюе. Яна вызывае get_weather, бачыць кантакт, вызывае get_weather знову, бачыць той самы кантакт, і продолжае так робіць, пакуль не заканчываюцца крэдыты вашай API. Захадам являецца параметр max_turns=5 у цыкле. Такі жорсткі механізм абаранення ёсць абавязковым, і жаднаму агенту ReAct не трэба даваць можлівасць працаваць без меры.

Занадта большы розметкі. Кожны цыкл дадае своі думкі, дзеяння і спазыранні да размовы. Калі інструмент вяртае пакет дадзеных у фармаце JSON з 10 000 лініяў, усё гэта падае ў вікно розметак. До наступнага крока мадэль тоне ў велікім коласце дадзеных, якосьць ўзглядуў пахудэўшае, і ёй можа стаць немагчымай запамятаць первісны пытанне. Ніколі не перадавайце суровыя адпаведзі API чы ўсю інфармацыю, выкаранутую з веб-сторонік, без перэробкі як спазыранні. Нават простая межа, напрыклад, першыя калькі тысяч сімвалаў плюс прыметка, што рэшта было адцячана, крашчая, чым перэплыванне мадэлі велікім коласцом дадзеных. Адначасова выкорыстоўваеце невялікую функцыю-прамежуточнік, якая спачатку падсумавае чы адцячвае рэзультаты, такім чынам заставляючы моналог застацца короткім і чыстым.

Існуе таксама меры, якую ня можа паспрацаваць жадны агент. Калі адзін агент вынужаны ўсё разам кераваць занадта большай колькасцю вялікіх інструментоў і занадта велікай колькасцю накопічанага кантэксту, яго спосаб мышлення стае заплутаным, і ён адхіляецца ад задачы. Агент ReAct — гэта хуткі тактык; але як керавальні элемент ён неэфектыўны.

Спалучэнне двух стыляў

Этыя два падходы рашаюць разныя проблемы. Падход «планаванне і выконанне» дае структуру, а падход ReAct — стойкасць. Багатыя працоўныя сістэмы ў канечнай часе вікорыстоўваюць оба: планавальнік складае загальныя крокі, а потым перадае кожную складную падзадачу своему агенту ReAct, які на месцы распрацоўвае дэталі. Коордынацыя кальколькох такіх агентоў, створаных з нуля без якіх-леба фрэймворкаў, ёсць наступным логічным крокам пасля гэтага цыклу.