Главная / Статьи

Обучите пользовательскую модель в Azure ML и вызовите её из агента Foundry.

Обучите, зарегистрируйте и разверните модель оттока на управляемом конечном пункте Azure ML, затем сделайте её доступной агенту Foundry в качестве FunctionTool с информацией об авторизации и рекомендациями для производственного использования.

Издатель: REACTAPP.TOP7 мин чтения1652 слов

Источники: Building and Serving a Custom Model with Azure ML, Then Wiring It Into a Foundry Agent

Каталог моделей Foundry довольно обширен, но существуют задачи, которые не входят в его рамки. Представьте себе прогнозирование ухода клиентов на основе собственных записей компании или оценку риска мошенничества по её собственной истории транзакций. Это традиционные задачи машинного обучения с использованием приватных табличных данных, и решением Microsoft для них является Azure Machine Learning — отдельный продукт с собственным SDK, собственной рабочей средой и уникальным способом развертывания моделей.

Приведённый ниже процесс охватывает весь путь: обучение модели с помощью SDK Azure ML, её регистрация, предоставление через управляемый конечный пункт и затем доступ к этому конечному пункту для агента Foundry в виде функционального инструмента. Агент может затем вызывать эту пользовательскую модель в ходе общения точно так же, как и любой другой инструмент.

