Печать
Категория: Шаблоны мультиагентных систем
Просмотров: 49

Мета-контроллер

В многоагентной команде вы, возможно, заметили некоторую жесткость. Мы жестко задали последовательность: News -> Technical -> Financial -> Writer.

 

А что, если пользователю нужен только технический анализ? Наша система все равно будет тратить время и деньги на запуск других аналитических инструментов.

 

В архитектуре Meta-Controller используется интеллектуальный диспетчер. Единственная задача этого агента-контроллера — проанализировать запрос пользователя и определить, какой специалист лучше всего подходит для выполнения этой задачи.

 

В системах RAG или агентных системах Мета-контроллер является центральной нервной системой. Это своего рода «входная дверь», которая направляет входящие запросы в нужный отдел.

 

Простейшая версия контроллера Meta работает следующим образом:

 

 

 Инструмент, использующий рабочий процесс (создан пользователем)

Фарид Хан

)

Получение запроса: Агент получает запрос от пользователя.

Решение: Агент анализирует запрос и рассматривает доступные инструменты. Затем он решает, необходим ли тот или иной инструмент для точного ответа на вопрос.

Действие: Если требуется инструмент, агент формирует вызов этого инструмента, например, конкретной функции с правильными аргументами.

Наблюдение: Система выполняет вызов инструмента, и результат («наблюдение») отправляется обратно агенту.

Синтез: Агент берет выходные данные инструмента, объединяет их со своими собственными рассуждениями и генерирует окончательный, обоснованный ответ для пользователя.

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

 

# Инициализируем инструмент. Мы можем установить максимальное количество результатов, чтобы контекст был кратким.

 search_tool = TavilySearchResults(max_results= 2 ) 

 

# Крайне важно дать инструменту четкое имя и описание для агента.

 search_tool.name = "web_search"

 search_tool.description = "Инструмент, который можно использовать для поиска в интернете актуальной информации по любой теме, включая новости, события и текущие события."

 

 tools = [search_tool]

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

 

class AgentState ( TypedDict ): 

    messages: Annotated[ list [AnyMessage], add_messages]

Далее нам необходимо сообщить LLM о предоставленных нам инструментах. Это критически важный шаг. Мы используем .bind_tools()метод, который, по сути, внедряет название и описание инструмента в системную подсказку LLM, позволяя ему самому решать, когда вызывать этот инструмент.

 

llm = ChatNebius(model= "meta-llama/Meta-Llama-3.1-8B-Instruct" , temperature= 0 ) 

 

# Привязываем инструменты к LLM, делая его совместимым с инструментами

 llm_with_tools = llm.bind_tools(tools)

Теперь мы можем определить рабочий процесс нашего агента с помощью LangGraph. Нам нужны два основных узла: agent_node«мозг», который вызывает LLM для принятия решения о дальнейших действиях, и tool_node«руки», которые фактически выполняют инструмент.

 

def agent_node ( state: AgentState ): 

    """Первичный узел, который вызывает LLM для принятия решения о следующем действии."""

     console. print ( "--- АГЕНТ: Размышляет... ---" ) 

    response = llm_with_tools.invoke(state[ "messages" ]) 

    return { "messages" : [response]} 

 

# ToolNode — это предварительно созданный узел из LangGraph, который выполняет инструменты

 tool_node = ToolNode(tools)

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

 

def router_function ( state: AgentState ) -> str : 

    """Проверяет последнее сообщение агента, чтобы решить, какой будет следующий шаг."""

     last_message = state[ "messages" ][- 1 ] 

    if last_message.tool_calls: 

        # Агент запросил вызов инструмента

         console. print ( "--- МАРШРУТИЗАТОР: Решение — вызвать инструмент. ---" ) 

        return "call_tool" 

    else : 

        # Агент предоставил окончательный ответ

         console. print ( "--- МАРШРУТИЗАТОР: Решение — завершить. ---" ) 

        return "__end__"

