Inicio / Artículos

Entrenar un modelo personalizado en Azure ML y llamarlo desde un agente de Foundry

Entrena, registra e implementa un modelo de retención en un endpoint gestionado por Azure ML, y luego exponlo a un agente de Foundry como FunctionTool, incluyendo información sobre autenticación y consideraciones para entornos de producción.

Editor: REACTAPP.TOP7 min de lectura1652 palabras

Fuentes: Building and Serving a Custom Model with Azure ML, Then Wiring It Into a Foundry Agent

El catálogo de modelos de Foundry es amplio, pero hay algunos trabajos que quedan fuera de él. Imagine predecir la rotación de clientes a partir de los registros propios de una empresa, o evaluar posibles fraudes basándose en su historial de transacciones. Esas son tareas convencionales de aprendizaje automático con datos tabulares privados, y la solución de Microsoft para ellas es Azure Machine Learning, un producto distinto con su propio SDK, espacio de trabajo independiente y método propio para desplegar modelos.

El proceso descrito abarca todo el camino: entrenar un modelo con el SDK de Azure ML, registrarlo, servirlo desde un endpoint gestionado y luego exponer ese endpoint a un agente de Foundry como una herramienta funcional. El agente puede entonces llamar al modelo personalizado en medio de una conversación, tal como lo haría con cualquier otra herramienta.

Dónde se encuentran las dos plataformas

