Blue Flower

Планирование

Паттерн ReAct отлично подходит для исследования проблемы и поиска решений на ходу. Однако он может быть несколько неэффективным для задач, где шаги предсказуемы. Это как человек, спрашивающий дорогу по одному повороту за раз, вместо того, чтобы сначала посмотреть на всю карту. Вот тут-то и пригодится архитектура планирования .

 

Эта модель вносит важный элемент предвидения.

 

Вместо того чтобы реагировать шаг за шагом, специалист по планированию сначала составляет полный «план действий», прежде чем предпринимать какие-либо шаги.

 

В системах искусственного интеллекта планирование — это ваш главный инструмент для любого структурированного многоэтапного процесса. Подумайте о конвейерах обработки данных, генерации отчетов или любом рабочем процессе, где последовательность операций известна заранее. Это обеспечивает предсказуемость и эффективность, упрощая отслеживание и отладку поведения агента.

Давайте разберемся, как протекает этот процесс.

 

 

Подход к планированию (разработан)

Фарид Хан

)

Цель получения задания: Агенту дается сложное задание.

Планирование: Специальный компонент «Планировщик» анализирует цель и генерирует упорядоченный список подзадач, необходимых для ее достижения. Например: ["Find fact A", "Find fact B", "Calculate C using A and B"].

Выполнение: Компонент «Исполнитель» принимает план и последовательно выполняет каждую подзадачу, используя необходимые инструменты.

Синтез: После завершения всех этапов плана заключительный этап объединяет результаты выполненных шагов в целостный итоговый ответ.

Давайте начнём его строить.

 

Мы создадим три основных компонента: один planner_nodeдля разработки стратегии, один executor_nodeдля ее реализации и один synthesizer_nodeдля составления итогового отчета.

 

Для начала нам нужен выделенный модуль planner_node. Ключевым моментом здесь является очень четкая подсказка, которая указывает модулю LLM, что его задача — создать список простых, выполнимых шагов.

 

class Plan ( BaseModel ): 

    """План вызовов инструментов для выполнения в ответ на запрос пользователя."""

     steps: List [ str ] = Field(description= "Список вызовов инструментов, которые при выполнении ответят на запрос." ) 

 

class PlanningState ( TypedDict ): 

    user_request: str

     plan: Optional [ List [ str ]] 

    intermediate_steps: List [ str ] # Будет хранить выходные данные инструментов

     final_answer: Optional [ str ] 

def planner_node ( state: PlanningState ): 

    """Генерирует план действий для ответа на запрос пользователя."""

     console. print ( "--- ПЛАНИРОВЩИК: Разложение задачи... ---" ) 

    planner_llm = llm.with_structured_output(Plan) 

    

    prompt = f"""Вы — опытный планировщик. Ваша задача — создать пошаговый план для ответа на запрос пользователя. 

        Каждый шаг в плане должен представлять собой отдельный вызов инструмента `web_search`. 

        **Запрос пользователя:** 

        {state[ 'user_request' ]}

     """

     plan_result = planner_llm.invoke(prompt) 

    console.print ( f " --- ПЛАНИРОВЩИК: Сгенерированный план: {plan_result.steps} ---" ) 

    return { "plan" : plan_result.steps}

Далее, executor_node. Это простой обработчик, который просто выполняет следующий шаг из плана, запускает инструмент и добавляет результат в наше состояние.

 

def executor_node ( state: PlanningState ): 

    """Выполняет следующий шаг в плане."""

     console. print ( "--- EXECUTOR: Выполняется следующий шаг... ---" ) 

    next_step = state[ "plan" ][ 0 ] 

    

    # В реальном приложении вы бы анализировали имя инструмента и аргументы. Здесь мы предполагаем 'web_search'.

     query = next_step.replace( "web_search('" , "" ).replace( "')" , "" ) 

 

result = search_tool.invoke({ "query" : query}) 

    

    return { 

        "plan" : state[ "plan" ][ 1 :], # Извлекаем выполненный шаг 

        "intermediate_steps" : state[ "intermediate_steps" ] + [result] 

    }