Хорошо, у нас есть все необходимые элементы. Давайте соединим их в граф. Ключевым моментом здесь является условное ребро, которое использует наш механизм router_functionдля создания основного цикла рассуждений агента: agent-> router-> tool-> agent.

 

graph_builder = StateGraph(AgentState) 

 

# Добавить узлы

 graph_builder.add_node( "agent" , agent_node) 

graph_builder.add_node( "call_tool" , tool_node) 

 

# Установить точку входа

 graph_builder.set_entry_point( "agent" ) 

 

# Добавить условный маршрутизатор

 graph_builder.add_conditional_edges( 

    "agent" , 

    router_function, 

 

# Добавить ребро от узла инструмента обратно к агенту, чтобы завершить цикл

 graph_builder.add_edge( "call_tool" , "agent" ) 

 

# Скомпилировать граф

 tool_agent_app = graph_builder. compile ()

 

 

 

 

 Мета-контроллер (создан пользователем)

Фарид Хан

)

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

 

# Тест 1: Следует перенаправить на специалиста широкого профиля

 run_agent( "Здравствуйте, как дела сегодня?" ) 

 

# Тест 2: Следует перенаправить на исследователя

 run_agent( "Какие были последние финансовые результаты NVIDIA?" ) 

# Тест 3: Следует перенаправить на программиста

 run_agent( "Можете ли вы написать мне функцию на Python для вычисления n-го числа Фибоначчи?" )

На графике выходных данных видно, что контроллер каждый раз принимает взвешенные решения.

 

--- 🧠 Мета-контроллер анализирует запрос --- 

[жёлтый]Решение о маршрутизации:[/жёлтый] Отправить [жирный]Специалисту[/жирный]. [курсив]Причина: Запрос пользователя — простое приветствие...[/курсив] 

Окончательный ответ: Здравствуйте! Чем я могу вам сегодня помочь? 

 

--- 🧠 Мета-контроллер анализирует запрос --- 

[жёлтый]Решение о маршрутизации:[/жёлтый] Отправить [жирный]Исследователю[/жирный]. [курсив]Причина: Пользователь спрашивает о недавнем событии...[/курсив] 

Окончательный ответ: Последние финансовые результаты NVIDIA... были исключительно сильными. Они сообщили о выручке в размере 26,04 миллиарда долларов... 

 

--- 🧠 Мета-контроллер анализирует запрос --- 

[жёлтый]Решение о маршрутизации:[/жёлтый] Отправить [жирный]Программисту[/жирный]. [курсив]Причина: Пользователь явно запрашивает функцию Python...[/курсив] 

Окончательный ответ: 

```python 

def fibonacci(n): 

    # ... (код) ... 

``` {data-source-line="130"}

Система работает безупречно. Приветствие отправляется специалисту широкого профиля, запрос новостей — исследователю, а запрос кода — программисту. Контроллер корректно распределяет задачи в зависимости от их содержания.

 

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

 

class RoutingEvaluation ( BaseModel ): 

    """Схема для оценки решения метаконтроллера о маршрутизации.""

     routing_correctness_score: int = Field(description= "Оценка от 1 до 10, показывающая, выбрал ли контроллер наиболее подходящего специалиста для запроса." ) 

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

На вопрос типа «Какова столица Франции?» оценка судьи будет следующей:

 

--- Оценка маршрутизации метаконтроллера --- 

    'routing_correctness_score': 8.5, 

    'justification': "Контроллер правильно определил запрос пользователя как фактический запрос, требующий актуальной информации, и направил его агенту 'Исследователь'. Это был оптимальный выбор, поскольку у специалиста широкого профиля могли быть устаревшие знания." 

}

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

 

Это делает системы искусственного интеллекта масштабируемыми и простыми в обслуживании, поскольку новые навыки можно добавлять, подключая специалистов и обновляя контроллер.