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.
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.
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
- 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.
- 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.
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.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
- Un agente de inversiones de LangChain respaldado por datos de mercado en tiempo real de EODHD, paso a paso — Por qué los agentes de inversión basados en LLM fracasan sin datos reales, cómo funciona el bucle ReAct y cómo crear un agente de LangChain con tres herramientas que utilice precios de EODHD, fundamentales y RSI.
- Notas prácticas: Microsoft construyó el servidor MCP de Playwright. Luego les dijo que — Guía detallada de Notas prácticas: Microsoft construyó el servidor MCP de Playwright. Luego les dijo que: contratos, verificaciones y espacios para código listo para usar para equipos que implementen este patrón.