Теперь нам просто нужно соединить их в граф. Маршрутизатор проверит, остались ли в плане какие-либо шаги. Если да, он вернется к исполнителю. В противном случае он перейдет к заключительному шагу synthesizer_node(который мы можем повторно использовать из предыдущего шаблона) для генерации ответа.

 

def planning_router ( state: PlanningState ): 

    """Маршрутизирует маршрут к исполнителю или синтезатору на основе плана.""" 

    if not state[ "plan" ]: 

        console. print ( "--- МАРШРУТИЗАТОР: Планирование завершено. Переходим к синтезатору. ---" ) 

        return "synthesize" 

    else : 

        console. print ( "--- МАРШРУТИЗАТОР: План содержит еще шаги. Продолжение выполнения. ---" ) 

        return "execute"

        

 

 planning_graph_builder = StateGraph(PlanningState) 

planning_graph_builder.add_node( "plan" , planner_node) 

planning_graph_builder.add_node( "execute" , executor_node) 

 

planning_graph_builder.add_node( "synthesize" , synthesizer_node) 

planning_graph_builder.set_entry_point( "plan" ) 

planning_graph_builder.add_conditional_edges( "plan" , planning_router) 

planning_graph_builder.add_conditional_edges( "execute" , planning_router) 

 

planning_graph_builder.add_edge( "synthesize" , END) 

 

 

 

 

 

 Планирование (создано)

Фарид Хан

)

Чтобы действительно увидеть разницу, давайте дадим нашему агенту задачу, для решения которой полезно предвидение. Агент ReAct может решить эту задачу, но его пошаговый процесс менее прямой.

 

plan_centric_query = """ 

Найдите население столиц Франции, Германии и Италии. 

Затем рассчитайте их суммарное население. 

"""

 

 console. print ( f"[bold green]Тестирование агента планирования на запросе, ориентированном на план:[/bold green] ' {plan_centric_query} '\n" ) 

 

# Правильная инициализация состояния, особенно списка для промежуточных шагов

 initial_planning_input = { "user_request" : plan_centric_query, "intermediate_steps" : []} 

final_planning_output = planning_agent_app.invoke(initial_planning_input) 

 

console. print ( "\n--- [bold green]Итоговый вывод от агента планирования[/bold green] ---" ) 

console. print (Markdown(final_planning_output[ 'final_answer' ]))

Разница в процессе очевидна сразу. Первое, что делает наш агент, — это излагает всю свою стратегию.

 

--- ПЛАНИРОВЩИК: Декомпозиция задачи... --- 

--- ПЛАНИРОВЩИК: Сгенерирован план: ["web_search('население Парижа')", "web_search('население Берлина')", "web_search('население Рима')"] --- 

--- МАРШРУТИЗАТОР: План содержит еще шаги. Продолжение выполнения. --- 

--- ИСПОЛНИТЕЛЬ: Выполнение следующего шага... ---

 ...

Агент разработал подробный, четкий план, прежде чем предпринять какое-либо действие. Затем он методично выполнил этот план. Этот процесс более прозрачен и надежен, поскольку он следует четкому набору инструкций.

 

Чтобы формализовать это, мы воспользуемся услугами нашего магистра права в качестве судьи, но на этот раз мы оценим эффективность процесса .

 

class ProcessEvaluation ( BaseModel ): 

    """Схема для оценки процесса решения проблем агентом."""

     task_completion_score: int = Field(description= "Оценка от 1 до 10 за выполнение задачи." ) 

    process_efficiency_score: int = Field(description= "Оценка от 1 до 10 за эффективность и прямолинейность процесса агента." ) 

    justification: str = Field(description= "Краткое обоснование оценок." )

При оценке эффективности работы агента по планированию выделяется своей прямолинейностью.

 

--- Оценка процесса работы агента по планированию --- 

    'task_completion_score': 8, 

    'process_efficiency_score': 9, 

    'justification': "Агент заранее разработал четкий, оптимальный план и выполнил его без каких-либо лишних шагов. Его процесс был очень прямым и эффективным для этой предсказуемой задачи."

 }

Мы получаем хорошие оценки, а это значит, что наш подход создает правильную систему планирования, так что…

 

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

 

 

planning_agent_app = planning_graph_builder.compile ( )