Los dos productos funcionan con entornos de ejecución separados pero pertenecen al mismo ecosistema, y Microsoft ha trabajado para mantener la conexión entre ellos lo más estrecha posible. Saber exactamente dónde se encuentra esa conexión facilita el seguimiento del proceso de desarrollo. Hay cuatro componentes involucrados:

  • Tarea de comando. Aquí se realizan las ejecuciones de entrenamiento. El SDK apunta a un script, a un destino de cómputo y a algunos datos de entrada, y ejecuta el código como una tarea registrada y reproducible.
  • Registro de modelos. Un modelo entrenado se convierte en un artefacto con nombre y versión que existe independientemente de la tarea que lo generó, de modo que se puede promover una versión a producción sin tener que repetir el proceso de entrenamiento.
  • Punto final en línea gestionado. Este sirve las predicciones provenientes de un modelo registrado. Se trata de infraestructura real, siempre activa o con escalado automático, con su propia URL, autenticación y costo, y se encuentra dentro del espacio de trabajo de Azure ML, no en Foundry.
  • Foundry FunctionTool. Este es el puente. Foundry no sabe ni le importa que la función detrás de la herramienta llame a un endpoint de Azure ML. Para el agente se trata de un nombre, un esquema y un resultado.
  • Requisitos previos

    1. Un espacio de trabajo de Azure ML y un proyecto de Foundry. Pueden encontrarse en los mismos grupos de recursos o en diferentes ones, ya que nada aquí requiere infraestructura compartida.
    2. Python 3.9 o superior con ambos SDKs:
    pip install azure-ai-ml azure-ai-projects azure-identity
    

    Un MLClient se autentica y dirige al espacio de trabajo:

    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>",
    )
    

    Entrenamiento como tarea de comando

    El paso de entrenamiento es una tarea de comando. Toma un script de entrenamiento, en este caso un clasificador común de scikit-learn, y lo ejecuta en recursos informáticos gestionados:

    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)
    

    La llamada a ml_client.jobs.stream espera e imprime registros hasta que finalice el entrenamiento. Es preferible utilizarla en cualquier script que realmente se ejecute. Un trabajo que falla silenciosamente en segundo plano es una forma desagradable de descubrir que un pipeline está dañado.

    Registro del modelo

    El registro convierte la salida del trabajo en un artefacto con nombre y versión al que se puede hacer referencia de forma independiente:

    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.",
        )
    )
    

    Elegir MLFLOW_MODEL como tipo es intencional. Si el script de entrenamiento registra el modelo mediante el autologging de MLflow, los endpoints gestionados pueden desplegarlo usando un contenedor de puntuación integrado, sin necesidad de un script de inferencia personalizado score.py. Esto ahorra tiempo en tiempo real, y el script de entrenamiento debe diseñarse teniéndolo en cuenta desde el principio.

    Despliegue detrás de un endpoint

    Un endpoint junto con un proceso de despliegue ponen el modelo en línea:

    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()
    

    El nombre de la implementación blue no es obligatorio, pero consérvelo, ya que fomenta el hábito útil de utilizarlo en la primera versión nueva del modelo. Crea una implementación green junto a blue, distribuye el tráfico entre ambas y transfiérelo por completo una vez que la nueva versión gane confianza. Sobrescribir blue y confiar en lo mejor es la alternativa arriesgada.

    Antes de conectar cualquier otra cosa, obtén una predicción real directamente:

    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)
    

    Depurar un punto de extremo dañado a través del bucle de llamadas a funciones de un agente es mucho más difícil que hacerlo directamente, así que primero confirme que funciona.

    Conectar el punto de extremo a un agente Foundry

    El agente nunca llama directamente a Azure ML. El código de la aplicación sí lo hace, cuando el agente se lo solicita. La configuración declara un FunctionTool, crea el agente y define la función que envía la solicitud HTTP al punto de extremo:

    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,
        ),
    )
    

    Luego se inicia una conversación, se intercepta la llamada a la función del modelo y se devuelve el resultado:

    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)
    

    El modelo decide si una pregunta justifica llamar a get_churn_score. Cuando lo hace, el código de la aplicación realiza la llamada HTTP a Azure ML, y el resultado vuelve a la misma conversación para que el modelo pueda completar su respuesta. Nada en la definición del agente ni en las llamadas a la API de respuestas depende de lo que hace la función en segundo plano. Podría consultar una base de datos, invocar una función Lambda o acceder a un espacio de trabajo diferente de Azure ML, como ocurre aquí.

    Dos recursos, dos límites de autenticación

    Hay un aspecto que permanece oculto en la documentación del SDK. El endpoint gestionado en Azure ML y el endpoint del proyecto en Foundry son dos recursos separados por dos sistemas de autenticación distintos, y nada en esta configuración los conecta. Azure ML cuenta con su propia clave o identidad gestionada para el espacio de trabajo, mientras que Foundry utiliza credenciales diferentes. Declarar un FunctionTool no establece confianza entre ellos; solo le da al modelo un nombre y un esquema para solicitar algo a tu código. Toda la autenticación hacia Azure ML se realiza dentro de tu propia función, exactamente como ocurriría al llamar a cualquier otra API externa.

    Eso es una ventaja. El lado de Azure ML puede ser gestionado, protegido y actualizado de forma independiente de Foundry, incluso por otro equipo, y ninguna de las partes necesita acceso de escritura a los recursos de la otra.

    Notas para producción

    • La llamada a una función vence después de 10 minutos. La salida debe volver a la conversación antes de que se cierre la ventana, de lo contrario la ejecución fallará. Si la llamada HTTP a Azure ML es lenta, considérela como un plazo estricto y tómelo en cuenta al planificar.
    • Pase a una identidad administrada más allá del prototipo. Una clave almacenada en una variable de entorno funciona bien localmente, pero representa un riesgo en cualquier situación sin supervisión. Asigne al identidad que ejecuta el código de la herramienta el rol AzureML Data Scientist, o un rol personalizado más restringido limitado a este endpoint y no al conjunto completo del espacio de trabajo.
    • Implemente los nuevos modelos de forma gradual. Lanzue la nueva versión junto a la existente e incorpore el tráfico de forma gradual. Esa es la única manera de detectar un problema en el reentrenamiento antes de que cada solicitud real dependa de él.
  • Mantenga activado strict=True. Los esquemas estrictos impiden que el modelo envíe argumentos incompletos o mal formados a get_churn_score, lo cual de otro modo causaría un error dentro de su función y no antes de la llamada.
  • El uso inactivo de recursos cuesta dinero. Un Endpoint en línea gestionado que ejecuta una instancia todo el día se factura incluso sin tráfico, a diferencia de las llamadas a modelos serverless. Para volúmenes bajos o irregulares, considere endpoints con escalado a cero o batch.
  • La desviación es silenciosa. Nada aquí se vuelve a entrenar cuando los datos reales se alejan de la distribución de entrenamiento. Registre las entradas y salidas de cada llamada a get_churn_score en un lugar accesible y revise los datos de entrenamiento periódicamente, no solo cuando las predicciones empeoren visiblemente.
  • Elegir la plataforma adecuada

    Para la mayoría de las tareas relacionadas con la IA generativa, el catálogo de Foundry es la herramienta adecuada, y los equipos que no cuentan con datos de entrenamiento propios probablemente nunca necesiten nada más. Cuando la tarea se convierte en una predicción sobre sus propios clientes a partir de su propia historia, Azure ML es la opción más adecuada. De lo contrario, se forcejea con un modelo de lenguaje para una tarea para la que no fue diseñado, o se debe construir manualmente la infraestructura de servicio. Lo esencial son solo unos pocos elementos: un trabajo de entrenamiento, un modelo registrado, un endpoint gestionado y una única FunctionTool. Las plataformas permanecen separadas, cada una haciendo lo que sabe hacer mejor.

    Lecturas relacionadas

  • Arquitectura de Servicio de Modelos: Por Qué la API Es Solo la Puerta de Entrada — Una guía sobre la plataforma que está detrás de un punto final de inferencia: enrutamiento, escalado, versionado, observabilidad, seguridad y cómo llamar a modelos servidos desde Spring AI o LangChain4j.