Точка соприкосновения двух платформ

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

  • Задание команды. Здесь происходит обучение. SDK направлен на скрипт, целевую машину для вычислений и определенные входные данные, после чего код выполняется в виде задания с возможностью отслеживания и воспроизведения.
  • Реестр моделей. Обученная модель превращается в артефакт с именем и версией, который существует независимо от задания, с которого он был создан; таким образом, одну версию можно запустить в производство без повторного обучения.
  • Управляемый онлайн-конечный пункт. Он предоставляет прогнозы на основе зарегистрированной модели. Это реальная инфраструктура, работающая постоянно или с автоматическим масштабированием, имеющая собственный URL, механизмы аутентификации и расчет стоимости; она находится внутри рабочего пространства Azure ML, а не в Foundry.
  • Foundry FunctionTool. Это мостовой элемент. Foundry не знает и не интересуется тем, что функция, находящаяся за этим инструментом, вызывает конечную точку Azure ML. Для агента это просто имя, схема и результат.
  • Предварительные требования

    1. Рабочее пространство Azure ML и проект Foundry. Они могут находиться в одных или разных группах ресурсов, поскольку здесь не требуется общая инфраструктура.
    2. Python 3.9 или новее с обоими SDK:
    pip install azure-ai-ml azure-ai-projects azure-identity
    

    MLClient аутентифицируется и настраивает работу с рабочим пространством:

    from azure.ai.ml import MLClient
    from azure.identity import DefaultAzureCredential
    
    ml_client = MLClient(
        DefaultAzureCredential(),
        subscription_id="<subscription-id>",
        resource_group_name="<resource-group>",
        workspace_name="<aml-workspace-name>",
    )
    

    Обучение как задача командного типа

    Шаг обучения представляет собой задачу командного типа. Он берёт скрипт обучения — в данном случае обычный классификатор scikit-learn — и выполняет его на управляемых вычислительных ресурсах:

    from azure.ai.ml import command, Input, Output
    
    train_job = command(
        code="./src",
        command="python train.py --data ${{inputs.training_data}} --model_output ${{outputs.model_output}}",
        inputs={"training_data": Input(type="uri_folder", path="azureml://datastores/workspaceblobstore/paths/churn-training/")},
        outputs={"model_output": Output(type="uri_folder")},
        environment="azureml://registries/azureml/environments/sklearn-1.5/labels/latest",
        compute="cpu-cluster",
        display_name="churn-model-training",
    )
    returned_job = ml_client.jobs.create_or_update(train_job)
    ml_client.jobs.stream(returned_job.name)
    

    Вызов ml_client.jobs.stream ожидает завершения обучения и выводит логи до его окончания. Его следует использовать во всех скриптах, которые действительно будут запускаться. Когда задача тихо терпит неудачу на фоне, это неприятный способ обнаружить сбой в работе конвейера.

    Регистрация модели

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

    from azure.ai.ml.entities import Model
    from azure.ai.ml.constants import AssetTypes
    
    model = ml_client.models.create_or_update(
        Model(
            path=f"azureml://jobs/{returned_job.name}/outputs/model_output",
            name="churn-classifier",
            type=AssetTypes.MLFLOW_MODEL,
            description="Customer churn classifier, trained on 18 months of account history.",
        )
    )
    

    Выбор типа MLFLOW_MODEL сделан намеренно. Если скрипт обучения регистрирует модель с помощью автоматического логирования MLflow, управляемые конечные точки могут развернуть её с использованием встроенного контейнера оценки, без необходимости в специальном скрипте инференса score.py. Это экономит время в реальном времени, и скрипт обучения лучше всего проектировать с учётом этого с самого начала.

    Развертывание за конечной точкой

    Конечная точка в сочетании с механизмом развертывания позволяет вывести модель в сеть:

    from azure.ai.ml.entities import ManagedOnlineEndpoint, ManagedOnlineDeployment
    
    endpoint = ManagedOnlineEndpoint(name="churn-endpoint", auth_mode="key")
    ml_client.online_endpoints.begin_create_or_update(endpoint).result()
    deployment = ManagedOnlineDeployment(
        name="blue",
        endpoint_name="churn-endpoint",
        model=model,
        instance_type="Standard_DS3_v2",
        instance_count=1,
    )
    ml_client.online_deployments.begin_create_or_update(deployment).result()
    endpoint.traffic = {"blue": 100}
    ml_client.online_endpoints.begin_create_or_update(endpoint).result()
    

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

    Прежде чем что-либо ещё настраивать, получите прямое реальное прогнозирование:

    import json
    
    test_input = {"input_data": {"columns": ["tenure_months", "monthly_spend", "support_tickets"], "data": [[14, 89.50, 3]]}}
    response = ml_client.online_endpoints.invoke(
        endpoint_name="churn-endpoint",
        request_file=None,
        deployment_name="blue",
        input_data=json.dumps(test_input),
    )
    print(response)
    

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

    Подключение конечной точки к агенту Foundry

    Агент никогда не обращается к самому Azure ML. Код приложения делает это, когда агент об этом просит. В настройках объявляется FunctionTool, создаётся агент и определяется функция, которая отправляет HTTP-запрос к конечной точке:

    import os
    import requests
    from azure.ai.projects import AIProjectClient
    from azure.ai.projects.models import PromptAgentDefinition, Tool, FunctionTool
    from azure.identity import DefaultAzureCredential
    
    def get_churn_score(tenure_months: int, monthly_spend: float, support_tickets: int) -> dict:
        payload = {"input_data": {"columns": ["tenure_months", "monthly_spend", "support_tickets"], "data": [[tenure_months, monthly_spend, support_tickets]]}}
        resp = requests.post(
            "https://churn-endpoint.<region>.inference.ml.azure.com/score",
            headers={"Authorization": f"Bearer {os.environ['AML_ENDPOINT_KEY']}", "Content-Type": "application/json"},
            json=payload,
            timeout=10,
        )
        resp.raise_for_status()
        return {"churn_probability": resp.json()[0]}
    func_tool = FunctionTool(
        name="get_churn_score",
        description="Predict churn probability for a customer given tenure, spend, and support ticket history.",
        parameters={
            "type": "object",
            "properties": {
                "tenure_months": {"type": "integer", "description": "How many months the customer has been active."},
                "monthly_spend": {"type": "number", "description": "Average monthly spend in dollars."},
                "support_tickets": {"type": "integer", "description": "Number of support tickets in the last 90 days."},
            },
            "required": ["tenure_months", "monthly_spend", "support_tickets"],
            "additionalProperties": False,
        },
        strict=True,
    )
    project = AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], credential=DefaultAzureCredential())
    tools: list[Tool] = [func_tool]
    agent = project.agents.create_version(
        agent_name="retention-agent",
        definition=PromptAgentDefinition(
            model="gpt-4.1-mini",
            instructions="Help the team assess churn risk. Call get_churn_score whenever specific customer numbers are provided.",
            tools=tools,
        ),
    )
    

    Затем происходит обмен сообщениями, вызов функции модели перехватывается, и возвращается результат:

    import json
    from openai.types.responses.response_input_param import FunctionCallOutput
    
    openai_client = project.get_openai_client()
    conversation = openai_client.conversations.create()
    response = openai_client.responses.create(
        input="A customer's been with us 14 months, spends about $90/month, and filed 3 tickets recently. Churn risk?",
        conversation=conversation.id,
        extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}},
    )
    for item in response.output:
        if item.type == "function_call" and item.name == "get_churn_score":
            result = get_churn_score(**json.loads(item.arguments))
            follow_up = openai_client.responses.create(
                input=[FunctionCallOutput(type="function_call_output", call_id=item.call_id, output=json.dumps(result))],
                conversation=conversation.id,
                extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}},
            )
            print(follow_up.output_text)
    

    Модель решает, требуется ли при заданном вопросе вызов функции get_churn_score. Если это необходимо, код приложения отправляет HTTP-запрос в Azure ML, а результат возвращается обратно в ту же конверсацию, чтобы модель могла завершить свой ответ. Ничто в определении агента или в вызовах API Responses не зависит от того, что происходит на фоне выполнения функции. Она может запрашивать данные из базы данных, вызывать функцию Lambda или обращаться к другому рабочему пространству Azure ML, как это делается здесь.

    Два ресурса, два уровня аутентификации

    В документации SDK скрывается один важный момент. Управляемый конечный пункт в Azure ML и проектный конечный пункт в Foundry представляют собой два ресурса, защищённые двумя отдельными системами аутентификации, и ничто в данной конфигурации не связывает их между собой. Azure ML имеет собственный ключ или управляемую идентичность для рабочей среды, в то время как Foundry использует другие учетные данные. Объявление FunctionTool не создаёт между ними доверия; оно лишь присваивает модели имя и схему для запроса данных у вашего кода. Вся аутентификация в Azure ML происходит внутри собственной функции, точно так же, как при вызове любого другого внешнего API.

    Это является преимуществом. Часть, связанная с Azure ML, может управляться, защищаться и обновляться независимо от Foundry, даже другой командой, причём ни одна из сторон не нуждается в правах на запись в ресурсах другой стороны.

    Замечания по эксплуатации

    • Запрос на выполнение функции истекает через 10 минут. Результат должен быть отправлен обратно в чат до закрытия окна, иначе выполнение сорвется. Если HTTP-запрос к Azure ML медленный, рассматривайте это как строгий срок и учитывайте его при планировании.
    • Переходите на управляемую идентичность вместо прототипа. Ключ, хранящийся в переменной среды, подходит для локального использования, но представляет риск при автономной работе. Предоставьте идентичности, запускающей код инструмента, роль AzureML Data Scientist или более узкую пользовательскую роль, ограниченную конкретным эндпоинтом, а не всей рабочей средой.
    • Постепенно внедряйте новые модели. Запустите новую версию рядом с существующей и постепенно перенаправляйте трафик на нее. Это единственный способ обнаружить проблемы с переобучением до того, как от нее будут зависеть реальные запросы.
  • Оставьте параметр strict=True включённым. Строгие схемы не позволяют модели отправлять неполные или некорректно сформированные аргументы в функцию get_churn_score, что иначе привело бы к сбоям внутри функции ещё до её вызова.
  • Неиспользуемые вычисления стоят денег. Управляемый онлайн-эндпоинт, работающий с одним экземпляром весь день, оплачивается даже при отсутствии трафика, в отличие от вызова безусловной модели. Для низкого или скачкообразного объёма данных рассмотрите варианты масштабирования до нуля или использование пакетных эндпоинтов.
  • Отклонения происходят незаметно. Ничто здесь не переобучается, когда реальные данные уходят от распределения, использованного при обучении. Записывайте входные и выходные данные каждого вызова функции get_churn_score в месте, доступном для анализа, и регулярно пересматривайте данные обучения, а не только тогда, когда точность прогнозов заметно снижается.
  • Выбор подходящей платформы

    Для большинства задач с использованием генеративных ИИ Foundry — подходящий инструмент, и командам, у которых нет собственных данных для обучения, возможно, никогда не понадобится что-то более сложное. Когда речь идет о прогнозировании поведения собственных клиентов на основе имеющейся истории, более подходящим решением становится Azure ML. В противном случае языковая модель используется для задач, для которых она не предназначалась, или инфраструктура обслуживания создается вручную. Все необходимое сводится к немногому: одна задача обучения, одна зарегистрированная модель, один управляемый конечный пункт и единственный FunctionTool. Платформы остаются раздельными, каждая выполняя ту функцию, в которой она лучше всего справляется.